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.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer