What codegen Does
npx playwright codegen launches a real browser alongside a separate Playwright Inspector window; as you click, type, and navigate in the browser, codegen records each action and emits corresponding Playwright Test code in real time, using Playwright's recommended locator strategies (getByRole, getByLabel, getByText) rather than brittle CSS selectors wherever the DOM provides accessible attributes. It's primarily a tool for quickly scaffolding a test's skeleton or discovering the right locator for an element, not a substitute for hand-writing a maintainable test suite.
Cricket analogy: codegen is like a video analyst watching a batter's net session and automatically drafting shot-by-shot notes in real time, giving the coach a first draft to refine rather than a finished report.
Running codegen
You launch it with npx playwright codegen <url>, optionally passing --target=python or --target=csharp to emit code in a different language binding, --browser=firefox to record against a non-Chromium engine, and --save-storage=auth.json to capture the resulting cookies and localStorage into a storage state file for reuse in authenticated test setups. Running codegen against your actual staging or local dev URL means the generated locators reflect your real DOM, including any dynamic IDs or ARIA roles your app happens to expose.
Cricket analogy: Running codegen with --target and --browser flags is like choosing which broadcast feed and commentary language to record a net session in before the cameras start rolling.
# Record against your local dev server with Firefox
npx playwright codegen --browser=firefox --target=javascript http://localhost:3000
# Capture auth state after logging in manually during recording
npx playwright codegen --save-storage=auth.json https://staging.example.com/loginRecording Interactions and Assertions
Beyond clicks and form fills, the Inspector toolbar has a dedicated 'assert' mode: clicking an element while assertion mode is active inserts an expect(locator).toBeVisible() or toHaveText() call at the cursor position, letting you build verification steps without typing assertion syntax by hand, and a record/pause toggle lets you pause recording to manually inspect the DOM in devtools before resuming. The generated script is a fully runnable spec file the moment you save it—no cleanup required to execute it, though it usually needs restructuring into reusable fixtures and page objects before it belongs in a real suite.
Cricket analogy: The assert mode inserting expect() calls when you click an element is like a scorer marking a boundary the instant the ball crosses the rope, capturing the verification at the exact moment it happens.
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('secret123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Welcome back')).toBeVisible();
});The Inspector's 'Pick locator' button lets you hover any element on the page and instantly copy Playwright's suggested locator string to your clipboard — a fast way to find a good selector for a test you're writing by hand, even without recording a full flow.
Codegen Limitations and Best Practices
Codegen output is intentionally verbose and flat: it hardcodes URLs, doesn't extract repeated interactions into helper functions, and can miss the semantically 'best' locator when an element has neither a role nor a label, falling back to nth-match or CSS chains that break the moment the DOM structure shifts. Treating codegen scripts as a first draft to refactor—renaming variables, extracting page objects, replacing any auto-generated nth() selectors with more resilient getByTestId() calls—is the difference between codegen accelerating your suite and quietly seeding it with maintenance debt.
Cricket analogy: Treating raw codegen output as final without refactoring is like fielding a team straight from a single unstructured net session without ever running proper drills — it'll play, but it won't hold up under real match pressure.
Codegen has no awareness of your test architecture: it will happily generate a script that logs in via the UI every single time instead of reusing a saved storage state, and it never extracts page objects or fixtures. Always review and refactor generated code before merging it into your suite.
- npx playwright codegen <url> records browser interactions and emits runnable Playwright Test code live.
- It prefers accessible locators (getByRole, getByLabel, getByText) over brittle CSS selectors when possible.
- --target, --browser, and --save-storage flags customize the language, engine, and captured auth state.
- Assertion mode lets you click an element to insert an expect() verification without typing it by hand.
- The 'Pick locator' tool in the Inspector is useful standalone for finding good selectors while hand-writing tests.
- Generated scripts run immediately but are typically flat, verbose, and hardcode values like URLs.
- Always refactor codegen output into reusable fixtures, page objects, and stable locators before merging it.
Practice what you learned
1. What two things does npx playwright codegen open when launched?
2. Which locator strategy does codegen prefer when the DOM provides the right attributes?
3. What does the --save-storage flag do during a codegen session?
4. What happens when you click an element while assertion mode is active in the Inspector?
5. What is a well-known limitation of raw, unrefactored codegen output?
Was this page helpful?
You May Also Like
Reporters and HTML Reports
How Playwright's built-in reporters work, how to configure and combine them, and how to build a custom reporter for bespoke workflows.
Retries and Flaky Test Handling
Understanding why Playwright tests flake, how to configure retries safely, and how auto-waiting fixes root causes instead of masking them.