Two tools ship with Playwright that have no equivalent in any other browser-automation framework: codegen records your manual interactions and writes the test code for you, and the inspector lets you step through a test command-by-command, edit locators live, and see exactly which element each one matches. They aren't toys — both are part of the official workflow that Microsoft uses on its own teams. By the end of this lesson you'll know when to reach for each, what they're good at, and what they shouldn't be used for.
Codegen — the test recorder
Run this against any URL:
npx playwright codegen https://www.saucedemo.comTwo windows open: a browser at the URL you gave, and the inspector showing live-generated TypeScript. Click the username field and type standard_user — the inspector instantly shows:
await page.getByPlaceholder("Username").fill("standard_user");Type the password, click Login, click an item, click "Add to cart" — every interaction appends a line. Stop, copy the generated code, paste it into a test file. You've just written a working spec without typing a single locator by hand.
What codegen gets right (and where it falls short)
Playwright's codegen is genuinely good at three things:
- Picking accessibility-first locators. It prefers
getByRole,getByLabel,getByTextover CSS class names — the same priority order chapter 2 will recommend. The output already follows best practice. - Capturing exact interactions. Multi-step flows like "open modal, fill form, click confirm, dismiss toast" record cleanly with no missed steps.
- Working across all three browsers.
--browser=firefoxor--browser=webkitrecords using that engine, useful when a flow renders differently per browser.
What codegen doesn't do is build a good test:
- It generates no assertions by default — you click around and get actions, but no
expect. Adding meaningful assertions is your job. - It produces flat scripts, not structured tests. There's no
test.describe, nobeforeEach, no shared setup. You'll restructure before checking the code into a real suite. - It records literal interactions. Generated code often contains redundant clicks, unnecessary waits, or selectors that work today but match by accident — you should always read the output and clean it before keeping it.
The right mental model: codegen is a scaffolder, not a test author. Use it when you need to learn an unfamiliar app's locators or quickly sketch the happy path of a new flow, then rewrite the output as a proper test.
Codegen with options
The recorder takes flags that match real testing scenarios:
# Mobile emulation — record what an iPhone 13 user does
npx playwright codegen --device="iPhone 13" https://shop.example.com
# Dark mode and locale
npx playwright codegen --color-scheme=dark --lang=de-DE https://shop.example.com
# Specific viewport
npx playwright codegen --viewport-size=1366,768 https://shop.example.com
# Save the recording to a file directly (instead of inspector window)
npx playwright codegen --target=javascript -o login.spec.ts https://shop.example.comThe most useful flag in real work is --save-storage:
npx playwright codegen --save-storage=auth.json https://shop.example.comYou log in manually one time, then close codegen. Playwright writes your cookies and localStorage to auth.json. Now any test can start authenticated by setting use: { storageState: 'auth.json' } in the config — you'll meet this pattern in chapter 6's authentication lesson. It's the fastest way to get past a complex SSO or OTP login while you're authoring tests.
The Playwright Inspector — step debugging
Codegen records new tests. The inspector debugs existing ones. Two ways to launch it:
# Run the whole suite under the inspector
npx playwright test --debug
# Or run a single test under the inspector
npx playwright test tests/checkout.spec.ts --debugA browser opens alongside an inspector panel. The test pauses before the first action. From here you have:
- Resume — run to the next pause or the end.
- Step over — execute the next action and pause again.
- Pick locator — click anywhere in the page; the inspector writes the best locator for that element.
- Locator explorer — type a locator (
page.getByRole('button')) and the inspector highlights every match in the page in real time. Wrong matches stand out instantly. - DOM and console — inspect the page exactly as it is at this step.
You can also drop a page.pause() call inside any test:
test("checkout flow", async ({ page }) => {
await page.goto("/cart");
await page.getByRole("button", { name: "Checkout" }).click();
await page.pause(); // ← inspector opens here
await page.getByLabel("Card number").fill("4242 4242 4242 4242");
});Save and run with npx playwright test --headed. The test pauses at page.pause(); you step through the rest interactively. This is the right pattern when only one specific test is misbehaving — you don't want every test to pause, just this one.
Codegen vs Inspector — different jobs, similar surface
When to reach for each tool
Codegen — record
You're writing a NEW test against an unfamiliar app
Click around, generate working code in seconds
Picks accessibility-first locators (getByRole, getByLabel)
Output is a scaffold — clean it up before committing
Inspector — debug
An EXISTING test is failing and you need to know why
Step through commands, see DOM at each step
Locator explorer — test selectors live before keeping them
Drop page.pause() to break in only at the line you care about
Both tools share the same right-side panel — that's intentional. The locator picker, the action log, the action playback are unified. You can move between recording and debugging in the same session: record a flow, switch to step mode, fix what looks wrong, copy the cleaned code into a spec.
A real workflow
Here's how a Playwright engineer actually uses these tools in a normal week:
-
New test for an unfamiliar feature. Run codegen against the staging URL, click through the happy path. Copy the generated actions. Open a fresh
tests/feature-X.spec.ts, paste, then addtest.describeandtest.beforeEach, replace the inline navigation, and add assertions. Five minutes of clicking saved 30 minutes of locator-hunting. -
Test failed in CI but passes locally. Download the trace.zip from the CI artifacts (chapter 9 covers traces in depth). Open it with
npx playwright show-trace trace.zip. The trace viewer is essentially the inspector replaying a recorded run — same panels, same DOM scrubber, same action log. You diagnose the CI flake without re-running anything. -
A locator is matching the wrong element. Add
await page.pause()before the suspicious line. Run withnpx playwright test --debug. In the locator explorer, paste your locator — the page shows zero, one, or many highlighted matches. If many, narrow with.first()or a more specific role; if zero, your selector assumption is wrong. Fix, remove thepause(), re-run. -
Picking up a complex form. Run codegen against the form, fill every field, submit. The output gives you every input's locator. Now you know what
getByRole,getByLabel, orgetByPlaceholderto use without guessing.
Coming from Cypress?
Cypress's Selector Playground is the closest equivalent — click an element, get a selector. Playwright's Inspector locator picker does the same job but better: it ranks suggestions by resilience (role > label > test ID > CSS), shows you live highlight feedback, and is integrated with codegen so you can record-and-debug in the same session. Cypress doesn't have a built-in test recorder at all; codegen has no Cypress equivalent.
A QA tip you'll actually use
When a teammate sends you a Slack message that says "the search dropdown is broken on staging," resist the urge to dive straight into the codebase. Run codegen against staging, reproduce the bug manually, watch what code Playwright generates for the broken interaction. If the locator codegen suggests is unstable (e.g., it's matching a generic <div> instead of a [role="combobox"]), that's already a useful signal — the bug might be that the developer removed the role attribute by accident.
The full Playwright reference is on the Playwright tools page; the commands cheat sheet covers every codegen and inspector flag.
⚠️ Common mistakes
- Treating codegen output as a finished test. It's a scaffold. Every codegen output should be reorganised into
test.describe+beforeEach, have meaningful assertions added, and have any literal-position selectors (.nth(2),[1]) replaced with semantic ones. Pasting raw codegen into a real suite is how you get a 200-line file that nobody can maintain. - Forgetting that
page.pause()is a no-op in headless CI. It only opens the inspector when you run with--debugor--headed. If you accidentally commit apage.pause()and CI runs headless, the test runs fine — but anyone who runs--debuglocally hits the pause unexpectedly. Treatpage.pause()like aconsole.log: useful while debugging, removed before commit. - Recording against production by accident.
npx playwright codegen <url>opens a real browser and runs against whatever URL you give it. If that's prod, every click is a real click on production data. Always use staging, a sandbox, or a local instance — and never give codegen a URL with admin credentials in the query string.
🎯 Practice task
Use codegen to scaffold a real test, then debug it with the inspector. 20-30 minutes.
- Record. Run
npx playwright codegen https://www.saucedemo.com. In the browser:- Type
standard_userin the username field. - Type
secret_saucein the password field. - Click Login.
- Click "Add to cart" on the Sauce Labs Backpack.
- Click the cart icon.
- Click "Checkout". Stop the recorder. Copy the generated code.
- Type
- Restructure. Create
tests/codegen-checkout.spec.ts. Wrap the recorded actions in atest.describe("Codegen checkout")and a singletest("logs in and starts checkout", async ({ page }) => { ... }). Moveawait page.goto("/")to the top of the test body (codegen records it inline; you might also lift it tobeforeEachlater). - Add assertions. Codegen generated zero. Add at least three:
await expect(page).toHaveURL(/inventory/)after login.await expect(page.locator(".shopping_cart_badge")).toHaveText("1")after add-to-cart.await expect(page).toHaveURL(/checkout-step-one/)after clicking Checkout.
- Run it.
npx playwright test tests/codegen-checkout.spec.ts. Should pass across all three browsers. - Debug it deliberately. Insert
await page.pause()immediately after the add-to-cart click. Run withnpx playwright test tests/codegen-checkout.spec.ts --debug. The inspector opens at the pause. In the locator explorer panel, pastepage.locator(".shopping_cart_badge")— the badge highlights. Now pastepage.locator(".inventory_item")— six items highlight. This is what locator explorer is for: confirming a locator does (or doesn't) match what you think. - Use --save-storage. Run codegen with
--save-storage=auth.jsonagainst Sauce Demo. Log in manually. Close. Inspectauth.json— it contains cookies and localStorage. (You won't use it in a test yet — chapter 6 will — but you've now produced an artefact that lets every future test skip the login step.) - Stretch: open the trace from any earlier failed run (
npx playwright show-trace test-results/.../trace.zip). Notice the trace viewer's panels are the same as the inspector's: the action log, the DOM scrubber, the locator picker. They're the same mental model — codegen records new behaviour, inspector debugs current behaviour, trace viewer reviews past behaviour.
You now have the three Playwright tooling surfaces that no other framework gives you in one box: record (codegen), debug live (inspector), and review post-mortem (trace viewer). The next chapter — chapter 2 — opens up locators in depth: every selector strategy Playwright supports, in priority order, with worked examples for the e-commerce app you've been building tests against.