Page-Only Hosts
A page-only host is a class without @RootSelector(...) that still has a page property. Child decorators chain from page.locator("body") — the same default scope as @RootSelector() with no argument.
Minimal example
Section titled “Minimal example”import type { Locator, Page } from "@playwright/test";import { Selector } from "playwright-page-object";
class CheckoutPage { constructor(readonly page: Page) {}
@Selector("PromoCodeInput") accessor PromoCodeInput!: Locator;}Resolves to:
page.locator("body").getByTestId("PromoCodeInput")When to pick this style
Section titled “When to pick this style”- Test ids in your application are globally unique.
- Wrapping every page in a container
data-testidis more noise than signal. - The page logically is the root.
Trade-off
Section titled “Trade-off”Without a container scope, selectors are not protected against collisions elsewhere on the page. If two pages reuse the same data-testid, switch to @RootSelector(...).
Equivalent forms
Section titled “Equivalent forms”These three are functionally equivalent in scope:
// 1. Page-only host (no decorator)class A { constructor(readonly page: Page) {}}
// 2. @RootSelector() with no argument@RootSelector()class B { constructor(readonly page: Page) {}}
// 3. @RootSelector() with body@RootSelector()class C { constructor(readonly page: Page) {}}All three scope children to page.locator("body"). Pick the page-only form when you don’t need the decorator for any other purpose; pick @RootSelector() if the class extends RootPageObject (the decorator is what wires the root locator into the base class).
See also
Section titled “See also”- Plain Classes — the scoped-root variant.
- Built-In POM — when you want wait helpers and
.expect().