Use when designing or fixing a Playwright end-to-end test suite, debugging flaky tests, choosing locator strategies (getByRole vs CSS vs test-id), structuring fixtures and auth-state reuse, configuring parallelism and sharding, mocking third-party APIs via route(), or wiring trace-on-first-retry into CI. Triggers: "tests are flaky", page.waitForTimeout, page.locator with brittle CSS, login runs in every test, third-party API takes test offline, "should I use sleep here", parallel mode, sharding across CI machines, soft vs hard assertions, trace.zip not available on CI failure. NOT for unit testing (Vitest/Jest), Cypress migration playbooks, mobile native testing (Detox/XCUITest), or visual regression testing as a primary concern.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Use when designing or fixing a Playwright end-to-end test suite, debugging flaky tests, choosing locator strategies (getByRole vs CSS vs test-id), structuring fixtures and auth-state reuse, configuring parallelism and sharding, mocking third-party APIs via route(), or wiring trace-on-first-retry into CI. Triggers: "tests are flaky", page.waitForTimeout, page.locator with brittle CSS, login runs in every test, third-party API takes test offline, "should I use sleep here", parallel mode, sharding across CI machines, soft vs hard assertions, trace.zip not available on CI failure. NOT for unit testing (Vitest/Jest), Cypress migration playbooks, mobile native testing (Detox/XCUITest), or visual regression testing as a primary concern.
allowed-tools
Read,Grep,Glob,Edit,Write,Bash
metadata
{"category":"Code Quality & Testing","tags":["playwright","e2e","testing","automation","flaky-tests","browser-testing"],"provenance":{"kind":"first-party","owners":["port-daddy"]},"pairs-with":[{"skill":"ideal-web-app-builder","reason":"The web app that skill scaffolds is what this suite exercises; testable markup (roles, labels, test-ids) is designed there"},{"skill":"ux-friction-analyzer","reason":"Both drive real user journeys in the browser; friction findings become the high-value flows the E2E suite must cover"}],"io-contract":{"kind":"deliverable","consumes":["[Truncated]","[Truncated]"],"produces":["[Truncated]","[Truncated]"]}}
Playwright E2E Design
Most "Playwright is flaky" stories come from three things: brittle selectors, manual sleeps instead of web-first assertions, and tests that share state. The official Playwright best-practices doc, browserstack/testdino field guides, and the auto-waiting docs all converge on the same playbook. (Playwright — Best Practices, Playwright — Auto-waiting)
The compressed rule:
getByRole / getByTestId + web-first expect() + one fresh storageState per test = not flaky
Last resort. Comment why a higher tier didn't fit.
// ✓ Resilientawait page.getByRole('button', { name: 'Save' }).click();
await page.getByLabel('Display name').fill('Alice');
// ✗ Fragile — designer changes a class name and everything breaksawait page.locator('button.btn.btn-primary.save-action').click();
The Playwright doc's exact framing: "Designer changes to CSS classes break brittle selectors. User-facing attributes remain stable across refactoring." (playwright-best-practices)
Web-first assertions
expect() on a Playwright locator auto-retries until the condition is met or the timeout expires. This is the single most important flake-elimination tool. (Playwright — Assertions)
// ✗ Read-once snapshot. Race condition guaranteed.expect(await page.getByText('Welcome').isVisible()).toBe(true);
// ✓ Retries until visible or timeout. No race.awaitexpect(page.getByText('Welcome')).toBeVisible();
// ✓ Same for text content, URL, count, value, …awaitexpect(page.getByRole('row')).toHaveCount(10);
awaitexpect(page).toHaveURL(/\/dashboard$/);
awaitexpect(page.getByLabel('Email')).toHaveValue('alice@example.com');
Anything that synchronously reads from the DOM (isVisible(), textContent(), count()) is a snapshot. It does not retry. The await expect(locator).toX() form is the retrying form.
Replace sleeps with auto-waiting
Playwright's actionability checks (visible, stable, enabled, receives events) run automatically before every action. (Playwright — Actionability) page.waitForTimeout(N) is almost always wrong:
// ✗ Hopes the API resolved within 2s. Sometimes it didn't.await page.click('text=Save');
await page.waitForTimeout(2000);
expect(await page.locator('.toast').textContent()).toBe('Saved');
// ✓ No timer. Wait for the thing that actually marks success.await page.getByRole('button', { name: 'Save' }).click();
awaitexpect(page.getByRole('status')).toHaveText('Saved');
If you genuinely need to wait for a network call, use page.waitForResponse(/api\/save/) — but usually a UI assertion is what you want.
Auth state reuse
Logging in inside every test is slow and flaky. Playwright's pattern: a one-time setup project that logs in and saves storageState to disk, then every test starts from that state. (playwright-best-practices)
For multi-role suites, run a setup per role and use test.use({ storageState: ... }) per file.
Test isolation
Each test must be independent. Playwright's docs are blunt: "Each test should be completely isolated from another test and should run independently with its own local storage, session storage, data, cookies etc." (playwright-best-practices)
In practice that means:
A fresh browser context per test (Playwright does this by default).
Test data isolated per test — typically by creating uniquely-named fixtures, or by tearing down via API after the test.
No relying on test execution order. Tests in test.describe.parallel run in parallel; tests anywhere can run on different workers.
test.beforeEach(async ({ request }) => {
// Clean slate via API, not via UI.await request.delete(`/api/test-utilities/clean`);
});
Mock at the network layer
The docs say: "Don't try to test links to external sites or third party servers that you do not control." (playwright-best-practices)
For Stripe/Auth0/etc.: don't drive their UIs in your E2E suite. Mock the redirect-back step or use their test-mode endpoints.
Trace-on-first-retry
Traces give you a timeline + DOM snapshots + network log of a failure — gold for debugging CI flake. The docs recommend running them only on retry to keep happy-path runs cheap: (playwright-best-practices)
In CI, upload test-results/ as an artifact so traces are downloadable. Open with npx playwright show-trace trace.zip.
Parallelism and sharding
// File-level parallelism (within a file).
test.describe.configure({ mode: 'parallel' });
# .github/workflows/e2e.yml — shard across N CI machines.strategy:matrix:shard: [1/4, 2/4, 3/4, 4/4]
steps:-run:npxplaywrighttest--shard=${{matrix.shard}}
Sharding distributes tests across runners (playwright-best-practices). Pair with github-actions-matrix-patterns for the CI side. Keep individual tests under ~30s; long tests bottleneck a shard.
Page-object pattern (lightweight)
Heavy POM hierarchies become their own maintenance burden. The lightweight version: helpers that wrap multi-step user journeys. Don't wrap every locator.
A helper layer is fine when it represents a real user workflow. A "ButtonPage" wrapping a single button is bureaucracy.
Soft assertions
Use to collect multiple failures before failing the test:
await expect.soft(page.getByTestId('total')).toHaveText('$108.69');
await expect.soft(page.getByTestId('shipping')).toHaveText('$0.00');
await page.getByRole('button', { name: 'Place order' }).click();
// Test fails at the end if any soft assertion failed,// but you see all the violations in one run.
Useful for end-of-test verification rows. Don't use them where a failure should stop further interaction.
Snapshots live in __screenshots__/ per platform. Maintenance cost is real — typically reserve for high-value canvases (login page, checkout). Better to spend the budget on functional E2Es first.
Anti-patterns
Brittle CSS selectors
Symptom: A designer changes a class, 20 tests break.
Diagnosis: Tests use .btn.btn-primary.save-action-style selectors.
Fix:getByRole, getByLabel, getByTestId (with intentional data-testid). CSS only as last resort, with a comment.
waitForTimeout everywhere
Symptom: Suite is slow AND flaky.
Diagnosis: Sleeps mask race conditions sometimes; let them through other times.
Fix:await expect(locator).toX() web-first assertions. Replace each waitForTimeout with the actual condition you were waiting for.
Login per test
Symptom: Suite is dominated by login latency; auth provider rate-limits in CI.
Diagnosis: Every test runs the full login flow.
Fix: Setup project + storageState reuse. (playwright-best-practices)
Reading state with synchronous methods then asserting
Symptom: Test passes 9 of 10 runs.
Diagnosis:expect(await locator.textContent()).toBe('X') reads once, doesn't retry.
Fix:await expect(locator).toHaveText('X'). Same for visibility, count, value, attribute.
Tests share data via DB
Symptom: Test A passes alone, fails when B runs first.
Diagnosis: Both write to the same row; no isolation.
Fix: Per-test unique data (suffix with test.info().testId). Reset via API in beforeEach.
Hitting third-party services live
Symptom: Stripe / Auth0 outage takes the suite red. Quota errors on busy CI days.
Diagnosis: E2E driving real third parties.
Fix:page.route() mocks. Test-mode endpoints where mocks aren't realistic. (playwright-best-practices)
Single-process serial run
Symptom: Suite takes 45 minutes; engineers stop running it pre-PR.
Diagnosis: No parallelism, no sharding.
Fix: Enable parallel mode within files; shard across CI workers; cap individual test latency.
Trace artifacts not retained
Symptom: A flake on CI; no trace, no screenshot, no video.
Diagnosis:trace: 'off' or no artifact upload.
Fix:trace: 'on-first-retry', upload test-results/ as a CI artifact. (playwright-best-practices)
Quality gates
Test: suite runs in parallel by default; mode: 'parallel' in test.describe.configure or globally.
Test: flake rate measured per nightly run; alert if > 1% over a week.
Test:npx playwright test --grep @smoke runs in < 2 min for the smoke subset.
No page.waitForTimeout calls in production tests. CI grep fails on hits.
No await locator.isVisible() / textContent() followed by a sync expect. Lint or grep enforces.
All locators are getByRole / getByLabel / getByTestId first; CSS as last resort with a comment.
Auth-state reuse via storageState; login flow itself tested once in a setup project.
Third-party calls mocked via page.route() or test-mode endpoints. CI denies network egress to unrelated hosts.
trace: 'on-first-retry' configured; CI uploads test-results/ as an artifact on failure.
retries: 2 on CI, retries: 0 locally (so flake is visible to authors).
Each test creates its own data with unique IDs; beforeEach cleans state via API.
Suite shards across CI workers (--shard=N/M); see github-actions-matrix-patterns for the matrix.
Individual test p95 < 30s. Long tests broken up.
NOT for
Unit / component testing — Vitest, Jest, Vitest Browser Mode, RTL. No dedicated skill yet.
Cypress migration specifically — overlapping but distinct. No dedicated skill yet.
Mobile native E2E (Detox, XCUITest, Espresso) — different runtime.
Visual regression as the primary concern — Chromatic, Percy, Argos are dedicated tools.
Load testing — k6, Gatling, Artillery. Different goal.
Security testing — ZAP, Burp. Different threat model.
CI matrix design for the suite — → github-actions-matrix-patterns.
Deterministic Audit
Before adopting (or reviewing) a suite design, write it as a JSON plan matching
schemas/playwright-e2e-design-plan.schema.json and run it through the deterministic auditor:
auditPlaywrightE2eDesign(plan) (in scripts/playwright_e2e_design_audit.mjs) turns this
skill's compressed rule — getByRole/getByTestId + web-first expect() + fresh state per test
— and its Quality Gates into machine-checkable rules over structured fields, no keyword
matching: snapshot-read assertions (the guaranteed race), remaining waitForTimeout calls,
CSS/XPath as the primary locator strategy, login-per-test instead of storageState reuse,
live third-party calls, missing test isolation, trace: off (or no artifact upload), inverted
retry policy (retries locally, none on CI), a serial-only suite, and tests blowing the 30s p95
budget. It returns { pass, score, findings, recommendations } so a reviewer or CI gate can
reject a flake-prone design without re-deriving the playbook. examples/sample-input.json is
a sharded, mocked, storage-state suite plan that audits pass: true. Version history lives in
CHANGELOG.md.