Incremental Adoption Strategy
Relevant source files
The following files were used as context for generating this wiki page:
The Incremental Adoption Strategy in playwright-page-object allows developers to transition from raw Playwright locator chains to a typed, decorator-driven Page Object Model (POM) without requiring a complete rewrite of existing test suites. It provides a "ladder" of abstractions that can coexist within the same codebase, fixture map, or even the same class instance.
This strategy is designed to solve the problem of locator duplication and flattened relative structures while respecting existing patterns in a codebase. By being class-instance agnostic, the library supports plain classes, external control libraries, and its own built-in base classes.
Sources: README.md:9-19, skills/playwright-page-object/SKILL.md:144-154
The Adoption Ladder
The framework suggests a three-step progression for adopting its features, allowing teams to choose the level of abstraction that fits their immediate needs.
1. Locator-First Approach
The initial step involves decorating existing classes to provide typed accessors that return raw Playwright Locator objects. This provides the benefit of centralized, relative selectors without introducing new base classes or custom control logic.
- Architecture: Uses
@RootSelectoron the class and@Selector(or variants) on accessors. - Data Flow: The accessor resolves to a
Locatorrelative to the root when accessed. - Key Constraint: The first constructor argument must be
page: Page.
Sources: README.md:156-170, skills/playwright-page-object/SKILL.md:147-150
2. External Controls
As UI patterns repeat, developers can move to external control classes. These are user-defined classes that accept a Locator in their constructor but do not necessarily inherit from the library's base classes.
- Logic: Child decorators take a constructor or factory as a trailing argument.
- Benefit: Encapsulates reusable UI behavior while maintaining independence from the library's inheritance model.
Sources: README.md:172-182, skills/playwright-page-object/SKILL.md:151-152
3. Built-in POM Layer
The final step is adopting the built-in RootPageObject, PageObject, and ListPageObject. This provides "batteries-included" features such as automatic waits, assertion wrappers via .expect(), and advanced list handling.
Sources: README.md:184-194, skills/playwright-page-object/SKILL.md:153-154
Architecture and Data Flow
The following diagram illustrates how the different adoption styles interact with the Playwright Page and Locator objects.
The diagram shows the single entry point (Root Decorator) branching into three distinct implementation paths that all ultimately interact with the Playwright API.
Sources: README.md:83-102, skills/playwright-page-object/SKILL.md:8-25
Implementation Patterns
The library allows mixing these styles within a single class, as demonstrated in the project's example files.
Component Comparison
| Feature | Locator-First | External Controls | Built-in POM |
|---|---|---|---|
| Inheritance | None required | None required | Extends RootPageObject / PageObject |
| Return Type | Locator | Custom Class | PageObject / ListPageObject |
| Wait Helpers | Manual (locator.waitFor) | Custom methods | Built-in (waitVisible, waitText, etc.) |
| Assertions | expect(locator) | Custom methods | control.expect() |
| List Handling | Manual loops | Custom logic | ListPageObject with async iteration |
Sources: README.md:104-142, example/e2e/page-objects/CheckoutPage.ts:13-28
Mixed Adoption Example
In a single Page Object, different accessors can utilize different strategies based on the complexity of the element:
@RootSelector("CheckoutPage")
export class CheckoutPage extends RootPageObject {
// Style 1: Raw Locator for simple fields
@Selector("PromoCodeInput")
accessor PromoCodeInput!: Locator;
// Style 2: External/Custom Control for reusable buttons
@SelectorByRole("button", { name: "Apply" })
accessor ApplyPromoButton = new ButtonControl();
// Style 3: Built-in List for complex collections
@ListStrictSelector("CartItem")
accessor CartItems = new ListPageObject(CartItemControl);
}
Sources: example/e2e/page-objects/CheckoutPage.ts:13-30, src/tests/decorators/selectors-external.spec.ts:98-115
Coexistence and Integration
Fixtures
The createFixtures() utility supports incremental adoption by accepting any root class as long as its first constructor argument is page: Page. It does not matter if the class is a plain decorated class or a RootPageObject subclass.
The sequence diagram illustrates how createFixtures abstracts the instantiation of various adoption styles for the test runner.
Sources: README.md:231-250, skills/playwright-page-object/SKILL.md:131-142
Technical Constraints for Adoption
- TypeScript Requirement: Requires TypeScript 5.0+ for the ECMAScript
accessorkeyword. - Root Context: Child decorators (
@Selector, etc.) always require a root context established by a root decorator (@RootSelector, etc.). - Lazy Resolution: All decorated accessors are lazy; the locator chain is only resolved when the property is accessed.
Sources: README.md:43-46, skills/playwright-page-object/SKILL.md:44-51
Summary
The Incremental Adoption Strategy enables a low-friction transition to better test abstractions. By allowing raw locators, external controls, and the built-in POM layer to coexist, teams can refactor high-value or high-complexity areas of their test suite first while leaving simpler tests in their original state. This approach ensures that adopting the framework does not become a blocker for feature development.