CtrlK
BlogDocsLog inGet started
Tessl Logo

acceptance-testing

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.

72

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

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

SuiteLocationTarget AppEnv Var
WingtipToyssrc/WingtipToys.AcceptanceTests/samples/AfterWingtipToys/WINGTIPTOYS_BASE_URL
ContosoUniversitysrc/ContosoUniversity.AcceptanceTests/samples/AfterContosoUniversity/CONTOSO_BASE_URL
Sample Appsamples/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

CategoryWhat to TestExample
NavigationAll navbar links load without errorsHomePage_Loads, NavbarLink_LoadsPage
Static AssetsCSS/JS/images served correctlyStaticAssets_CssLoads, StaticAssets_ImagesLoad
Data DisplayData-bound pages show contentProductList_DisplaysProducts
User FlowsMulti-step workflows completeAddItemToCart_AppearsInCart
AuthenticationLogin/register pages load, forms workLoginLink_LoadsPage, RegisterLink_LoadsPage
Error Handling404/500 pages render gracefullyNonexistentPage_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

ProblemSolution
Browser not foundRun playwright.ps1 install chromium
Connection refusedEnsure target app is running on the expected port
Timeout waiting for selectorAdd await page.WaitForLoadStateAsync(LoadState.NetworkIdle)
SSL errorIgnoreHTTPSErrors = true is set in PlaywrightFixture.NewPageAsync()
Flaky testAdd explicit waits, avoid timing-dependent assertions
Element not foundUse 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
Repository
FritzAndFriends/BlazorWebFormsComponents
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.