./build.sh --download-driver # download the Playwright driver
dotnet build ./src # build the entire solutionFollow .github/workflows/tests.yml for the canonical sequence.
# Install browsers (pick one: chromium, firefox, webkit)
pwsh src/Playwright/bin/Debug/netstandard2.0/playwright.ps1 install --with-deps chromium
# Run tests
BROWSER=chromium dotnet test ./src/Playwright.Tests/Playwright.Tests.csproj -c Debug -f net8.0 --logger:"console;verbosity=detailed"Tests take ~8 minutes. Always save output to a file and grep from there:
BROWSER=chromium dotnet test ./src/Playwright.Tests/Playwright.Tests.csproj \
-c Debug -f net8.0 --logger:"console;verbosity=detailed" > /tmp/test-results.txt 2>&1
grep "^ Failed" /tmp/test-results.txt # list failures
tail -5 /tmp/test-results.txt # summary- Public API interfaces (e.g.
src/Playwright/API/Generated/IPage.cs) are generated by../playwright/utils/doclint/generateDotnetApi.jsfrom the upstream API docs. Do not hand-edit these — update the generator instead. - The generator uses
classNameMapfor type mappings (e.g.Disposable→IAsyncDisposable,boolean→bool). Add entries there when a Playwright type should map to a different .NET type. - The generator skips generating interface files for types like
TimeoutExceptionandIAsyncDisposablethat map to built-in .NET types. - Supplement interfaces (
src/Playwright/API/Supplements/) are hand-written and extend the generated interfaces with .NET-specific overloads. - Internal implementations live in
src/Playwright/Core/(namespaceMicrosoft.Playwright.Core). These implement both the generated and supplement interfaces.
- All Playwright objects extend
ChannelOwnerand communicate viaSendMessageToServerAsync. Connection.cshas a factory switch that creates the rightChannelOwnersubclass based onChannelOwnerType.- New channel object types require: enum entry in
ChannelOwnerType.cs, case inConnection.cs, initializer inTransport/Protocol/Generated/, and aCore/class. - Public APIs should use .NET standard types (e.g.
IAsyncDisposable) not custom Playwright types. Internal helpers (e.g.Disposableclass inCore/) stay internal.
Before committing, run dotnet format ./src/ -v:diag and fix style issues.
Semantic commit messages: label(scope): description
Labels: fix, feat, chore, docs, test, devops
git checkout -b fix-39562
# ... make changes ...
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix(proxy): handle SOCKS proxy authentication
Fixes: https://github.com/microsoft/playwright/issues/39562
EOF
)"
git push origin fix-39562
gh pr create --repo microsoft/playwright --head username:fix-39562 \
--title "fix(proxy): handle SOCKS proxy authentication" \
--body "$(cat <<'EOF'
## Summary
- <describe the change very! briefly>
Fixes https://github.com/microsoft/playwright/issues/39562
EOF
)"Never add Co-Authored-By agents in commit message.
Never add "Generated with" in commit message.
Never add test plan to PR description. Keep PR description short — a few bullet points at most.
Branch naming for issue fixes: fix-<issue-number>
- Do not include "co-authored" block in the commit message.