Save a Passing Check as a Re-runnable Test
1. Two different Playwrights (clear this up first)
You've been using Playwright MCP — Claude live-driving a browser, deciding each step. There's
a second tool with the same name: Playwright Test (@playwright/test), a CLI that
runs saved test files. Same browser engine, totally different job.
Playwright MCP (L1–L3)
Claude decides each step live. Perfect for exploring. Gone when the session ends.Playwright Test (today)
A file of steps + assertions, run bynpx playwright test. Repeatable. No Claude needed to run it.The winning workflow uses both: explore with MCP → have Claude write the equivalent test file → run it with the CLI forever. Agentic discovery, durable persistence. Full command reference lives in your new Playwright Test Cheat Sheet.
2. Scaffold the test runner (3 min)
From your workspace root, set up @playwright/test once:1
npm init playwright@latest
Accept the defaults (TypeScript is fine; it'll install browsers). It creates a tests/ folder, a
playwright.config.ts, and a sample tests/example.spec.ts. Confirm it works by running
the sample:
npx playwright test
You should see the example tests pass. That's the runner alive on your machine.
3. Turn a manual check into a file (the key move)
Make sure the practice app is running (cd practice-app && python3 -m http.server 8000),
then ask Claude to translate the check you already proved by hand into a saved test:
tests/signup.spec.ts that tests the app at
http://localhost:8000. Include three test cases: (a) valid signup
(a@b.com, age 30) shows a success message; (b) an invalid email shows the email error;
(c) age 15 shows the 18+ error. Use stable role/label locators per our selector cheat sheet. Then
run it with npx playwright test tests/signup.spec.ts and show me the result.
Claude writes real code this time, not live tool calls. The result is a file like:
import { test, expect } from '@playwright/test';
test('valid signup succeeds', async ({ page }) => {
await page.goto('http://localhost:8000');
await page.getByLabel('Email address').fill('a@b.com');
await page.getByLabel('Age').fill('30');
await page.getByRole('button', { name: 'Subscribe' }).click();
await expect(page.getByText('subscribed')).toBeVisible();
});
Read it top to bottom — it's just your Lesson 02/03 steps, written down. goto → fill → click → expect.
The expect(...).toBeVisible() is a web-first assertion: it auto-waits until the
message appears or times out, which is what kills most flakiness.1
4. Prove it's real: run it without Claude
This is the payoff. Close nothing, just run it yourself in a plain terminal:
npx playwright test tests/signup.spec.ts
It passes — and Claude wasn't involved. That's the leap: the test now stands on its own. Then open the visual report:
npx playwright show-report
Or re-run in interactive mode to watch each step time-travel:
npx playwright test --ui
5. The moment it earns its keep: catch a regression
A saved test exists to fail when something breaks. Cause a real break and watch it get caught — by a command, not a person:
- Edit
practice-app/index.htmland break the success message (e.g. change "subscribed" to "done"). - Run
npx playwright testagain → the valid-signup test should now FAIL, pointing at the exact assertion. - Run
npx playwright show-report→ see the failure with a screenshot of the actual page state. - Revert your change, re-run → green again.
You just experienced the entire value proposition of E2E testing in 90 seconds: a change broke behavior, and a re-runnable test caught it automatically. Multiply that across every feature and every future edit.
tests/signup.spec.ts that you ran with npx playwright test without
Claude, and you watched it turn red when you broke the app and green when you fixed it. Your testing is now
persistent.
6. Your turn
- Ask Claude to add a 4th case: age
"18"exactly → should it pass? Make the test encode your intent. - Add a deliberately wrong assertion, run, watch it fail, then fix it — confirm you can read the report.
- Ask Claude: "explain each line of
signup.spec.ts" until no line is mysterious. - Commit the test to git (
git initif needed). A tracked test is a promise that survives you.
toHaveText and toContainText?", "how do I avoid hard-coding
localhost:8000 everywhere?" (answer: baseURL in the config + a
webServer block that starts your app automatically), or "can the test start my app for
me?" (yes — that's the next lesson's territory).
7. Where this is heading
You can now persist and re-run tests by hand. The final rung of the core mission: making them run
automatically — a webServer config so the test starts your app itself, and a CI workflow
(GitHub Actions) that re-runs your whole suite on every push. That's when testing stops being something you
remember to do and becomes something that just happens.
References
- Playwright — Getting Started:
npm init playwright@latest, test/expect structure,page.goto, web-first assertions, and run commands (npx playwright test,--ui,show-report). playwright.dev/docs/intro