Lesson 04 · ~20 minutes · The big leap

Save a Passing Check as a Re-runnable Test

Everything so far vanished when the session ended. Now we make a check permanent — a file you (and a robot) can run forever, without Claude in the loop.
Why this, why now → Your mission explicitly includes turning a successful manual check into a repeatable Playwright test that I (or CI) can re-run later. This is that step — the difference between "Claude tested it once" and "this feature is tested." It's the moment your effort starts compounding instead of evaporating.

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 by npx 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.

Let Claude do the toil You can hand this whole step to Claude: "set up @playwright/test in this project and run the sample to confirm it works." Watch what it does the first time, though — understanding the pieces pays off when a test misbehaves later.

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:

Prompt to Claude
Write a Playwright test file at 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:

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.

✓ You've won this lesson when… You have a 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

Your teacher is in the room. Ask me anything — "what's the difference between 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

  1. 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