Playwright testing
Discipline for Playwright end-to-end tests. Use when the repo has `@playwright/test` in deps, a `playwright.config.{ts,cjs}`, a `tests/e2e/` directory, or the user mentions e2e, flaky tests, selectors, traces, or Playwright. Enforces selector hierarchy, fixtures-first imports, no-mock policy in protected paths, emulator-only project IDs, trace-on-failure artifact discipline, and structured flake triage.From its SKILL.md
npx -y skills add JordanChoo/acfs-agent-skills --skill playwright-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.0 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
playwright-testing
Decision logic for working safely in Playwright E2E suites. Goal: prevent the recurring failures — flaky selectors, mocks creeping into E2E, suites that pass locally but fail (or worse, succeed-against-prod) in CI.
Triggers
Load this skill when ANY of:
- Files:
playwright.config.{ts,cjs,js},tests/e2e/,e2e/,*.spec.tsunder a tests dir - Deps:
@playwright/test - User mentions: e2e, flaky test, playwright, trace, selector, getByRole, page.goto, fixtures (in test context)
Pre-flight: discover project conventions
Real Playwright suites in this environment ship discipline as scripts. Look first; respect them.
tests/e2e/fixtures/index.ts # custom test/expect re-export
tests/no-mock.protected.txt # paths where vi.mock / mockX is forbidden
tests/no-mock.allowlist.txt # path:line | reason exceptions
scripts/check-no-mock.mjs # CI gate: scans protected paths
scripts/check-e2e-test-logger.mjs # CI gate: enforces fixtures import + logger hooks
scripts/check-route-coverage.sh # CI gate: every route has an e2e
scripts/run-e2e-local-with-emulators.sh # canonical local run
If a check script exists, its rule is non-negotiable — the script will fail the PR. Read the script before editing tests, not after.
Project ID safety net (Firebase-emulated suites)
Both scry and philomena pin VITE_FIREBASE_PROJECT_ID=demo-test in playwright.config.*. The demo-* prefix is what tells the Firebase SDK "emulator only, no real auth/credentials accepted". Never edit a config to use a real project ID for E2E. If you see one, stop — that suite will hit prod Firestore.
Verification: grep -E 'PROJECT_ID|projectId' playwright.config.* — every value must start with demo-.
Playbooks
Writing a new E2E test
- Imports: import
testandexpectfromtests/e2e/fixtures(or whatever the project's fixtures path is), NOT from@playwright/testdirectly. Thecheck-e2e-test-logger.mjsgate enforces this — direct imports are rejected unless allowlisted, and allowlisted files must callattachAllHooks()/createTestLogger(). - Selectors, in priority order:
page.getByTestId('...')ifdata-testidis on the elementpage.getByRole('button', { name: /save/i })— semantic, accessibility-friendlypage.getByLabel(...)for form fieldspage.getByText(...)for static content- CSS / XPath only when nothing above works, and add a comment explaining why
- Waits: never
waitForTimeout(<number>)to "fix" a race. Useexpect(locator).toBeVisible(),page.waitForResponse(/api/), orwaitForLoadState('networkidle'). A barewaitForTimeoutis a flake bomb with a delayed fuse. - Auth: reuse
storageStatefrom a setup project — never log in fresh per test. If the project doesn't have one, that's the first thing to add. - Mocks: defer to project policy. If
tests/no-mock.protected.txtcovers the path you're editing,vi.mock/mockXis forbidden — use the emulator instead. To get an exception, add apath:line | reasonline totests/no-mock.allowlist.txt; the reason is reviewed.
Debugging a failing test
Order matters — classify before fixing:
- Did it fail in CI but pass locally? → Pull the trace. Configs use
trace: 'retain-on-failure'or'on-first-retry'; trace is inplaywright-report/ortest-results/.npx playwright show-trace <path>opens it. Don't guess from stdout. - Classify the flake before changing anything:
- (a) timing — assertion before the UI settled. Fix: replace
waitForTimeoutwith a proper wait, or assert on the post-condition. - (b) shared state — test N depends on test N-1's leftovers. Fix: per-test fresh emulator data (Firestore emulator REST clear), or
test.describe.serialif ordering is genuinely required. - (c) port collision — two emulator suites on the same port. Fix: integration suite uses 18080/19199 (different from default 8080/9199); check the project's
run-firestore-integration.sh. - (d) real race in app code — the test is correctly catching a bug. Fix the app, not the test.
- (a) timing — assertion before the UI settled. Fix: replace
- Never disable retries, traces, screenshots, or video to "make CI green." That deletes the diagnostics you need next time.
Adding a route or feature
If scripts/check-route-coverage.sh exists, every new route needs at least one e2e. The gate will block the PR otherwise. Add the spec in the same change, not as a follow-up bead.
Running locally
- Default:
npm run test:e2e:local(or whatever the project's wrapper is). It usually starts emulators, waits for them, then runs Playwright. - For UI debugging:
npm run test:e2e:ui(Playwright UI mode) ornpx playwright test --debug. - Single test:
npx playwright test tests/e2e/foo.spec.ts -g "test name". - Reuse-existing-server: configs set
reuseExistingServer: !process.env.CI, so a long-runningnpm run devwill be picked up — no need to restart.
Parallelism and CI
fullyParallel: trueis the default;workers: 1is set in CI to avoid emulator port contention.- For emulator-backed integration tests (vitest, not playwright): use
--no-file-parallelismbecause they share fixed ports and clear global state. - If you add a test that mutates a singleton (auth user, global config), put it in its own
test.describe.serialblock.
Red flags — stop and ask
- A spec with
import { test, expect } from '@playwright/test'(not from fixtures) in a project that hascheck-e2e-test-logger.mjs— the gate will fail - Adding
vi.mock(...)to a file intests/no-mock.protected.txt - Disabling
trace,retries,screenshot, orvideoto fix flakiness - A
playwright.config.*change that pointsPROJECT_IDat anything other thandemo-* waitForTimeout(<seconds>)added to "stabilize" a test- Replacing
getByRole/getByTestIdwith brittle CSS to make a test pass faster - Deleting an e2e spec instead of fixing it (recurring user concern: do not delete tests without permission)
What to read first in an unfamiliar Playwright suite
In order:
playwright.config.*— testDir, projects, baseURL, env, trace policy, webServertests/e2e/fixtures/index.ts— the custom test/expect contractscripts/check-*.mjsandscripts/check-*.sh— the discipline gatestests/no-mock.protected.txtandtests/no-mock.allowlist.txt— mock policy scope- One existing spec next to what you're working on — copy its setup/teardown shape
- The project's
AGENTS.md/CLAUDE.mdfor any test-discipline overrides
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.