playwright-page-object
API

PageObject

Built-in nested-control class with wait helpers and assertions.

PageObject is the base class for nested controls. Use it as the initializer for a decorated accessor:

@Selector("PromoCode")
accessor PromoCode = new PageObject();

For root-level page objects, use RootPageObject instead.

Instance members

.$

The raw Playwright Locator bound to this control's resolved chain.

await checkout.PromoCode.$.fill("SAVE20");
await checkout.PromoCode.$.click();

Use whenever a built-in wait helper does not cover your case.

.page

A getter that returns the underlying Page instance.

const page = checkout.PromoCode.page;
await page.keyboard.press("Escape");

.expect(options?)

Returns Playwright's assertion API on .$. Accepts the same options as Playwright's expect:

await control.expect().toBeVisible();
await control.expect({ soft: true }).toHaveText("Click me");
await control.expect({ message: "Apply must be enabled" }).toBeEnabled();

Wait helpers

MethodAccepts
waitVisible()
waitHidden()
waitText(text)string | RegExp
waitValue(value)string | RegExp | number
waitCount(count)number
waitChecked()
waitUnChecked()
await control.waitVisible();
await control.waitText("Applied");
await control.waitText(/applied/i);
await control.waitValue(42);   // coerced to "42"

waitValue accepts number and coerces with String(value) for convenience. Pass a RegExp for pattern matching.

Subclassing

For typed nested children, extend PageObject and add decorated accessors:

class CartItemControl extends PageObject {
  @Selector("Name")
  accessor Name = new PageObject();

  @SelectorByRole("button", { name: "Remove" })
  accessor RemoveButton = new PageObject();

  async remove() {
    await this.RemoveButton.$.click();
  }
}

Use the subclass as an initializer:

@Selector("CartItem_1")
accessor FirstItem = new CartItemControl();

cloneWithContext for custom constructors

If your subclass has constructor arguments beyond the default, override cloneWithContext(root, selector). The library calls this on every access to bind the instance to its parent's resolved scope.

class TimedItem extends PageObject {
  constructor(readonly timeout: number) {
    super();
  }

  cloneWithContext(root, selector) {
    const next = new TimedItem(this.timeout);
    return next.attach(root, selector);
  }
}

Most users do not need this — the default cloneWithContext covers the no-arg constructor case.

Static guards

StaticReturns
PageObject.isClass(v)true if v is a class extending PageObject
PageObject.isInstance(v)true if v is an instance of PageObject

Used internally and exposed for advanced use cases (e.g., generic helpers that inspect accessor types).

See also

Last updated on

On this page