From grimoire
Encapsulates UI page interactions behind dedicated objects so tests express user intent and stay maintainable when selectors or layout change.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-page-object-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Encapsulate all interaction with a UI page or component behind a dedicated object, so tests express user intent in domain language and remain insulated from changes to HTML structure, selectors, or page layout.
Encapsulate all interaction with a UI page or component behind a dedicated object, so tests express user intent in domain language and remain insulated from changes to HTML structure, selectors, or page layout.
Adopted by: Selenium WebDriver documentation (recommended as the official pattern for maintainable browser automation), Google (internal standard for UI test automation), Playwright documentation, Cypress best practices guide, and standard in every major UI automation framework and testing curriculum.
Impact: Google's test engineering team reports that without Page Objects, UI test maintenance consumes 60–80% of test suite engineering time as the UI evolves. With Page Objects, the same selector change requires updating one method in one Page Object rather than dozens of test methods. Fowler's documentation estimates that Page Objects reduce test maintenance cost by 3–5× for UIs that change regularly. Amazon's Alexa team reported a 70% reduction in test maintenance time after adopting Page Objects across their UI automation suite.
Why best: Without Page Objects, UI tests directly reference selectors (#login-button, .submit-form). When the UI changes — as it always does — every test that references the changed element breaks. With Page Objects, tests reference actions (loginPage.clickSubmit()) not selectors. The selector lives in exactly one place. The test stays stable; only the Page Object changes when the UI does. Tests also become readable — checkoutPage.addToCart(product) reads like a user story, not an HTML interrogation.
Sources: Fowler "PageObject" (martinfowler.com/bliki/PageObject.html 2013); Selenium WebDriver "Page Objects" design pattern documentation; Google Testing Blog "Page Objects That Suck Less" (2014); Meszaros "xUnit Test Patterns" (2007) Ch. 10; Playwright Best Practices guide
login(username, password) not findUsernameField().clear().type(username). Properties: isErrorMessageVisible() not findElement('.error-msg').isDisplayed().loginPage.submitLogin() returns a DashboardPage. This chains Page Objects and makes test flow explicit: const dashboard = loginPage.login(user, pass); dashboard.verifyWelcomeMessage().getErrorMessage() returns the message text; the test asserts expect(loginPage.getErrorMessage()).toBe("Invalid password"). This keeps Page Objects reusable and separates the mechanism (what to access) from the check (what to assert).LoginPage.withValidUser(), CheckoutPage.withItemInCart(product). This removes repeated setup boilerplate from tests.data-testid or data-cy attributes to elements that tests need to interact with. These attributes are stable, explicit, and communicate the test contract to developers. CSS class selectors break when styling is refactored.loginPage.verifyErrorMessage() that both reads the message and asserts it is non-empty couples Page Object to a specific test expectation. When the test needs a different assertion, the Page Object breaks the test or the developer duplicates it.Login Page Object (TypeScript/Playwright):
class LoginPage {
private usernameInput = '[data-testid="username"]';
private passwordInput = '[data-testid="password"]';
private submitButton = '[data-testid="login-submit"]';
private errorMessage = '[data-testid="login-error"]';
async login(username: string, password: string): Promise<DashboardPage> {
await this.page.fill(this.usernameInput, username);
await this.page.fill(this.passwordInput, password);
await this.page.click(this.submitButton);
return new DashboardPage(this.page);
}
async getErrorMessage(): Promise<string> {
return this.page.textContent(this.errorMessage);
}
}
// Test:
const dashboard = await loginPage.login('[email protected]', 'password');
expect(await loginPage.getErrorMessage()).toBe('');
Checkout Page Object: addToCart(product), proceedToCheckout() returns PaymentPage, getCartItemCount() returns number. Test: const payment = checkoutPage.addToCart(item).proceedToCheckout(); expect(payment.getOrderTotal()).toBe(29.99).
npx claudepluginhub jeffreytse/grimoire --plugin grimoireGuides creating page objects and refactoring Playwright tests using Page Object Model patterns for maintainability, reusability, and scalability. Covers locators, principles, and TypeScript examples.
Expert approach to page-object-model in test automation. Use when working with .
Writes maintainable Playwright E2E tests using page objects, accessible locators, fixtures, and parallel execution. Helps debug flaky tests and manage complex user flows.