Building External Controls
Relevant source files
The following files were used as context for generating this wiki page:
External controls allow developers to integrate existing Playwright class structures or custom UI component abstractions into the playwright-page-object decorator system without requiring inheritance from built-in base classes like PageObject. This approach is ideal for incremental adoption in projects that already have established control classes or prefer to maintain a decoupled architecture.
Sources: README.md:120-125, skills/playwright-page-object/SKILL.md:105-110
Architecture and Integration
External controls are standard TypeScript classes that typically accept a Playwright Locator in their constructor. Unlike the built-in POM layer, these classes do not provide automatic wait helpers or assertion wrappers unless explicitly implemented by the developer. They are instantiated lazily by child decorators at the moment of accessor access.
Integration Patterns
There are two primary ways to wire an external control to a decorated accessor:
- Constructor Injection: Passing the class constructor as the second argument to a selector decorator. The library automatically calls
new Control(resolvedLocator). - Factory Functions: Passing a callback that receives the
resolvedLocatorand returns a new instance. This is useful for passing additional configuration or parameters to the control's constructor.
Sources: src/tests/decorators/selectors-external.spec.ts:35-45, example/e2e/page-objects/ExternalCheckoutPage.ts:25-35
Implementation Logic Flow
The following diagram illustrates how the library resolves an external control when a test accesses a decorated property.
Each access to the accessor results in a new instance of the external control, ensuring the locator is always resolved fresh from the current page state.
Sources: src/tests/decorators/selectors-external.spec.ts:55-65, README.md:45-50
Defining External Controls
An external control is effective when it encapsulates specific UI logic or repeating Playwright actions.
// Example of a basic external control
class ExternalInputControl {
constructor(readonly locator: Locator) {}
async fill(value: string) {
await this.locator.fill(value);
}
}
Sources: README.md:127-135, src/tests/decorators/selectors-external.spec.ts:13-16
Using External Controls in Root Classes
To use these controls, the root class must be decorated with a root selector (e.g., @RootSelector). For plain classes not extending RootPageObject, the first constructor argument must be page: Page.
| Element | Description |
|---|---|
| Root Decorator | Establishes the base locator context (e.g., @RootSelector("CheckoutPage")). |
| Accessor | A class property using the accessor keyword, decorated with a child selector. |
| Trailing Argument | The class name or factory function passed to the selector decorator. |
Sources: README.md:140-150, skills/playwright-page-object/SKILL.md:65-70
Comparison of Control Styles
The system supports a mix of output shapes for child decorators, allowing external controls to live alongside raw locators and built-in objects.
Key Technical Differences
| Feature | External Control | Built-in PageObject |
|---|---|---|
| Base Class | None required | Must extend PageObject |
| Instantiation | New instance per access | Cloned via cloneWithContext |
| Constructor | (locator: Locator) | (page, root, selector) |
| Wait Helpers | Manual implementation | Built-in (waitVisible, etc.) |
| Locator Access | Typically via property (e.g., .locator) | Via $ property |
Sources: README.md:180-195, skills/playwright-page-object/SKILL.md:145-155, src/tests/decorators/selectors-external.spec.ts:55-65
Usage Example: Factory Functions
Factory functions allow for complex initialization, such as capturing variables from the surrounding scope or setting default state.
@RootSelector("CheckoutPage")
export class ExternalCheckoutPage {
constructor(readonly page: Page) {}
@SelectorByRole(
"button",
{ name: "Apply" },
(locator) => new ExternalButtonControl(locator)
)
accessor ApplyPromoButton!: ExternalButtonControl;
}
Sources: example/e2e/page-objects/ExternalCheckoutPage.ts:38-45, src/tests/decorators/selectors-external.spec.ts:75-85
Conclusion
Building external controls provides a non-intrusive way to structure Playwright tests. By leveraging constructors and factory functions within decorators, developers can maintain typed, reusable abstractions while keeping the flexibility of plain classes. This mechanism ensures that as the UI changes, selector logic remains centralized within the control graph without forcing a specific inheritance model.
Sources: README.md:235-245, skills/playwright-page-object/SKILL.md:175-185