Stack conventions
Playwright end-to-end tests (TypeScript)
End-to-end and component tests with Playwright Test in TypeScript: role-based locators, web-first assertions and isolated parallel tests.
End-to-end and component tests with Playwright Test in TypeScript: role-based locators, web-first assertions and isolated parallel tests.
Used for: playwright.
Principles
- Test user-visible behaviour, not implementation details: what the user sees and does, through the rendered page.
- Locate elements the way users and assistive technology do:
getByRolewith an accessible name first, thengetByLabel,getByPlaceholder,getByText;getByTestIdwhen no user-facing handle exists; CSS/XPath last. - Use web-first assertions (
await expect(locator).toBeVisible(),toHaveText,toHaveURL) that retry until the condition holds. - Never use fixed sleeps (
page.waitForTimeout) or manual polling; wait on assertions, or on a specific response or event. - Keep every test isolated: its own browser context, its own data, no dependency on test order.
- Authenticate once with a setup project and reuse
storageState; do not log in through the UI in every test. - Create test data through APIs or fixtures, not by clicking through unrelated screens.
- Treat a flaky pass (passed on retry) as a failure to fix, not a success.
- Keep the compiler in strict mode and let types describe the real shape of data.
- Never use any in new code; use unknown and narrow it, or write the precise type.
- Validate data that crosses a trust boundary (HTTP, storage, environment) at runtime before giving it a static type.
- Model states with discriminated unions so impossible states cannot be represented.
Conventions
Accessibility
- Role-based locators double as an accessibility check: if an element cannot be found by role and name, report the accessibility gap.
- Where the repo uses it, run
@axe-core/playwrightscans on key pages. - When the change renders UI, use semantic HTML elements and accessible names for controls.
Concurrency
- Assume tests in a file and across files run in parallel workers; use unique data and never share mutable state between tests.
- Use
test.describe.configure({ mode: 'serial' })only when the scenario genuinely depends on order, and say why. - Use async and await consistently; do not mix callbacks and promises in one flow.
- Bound parallel requests to external services and handle partial failure with Promise.allSettled where appropriate.
- Pass AbortSignal to long-running operations so callers can cancel them.
Dependencies
- Keep
@playwright/testand installed browsers in step (npx playwright install --with-depsin CI). - Add packages with the repository's package manager and commit its lock file.
- Prefer packages that ship their own types; add @types packages as dev dependencies only.
- Do not add a dependency for a few lines of standard code.
Documentation
- Each spec's title and steps read as the acceptance criterion it verifies; reference the story or ticket id in a tag or annotation.
- Document exported functions and types with TSDoc comments that state behaviour, parameters and thrown errors.
- Let types document shapes; comment the reasons behind non-obvious decisions.
- Update the README or docs when commands, configuration or public APIs change.
Errors
- Let assertions fail with clear messages; add a custom message (
expect(x, 'why').toBe…) where the failure would be ambiguous. - Do not catch and ignore errors inside tests.
- Throw Error objects (or subclasses), never strings or plain objects.
- Narrow caught values from unknown before reading their properties.
- Await or explicitly handle every promise; do not leave floating promises.
- Add context when re-throwing by using the cause option of Error.
Logging
- Use traces (
trace: 'on-first-retry'), screenshots and videos on failure instead of console logging. - Use the repository's logger rather than console in application code.
- Log structured fields (ids, counts, durations) rather than whole objects.
- Never log tokens, passwords, cookies or personal data.
Naming
- Spec files end in
.spec.tsand are named after the feature; test titles state the behaviour ('saves a draft invoice'). - Page objects are named
<Page>Pageor<Component>Component, one per file. - Use camelCase for variables and functions, PascalCase for types, classes and enums, and UPPER_SNAKE_CASE for true constants.
- Do not prefix interfaces with I or types with T.
- Name files the way the repository already does (kebab-case or camelCase), consistently.
Performance
- Reuse authentication state; seed data via API; avoid full-page navigations that are not part of the behaviour.
- Use sharding in CI for large suites rather than reducing coverage.
- Avoid blocking the event loop with synchronous I/O or heavy CPU work in request handlers.
- Run independent async operations concurrently with Promise.all, with a bound on concurrency.
- Avoid repeated work inside loops and render paths; memoise only after measuring.
Security
- Read credentials from environment variables or the secret store; never hard-code them in specs or commit
storageStatefiles. - Point tests only at test/sandbox environments, never production data.
- Validate and limit all request input with a schema before use.
- Never pass user input to eval, new Function, child_process with a shell, or dynamic require.
- Use parameterised queries or the ORM's query builder for databases.
- Read secrets from environment or a secret store and never ship them to browser bundles.
- Escape or sanitise data rendered as HTML; prefer the framework's safe rendering.
Structure
- Put specs in the configured
testDir; page objects and fixtures in a sibling folder; never mix specs with application code. - Group related tests with
test.describe; use tags (@smoke) or projects to select suites. - Keep configuration (baseURL, retries, projects, reporters, trace) in
playwright.config.ts, not in specs. - Keep one responsibility per module and export only what other modules need.
- Use import type for imports used only as types.
- Prefer named exports over default exports unless the framework requires a default.
- Follow the repository's path aliases and module system (ESM or CommonJS); do not mix them.
Testing
- One behaviour per test; tests run in parallel by default — keep them independent.
- Use
test.fixme/test.skipwith a reason (and ticket) rather than commenting tests out. - Cover cross-browser projects where the app supports them; run Chromium at least in CI.
- Locate elements by role and accessible name (
getByRole,getByLabel), then test ids; never by CSS structure or XPath. - Never wait for a fixed time (
waitForTimeout); rely on auto-waiting locators and web-first assertions (await expect(locator).toBeVisible()). - Record traces on the first retry (
trace: 'on-first-retry') and keep retries for CI only. - End-to-end specs cover user journeys across the integrated system; keep unit-level checks in the application's own unit tests, and mock only third-party services with
page.route. - Done means tested: each new or changed behaviour in the acceptance criteria has a spec that fails without it.
Review checklist
- Do locators use role, label or text before test ids and CSS?
- Are all assertions web-first (awaited
expect(locator)…)? - Is the spec free of
waitForTimeoutand manual polling? - Is each test independent of other tests and of execution order?
- Is test data created per test (API/fixture) with unique values?
- Is authentication reused through
storageStaterather than repeated UI logins? - Are credentials read from the environment, never hard-coded?
- Do page objects contain actions and locators only, without sleeps?
- Does each test title state the behaviour it proves?
- Is any network mocking justified and limited to services outside the system under test?
- Would a flaky retry be surfaced rather than hidden?
- Does the change compile under the repository's strict compiler settings without new any or ts-ignore?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
playwright-list |
test | playwright | no |
npm-audit |
security | npm audit | no |
playwright-no-fixed-waits |
lint | grep | no |
playwright-trace-on-retry |
lint | grep | no |
tsc |
typecheck | tsc | no |
eslint |
lint | eslint | no |
biome |
lint | biome | no |
prettier |
format | prettier | no |
gitleaks |
security | gitleaks | no |