‹ 首页

acceptance-testing

@fritzandfriends · 收录于 昨天 · 上游提交 1 个月前

Write Playwright-based acceptance tests for migrated Blazor applications. Covers PlaywrightFixture setup, test organization with xUnit collection fixtures, page navigation, element interaction, assertion patterns, environment configuration, CI/CD browser setup, and screenshot capture. Use when adding acceptance tests for benchmark apps, verifying migration quality, or debugging Playwright test failures.

适合你,如果正在为 Blazor 应用添加验收测试或排查 Playwright 问题

/ 通过 npx 安装 校验哈希
npx oh-my-skill add fritzandfriends/blazorwebformscomponents/acceptance-testing
/ 通过 bash 安装
curl -fsSL https://oh-my-skill.com/install.sh | bash -s -- fritzandfriends/blazorwebformscomponents/acceptance-testing
/ 已经装过?验证本机副本,不用重装
npx oh-my-skill verify fritzandfriends/blazorwebformscomponents/acceptance-testing
安装目标可用 --agent / --scope 或 --to 明确指定;省略时只会在唯一已存在的 agent 目录上自动选择,零命中或多命中会停止并提示。content_hash 缺失或不一致均拒装。
449GitHub stars
~1.5K上下文体积 · 单文件
索引托管

怎么用

技能原文 SKILL.md作者撰写 · MIT · 3154758

Acceptance Test Authoring (Playwright)

This skill covers writing end-to-end acceptance tests using Playwright (.NET) to verify migrated Blazor applications work correctly.

Architecture
Test Suites

| Suite | Location | Target App | Env Var | |-------|----------|------------|---------| | WingtipToys | src/WingtipToys.AcceptanceTests/ | samples/AfterWingtipToys/ | WINGTIPTOYS_BASE_URL | | ContosoUniversity | src/ContosoUniversity.AcceptanceTests/ | samples/AfterContosoUniversity/ | CONTOSO_BASE_URL | | Sample App | samples/AfterBlazorServerSide.Tests/ | samples/AfterBlazorServerSide/ | N/A (built inline) |

Shared Infrastructure

Every test suite uses the same pattern:

PlaywrightFixture.cs — Shared browser instance:

public class PlaywrightFixture : IAsyncLifetime
{
    public IPlaywright Playwright { get; private set; } = null!;
    public IBrowser Browser { get; private set; } = null!;

    public async Task InitializeAsync()
    {
        Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
        Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
        {
            Headless = true
        });
    }

    public async Task DisposeAsync()
    {
        await Browser.DisposeAsync();
        Playwright.Dispose();
    }

    public async Task<IPage> NewPageAsync()
    {
        var context = await Browser.NewContextAsync(new BrowserNewContextOptions
        {
            IgnoreHTTPSErrors = true
        });
        return await context.NewPageAsync();
    }
}

[CollectionDefinition("Playwright")]
public class PlaywrightCollection : ICollectionFixture<PlaywrightFixture> { }

TestConfiguration.cs — Environment-driven base URL:

public static class TestConfiguration
{
    public static string BaseUrl =>
        Environment.GetEnvironmentVariable("WINGTIPTOYS_BASE_URL")
        ?? "https://localhost:5001";
}
Writing a New Test
Step 1: Create the Test Class
using Microsoft.Playwright;

namespace WingtipToys.AcceptanceTests;

[Collection("Playwright")]
public class MyFeatureTests
{
    private readonly PlaywrightFixture _fixture;

    public MyFeatureTests(PlaywrightFixture fixture) => _fixture = fixture;

    [Fact]
    public async Task PageName_Loads_Successfully()
    {
        var page = await _fixture.NewPageAsync();
        var response = await page.GotoAsync($"{TestConfiguration.BaseUrl}/MyPage");

        Assert.NotNull(response);
        Assert.True(response.Ok, $"Page returned {response.Status}");
    }
}
Step 2: Common Patterns

Navigation and page load:

var page = await _fixture.NewPageAsync();
await page.GotoAsync($"{TestConfiguration.BaseUrl}/ProductList");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

Click a link by href:

var link = page.Locator("a[href='/ProductList']").First;
await link.ClickAsync();
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

Click a link by text:

var link = page.GetByRole(AriaRole.Link, new() { Name = "Products" });
await link.ClickAsync();

Fill a form field:

var input = page.Locator("input[type='text']").First;
await input.ClearAsync();
await input.FillAsync("new value");

Submit a form:

var submitButton = page.GetByRole(AriaRole.Button, new() { Name = "Submit" });
await submitButton.ClickAsync();
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

Assert page content:

// Check URL
Assert.Contains("ProductList", page.Url, StringComparison.OrdinalIgnoreCase);

// Check title
var title = await page.TitleAsync();
Assert.False(string.IsNullOrEmpty(title));

// Check element exists
var count = await page.Locator("table tbody tr").CountAsync();
Assert.True(count > 0, "Table should have data rows");

// Check text content
var content = await page.ContentAsync();
Assert.Contains("expected text", content, StringComparison.OrdinalIgnoreCase);

Screenshot capture (for benchmark reports):

await page.ScreenshotAsync(new PageScreenshotOptions
{
    Path = "screenshots/my-test.png",
    FullPage = true
});
Step 3: Helper Methods

Extract reusable navigation sequences:

/// <summary>
/// Helper: navigates to product list and adds the first product to cart.
/// </summary>
private static async Task AddFirstProductToCart(IPage page)
{
    await page.GotoAsync($"{TestConfiguration.BaseUrl}/ProductList");
    await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

    var firstProduct = page.Locator("a[href*='ProductDetails']").First;
    await firstProduct.ClickAsync();
    await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

    var addToCartLink = page.Locator("a[href*='AddToCart']").First;
    if (await addToCartLink.CountAsync() > 0)
        await addToCartLink.ClickAsync();
    else
        await page.GetByRole(AriaRole.Button, new() { Name = "Add To Cart" }).ClickAsync();

    await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
}
Step 4: Parameterized Tests

Use [Theory] with [InlineData] for testing multiple similar pages:

[Theory]
[InlineData("About")]
[InlineData("Contact")]
[InlineData("ProductList")]
public async Task NavbarLink_LoadsPage(string pageName)
{
    var page = await _fixture.NewPageAsync();
    await page.GotoAsync(TestConfiguration.BaseUrl);

    var link = page.Locator($"a[href='/{pageName}']").First;
    await link.ClickAsync();
    await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

    Assert.DoesNotContain("Error", await page.TitleAsync(), StringComparison.OrdinalIgnoreCase);
    Assert.True(page.Url.Contains(pageName, StringComparison.OrdinalIgnoreCase));
}
Test Categories

| Category | What to Test | Example | |----------|-------------|---------| | Navigation | All navbar links load without errors | HomePage_Loads, NavbarLink_LoadsPage | | Static Assets | CSS/JS/images served correctly | StaticAssets_CssLoads, StaticAssets_ImagesLoad | | Data Display | Data-bound pages show content | ProductList_DisplaysProducts | | User Flows | Multi-step workflows complete | AddItemToCart_AppearsInCart | | Authentication | Login/register pages load, forms work | LoginLink_LoadsPage, RegisterLink_LoadsPage | | Error Handling | 404/500 pages render gracefully | NonexistentPage_Returns404 |

Running Tests
# Build the test project
dotnet build src/WingtipToys.AcceptanceTests

# Install Playwright browsers (first time only)
pwsh src/WingtipToys.AcceptanceTests/bin/Debug/net10.0/playwright.ps1 install chromium

# Start the target app (in another terminal)
dotnet run --project samples/AfterWingtipToys

# Run tests (set base URL if not default)
$env:WINGTIPTOYS_BASE_URL = "https://localhost:5001"
dotnet test src/WingtipToys.AcceptanceTests
Troubleshooting

| Problem | Solution | |---------|----------| | Browser not found | Run playwright.ps1 install chromium | | Connection refused | Ensure target app is running on the expected port | | Timeout waiting for selector | Add await page.WaitForLoadStateAsync(LoadState.NetworkIdle) | | SSL error | IgnoreHTTPSErrors = true is set in PlaywrightFixture.NewPageAsync() | | Flaky test | Add explicit waits, avoid timing-dependent assertions | | Element not found | Use page.Locator(...).First and check CountAsync() > 0 before interacting |

Checklist
  • [ ] Test class is decorated with [Collection("Playwright")]
  • [ ] Constructor accepts PlaywrightFixture fixture
  • [ ] All test methods are async Task
  • [ ] Pages wait for LoadState.NetworkIdle after navigation
  • [ ] URLs use TestConfiguration.BaseUrl (not hardcoded)
  • [ ] Assertions check for positive state (not just absence of errors)
  • [ ] Helper methods extract reusable navigation sequences
  • [ ] dotnet test passes with target app running
按 MIT 许可原样转载,未经改动 · 在 GitHub 查看 →

评论

登录即可评论;带「已验证安装」的,是发布者名下有本店的安装或持有记录。