Playwright Codegen Best Practices
July 6, 2026 Β· View on GitHub
This guide explains how to improve Playwright codegen output to align with our lint standards.
Quick Fix Commands
1. Post-process any codegen test file
# After generating a test with codegen, run this to fix common issues:
npm run fix-codegen -- <path-to-test-file>
2. Generate and fix in one command
# Generate test and auto-fix it
npx playwright codegen && npm run fix-codegen -- playwright/tests/your-new-test.test.ts
Manual Improvements
Current Issues with Codegen Output
The codegen test you provided has these issues:
// β No describe block wrapper
test("should toggle between light and dark themes", async ({ page }) => {
// β Raw locator instead of semantic
page.getByTestId("app-container").getByText("πUptime Watcher0%").first()
// β Title could be improved
test("should toggle between light and dark themes"
Improved Version
/**
* Theme switch UI tests
*
* @remarks Generated by Playwright codegen and improved for lint compliance
* @file Playwright test for theme switching functionality
*/
import { test, expect } from "@playwright/test";
test.describe("theme switch UI", () => {
test("should toggle between light and dark themes", async ({ page }) => {
// Test initial state
await expect(page.getByLabel("Toggle theme")).toContainText("βοΈ");
// Toggle to dark theme
await page.getByRole("button", { name: "Toggle theme" }).click();
await expect(page.getByLabel("Toggle theme")).toContainText("π");
// Toggle back to light theme
await page.getByRole("button", { name: "Toggle theme" }).click();
await expect(page.getByLabel("Toggle theme")).toContainText("βοΈ");
// Verify app container is visible
await expect(page.getByTestId("app-container")).toBeVisible();
// Verify main UI elements
await expect(page.getByTestId("app-container")).toMatchAriaSnapshot(\`
- text: /π Uptime Watcher 0% Health 0 Up 0 Down 0 Pending \\d+ Paused \\d+ Total/
- button "Add new site"
- button "Toggle theme"
- button "Settings"
\`);
});
});
Codegen Best Practices
1. Always wrap in describe blocks
test.describe("feature name", () => {
test("should do something", async ({ page }) => {
// test content
});
});
2. Use semantic locators when possible
// β
Good - semantic
page.getByRole("button", { name: "Click me" });
page.getByLabel("Username");
page.getByPlaceholder("Enter email");
// β Avoid - raw CSS
page.getByRole("button");
page.getByRole("#submit");
page.getByRole(".my-class");
3. Use testId for complex elements
// β
Good - when semantic locators aren't sufficient
page.getByTestId("complex-component");
// β Avoid - complex CSS selectors
page.getByRole("div.container > ul.list > li:nth-child(3)");
4. Write descriptive test titles
// β
Good
test("should toggle between light and dark themes");
test("should display error message for invalid input");
// β Avoid
test("test theme toggle");
test("Test error");
5. Group related assertions
// β
Good - group related checks
const themeButton = page.getByRole("button", { name: "Toggle theme" });
await expect(themeButton).toBeVisible();
await expect(themeButton).toContainText("βοΈ");
// β Avoid - scattered individual assertions
await expect(page.getByRole("button", { name: "Toggle theme" })).toBeVisible();
// ... other code ...
await expect(page.getByRole("button", { name: "Toggle theme" })).toContainText(
"βοΈ"
);
Configuration Improvements
Our playwright.config.ts already includes:
testIdAttribute: "data-testid"- Makes codegen prefer test IDs- Proper timeout settings
- TypeScript support
Automated Linting
After generating tests, always run:
npm run lint:fix
npm run lint
This will catch most issues automatically.