Skip to content
Web apps · Workflow

Playwright end-to-end tests agents don't make flaky

Rules for agents writing and healing Playwright tests, including Playwright's test agents: role locators, web-first assertions, no sleeps, never weaken a test to pass.

Verified install Not agent-tested v1.0.0 MIT updated playwrighttypescript
Equips
  • 1 1 Rules
  • 1 1 Skills
  • – 0 MCP

Playwright now ships its own planner, generator and healer agents, and every coding agent writes Playwright tests on request. Left alone they reach for the same shortcuts: waitForTimeout, CSS chains, force clicks, expect(await isVisible()) that never retries, and a healer that makes a red test green by deleting the assertion. This loadout gives your agent Playwright's own best practices as rules, a healing procedure that decides whether the app or the test is wrong before touching anything, and a linter for the anti-patterns. The verifier runs the example spec twice over in real Chrome against a page that renders late, and lints a flaky spec.

What's in it

  • Works alongside npx playwright init-agents (planner, generator, healer)
  • getByRole first; web-first assertions only; no fixed waits
  • Healing rule: if the app regressed, report it, never edit the test to match
  • Linter for waitForTimeout, force clicks, test.only and non-retrying assertions
  • Example passes repeat runs in Chrome against a deliberately slow page

You'll need

  • Tools: node, npm
Level 1 · automated

Verification

Verified install

Checked at commit 021470d · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 6 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 6 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 5 files, 1 skill in .claude/skills/
  • Codex CLI: 4 files, 1 skill in .agents/skills/
  • Cursor: 4 files, 1 skill in .agents/skills/
  • Gemini CLI: 5 files, 1 skill in .agents/skills/
  • Antigravity CLI: 4 files, 1 skill in .agents/skills/
  • GitHub Copilot: 4 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network no (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 1 reference between files resolves for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
6 files, 7.9 KB total; largest AGENTS.md 2.3 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
The write-e2e-test skill's example spec passes twice over (--repeat-each 2) in real Chrome against a todo page that renders late, the spec linter passes it, and the linter reports all seven anti-patterns in a flaky spec
  • Applied 9 loadout files at every agent's paths
  • ✓ place the example spec: bash -c mkdir -p tests && cp .agents/skills/write-e2e-test/references/todo.spec.ts tests/ && echo placed
  • ✓ install @playwright/test (system Chrome, no browser download): npm ci --no-audit --no-fund
  • ✓ list tests: npx playwright test --list
  • ✓ run in Chrome, twice each: npx playwright test --repeat-each 2
  • ✓ linter passes the example: node .agents/skills/write-e2e-test/scripts/lint_specs.mjs tests
  • ✓ linter catches a flaky spec: bash -c node .claude/skills/write-e2e-test/scripts/lint_specs.mjs bad; test $? -eq 1 && echo caught
  • Covers: getByRole locators and web-first assertions survive a 150 ms delayed render
  • Covers: webServer starts the app; retries and traces only in CI
  • Covers: Linter: fixed waits, non-retrying assertions, brittle selectors, force clicks, test.only, page.$, toBeTruthy

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/playwright-e2e-tests/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/playwright-e2e-tests/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/write-e2e-test/SKILL.md https://loadout.jnw-it.com/raw/playwright-e2e-tests/1.0.0/skills/write-e2e-test/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/write-e2e-test/references/todo.spec.ts https://loadout.jnw-it.com/raw/playwright-e2e-tests/1.0.0/skills/write-e2e-test/references/todo.spec.ts
curl -fsSL --create-dirs -o .claude/skills/write-e2e-test/scripts/lint_specs.mjs https://loadout.jnw-it.com/raw/playwright-e2e-tests/1.0.0/skills/write-e2e-test/scripts/lint_specs.mjs
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/write-e2e-test/SKILL.md.claude/skills/write-e2e-test/SKILL.md
skills/write-e2e-test/references/todo.spec.ts.claude/skills/write-e2e-test/references/todo.spec.ts
skills/write-e2e-test/scripts/lint_specs.mjs.claude/skills/write-e2e-test/scripts/lint_specs.mjs

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md Rules for writing, running and healing Playwright end-to-end tests. 36 lines
Download Raw
# AGENTS.md: Playwright end-to-end tests

End-to-end tests with Playwright Test (`@playwright/test`). These rules apply when you write tests by hand and when
you use Playwright's own test agents (planner, generator, healer from `npx playwright init-agents`).

## Commands
- Run: `npx playwright test`. One file: `npx playwright test tests/todo.spec.ts`. List without running:
  `npx playwright test --list`.
- Debug a failure: `npx playwright show-trace test-results/<test>/trace.zip`. Do not guess from the error alone.
- Before finishing, run the `write-e2e-test` skill's linter (`scripts/lint_specs.mjs` in the skill folder) over the
  test folder. It must report no findings.

## Writing tests
- Locate by what the user sees: `getByRole` with a name first, then `getByLabel`, `getByPlaceholder`, `getByText`.
  `getByTestId` when there is nothing better. No CSS chains, XPath or `nth-child`.
- Web-first assertions only: `await expect(locator).toBeVisible()`, `toHaveText`, `toHaveCount`, `toHaveURL`.
  Never `expect(await locator.isVisible()).toBe(true)`: it does not retry.
- No fixed waits: no `page.waitForTimeout`, no `waitForLoadState("networkidle")`. Wait for the thing you need.
- No `force: true` on clicks. If an element is covered, that is a bug or a wrong locator.
- Each test sets up its own state and runs alone and in any order. Share setup with fixtures or `beforeEach`, and
  authentication with a setup project and `storageState`.
- Never commit `test.only`. Use `test.skip` with a reason, or `test.fixme` and an issue link.
- Test data and accounts are test-only. Never use production credentials; read secrets from the environment.

## Healing failing tests
- First decide whether the test or the app is wrong. Read the trace. If the app regressed, report it; do not change
  the test to match a bug.
- Never make a test pass by deleting assertions, loosening them to `toBeTruthy`, adding timeouts or retries, or
  skipping it without saying so.
- Locator changed because the UI changed on purpose: update the locator to a role-based one and say what changed.

## Configuration
- `retries` only in CI (`process.env.CI ? 2 : 0`), with `trace: "on-first-retry"`.
- `webServer` starts the app; do not assume a server is already running.
- Ask before adding browsers, projects or dependencies. Pin `@playwright/test` to an exact version.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 10 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `write-e2e-test`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Playwright end-to-end tests agents don't make flaky" v1.0.0 — https://loadout.jnw-it.com/loadouts/playwright-e2e-tests/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 10 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `write-e2e-test`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Playwright end-to-end tests agents don't make flaky" v1.0.0 — https://loadout.jnw-it.com/loadouts/playwright-e2e-tests/ -->
Skill skills/write-e2e-test/SKILL.md Write or repair a test, lint it and run it three times. 26 lines
Download Raw
---
name: write-e2e-test
description: Write or repair a Playwright end-to-end test with role-based locators, web-first assertions and no fixed waits, then lint it and run it. Use for new user flows and for failing or flaky tests.
---

# Write or repair an end-to-end test

Example: `references/todo.spec.ts`.

## New test
1. Write the flow as steps a user takes and what they should see. One behaviour per test.
2. Open the page and find each element the way a user would: `page.getByRole("button", { name: "Add" })`.
   If you cannot find a good role or label, ask whether the app should have one before reaching for a test id.
3. Assert with web-first assertions after every meaningful step (`toBeVisible`, `toHaveText`, `toHaveCount`,
   `toHaveURL`).
4. Set up state inside the test or a fixture. Do not depend on another test having run.
5. Lint: `node scripts/lint_specs.mjs tests`. Fix every finding.
6. Run it three times in a row (`npx playwright test <file> --repeat-each 3`). Flaky means wrong, not unlucky.

## Failing or flaky test
1. Run it once with tracing: `npx playwright test <file> --trace on`, then open the trace.
2. Decide: is the app wrong, or the test? Write down which and why.
3. App wrong: stop and report the regression with the trace path. Do not edit the test.
4. Test wrong: fix the locator or the setup. Never add sleeps, never loosen assertions, never add `force: true`.
5. Lint and run three times as above.
Skill file skills/write-e2e-test/references/todo.spec.ts Example spec with role locators and web-first assertions. 32 lines
Download Raw
import { expect, test, type Page } from "@playwright/test";

async function addTodo(page: Page, text: string) {
  await page.getByRole("textbox", { name: "New todo" }).fill(text);
  await page.getByRole("button", { name: "Add" }).click();
}

test.beforeEach(async ({ page }) => {
  await page.goto("/");
  await expect(page.getByRole("heading", { name: "Todos" })).toBeVisible();
});

test("adds a todo and updates the counter", async ({ page }) => {
  await addTodo(page, "Buy milk");
  await expect(page.getByRole("listitem")).toHaveText(["Buy milk"]);
  await expect(page.getByRole("status")).toHaveText("1 item left");
});

test("completing a todo moves it out of the active count", async ({ page }) => {
  await addTodo(page, "Write tests");
  await addTodo(page, "Ship it");
  await page.getByRole("checkbox", { name: "Write tests" }).check();
  await expect(page.getByRole("status")).toHaveText("1 item left");
  await expect(page.getByRole("checkbox", { name: "Write tests" })).toBeChecked();
});

test("rejects an empty todo with a visible message", async ({ page }) => {
  await page.getByRole("button", { name: "Add" }).click();
  await expect(page.getByRole("alert")).toHaveText("Write something first.");
  await expect(page.getByRole("listitem")).toHaveCount(0);
});
Skill file skills/write-e2e-test/scripts/lint_specs.mjs Linter for Playwright test anti-patterns. 41 lines
Download Raw
#!/usr/bin/env node
// Flags Playwright test anti-patterns: fixed waits, non-retrying assertions, brittle selectors, force clicks, test.only.
// Usage: node lint_specs.mjs <dir-or-file> [...]. Exit 1 on any finding. Reads files only.
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";

const RULES = [
  [/\bwaitForTimeout\s*\(/, "fixed wait: wait for a locator or assertion instead"],
  [/waitForLoadState\(\s*["']networkidle["']/, "networkidle is discouraged: wait for the element you need"],
  [/expect\(\s*await\s+.*\.(?:isVisible|isHidden|isChecked|isEnabled|textContent|innerText|count)\(\)\s*\)/, "non-retrying assertion: use a web-first assertion such as toBeVisible() or toHaveText()"],
  [/\bforce\s*:\s*true/, "force: true hides real problems: fix the locator or the app"],
  [/\btest\.only\(|\bdescribe\.only\(/, "test.only must not be committed"],
  [/\bpage\.\$\$?\(|\bpage\.\$eval\(/, "page.$ is the old API: use page.locator or getByRole"],
  [/locator\(\s*["'`][^"'`]*(?::nth-child|:nth-of-type|\/\/|>>|\.[a-z][\w-]*\s+\.[a-z])/i, "brittle selector: prefer getByRole, getByLabel or getByTestId"],
  [/\bsetTimeout\s*\(/, "setTimeout in a test: wait for state instead"],
  [/\.toBeTruthy\(\)/, "toBeTruthy proves little: assert the actual value or state"],
];

function files(p, out = []) {
  if (statSync(p).isDirectory()) {
    for (const n of readdirSync(p)) if (n !== "node_modules") files(join(p, n), out);
  } else if (/\.(?:spec|test)\.[cm]?[jt]sx?$/.test(p)) out.push(p);
  return out;
}

const targets = process.argv.slice(2);
if (!targets.length) {
  console.error("usage: node lint_specs.mjs <dir-or-file> [...]");
  process.exit(2);
}
const findings = [];
const all = targets.flatMap((t) => files(t));
for (const f of all) {
  readFileSync(f, "utf8").split("\n").forEach((line, i) => {
    for (const [re, msg] of RULES) if (re.test(line)) findings.push(`${f}:${i + 1}: ${msg}`);
  });
}
for (const x of findings) console.log(x);
console.log(findings.length ? `${findings.length} finding(s) in ${all.length} file(s)` : `ok: ${all.length} spec file(s), no findings`);
process.exit(findings.length ? 1 : 0);

All files: source zip · Licence MIT · By Agent Loadout · First listed

Credits

Public work that inspired this loadout. The wording here is our own; follow the links for the originals.

Installing and testing

How do I install Playwright end-to-end tests agents don't make flaky in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/write-e2e-test/SKILL.md at .claude/skills/write-e2e-test/SKILL.md, skills/write-e2e-test/references/todo.spec.ts at .claude/skills/write-e2e-test/references/todo.spec.ts, skills/write-e2e-test/scripts/lint_specs.mjs at .claude/skills/write-e2e-test/scripts/lint_specs.mjs. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/playwright-e2e-tests/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Playwright end-to-end tests agents don't make flaky in Codex CLI?

From your project root, put AGENTS.md, skills/write-e2e-test/SKILL.md at .agents/skills/write-e2e-test/SKILL.md, skills/write-e2e-test/references/todo.spec.ts at .agents/skills/write-e2e-test/references/todo.spec.ts, skills/write-e2e-test/scripts/lint_specs.mjs at .agents/skills/write-e2e-test/scripts/lint_specs.mjs. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/playwright-e2e-tests/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Playwright end-to-end tests agents don't make flaky in Cursor?

From your project root, put AGENTS.md, skills/write-e2e-test/SKILL.md at .agents/skills/write-e2e-test/SKILL.md, skills/write-e2e-test/references/todo.spec.ts at .agents/skills/write-e2e-test/references/todo.spec.ts, skills/write-e2e-test/scripts/lint_specs.mjs at .agents/skills/write-e2e-test/scripts/lint_specs.mjs. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/playwright-e2e-tests/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Playwright end-to-end tests agents don't make flaky in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/write-e2e-test/SKILL.md at .agents/skills/write-e2e-test/SKILL.md, skills/write-e2e-test/references/todo.spec.ts at .agents/skills/write-e2e-test/references/todo.spec.ts, skills/write-e2e-test/scripts/lint_specs.mjs at .agents/skills/write-e2e-test/scripts/lint_specs.mjs. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/playwright-e2e-tests/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Playwright end-to-end tests agents don't make flaky in Antigravity CLI?

From your project root, put AGENTS.md, skills/write-e2e-test/SKILL.md at .agents/skills/write-e2e-test/SKILL.md, skills/write-e2e-test/references/todo.spec.ts at .agents/skills/write-e2e-test/references/todo.spec.ts, skills/write-e2e-test/scripts/lint_specs.mjs at .agents/skills/write-e2e-test/scripts/lint_specs.mjs. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/playwright-e2e-tests/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Playwright end-to-end tests agents don't make flaky in GitHub Copilot?

From your project root, put AGENTS.md, skills/write-e2e-test/SKILL.md at .agents/skills/write-e2e-test/SKILL.md, skills/write-e2e-test/references/todo.spec.ts at .agents/skills/write-e2e-test/references/todo.spec.ts, skills/write-e2e-test/scripts/lint_specs.mjs at .agents/skills/write-e2e-test/scripts/lint_specs.mjs. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/playwright-e2e-tests/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has Playwright end-to-end tests agents don't make flaky been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.