Playwright Codegen Explained: How to Use It Correctly and When NOT to Rely on It
I wasted hours writing bad locators until I discovered Playwright Codegen. Run one command, click through your app, and get working test code instantly.
But here is the truth most tutorials skip: Codegen is a starting point, not the final solution. Good engineers do not copy-paste blindly. They refine. They simplify. They make locators stable.
This guide shows you how to use Codegen correctly — and when to stop relying on it.
Contents
Getting Started with Codegen
# Launch Codegen against any URL
npx playwright codegen https://your-app.com
# With specific viewport
npx playwright codegen --viewport-size=1280,720 https://your-app.com
# With device emulation
npx playwright codegen --device="iPhone 13" https://your-app.com
Codegen opens a browser and a code panel. Every click, type, and navigation you perform gets recorded as test code in real time.
Understanding Codegen’s Locator Strategy
Codegen prioritizes locators in this order:
getByRole()— most stable, based on ARIA rolesgetByLabel()— for form inputs with associated labelsgetByText()— matches visible text contentgetByTestId()— if data-testid attributes exist- CSS/XPath selectors — last resort, most fragile
How to Refactor Codegen Output
Before Refactoring (Raw Codegen Output)
test('login flow', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.getByLabel('Email').fill('user@test.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByText('Welcome back').click();
});
After Refactoring (Production-Ready)
test('should login successfully with valid credentials', async ({ page }) => {
// Arrange
await page.goto('/login');
// Act
await page.getByLabel('Email').fill('user@test.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
// Assert
await expect(page.getByText('Welcome back')).toBeVisible();
await expect(page).toHaveURL(/dashboard/);
});
When NOT to Use Codegen
- Complex assertions — Codegen records actions, not verifications. You must add assertions manually.
- API-dependent flows — if test data needs API setup, Codegen cannot capture that.
- Dynamic content — Codegen may generate fragile selectors for dynamically loaded elements.
- Page Object patterns — Codegen outputs flat tests. Restructuring into Page Objects is always manual.
The Codegen Workflow for SDETs
- Record — Use Codegen to capture the basic flow
- Review — Check every locator for stability
- Refactor — Add assertions, extract Page Objects, parameterize data
- Harden — Replace fragile selectors, add waits for dynamic content
- Commit — Only commit the refactored version, never raw Codegen output

