This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
PlaywrightNative is a .NET port of Microsoft Playwright for automating Chromium, Firefox, and WebKit browsers. Inspired by the original Playwright library, adapted to C# idioms and .NET practices.
Public launch entry points match upstream playwright-dotnet:
Playwright.CreateAsync() returns IPlaywright, then
playwright.Chromium.LaunchAsync() / Firefox / Webkit.
There is no Node.js driver. Static Playwright.Chromium /
LaunchChromiumAsync helpers remain for convenience.
Every portable upstream tests/page and tests/library title already has a
local [PlaywrightTest] twin. The remaining upstream titles are Node-only
internals. tasks/validate-playwright-tests.py checks this.
# Build
dotnet build ./src/PlaywrightNative.sln
# Chromium — matches CI (ubuntu/windows, headless + headful)
PRODUCT=CHROMIUM dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings
# WebKit — matches CI (macos-14 + ubuntu, headless)
PRODUCT=WEBKIT dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings
# Firefox — launches today; not in the CI matrix
PRODUCT=FIREFOX dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings
# Run a single test class
PRODUCT=CHROMIUM dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --filter "ClassName=PlaywrightNative.Tests.PlaywrightTests" -f net10.0
# Run a single test method
PRODUCT=CHROMIUM dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --filter "FullyQualifiedName~ShouldCreateNewPage" -f net10.0
# Lint (run before pushing — CI enforces this)
dotnet format whitespace ./src/PlaywrightNative.sln --verify-no-changes && dotnet format style ./src/PlaywrightNative.sln --verify-no-changesEnvironment variables: PRODUCT (CHROMIUM/FIREFOX/WEBKIT), HEADLESS (true/false), SLOW_MO (ms delay).
Always set PRODUCT explicitly when running tests.
HTTPS tests require: dotnet dev-certs https -ep src/PlaywrightNative.TestServer/testCert.cer
These rules cause build failures. Violating them will break CI:
| Rule | Requirement |
|---|---|
TreatWarningsAsErrors |
All warnings are errors in library project (PlaywrightNative.csproj) |
AnalysisMode=AllEnabledByDefault |
All code analysis rules active by default (StyleCop 1.2, Roslynator 4.12, VSTHRD analyzers) |
SX1309 (error) |
Private fields must use _camelCase prefix |
CA2007 (error) |
ConfigureAwait(false) required on every await |
VSTHRD200 (error) |
Async methods must have Async suffix |
CA1725 (error) |
Parameter names must match base declaration |
CA1823 (error) |
No unused private fields |
CA1062 (disabled in ruleset) |
Validate arguments of public methods — off globally; thin *CompatExtensions passthroughs would otherwise need null checks on every parameter |
CA2000 (disabled in ruleset) |
Dispose objects before losing scope — off globally; many false positives on intentional ownership transfer (returned browsers/pages/handles, HttpClient handlers, wrapped streams) |
csharp_style_var_for_built_in_types (error) |
Never use var for built-in types (int, string, bool, etc.) — use explicit types |
dotnet_style_readonly_field (error) |
Fields that are never reassigned must be readonly |
SA1204/SA1202 |
Static members before instance members. Never place a static method after instance methods |
| XML docs | Required on all public APIs (CS1591) |
| No "puppeteer" | CI script checks that no .cs file references "puppeteer" |
| Line endings | CRLF required on all .cs files (enforced by .editorconfig) |
varelsewhere: allowed by suggestion, but prefer explicit types when type isn't apparent.- 4-space indentation, Allman braces,
usingdirectives outside namespace. systemusings sorted first.- Expression-bodied members preferred as refactoring suggestion.
- Avoid
this.qualification.
Directory.Build.props sets <LangVersion>latest</LangVersion>. Modern C# features are available.
PlaywrightNative/ ← Main library (net10.0)
├── Chromium/ ← Direct CDP (CRPage, CRNetworkManager, ...)
├── Firefox/ ← Direct Juggler (FFPage, FFConnection, ...)
├── WebKit/ ← Direct WIP (WKPage, WKConnection, ...)
├── Transport/ ← Browser pipe / WebSocket I/O (not a driver)
├── Helpers/ ← Internal utilities
└── Contracts/ ← Interface definitions
PlaywrightNative.NUnit/ ← NUnit fixtures + PlaywrightTestAttribute
├── PageTest.cs / ContextTest.cs / BrowserTest.cs / PlaywrightTest.cs
├── WorkerAwareTest.cs / SkipAttribute.cs
└── TestExpectations/ ← JSON skip/fail expectations per browser/platform
PlaywrightNative.Tests/ ← Test suite (NUnit 4.1, net10.0)
├── BaseTests/ ← BrowserLauncher, BrowserExecutableFixture, PageTestEx
├── Chromium/ ← CRTestBase protocol-level Chromium tests
└── *.cs ← Public-API tests plus fetcher / installer unit tests
PlaywrightNative.TestServer/ ← HTTP/HTTPS server for test fixtures
- Direct browser connections:
Playwright.CreateAsync()thenplaywright.Chromium/Firefox/Webkit.LaunchAsync()spawn the browser and speak its native protocol.Transport/isPipeTransport,WebSocketTransport, andBrowserProcessManager— browser I/O, not leftover Node-driver plumbing. - InternalsVisibleTo: The main library exposes internals to tests via strong-name signed assemblies (keys in
src/keys/). - ConfigureAwait(false) everywhere: All async code uses
.ConfigureAwait(false)to avoid deadlocks in sync-over-async scenarios. - Typed protocol messages: Do not poke at
JsonElementwithTryGetPropertyfor protocol messages with a known shape. Create a proper type for the message (a class/record with[JsonPropertyName]for the wire names) and deserialize into it. Reserve rawJsonElementfor genuinely dynamic/unknown-shape payloads (e.g. by-value evaluation results).
- Framework: NUnit 4.1 with
Microsoft.NET.Test.Sdk 17.12. - NUnit fixtures:
PlaywrightTest→BrowserTest→ContextTest→PageTestinPlaywrightNative.NUnit. Tests wrap those asPlaywrightTestEx/BrowserTestEx/ContextTestEx/PageTestEx(adds the shared HTTP/HTTPS servers). The oldPlaywrightNativeBaseTesthierarchy is gone. - Chromium protocol tests:
Chromium/CRTestBaselaunches viaChromiumBrowserTypeand exercisesCRPage/CRBrowserdirectly. [PlaywrightTest]attribute: Links each test to its upstream spec file. Also implements the test expectations system — checksTestExpectations.local.jsonto skip/fail tests per browser/platform/mode.[Skip]:PlaywrightNative.NUnit.SkipAttributeskips by browser/OS flags. Also seeBrowserLauncher.SkipUnlessChromium()for Chromium-only Direct tests.- Test pattern:
[PlaywrightTest("spec-file.ts", "test name")]+[Test, Timeout(TestConstants.DefaultTestTimeout)]. - Assertions: NUnit constraint model —
Assert.That(value, Is.EqualTo(...)),Does.Contain(...),Has.Exactly(n).Items, etc. TestConstants: Provides server URLs (port 8081/8082), browser product detection, default launch/timeout options.- Test expectations:
TestExpectations.local.jsoninPlaywrightNative.NUnitcontrols which tests are expected to skip/fail/pass per browser+platform+mode combination. Tests markedskip/fail/timeoutare automatically skipped viaPlaywrightTestAttribute. - Golden rule: Tests must always match upstream. Never modify tests to match local code.
CI (.github/workflows/dotnet.yml) runs net10.0 with test.runsettings (2.5-hour
session timeout). Jobs are split into shards by test class
(.github/workflows/compute-test-shard-filter.sh sets TEST_FILTER). Matrix:
- Chromium headless + headful on
ubuntu-latestandwindows-latest(PRODUCT=CHROMIUM, fullPlaywrightNative.Testssuite) - WebKit headless on
macos-14andubuntu-latest(PRODUCT=WEBKIT, full suite; known gaps live inTestExpectations.local.json)
Firefox is not in the CI matrix. Browser binaries come from BrowserFetcher,
cached under PLAYWRIGHT_BROWSERS_PATH.
When porting from upstream TypeScript to C#:
| TypeScript | C# |
|---|---|
camelCase |
PascalCase (public), _camelCase (private fields) |
Promise<T> |
Task<T> |
| interfaces | Prefix with I |
| events | C# event pattern (event EventHandler<T>) |
| getter functions | Property accessors |
async function foo() |
async Task FooAsync() |
If the upstream file contains "Copyright (c) Microsoft Corporation.", add that copyright to the local file.
- Every test inspired by the upstream repo must carry a
[PlaywrightTest("<spec-file>.spec.ts", "<original test name>")]attribute targeting the original upstream test it is derived from. Name the C# method as the PascalCase form of the upstream test name. - Every file generated by reading upstream code must include
* Modifications copyright (c) Microsoft Corporation.in its header, even when the new file does not map 1:1 to a single upstream file.
These repositories are used as reference. You may run git commands to update them locally.
- upstream:
../../microsoft/playwright— canonical Playwright Node.js implementation. Upstream code:packages/playwright-core/src/server/. Upstream tests:tests/library/andtests/page/. - upstream-dotnet:
../../microsoft/playwright-dotnet— Microsoft's official .NET Playwright client. Reference for .NET API conventions.
When porting, check both upstream (canonical logic) and upstream-dotnet (.NET-idiomatic patterns).
- Always verify which branch you are on before commits or PRs.
- Never commit to main/master.
- Create a new branch immediately when starting work. Confirm with
git branch --show-current.
- Investigate flaky test root causes (race conditions, non-deterministic ordering, frame assumptions). Never skip tests or add them to expected failures as a workaround.
- When running tests locally on macOS, WebKit may need extra native deps. Chromium is the primary target for local development.
When you encounter a new code style error during builds (SA/CA rule failures), update the "C# Code Style" section above with the new rule and how to comply. This keeps the document accurate as the codebase evolves.