Skip to content
Back to skills

E2e Browser Test

ASecurity

Use when a ticket asks for end-to-end or browser coverage of a user journey — sign-up, checkout, a form submission, a dashboard flow — driven through the real UI with Playwright, Cypress, or the repo's browser harness. Invoke for "add an e2e test", "cover the flow in the browser", or when an acceptance criterion can only be shown by clicking through the app.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 28, 2026
ai-agentstypescriptgoreactvueangularnodetestingapifrontendbackend

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add tmj-90/gaffer --skill e2e-browser-test --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of E2e Browser Test?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for E2e Browser Test
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tmj-90-e2e-browser-test/badge)](https://www.skillsdirectory.com/skills/tmj-90-e2e-browser-test)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: e2e-browser-test
description: Use when a ticket asks for end-to-end or browser coverage of a user journey — sign-up, checkout, a form submission, a dashboard flow — driven through the real UI with Playwright, Cypress, or the repo's browser harness. Invoke for "add an e2e test", "cover the flow in the browser", or when an acceptance criterion can only be shown by clicking through the app.
stack: [react, web, next, vue, svelte, angular, node]
area: testing
---

# Write an end-to-end browser test

An end-to-end test proves a user journey works through the real UI against a real (or
faithfully stubbed) backend. It is the slowest and most flake-prone test in the suite —
Google's data shows flakiness grows with test size and with WebDriver-style tooling — so
it earns its place by covering a journey no cheaper test can, and by being deterministic:
no sleeps, no order dependence, no data another test created.

## Procedure

1. **Find the harness.** Locate the repo's browser setup (`playwright.config.*`,
   `cypress/`, a `test:e2e` script). Use it exactly — its browsers, base URL, `webServer`
   block and CI command. Do not add a second framework. If none exists and the ticket does
   not ask for one, raise `request_decision` with the smallest viable setup. (Independent
   tester: never raise `request_decision`; follow `black-box-test` — scaffold a disposable
   rig in a test directory only with what the repo already has installed, run the spec
   once, and record `manual_note` rows.)
2. **Push coverage down first.** For each acceptance criterion ask whether a component or
   integration test proves it (the `frontend-testing` and `add-integration-test` skills).
   Only journeys that need the real browser, routing, cookies or the full stack belong
   here.
3. **Write the journey in plain words:** start state → user actions → what the user must
   observe. Map each AC to one observable outcome on screen or one persisted result. Name
   the test after the journey and cite the AC.
4. **Own your data.** Create what the test needs through an API call, seed script or
   factory inside the test (or a fixture), with unique values, and clean up. Never depend
   on another test's leftovers or a shared account; each test gets a fresh browser context.
   Save authenticated state once (`storageState`) rather than logging in through the UI in
   every test.
5. **Locate like a user.** `getByRole` with the accessible name, then `getByLabel`,
   `getByText`; chain and `filter({ hasText })` to scope. `getByTestId` only when no
   accessible handle exists, with a comment why. No XPath or styling-class CSS selectors.
6. **Use web-first assertions; never wait for time.** `await expect(locator).toBeVisible()`,
   `toHaveText`, `toHaveURL`, `page.waitForResponse`. Never `waitForTimeout`/`cy.wait(ms)`,
   and never `expect(await locator.isVisible()).toBe(true)` — it does not retry. Every
   Playwright call is awaited (`@typescript-eslint/no-floating-promises` catches misses).
7. **Assert the outcome, not the DOM.** What the user sees, and what the system persisted:
   reload the page or query the API to prove a save survived, check the stub outbox for the
   email. A toast saying "Saved" is not proof the data was saved.
8. **Stub only what you do not control.** Third-party services go through
   `page.route(...)`/`cy.intercept` with recorded responses; your own backend stays real.
9. **When an AC concerns concurrent users or failure,** drive it for real: two browser
   contexts editing the same record (assert the documented conflict behaviour, no silent
   overwrite); the backend returning an error or going offline via `page.route` →
   `abort()`; the user sees the documented error and can retry.
10. **Keep failures diagnosable.** Traces on first retry (`trace: 'on-first-retry'`),
    screenshots on failure. Retries may be on in CI to _collect_ traces, but a test that
    passes only on retry is flaky and not done.
11. **Run it until stable:** the spec three times in a row headless, e.g.
    `npx --no -- playwright test <spec> --repeat-each=3`, then the repo's CI command. Record the
    command and result with the `record-evidence` skill (`test_output`) per AC.

## Done when

- Each AC that needs a browser has a journey test asserting its observable outcome.
- The spec passes three consecutive headless runs without retries, and in the CI command.
- No fixed sleeps, CSS-structure selectors, shared accounts or order dependence remain.

## Rules

- One journey per test; keep the count small and the coverage wide.
- The backend under test is real or a faithful stub declared in the test; never mock the
  browser side to make the assertion pass.
- Match the repo's harness, browser pins and CI command exactly.
- Work on the delivery branch the runner prepared (the `create-branch` skill verifies),
  never a protected branch.

## Capture lore

This skill is one of the places durable, reusable knowledge naturally surfaces:
**While standing the app up for a browser journey you learn how it is really started, which env vars and ports it needs, and which selectors the design system exposes.** That kind of fact is *lore*. Capture it via the **lore-capture
protocol in your brief** (`CLAUDE.factory.md`, step 11 "Memory contribution"):
call the Memory MCP `suggest_lore` once at the close of your work — reusable
conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…