# Browser QA

> Drive a real browser (Playwright) to validate user flows end-to-end — click buttons, fill forms, assert on rendered output, screenshot the moment a step breaks. Use when the user says "test this flow", "run the e2e tests", "verify the signup works", "qa my app", "does the checkout work", or asks Claude to confirm a UI change actually behaves correctly in a browser. Closes the gap between "code compiles" and "user flow works".

- Skill: `ak-ship/browser-qa` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ak-ship/browser-qa`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ak-ship/browser-qa/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ak-ship (https://skillmd.com/u/ak-ship)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ak-ship/browser-qa

---


# browser-qa — verify with the browser, not by guessing

## When to use this skill

Trigger when the user wants confirmation that a *user-visible flow* actually works. Strong signals:

- "test the signup flow", "verify checkout", "qa this page"
- After implementing a UI change, before declaring it done
- When a unit test passes but the user says "it's still broken"
- "screenshot what the page looks like at <state>"

Do *not* trigger for: pure logic tests (use `test-architect`), API contract tests (use `api-architect`), or for code that has no UI surface.

## The output contract

A Playwright run that produces:

1. A test file (or files) that another engineer can read in 60 seconds and understand the flow.
2. A pass/fail result with the specific failing step and a screenshot of that step.
3. No flaky waits — every wait is anchored to an observable condition (a network response, a visible element, a URL change).
4. A trace file the user can open in `npx playwright show-trace` for any failure.

## Workflow

### 1 — Reconnaissance

- Is Playwright already installed? Check `package.json` and `playwright.config.{ts,js}`.
- If not: `npm i -D @playwright/test && npx playwright install --with-deps chromium`.
- Read the existing test directory (`tests/`, `e2e/`, `playwright/`) to learn the project's conventions before writing new tests.

### 2 — Map the flow

Before writing the test, write the steps in plain English. Example:

```
signup flow:
  1. visit /signup
  2. fill email + password
  3. click "Create account"
  4. expect navigation to /verify-email
  5. open the verification link from the test mailbox
  6. expect /onboarding
```

Show this to the user. Confirm the flow matches before coding. Half of bad e2e tests fail because they tested the wrong sequence.

### 3 — Write the test

Use Playwright's built-in locators in this priority order:

1. `getByRole('button', { name: 'Sign in' })` — accessible, resilient to copy changes
2. `getByLabel('Email')` — for form inputs
3. `getByTestId('checkout-submit')` — when nothing else is stable
4. CSS selectors — last resort, only when the above don't fit

Never use XPath. Never use `page.locator('div > div > div:nth-child(3)')`. Those are landmines.

### 4 — Eliminate flake at write time

For every interaction that triggers async work:

- After a click that submits a form → `await page.waitForResponse(r => r.url().includes('/api/auth/signup') && r.ok())`
- After a navigation → `await expect(page).toHaveURL('/onboarding')`
- After a state change → `await expect(page.getByText('Welcome')).toBeVisible()`

Never `await page.waitForTimeout(2000)`. If you find yourself reaching for it, the test is wrong.

### 5 — Capture on failure

Configure `playwright.config.ts`:

```ts
use: {
  trace: 'retain-on-failure',
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
},
```

When a test fails, point the user at the trace: `npx playwright show-trace test-results/<name>/trace.zip`.

### 6 — Run and report

Run with `--reporter=list` for human-readable output. After the run:

- If green: report which flows passed, and which assertions actually fired.
- If red: report the specific step that failed, the expected vs actual, and the path to the screenshot.

## Patterns and anti-patterns

✅ **Do**:
- One flow per test file. `signup.spec.ts`, `checkout.spec.ts`, `password-reset.spec.ts`.
- Use `test.describe.serial(...)` only when state is genuinely shared. Default to parallel.
- Pin Playwright + browser versions in `package.json`. A floating browser breaks tests in CI.
- Mock or stub external services (Stripe webhooks, email providers) — your test owns the failure, not their flakiness.

❌ **Don't**:
- Don't share auth state via global variables. Use `storageState` per test or per worker.
- Don't assert on the absence of an element with `toBeHidden()` without a `timeout` — the negative path is the flakiest in Playwright.
- Don't run against prod. Have the user point at staging or local; refuse to run destructive flows against prod URLs.
- Don't catch the error and `console.log` it. Let the test framework fail.

## Example invocation

> User: "Verify the signup → first-login flow works on localhost:3000."

1. Check the project for an existing Playwright config — none found.
2. Install Playwright and Chromium with deps.
3. Map the flow with the user: visit /signup → fill form → submit → land on /onboarding → see welcome message.
4. Write `tests/signup.spec.ts` using `getByRole` and `getByLabel`.
5. Add `waitForResponse` on the signup POST and `toHaveURL('/onboarding')` after redirect.
6. Run: `npx playwright test tests/signup.spec.ts --reporter=list`.
7. Report: 1/1 passed, trace at test-results/signup/trace.zip if you want to scrub through it.

## See also

- `test-architect` — for unit and integration tests that don't need a browser
- `code-auditor` — to find logic bugs the e2e didn't reach
- `ui-polish` — when the test passes but the page still looks broken

