--- name: playwright-spec-authoring description: "Turn a recorded running list of playwright-cli steps into a Playwright spec tests/.spec.ts that loads storageState, contains no login or credentials, reuses or creates a per-site helpers file (manual-login wait, overlay dismissal, re-auth wait, downloads, popups, native print stub) and asserts every step. Use when: write the spec, generate the test file from the running list, add a section to an existing spec, which helper to use, what assertion to add after a step, create playwright.config.ts." license: MIT argument-hint: "Spec name and the running list (or 'add
to tests/.spec.ts')" metadata: phase: playwright --- # Writing a spec from a running list Input: the numbered running list produced while recording (one `await page...` line per step with notes). Output: `tests/.spec.ts` that passes with `npx playwright test tests/.spec.ts`. ## 1. Project scaffolding (only if missing) ```bash ls package.json playwright.config.ts tests/helpers 2>/dev/null ``` - No `package.json` → `npm init -y && npm i -D @playwright/test @types/node && npx playwright install chromium`. - No `playwright.config.ts` → copy [config template](./assets/playwright.config.template.ts). It runs headed, 1 worker, with `storageState: '.auth/state.json'`, a long test timeout (manual login) and trace/video on failure. - Add `.auth/`, `.playwright-cli/`, `test-results/`, `playwright-report/`, `node_modules/` to `.gitignore`. - No `tests/helpers/.ts` → copy [helpers template](./assets/site-helpers.template.ts) to `tests/helpers/.ts` and adapt the three site-specific constants at the top (logged-in URL pattern, login URL pattern, overlay close-button names, re-auth heading). Everything else is generic. Look at an existing spec for the same site first and mirror its structure. ## 2. Mandatory frame ```ts import { test, expect } from '@playwright/test'; import { STORAGE_STATE, openLandingPage, /* …other helpers */ } from './helpers/'; test.use({ storageState: STORAGE_STATE }); test(': ', async ({ page }) => { await stubPrint(page); // only if the journey presses a Print button await openLandingPage(page); // goto + manual-login wait + overlay dismissal + state refresh … }); ``` Never write a login step, never fill credentials, never hard-code snapshot refs (`f24e316`), never `test.only`. ## 3. Translate each recorded step | Recorded step / note | Write in the spec | |----------------------|-------------------| | `goto()` + *(welcome dialog, conditional)* | `await openLandingPage(page);` | | *(manual, not recorded)* login / 2FA / captcha | nothing — the helper waits for the user | | a step after which the site showed a re-authentication screen | right after the click: `await passSecurityCheckIfNeeded(page, );` | | a click that printed `Downloaded file …` | `const d = await downloadFrom(page, ); expect(d.suggestedFilename()).toMatch(/$/i);` | | a click after which `tab-list` showed a second tab | `await expectPopupFrom(page, , //);` | | a Print button *(native dialog)* | `await .click(); await expectPrintCalled(page);` | | a menu opened by clicking an unnamed icon (`getByTestId(...)`) | wrap in a helper with retry (`openMenu` in the template) because headers often re-render after navigation | | chip radio/checkbox clicked via its label | keep the label click, then `await expect(page.getByRole('radio', { name: '