Write one Playwright end-to-end test covering this user flow: $ARGUMENTS
If $ARGUMENTS is empty, ask the user which flow to test and stop; do not guess.
1. Detect the existing setup before writing anything
- Read
playwright.config.*fortestDir,baseURL,projects,webServer, and any global setup/auth storageState. If there is no Playwright config, say so and propose installing it (npm init playwright@latest) instead of inventing one. - Open 2-3 existing specs in
testDirand mirror their conventions: file naming, fixture imports (projecttestfixture vs bare@playwright/test), page-object usage, auth pattern, and how they get a base URL. Never hardcodehttp://localhost:PORT; rely onbaseURLand relative paths. - Grep the app source for the routes/components in the flow to learn the real roles, labels, and existing
data-testids. Do not invent selectors from assumption.
2. Write the test
- Place it in
testDirwith the project's naming pattern (e.g.<flow>.spec.ts). Onetest()per flow, wrapped in atest.describeif peers do. - Structure the body as explicit Arrange / Act / Assert steps, optionally grouped with
test.step()for readable traces. - Selectors, in this priority:
getByRole(name:),getByLabel,getByPlaceholder,getByText, thengetByTestId. Never CSS/XPath tied to structure, nth-child, or generated class names. If no accessible handle exists, add adata-testidto the component and use it. - Assertions: only web-first, auto-retrying
expect(locator)(toBeVisible,toHaveText,toHaveURL,toBeEnabled). Assert observable user-facing outcomes, not implementation. Never assert on a bare boolean snapshot (expect(await locator.isVisible()).toBe(true)). - Actions auto-wait, so chain them directly. Await every locator action and every assertion.
- Stub non-deterministic and third-party dependencies inside the flow (payment gateways, analytics, maps, email/SMS, feature flags,
Date.now/random-driven UI) withpage.route(url, route => route.fulfill(...))so the test asserts your app's behavior, not a live external service. Register routes before the navigation that triggers them. Do not mock the first-party API you are seeding through — exercise it for real.
3. Independence and state
- The test must pass alone and in any order, repeatedly. No dependence on a prior test or leftover data.
- Seed and tear down state via API request context, DB fixture, or the project's existing seeding helper — never by clicking through unrelated UI to set up.
- Make identifiers unique per run (timestamp/uuid) so reruns don't collide. Clean up created records in
afterEach/afterAllor use an isolated/transactional test account. - Reuse the project's auth storageState if present; otherwise authenticate in a fixture or
beforeEach, not inline in the flow under test.
4. Never
- Never
waitForTimeout,sleep, or arbitrary delays — wait on a condition (locator state,waitForURL,waitForResponse) instead. - Never
page.waitForSelectorwhen a web-firstexpectcovers it. - Never leave
test.only,console.log, commented code, ornetworkidlewaits.
5. Verify before finishing
- Ensure the app is reachable first. If
playwright.config.*defines awebServer, Playwright starts it automatically. If it does not, the run targets an already-running server atbaseURL— start the dev server (or pointbaseURLat a live environment) before running, or the spec fails instantly on a dead URL and no selector/wait/seed fix will help. - Find the project's real e2e command in
package.jsonscripts(e.g.npm run test:e2e,yarn e2e,pnpm test:e2e) and use it, appending the file path so only this spec runs. Fall back tonpx playwright test <file>only if no such script exists. - The spec must pass. If it flakes or fails, fix the selector/wait/seed/mock — do not paper over it with a timeout or a retry count. Report the exact command run, pass/fail result, and the final test file path.