playwright-page-object
v2.0.3
Published
Typed, decorator-driven Page Object Model for Playwright. Reusable, lazy locator chains in plain TypeScript classes.
Downloads
180
Maintainers
Readme
playwright-page-object
Typed, decorator-driven Page Object Model for Playwright. Reusable, lazy locator chains in plain TypeScript classes.
Documentation: https://sergeyshmakov.github.io/playwright-page-object/
Before / after
// Before — selectors duplicated, structure invisible
await page.getByTestId("CheckoutPage").getByTestId("PromoCodeInput").fill("SAVE20");
await page.getByTestId("CheckoutPage").getByRole("button", { name: "Apply" }).click();// After — typed, composable, reusable
await checkoutPage.applyPromoCode("SAVE20");What it is
A locator-composition layer, not a framework. Decorators scope a class to a Playwright locator; child decorators resolve relative to that scope. The accessor type determines output: raw Locator, a custom control, or a built-in PageObject.
- No inheritance required for basic use
- Lazy locator chains rebuild only when accessed
- Three output styles coexist in the same suite
- TypeScript-first, ECMAScript decorators (no
experimentalDecoratorsneeded)
Install
npm install -D playwright-page-objectRequirements:
- Node
>=20 @playwright/test >=1.35.0- TypeScript
>=5.0(withtarget: "ES2015"or higher)
Quick start
import type { Locator, Page } from "@playwright/test";
import { RootSelector, Selector, SelectorByRole } from "playwright-page-object";
@RootSelector("CheckoutPage")
class CheckoutPage {
constructor(readonly page: Page) {}
@Selector("PromoCodeInput")
accessor PromoCodeInput!: Locator;
@SelectorByRole("button", { name: "Apply" })
accessor ApplyButton!: Locator;
async applyPromoCode(code: string) {
await this.PromoCodeInput.fill(code);
await this.ApplyButton.click();
}
}import { test } from "@playwright/test";
test("apply promo code", async ({ page }) => {
const checkout = new CheckoutPage(page);
await checkout.applyPromoCode("SAVE20");
});See the Quick Start guide for fixtures, page-only hosts, and the next steps.
Output styles
Three styles, picked per accessor. Mix freely in the same class.
Raw Locator
Minimal abstraction. Typed accessor, no helpers.
@Selector("PromoCodeInput")
accessor PromoCodeInput!: Locator;Custom controls
Pass any class whose constructor accepts a Locator. Reuse your existing control library.
@Selector("PromoCodeInput", InputControl)
accessor PromoCode!: InputControl;Built-in POM
PageObject / ListPageObject for wait helpers, soft assertions, and filter chains.
class CheckoutPage extends RootPageObject {
@Selector("PromoCodeInput")
accessor PromoCode = new PageObject();
async applyPromo(code: string) {
await this.PromoCode.waitVisible();
await this.PromoCode.$.fill(code);
}
}Context resolution
Child decorators resolve in this order: a @RootSelector-managed locator, then a locator property on the host, then page.locator("body") if page is present. The first match wins.
Context Resolution reference →
Documentation
The full documentation site covers every guide, the API reference, and the v1 → v2 migration:
https://sergeyshmakov.github.io/playwright-page-object/
- Getting Started — install, quick start, choosing a style
- Guides — plain classes, fragments, custom controls, built-in POM, lists, fixtures, incremental adoption
- Reference — context resolution, migration v1 → v2, troubleshooting
- API — decorators,
PageObject,RootPageObject,ListPageObject,createFixtures
AI tooling
This package ships an Agent Skills-compatible skill so AI assistants load library-specific guidance on demand:
npx ctx7 skills install /sergeyshmakov/playwright-page-object playwright-page-objectIt is also indexed in Context7 and documented in a Cubic wiki. See AI Tooling in the docs.
Migrating from v1
See the migration guide. Most changes are mechanical renames.
Contributing
See CONTRIBUTING.md.
