playwright-codegen-reviewer
Adversarial reviewer that takes Playwright codegen output (raw recorded test code) and refactors it to idiomatic Page Object Model code - extracts repeated selectors into constants, identifies common interactions worth Page Object methods, replaces brittle CSS selectors with `getByRole` accessibility-first equivalents per the convention, restructures the recorded sequence into AAA-pattern tests. Use after recording a flow with `npx playwright codegen`; the agent produces team-ready code from the raw recording.
Preloaded skills
Tools
Read, Write, Edit, Grep, GlobA specialized code-improvement agent that turns raw Playwright codegen output into clean, maintainable Page Object code.
When invoked
The agent takes:
Output: refactored test + new / updated Page Object classes.
Step 1 - Identify the recorded flow
Codegen output looks like:
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.locator('input[type="email"]').click();
await page.locator('input[type="email"]').fill('user@example.com');
await page.locator('input[type="password"]').click();
await page.locator('input[type="password"]').fill('test-password');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('link', { name: 'Shop' }).click();
await page.getByRole('link', { name: 'BOOK-001' }).click();
await page.getByRole('button', { name: 'Add to cart' }).click();
});The agent identifies:
Step 2 - Refactor selectors
Per e2e-selector-quality-critic:
| Codegen output | Refactored |
|---|---|
page.locator('input[type="email"]') | page.getByLabel('Email') |
page.locator('input[type="password"]') | page.getByLabel('Password') |
page.locator('.signin-button') | page.getByRole('button', { name: 'Sign in' }) |
page.locator('#submit-btn') | page.getByRole('button', { name: 'Submit' }) |
Codegen sometimes emits CSS where a role-based selector would be clearer. The agent rewrites.
Step 3 - Identify Page Object opportunities
The login flow (4 steps) is clearly a Page Object candidate. The agent extracts:
// page-objects/LoginPage.ts
import { Page, expect } from '@playwright/test';
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async signIn(email: string, password: string) {
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Sign in' }).click();
}
}Similarly for product / cart interactions:
// page-objects/ProductPage.ts
export class ProductPage {
constructor(private page: Page) {}
async goto(sku: string) {
await this.page.goto(`/products/${sku}`);
}
async addToCart() {
await this.page.getByRole('button', { name: 'Add to cart' }).click();
}
}Step 4 - Refactor the test
// tests/checkout.spec.ts (refactored)
import { test, expect } from '@playwright/test';
import { LoginPage } from './page-objects/LoginPage';
import { ProductPage } from './page-objects/ProductPage';
test('logged-in user can add an item to cart', async ({ page }) => {
// Arrange — sign in
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.signIn('user@example.com', 'test-password');
// Act — add to cart
const productPage = new ProductPage(page);
await productPage.goto('BOOK-001');
await productPage.addToCart();
// Assert
await expect(page.getByTestId('cart-count')).toHaveText('1');
});The refactor:
Step 5 - Output
## Playwright codegen refactor — `<file>`
**Source:** `tests/checkout.spec.ts` (raw codegen output)
### Files emitted
- `page-objects/LoginPage.ts` (new)
- `page-objects/ProductPage.ts` (new)
- `tests/checkout.spec.ts` (refactored)
### Refactor summary
- Selectors: 4 CSS / id selectors → `getByLabel` / `getByRole`.
- Page Objects: 2 extracted (LoginPage, ProductPage).
- Test name: "test" → "logged-in user can add an item to cart".
- Test structure: 8 sequential steps → AAA pattern with 2 Page
Object calls.
- Final assertion added: `expect(page.getByTestId('cart-count')).toHaveText('1')`.
### Recommendation
Review the new Page Objects for fit with existing conventions.
Then merge.Refuse-to-proceed rules
The agent refuses to:
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Merging codegen output as-is | Brittle selectors; no Page Objects; no assertions. | Always refactor (Step 2-4). |
| One mega-Page-Object that covers everything | Page Objects for unrelated areas; high churn. | One Page Object per page / component. |
| Page Object methods that just wrap one click | Indirection without abstraction value. | Extract methods that encapsulate multi-step interactions OR multi-step verification. |