Page Objects are the right abstraction for almost every UI test suite — they isolate selectors from test logic, make tests readable, and reduce maintenance cost when the UI changes. Building them is also time-consuming work that Claude Code handles well, especially when Playwright MCP gives it access to the real page.
Two paths to a Page Object
Path 1 — with Playwright MCP (preferred): Claude Code navigates the real page, analyses the DOM, and generates locators from what it actually finds. Selectors are real. This requires Playwright MCP to be connected — covered in Chapter 4 of this course and in the Playwright MCP course.
Visit https://staging.myapp.com/products/123 and analyse the page.
Generate a Playwright Page Object:
- Save to src/pages/ProductDetailPage.ts
- Use getByRole, getByLabel, getByTestId in that preference order
- Methods: addToCart(quantity), selectVariant(variantName), expandReviews()
- Getters: productName, currentPrice, stockStatus
- TypeScript, constructor takes only pagePath 2 — from source code: When you cannot connect Playwright MCP, point Claude Code at the application source instead of pasted HTML:
The React component for the product detail page is at src/components/ProductDetail.tsx.
Read it and generate a Playwright Page Object for testing.
Save to src/pages/ProductDetailPage.ts.
Match the style of the other page objects in src/pages/.Source code is more reliable than HTML snapshots because it shows the data-testid values the developers added — not what a browser happened to render at one point in time.
Structuring Page Objects for real applications
For pages with multiple distinct sections, generate component-level objects rather than one large class:
The order history page has a search bar, a filter panel, and a results table.
Each is a standalone component in the source.
Generate three objects:
- src/pages/components/OrderSearchBar.ts
- src/pages/components/OrderFilterPanel.ts
- src/pages/OrderHistoryPage.ts (composes the above two)
Read the existing page objects in src/pages/ to match the composition pattern.This mirrors the component hierarchy. When the filter panel changes, only OrderFilterPanel.ts needs updating.
What to include in the prompt
Good Page Object prompts specify:
- File path and class name — where it lives
- Locator strategy — which approach to prefer (
getByRolevsgetByTestIdvs CSS) - Methods to expose — name the user-facing actions, not the implementation
- Getters for readable state — values the test needs to assert on
- An existing example to match — always point at one existing page object for style
What to leave out: assertions (they belong in tests, not page objects), and overly specific implementation detail (name the method addToCart, let Claude Code decide how to implement it).
After generation — verifying selectors
Generated Page Objects need the same review as generated tests, plus one specific check: verify selectors against the real page.
Paste three or four locators from the generated file into your browser's DevTools console and confirm they find elements. For getByRole('button', { name: 'Add to cart' }), check that a button with exactly that accessible name exists on the page.
If a selector is wrong, fix it precisely:
> The generated POM uses getByRole('button', { name: 'Add to cart' }).
The actual button text is 'Add to Bag'. Fix ProductDetailPage.ts.For Selenium/Java POMs, the same workflow applies with @FindBy annotations:
Generate a Selenium WebDriver Page Object in Java for the same page.
Use @FindBy with id, css, or xpath. Include the @PageFactory.initElements pattern.
Extend BasePage from src/test/java/com/myapp/base/BasePage.java.⚠️ Common Mistakes
- Putting assertions in Page Objects. A Page Object that calls
expect(this.productName).toBe('...')mixes responsibilities. Assertions belong in tests. Page Objects expose state and actions — that is all. - Generating a POM without verifying selectors. A Page Object with broken locators is worse than no Page Object — it fails in ways that look like test logic bugs. Always verify a few selectors before committing.
- One massive Page Object per page. A 300-method class for a complex page is a maintenance burden. Split by component or by user journey, not by page URL.
🎯 Practice Task
Build a Page Object for a real page. 20–30 minutes.
- Pick a page in a project you test that currently has inline selectors scattered across test files.
- Ask Claude Code to read one existing Page Object as a style reference (if you have one).
- Use Path 1 (Playwright MCP) or Path 2 (component source) to generate the POM. Be specific about the methods and getters you need.
- Verify three or four of the generated selectors against the real page in DevTools.
- Write one test that uses the generated POM for a happy-path scenario.
The next lesson covers the part of test setup that is arguably more tedious than writing tests: generating and managing test data and fixtures.