dotnet-playwright
Automating browser tests in .NET. Playwright E2E, CI browser caching, trace viewer, codegen.
What this skill does
# dotnet-playwright
Playwright for .NET: browser automation and end-to-end testing. Covers browser lifecycle management, page interactions, assertions, CI caching of browser binaries, trace viewer for debugging failures, and codegen for rapid test scaffolding.
**Version assumptions:** Playwright 1.40+ for .NET, .NET 8.0+ baseline. Playwright supports Chromium, Firefox, and WebKit browsers.
**Out of scope:** Shared UI testing patterns (page object model, selectors, wait strategies) are in [skill:dotnet-ui-testing-core]. Testing strategy (when E2E vs unit vs integration) is covered by [skill:dotnet-testing-strategy]. Test project scaffolding is owned by [skill:dotnet-add-testing].
**Prerequisites:** Test project scaffolded via [skill:dotnet-add-testing] with Playwright packages referenced. Browsers installed via `pwsh bin/Debug/net8.0/playwright.ps1 install` or `dotnet tool run playwright install`.
Cross-references: [skill:dotnet-ui-testing-core] for page object model and selector strategies, [skill:dotnet-testing-strategy] for deciding when E2E tests are appropriate.
---
## Package Setup
```xml
<PackageReference Include="Microsoft.Playwright" Version="1.*" />
<!-- For xUnit integration: -->
<PackageReference Include="Microsoft.Playwright.Xunit" Version="1.*" />
<!-- For NUnit integration: -->
<!-- <PackageReference Include="Microsoft.Playwright.NUnit" Version="1.*" /> -->
```
### Installing Browsers
Playwright requires downloading browser binaries before tests can run:
```bash
# After building the test project:
pwsh bin/Debug/net8.0/playwright.ps1 install
# Or install specific browsers:
pwsh bin/Debug/net8.0/playwright.ps1 install chromium
pwsh bin/Debug/net8.0/playwright.ps1 install firefox
# Using dotnet tool:
dotnet tool install --global Microsoft.Playwright.CLI
playwright install
```
---
## Basic Test Structure
### With Playwright xUnit Base Class
```csharp
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
// PageTest provides Page, Browser, BrowserContext, and Playwright properties
public class HomePageTests : PageTest
{
[Fact]
public async Task HomePage_Title_ContainsAppName()
{
await Page.GotoAsync("https://localhost:5001");
await Expect(Page).ToHaveTitleAsync(new Regex("My App"));
}
[Fact]
public async Task HomePage_NavLinks_AreVisible()
{
await Page.GotoAsync("https://localhost:5001");
var nav = Page.Locator("nav");
await Expect(nav.GetByRole(AriaRole.Link, new() { Name = "Home" }))
.ToBeVisibleAsync();
await Expect(nav.GetByRole(AriaRole.Link, new() { Name = "About" }))
.ToBeVisibleAsync();
}
}
```
### Manual Setup (Without Base Class)
```csharp
public class ManualSetupTests : IAsyncLifetime
{
private IPlaywright _playwright = null!;
private IBrowser _browser = null!;
private IBrowserContext _context = null!;
private IPage _page = null!;
public async ValueTask InitializeAsync()
{
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true
});
_context = await _browser.NewContextAsync(new BrowserNewContextOptions
{
ViewportSize = new ViewportSize { Width = 1280, Height = 720 },
Locale = "en-US"
});
_page = await _context.NewPageAsync();
}
public async ValueTask DisposeAsync()
{
await _page.CloseAsync();
await _context.CloseAsync();
await _browser.CloseAsync();
_playwright.Dispose();
}
[Fact]
public async Task Login_ValidUser_RedirectsToDashboard()
{
await _page.GotoAsync("https://localhost:5001/login");
await _page.FillAsync("[data-testid='email']", "[email protected]");
await _page.FillAsync("[data-testid='password']", "P@ssw0rd!");
await _page.ClickAsync("[data-testid='login-btn']");
await Expect(_page).ToHaveURLAsync(new Regex("/dashboard"));
}
}
```
---
## Locators and Interactions
### Recommended Locator Strategies
```csharp
// BEST: Role-based (accessible and semantic)
var submitBtn = Page.GetByRole(AriaRole.Button, new() { Name = "Submit Order" });
// GOOD: Test ID (stable, explicit)
var emailInput = Page.Locator("[data-testid='email-input']");
// GOOD: Label text (user-visible, accessible)
var nameField = Page.GetByLabel("Full Name");
// GOOD: Placeholder (user-visible)
var searchBox = Page.GetByPlaceholder("Search products...");
// AVOID: CSS class (fragile, changes with styling)
var card = Page.Locator(".card-primary");
// AVOID: XPath (brittle, hard to read)
var cell = Page.Locator("//table/tbody/tr[1]/td[2]");
```
### Common Interactions
```csharp
// Text input
await Page.FillAsync("[data-testid='name']", "Alice Johnson");
// Click
await Page.ClickAsync("[data-testid='submit']");
// Select dropdown
await Page.SelectOptionAsync("[data-testid='country']", "US");
// Checkbox / radio
await Page.CheckAsync("[data-testid='agree-terms']");
// File upload
await Page.SetInputFilesAsync("[data-testid='avatar']", "testdata/photo.jpg");
// Keyboard
await Page.Keyboard.PressAsync("Enter");
await Page.Keyboard.TypeAsync("search query");
// Hover (for dropdowns, tooltips)
await Page.HoverAsync("[data-testid='user-menu']");
```
### Assertions (Expect API)
Playwright assertions auto-retry until the condition is met or the timeout expires:
```csharp
// Element visibility
await Expect(Page.Locator("[data-testid='success']")).ToBeVisibleAsync();
await Expect(Page.Locator("[data-testid='spinner']")).ToBeHiddenAsync();
// Text content
await Expect(Page.Locator("[data-testid='total']")).ToHaveTextAsync("$99.99");
await Expect(Page.Locator("[data-testid='status']")).ToContainTextAsync("Completed");
// Attribute
await Expect(Page.Locator("[data-testid='submit']")).ToBeEnabledAsync();
await Expect(Page.Locator("[data-testid='email']")).ToHaveValueAsync("[email protected]");
// Page-level
await Expect(Page).ToHaveURLAsync(new Regex("/orders/\\d+"));
await Expect(Page).ToHaveTitleAsync("Order Details - My App");
// Count
await Expect(Page.Locator("[data-testid='order-row']")).ToHaveCountAsync(5);
```
---
## Network Interception
### Mocking API Responses
```csharp
[Fact]
public async Task OrderList_WithMockedApi_DisplaysOrders()
{
// Intercept API calls and return mock data
await Page.RouteAsync("**/api/orders", async route =>
{
var json = JsonSerializer.Serialize(new[]
{
new { Id = 1, CustomerName = "Alice", Total = 99.99 },
new { Id = 2, CustomerName = "Bob", Total = 149.50 }
});
await route.FulfillAsync(new RouteFulfillOptions
{
Status = 200,
ContentType = "application/json",
Body = json
});
});
await Page.GotoAsync("https://localhost:5001/orders");
await Expect(Page.Locator("[data-testid='order-row']")).ToHaveCountAsync(2);
}
```
### Waiting for Network Requests
```csharp
[Fact]
public async Task CreateOrder_SubmitForm_WaitsForApiResponse()
{
await Page.GotoAsync("https://localhost:5001/orders/new");
await Page.FillAsync("[data-testid='customer']", "Alice");
await Page.FillAsync("[data-testid='amount']", "99.99");
// Wait for the API call triggered by form submission
var responseTask = Page.WaitForResponseAsync(
response => response.Url.Contains("/api/orders") && response.Status == 201);
await Page.ClickAsync("[data-testid='submit']");
var response = await responseTask;
Assert.Equal(201, response.Status);
}
```
---
## CI Browser Caching
Downloading browser binaries on every CI run is slow (500MB+). Cache them to speed up builds.
### GitHub Actions Caching
```yaml
# .github/workflows/e2e-tests.yml
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SetuRelated in Cloud & DevOps
appbuilder-action-scaffolder
IncludedCreate, implement, deploy, and debug Adobe Runtime actions with consistent layout, validation, and error handling. Use this skill whenever the user needs to add actions to an App Builder project, understand action structure (params, response format, web/raw actions), configure actions in the manifest, use App Builder SDKs (State, Files, Events, database), deploy and invoke actions via CLI, debug action issues, or implement patterns such as webhook receivers, custom event providers, journaling consumers, large payload redirects, action sequence pipelines, and Asset Compute workers. Also trigger when users mention serverless functions in Adobe context, action logging, IMS authentication for actions, or cron-style scheduled actions.
orchestrating-datacloud
IncludedSalesforce Data Cloud product orchestrator for connect→prepare→harmonize→segment→act workflows. Use this skill when the user needs a multi-step Data Cloud pipeline, cross-phase troubleshooting, or data space and data kit management. TRIGGER when: user needs a multi-step Data Cloud pipeline, asks to set up or troubleshoot Data Cloud across phases, manages data spaces or data kits, or wants a cross-phase sf data360 workflow. DO NOT TRIGGER when: work is isolated to a single phase (use the matching phase-specific skill), the task is STDM/session tracing/parquet telemetry (use observing-agentforce), standard CRM SOQL (use querying-soql), or Apex implementation (use generating-apex).
github-project-automation
IncludedAutomate GitHub repository setup with CI/CD workflows, issue templates, Dependabot, and CodeQL security scanning. Includes 12 production-tested workflows and prevents 18 errors: YAML syntax, action pinning, and configuration. Use when: setting up GitHub Actions CI/CD, creating issue/PR templates, enabling Dependabot or CodeQL scanning, deploying to Cloudflare Workers, implementing matrix testing, or troubleshooting YAML indentation, action version pinning, secrets syntax, runner versions, or CodeQL configuration. Keywords: github actions, github workflow, ci/cd, issue templates, pull request templates, dependabot, codeql, security scanning, yaml syntax, github automation, repository setup, workflow templates, github actions matrix, secrets management, branch protection, codeowners, github projects, continuous integration, continuous deployment, workflow syntax error, action version pinning, runner version, github context, yaml indentation error
sf-datacloud
IncludedSalesforce Data Cloud product orchestrator for connect→prepare→harmonize→segment→act workflows. TRIGGER when: user needs a multi-step Data Cloud pipeline, asks to set up or troubleshoot Data Cloud across phases, manages data spaces or data kits, or wants a cross-phase `sf data360` workflow. DO NOT TRIGGER when: work is isolated to a single phase (use the matching sf-datacloud-* skill), the task is STDM/session tracing/parquet telemetry (use sf-ai-agentforce-observability), standard CRM SOQL (use sf-soql), or Apex implementation (use sf-apex).
fabric-cli
IncludedUse this skill for Fabric.so CLI workflows with the `fabric` terminal command: diagnose/install/login, search or browse a Fabric library, save notes/links/files, create folders, ask the Fabric AI assistant, manage tasks/workspaces, generate shell completion, check subscription usage, produce JSON output, and use Fabric as persistent agent memory. Do not use for Microsoft Fabric/Azure/Power BI `fab`, Daniel Miessler's Fabric framework, Python Fabric SSH, Fabric.js, or textile/fashion fabric.
lark
IncludedLark/Feishu CLI skills: lark-cli operations for docs, markdown, sheets, base, calendar, im, mail, task, okr, drive, wiki, slides, whiteboard, apps, approval, attendance, contact, vc, minutes, event. Use when the user needs to operate Lark/Feishu resources via lark-cli, send messages, manage documents, spreadsheets, calendars, tasks, OKRs, deploy web pages, or any Feishu/Lark workspace operations.