Writing Playwright tests from scratch can feel intimidating if you are new to test automation. You need to learn locator strategies, understand async/await patterns, figure out assertion syntax, and remember the Playwright API surface. What if you could skip all of that and just click through your application while Playwright writes the test code for you?
That is exactly what Playwright codegen does. It is a record-and-playback test generator built directly into the Playwright framework. You launch it, interact with your website normally, and Playwright generates clean, runnable test code in real time. No extensions, no third-party tools, no configuration. Just one command: npx playwright codegen.
In this playwright codegen tutorial, you will learn how to set it up, record complex user flows, handle authentication, emulate mobile devices, and combine codegen output with Claude AI to produce production-grade test suites. Whether you are a beginner transitioning to automation or a developer who wants to scaffold tests quickly, codegen is the fastest path to working Playwright tests in 2026.
What Is Playwright Codegen?
Playwright codegen (short for "code generation") is a built-in test recorder that ships with every Playwright installation. It opens a browser window alongside a Playwright Inspector panel. As you navigate pages, click buttons, fill out forms, and perform any browser action, the Inspector panel generates the corresponding test code line by line.
Under the hood, codegen hooks into Playwright's browser context and listens to every DOM event. When you click an element, it analyzes the DOM to find the most resilient locator strategy. Instead of generating fragile CSS selectors like #app > div:nth-child(3) > button, it prefers semantic locators:
getByRole('button', { name: 'Submit' })— accessibility-based, the most resilientgetByLabel('Email address')— form fields by their labelgetByText('Welcome back')— visible text contentgetByPlaceholder('Enter your email')— placeholder textgetByTestId('login-form')— data-testid attributes
This means the generated code is not just functional — it follows Playwright's own best practices for locator selection. The tests are more maintainable than what many engineers write manually, because codegen does not take shortcuts with fragile XPath or deeply nested CSS selectors.
Pro tip: Playwright codegen is not a "record and pray" tool like older Selenium IDE recordings. Because it uses Playwright's auto-waiting and semantic locators, the generated tests are genuinely reliable and can run in CI without modification for simple flows.
Why Codegen Is a Game-Changer for Beginners
If you are new to test automation, codegen solves the cold-start problem. Instead of spending hours reading API docs before writing your first test, you can:
- Run one command to launch the recorder
- Click through your app like a normal user
- Copy the generated code into a test file
- Run the test and see it pass
This immediate feedback loop builds confidence and teaches you Playwright's API by example. You see exactly which methods correspond to which browser actions, and you learn the locator hierarchy naturally.
Getting Started: Prerequisites and Installation
Before you can use the Playwright test generator, you need Node.js and a Playwright project. Here is the complete setup from zero.
Step 1: Install Node.js
Playwright requires Node.js 18 or later. Check your version:
node --version
# Should output v18.x.x or higher
If you do not have Node.js, download it from nodejs.org or use a version manager like nvm.
Step 2: Create a Playwright Project
Initialize a new Playwright project with the official scaffolding command:
npm init playwright@latest # Choose these options when prompted: # - TypeScript (recommended) # - tests folder: tests # - Add GitHub Actions workflow: yes # - Install Playwright browsers: yes
This installs Playwright, downloads Chromium, Firefox, and WebKit browsers, and creates a project structure with a sample test file and configuration.
Step 3: Launch Codegen
With the project set up, launch the playwright code generation tool:
# Open codegen with a blank page npx playwright codegen # Open codegen pointed at a specific URL npx playwright codegen https://demo.playwright.dev/todomvc
Two windows open: a browser window where you interact with the site, and the Playwright Inspector where generated code appears in real time. The Inspector has a toolbar at the top with Record, Assert, and language selector controls.
Pro tip: If you already have an existing Playwright project, you do not need to create a new one. Just run npx playwright codegen from any directory with Playwright installed.
Recording Your First Test
Let us walk through recording a complete test against the Playwright TodoMVC demo application. This will demonstrate navigation, form filling, clicking, and assertions.
Launch the Recorder
npx playwright codegen https://demo.playwright.dev/todomvc
The TodoMVC app opens in the browser window. The Inspector panel shows the beginning of a test:
import { test, expect } from '@playwright/test'; test('test', async ({ page }) => { await page.goto('https://demo.playwright.dev/todomvc'); });
Action 1: Add a Todo Item
Click on the input field labeled "What needs to be done?" and type "Buy groceries", then press Enter. The Inspector immediately appends:
await page.getByPlaceholder('What needs to be done?').click(); await page.getByPlaceholder('What needs to be done?').fill('Buy groceries'); await page.getByPlaceholder('What needs to be done?').press('Enter');
Notice how codegen chose getByPlaceholder as the locator strategy — it found the placeholder text and used that instead of a CSS selector. This is semantic and resilient to layout changes.
Action 2: Add a Second Item
Type "Write Playwright tests" and press Enter. The same pattern appends to the generated code.
Action 3: Complete a Todo
Click the checkbox next to "Buy groceries" to mark it complete. Codegen generates:
await page.getByRole('checkbox', { name: 'Buy groceries' }).check();
Action 4: Add an Assertion
Click the Assert button (checkmark icon) in the Inspector toolbar, then click on the "1 item left" text in the app. Codegen generates a visibility assertion:
await expect(page.getByText('1 item left')).toBeVisible();
Pro tip: The Assert mode supports three types: visibility (is the element visible?), text content (does it contain specific text?), and value (what is the input value?). Toggle between them using the dropdown in the Inspector toolbar.
The Complete Generated Test
After your recording session, the full generated test looks like this:
import { test, expect } from '@playwright/test'; test('test', async ({ page }) => { await page.goto('https://demo.playwright.dev/todomvc'); // Add first todo await page.getByPlaceholder('What needs to be done?').click(); await page.getByPlaceholder('What needs to be done?').fill('Buy groceries'); await page.getByPlaceholder('What needs to be done?').press('Enter'); // Add second todo await page.getByPlaceholder('What needs to be done?').fill('Write Playwright tests'); await page.getByPlaceholder('What needs to be done?').press('Enter'); // Complete first todo await page.getByRole('checkbox', { name: 'Buy groceries' }).check(); // Assert remaining count await expect(page.getByText('1 item left')).toBeVisible(); });
Save this to tests/todo.spec.ts and run it:
npx playwright test tests/todo.spec.ts
The test passes on the first try. No manual waits, no sleep calls, no flakiness. That is the power of the Playwright test generator.
Codegen Options and Flags
The npx playwright codegen command accepts several flags that control the browser, language target, viewport, and more. Mastering these flags lets you generate tests for any scenario.
Language Target: --target
By default, codegen generates JavaScript. Use --target to switch:
# TypeScript (Playwright Test format) npx playwright codegen --target playwright-test # Python (pytest-playwright format) npx playwright codegen --target python-pytest # Python (library format) npx playwright codegen --target python # Java npx playwright codegen --target java # C# / .NET npx playwright codegen --target csharp
Each target produces idiomatic code for that language. The Python target uses sync_api, Java uses com.microsoft.playwright, and C# uses Microsoft.Playwright.
Browser Selection: --browser
# Record in Firefox npx playwright codegen --browser firefox https://example.com # Record in WebKit (Safari engine) npx playwright codegen --browser webkit https://example.com # Default is Chromium npx playwright codegen --browser chromium https://example.com
Device Emulation: --device
Test mobile layouts by emulating specific devices:
# iPhone 15 Pro npx playwright codegen --device="iPhone 15 Pro" https://example.com # Pixel 7 npx playwright codegen --device="Pixel 7" https://example.com # iPad Pro 11 npx playwright codegen --device="iPad Pro 11" https://example.com
Device emulation sets the viewport size, user agent, device scale factor, and touch support automatically. The generated test includes the device configuration so it reproduces the same environment when run.
Custom Viewport: --viewport-size
npx playwright codegen --viewport-size="1920,1080" https://example.com npx playwright codegen --viewport-size="375,812" https://example.com
Color Scheme: --color-scheme
# Test dark mode npx playwright codegen --color-scheme=dark https://example.com # Test light mode explicitly npx playwright codegen --color-scheme=light https://example.com
Timezone and Geolocation
# Emulate timezone and geolocation
npx playwright codegen --timezone="Europe/London" --geolocation="51.5074,-0.1278" https://example.com
Pro tip: Combine multiple flags for comprehensive device testing. For example: npx playwright codegen --device="iPhone 15 Pro" --color-scheme=dark --timezone="Asia/Tokyo" emulates a Japanese user on an iPhone in dark mode.
Authentication with Codegen: Save and Reuse Login State
Most applications require authentication. Recording the login flow every time is tedious and slow. Playwright codegen solves this with storage state — the ability to save and reload cookies, local storage, and session storage.
Step 1: Record the Login and Save Storage
npx playwright codegen --save-storage=auth.json https://myapp.com/login
This opens the browser. Log in to your application normally — enter your email, password, click the login button, wait for the dashboard to load. Then close the browser. Playwright saves all cookies and storage data to auth.json.
Step 2: Record Authenticated Tests
npx playwright codegen --load-storage=auth.json https://myapp.com/dashboard
The browser opens already logged in. You can now record tests against authenticated pages without repeating the login flow. The generated code will not include any login steps — it starts from the authenticated state.
Using Storage State in Your Test Suite
For your actual test suite, set up a global authentication fixture that all tests share:
// global-setup.ts import { chromium } from '@playwright/test'; async function globalSetup() { const browser = await chromium.launch(); const context = await browser.newContext(); const page = await context.newPage(); // Perform login await page.goto('https://myapp.com/login'); await page.getByLabel('Email').fill('user@example.com'); await page.getByLabel('Password').fill('password123'); await page.getByRole('button', { name: 'Sign in' }).click(); await page.waitForURL('**/dashboard'); // Save storage state await context.storageState({ path: 'auth.json' }); await browser.close(); } export default globalSetup;
// playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ globalSetup: './global-setup', use: { storageState: 'auth.json', }, });
Now every test in your suite starts authenticated without repeating login. This is the same pattern codegen uses with --save-storage and --load-storage, but integrated into your project configuration.
Codegen vs Manual Test Writing
When should you use the Playwright test generator, and when should you write tests by hand? Here is an honest comparison.
| Feature | Codegen | Manual |
|---|---|---|
| Speed to first test | Seconds — just click through the app | Minutes to hours depending on complexity |
| Locator quality | Semantic locators (getByRole, getByLabel) | Varies — depends on engineer skill |
| Assertion depth | Basic visibility/text checks only | Complex assertions, soft assertions, custom matchers |
| Page Object Model | Not supported — generates flat tests | Full POM architecture from the start |
| Data-driven tests | Single scenario only | Parameterized with multiple datasets |
| API mocking | Not supported | Full route/intercept control |
| Learning curve | Zero — just use the browser | Requires Playwright API knowledge |
| Maintainability | Good locators but flat structure | Structured, reusable, scalable |
| Best for | Scaffolding, prototyping, learning | Production suites, complex logic |
The key insight: codegen and manual writing are not competitors. The best workflow uses codegen to generate the initial test structure, then refines it manually (or with Claude AI) into production-ready code. Think of codegen as a first draft, not a final product.
Codegen + Claude AI: The 2026 Workflow
In 2026, the most effective way to write Playwright tests is a hybrid approach: use codegen for the initial recording, then hand the generated code to Claude AI for refinement. This combines the speed of recording with the intelligence of AI-powered code generation.
The Workflow: Record, Refine, Deploy
- Record with codegen — Click through the user flow to capture the basic navigation, clicks, and form fills
- Feed to Claude AI — Paste the generated code and ask Claude to add comprehensive assertions, refactor to Page Object Model, handle edge cases, and parameterize test data
- Review and commit — Review the AI-refined code, run it locally, and commit to your repository
- Run in CI/CD — The production-ready test runs automatically on every push via GitHub Actions
Example: Codegen Output to Production Test
Here is what codegen generates for a simple login test:
import { test, expect } from '@playwright/test'; test('test', async ({ page }) => { await page.goto('https://myapp.com/login'); await page.getByLabel('Email').fill('user@example.com'); await page.getByLabel('Password').fill('password123'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page.getByText('Dashboard')).toBeVisible(); });
Here is what Claude AI produces after refinement:
import { test, expect } from '@playwright/test'; import { LoginPage } from './pages/login-page'; import { DashboardPage } from './pages/dashboard-page'; const testUsers = [ { email: 'admin@example.com', password: 'admin123', role: 'admin' }, { email: 'user@example.com', password: 'user123', role: 'user' }, ]; for (const user of testUsers) { test(`login as ${user.role} shows correct dashboard`, async ({ page }) => { const loginPage = new LoginPage(page); const dashboardPage = new DashboardPage(page); await loginPage.goto(); await loginPage.login(user.email, user.password); // Verify successful redirect await expect(page).toHaveURL(/.*dashboard/); // Verify dashboard loaded with correct role await expect(dashboardPage.heading).toBeVisible(); await expect(dashboardPage.roleLabel).toHaveText(user.role); // Verify no console errors const errors: string[] = []; page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); }); await expect(errors).toHaveLength(0); }); } test('login with invalid credentials shows error', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('wrong@email.com', 'wrongpassword'); await expect(loginPage.errorMessage).toBeVisible(); await expect(loginPage.errorMessage).toContainText('Invalid credentials'); await expect(page).toHaveURL(/.*login/); });
Claude AI took the 8-line codegen output and produced a production-ready test suite with Page Object Model, data-driven parameterization, negative test cases, URL assertions, and console error monitoring. This is the workflow you learn in the Playwright + Claude AI course.
Using Claude AI via Playwright MCP Server
The Playwright MCP Server lets Claude AI interact directly with your Playwright project. Instead of copy-pasting code, you can ask Claude to:
- Analyze your codegen output and suggest improvements
- Generate Page Object classes from recorded flows
- Add comprehensive assertions based on the application context
- Create CI/CD pipeline configuration for your test suite
- Refactor existing tests to reduce duplication
This codegen-to-AI pipeline is the fastest way to build a complete, maintainable test suite in 2026. You get the speed of recording with the quality of expert-written code.
Codegen Limitations: When to Write Tests Manually
Playwright codegen is powerful, but it has clear boundaries. Understanding these limitations helps you decide when to reach for codegen and when to write code by hand.
What Codegen Cannot Do
- Complex assertions — Codegen only generates basic visibility and text assertions. It cannot assert on element counts, CSS properties, network responses, or perform soft assertions.
- Data-driven testing — Codegen records a single scenario. It cannot parameterize with multiple datasets or read from CSV/JSON files.
- API mocking and network interception — Codegen has no way to record
page.route()calls or mock API responses. If your test needs to simulate a 500 error or stub a slow endpoint, you must write that manually. - Custom fixtures and hooks —
beforeEach,afterAll, custom fixture dependencies, and test lifecycle hooks are not part of the recording process. - Conditional logic — Real-world tests sometimes need
if/elsebranches based on feature flags or environment state. Codegen records linear flows only. - Drag-and-drop and complex gestures — While codegen handles clicks and keyboard input well, drag-and-drop, multi-touch, and custom pointer sequences often need manual implementation.
- File uploads and downloads — Codegen cannot record file chooser interactions or verify downloaded files.
- iframes and shadow DOM — While Playwright supports both, codegen may struggle to generate correct locators inside nested iframes or shadow roots.
When Manual Writing Is Better
Use manual test writing (or Claude AI generation) when your test requires:
- Database setup and teardown — Seeding test data or cleaning up after tests
- Multi-browser coordination — Tests that involve two browser contexts (e.g., admin approves, user sees result)
- Performance testing — Measuring load times, Core Web Vitals, or network waterfall analysis
- Visual regression testing — Screenshot comparison with
toHaveScreenshot() - API testing — Using Playwright's
requestcontext to test REST/GraphQL endpoints directly
Important: Do not try to force codegen into scenarios it was not designed for. If you find yourself heavily editing every generated test, it is more efficient to write from scratch or use Claude AI to generate the test directly from your requirements.
Best Practices for Using Playwright Codegen
Follow these guidelines to get the most out of the Playwright record and playback workflow without creating a maintenance burden.
1. Use Codegen for Scaffolding, Not Production
Treat codegen output as a starting point. Record the happy path, capture the navigation flow and locators, then restructure the test with proper naming, assertions, and error handling before committing.
2. Always Add Meaningful Assertions
Codegen captures what you click, not what you expect. After recording, add assertions that verify the actual business outcome:
// Codegen gave you this: await page.getByRole('button', { name: 'Add to cart' }).click(); // Add these assertions manually: await expect(page.getByTestId('cart-count')).toHaveText('1'); await expect(page.getByRole('alert')).toContainText('Added to cart'); await expect(page).toHaveURL(/.*cart/);
3. Rename the Test Descriptively
Codegen names every test 'test'. Rename it immediately:
// Bad (codegen default) test('test', async ({ page }) => { ... }); // Good test('user can add item to cart and see updated count', async ({ page }) => { ... });
4. Use the TypeScript Target
Even if your project uses JavaScript, prefer the TypeScript target (--target playwright-test). TypeScript gives you autocompletion, type checking, and catches locator errors at compile time. The generated code is identical to JavaScript with the addition of type safety.
5. Refactor to Page Object Model
If you are generating multiple tests for the same pages, extract common locators into Page Object classes. This prevents duplication and makes maintenance easier when the UI changes:
// pages/checkout-page.ts import { Page, Locator } from '@playwright/test'; export class CheckoutPage { readonly emailInput: Locator; readonly placeOrderButton: Locator; readonly confirmationMessage: Locator; constructor(private page: Page) { this.emailInput = page.getByLabel('Email address'); this.placeOrderButton = page.getByRole('button', { name: 'Place order' }); this.confirmationMessage = page.getByRole('heading', { name: 'Order confirmed' }); } async fillEmail(email: string) { await this.emailInput.fill(email); } async placeOrder() { await this.placeOrderButton.click(); } }
6. Clean Up Generated Locators
Codegen sometimes generates redundant click-then-fill sequences. Clean these up:
// Codegen generates (redundant click before fill): await page.getByLabel('Email').click(); await page.getByLabel('Email').fill('user@example.com'); // Simplified (fill already focuses the element): await page.getByLabel('Email').fill('user@example.com');
7. Record in Short Segments
Instead of recording a 20-step user flow in one session, break it into smaller recordings. Record the login flow, then record the search flow, then record the checkout flow. Combine them into a structured test suite afterward. Shorter recordings are easier to clean up and less prone to errors.
Advanced Codegen Techniques
Generating Tests for Multiple Browsers
Record once, then configure your playwright.config.ts to run the generated test across all browsers:
// playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } }, { name: 'mobile-safari', use: { ...devices['iPhone 15'] } }, ], });
Recording with Trace Viewer Integration
Enable tracing in your configuration so every test run (including those from codegen) captures a full trace file you can inspect after the fact:
// playwright.config.ts export default defineConfig({ use: { trace: 'on-first-retry', // captures trace on failure screenshot: 'only-on-failure', video: 'retain-on-failure', }, });
After a test fails, inspect the trace:
npx playwright show-trace test-results/my-test/trace.zip
The Trace Viewer shows every action, screenshot, network request, and console log from the test run — invaluable for debugging generated tests that fail in CI but pass locally.
Frequently Asked Questions
What is Playwright codegen and how does it work?
Playwright codegen is a built-in test recorder that ships with every Playwright installation. You launch it with npx playwright codegen, interact with a website normally, and Playwright generates the corresponding test code in real time. It uses semantic locators like getByRole and getByLabel to produce resilient, maintainable selectors. The generated code can target TypeScript, JavaScript, Python, Java, or C#.
How do I install and run Playwright codegen?
Install Playwright with npm init playwright@latest, which sets up your project and downloads browsers. Then run npx playwright codegen https://your-site.com to open the recorder. A browser window and an Inspector panel open side by side. Interact with the browser and watch the code generate automatically in the Inspector.
Can Playwright codegen generate tests in Python or Java?
Yes. Use the --target flag: npx playwright codegen --target python-pytest for Python, npx playwright codegen --target java for Java, or npx playwright codegen --target csharp for C#/.NET. Each target generates idiomatic code for that language.
How do I handle authentication with Playwright codegen?
Record your login once with npx playwright codegen --save-storage=auth.json https://your-app.com. Log in normally, then close the browser to save the session. For subsequent recordings, run npx playwright codegen --load-storage=auth.json https://your-app.com to start already authenticated.
Is Playwright codegen good enough for production tests?
Codegen is excellent for scaffolding, but generated code needs refinement for production. Add meaningful assertions, refactor to Page Object Model, parameterize test data, and clean up redundant locators. Think of codegen as a first draft — use Claude AI to refine it into production-grade code.
What are the limitations of Playwright codegen?
Codegen cannot handle complex assertions, data-driven testing, API mocking, custom fixtures, conditional flows, drag-and-drop, or file uploads. It records linear user journeys well but needs manual or AI-assisted refinement for advanced scenarios.
Can I use Playwright codegen with Claude AI?
Yes — this is the recommended 2026 workflow. Record with codegen to capture the basic flow, then feed the generated code to Claude AI via the Playwright MCP Server. Claude refines it with Page Object Model architecture, comprehensive assertions, edge case coverage, and CI/CD integration. This approach combines recording speed with AI intelligence.
Asim Noaman
Senior QA Automation Engineer & AI Testing Specialist
With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.