Guide for E2E testing with Playwright for .NET using Microsoft.Playwright.MSTest.v4 integration
This skill provides guidance for end-to-end (E2E) testing with Playwright for .NET using Microsoft.Playwright.MSTest.v4 integration in this .NET 10 project.
Playwright is a modern E2E testing framework for web applications. It supports:
This project uses the MSTest integration which provides:
PageTest base class - one browser context per testContextTest base class - browser context with multiple pagesBrowserTest base class - full browser controlVersion: 1.55.0-beta-4 (configured in Directory.Packages.props)
This repository has 2 Playwright test projects:
tests/ClaudeStack.Web.Tests.Playwright/ - Tests for MVC applicationtests/ClaudeStack.API.Tests.Playwright/ - Tests for API applicationBoth projects use this configuration:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableMSTestRunner>true</EnableMSTestRunner>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Playwright.MSTest.v4" />
<PackageReference Include="MSTest" />
</ItemGroup>
</Project>
Key points:
# Step 1: Create MSTest project
dotnet new mstest -o tests/Example.NewApp.Tests.Playwright
# Step 2: Edit .csproj to add required properties
# <PropertyGroup>
# <EnableMSTestRunner>true</EnableMSTestRunner>
# <OutputType>Exe</OutputType>
# </PropertyGroup>
# Step 3: Add Playwright package reference (version in Directory.Packages.props)
# <PackageReference Include="Microsoft.Playwright.MSTest.v4" />
# <PackageReference Include="MSTest" />
# Step 4: Install browsers (see next section)
# Step 5: Add MSTestSettings.cs with parallelization
# [assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
After creating a Playwright project or after building, install browsers:
# Navigate to the build output directory
cd tests/ClaudeStack.Web.Tests.Playwright/bin/Debug/net10.0
# Run the Playwright PowerShell script
./playwright.ps1 install
Important: Browsers must be installed after building the project because the playwright.ps1 script is generated during build.
Windows (PowerShell):
pwsh -Command "cd tests/ClaudeStack.Web.Tests.Playwright/bin/Debug/net10.0; ./playwright.ps1 install"
Linux/macOS (Bash):
pwsh tests/ClaudeStack.Web.Tests.Playwright/bin/Debug/net10.0/playwright.ps1 install
# Install only Chromium
./playwright.ps1 install chromium
# Install Chromium and Firefox
./playwright.ps1 install chromium firefox
# Install with dependencies (for Linux CI)
./playwright.ps1 install --with-deps
Browsers are installed in:
%USERPROFILE%\AppData\Local\ms-playwright~/.cache/ms-playwright~/Library/Caches/ms-playwrightAll tests inherit from PageTest:
using System.Threading.Tasks;
using Microsoft.Playwright.MSTest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ClaudeStack.Web.Tests.Playwright;
[TestClass]
public class HomePageTests : PageTest
{
[TestMethod]
public async Task HomePageLoadsSuccessfully()
{
await Page.GotoAsync("https://localhost:5001");
await Expect(Page).ToHaveTitleAsync("Home Page");
}
}
Key points:
PageTestasync TaskPage property available automaticallyExpect for assertionsPageTest provides automatic fixtures:
public class MyTests : PageTest
{
// Available properties:
// - Page: IPage (automatically created per test)
// - Context: IBrowserContext
// - Browser: IBrowser
// - Playwright: IPlaywright
}
Use [TestInitialize] and [TestCleanup] as needed. Page is automatically disposed.
Page.Locator("css") // CSS selector
Page.Locator("text=Get Started") // Text
Page.GetByTestId("id") // Test ID
Page.GetByRole(AriaRole.Button) // Accessibility
Page.GetByLabel("Email") // Form label
Page.Locator("div").Locator("button") // Chaining
await Expect(Page).ToHaveTitleAsync("Title");
await Expect(element).ToBeVisibleAsync();
await Expect(element).ToHaveTextAsync("text");
await Expect(element).ToHaveAttributeAsync("href", "/about");
await element.ClickAsync();
await element.FillAsync("value");
await element.CheckAsync();
await element.SelectOptionAsync("option");
$env:HEADED="1"
dotnet run --project tests/ClaudeStack.Web.Tests.Playwright
// Screenshot
await Page.ScreenshotAsync(new() { Path = "screenshot.png" });
// Tracing
await Context.Tracing.StartAsync(new() { Screenshots = true, Snapshots = true });
// ... test actions ...
await Context.Tracing.StopAsync(new() { Path = "trace.zip" });
View trace: pwsh .../playwright.ps1 show-trace trace.zip
$env:PWDEBUG="1"
dotnet run --project tests/ClaudeStack.Web.Tests.Playwright
# Azure DevOps / GitHub Actions
- Build project: dotnet build tests/**/*.Playwright.csproj
- Install browsers: pwsh .../playwright.ps1 install --with-deps
- Run tests: dotnet run --project tests/ClaudeStack.Web.Tests.Playwright
Important: Use --with-deps flag in CI to install system dependencies (Linux).
Error: "Executable doesn't exist at..."
Solution: Install browsers after building:
cd tests/ClaudeStack.Web.Tests.Playwright/bin/Debug/net10.0
./playwright.ps1 install
Cause: Project not built yet.
Solution: Build first, then install browsers:
dotnet build tests/ClaudeStack.Web.Tests.Playwright
# Then install browsers
Cause: Default timeout (30s) exceeded.
Solution: Increase timeout:
[TestMethod]
[Timeout(60000)] // 60 seconds
public async Task SlowTest()
{
// Or set per-action timeout:
await Page.GotoAsync("https://example.com", new() { Timeout = 60000 });
}
Solution: Playwright auto-waits, but verify selector:
// Debug: Get all matching elements
var count = await Page.Locator("button").CountAsync();
Console.WriteLine($"Found {count} buttons");
// Use more specific selector
var button = Page.GetByRole(AriaRole.Button, new() { Name = "Submit" });
Solutions:
Expect assertions (built-in retries)Page.WaitForLoadStateAsync(LoadState.NetworkIdle)PageTest // Single page per test (most common)
ContextTest // Browser context, create own pages
BrowserTest // Full browser control
// Navigation
await Page.GotoAsync("url");
// Waiting
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await Page.WaitForSelectorAsync("button");
// Locators
Page.Locator("css")
Page.GetByTestId("id")
Page.GetByRole(AriaRole.Button)
Page.GetByText("text")
// Assertions
await Expect(Page).ToHaveTitleAsync("title");
await Expect(element).ToBeVisibleAsync();
await Expect(element).ToHaveTextAsync("text");
// Actions
await element.ClickAsync();
await element.FillAsync("value");
await element.CheckAsync();
# Run all Playwright tests
dotnet run --project tests/ClaudeStack.Web.Tests.Playwright
# Headed mode
$env:HEADED="1"
dotnet run --project tests/ClaudeStack.Web.Tests.Playwright
# Debug mode
$env:PWDEBUG="1"
dotnet run --project tests/ClaudeStack.Web.Tests.Playwright
cd tests/ClaudeStack.Web.Tests.Playwright/bin/Debug/net10.0
./playwright.ps1 install # All browsers
./playwright.ps1 install chromium # Chromium only
./playwright.ps1 install --with-deps # With system dependencies (Linux)
This skill is accurate as of Playwright 1.55 beta. Some APIs may change in future versions.