playwright-page-object
Reference

Migration v1 → v2

Breaking changes from v1 to v2 and how to update existing code.

v2 removes deprecated APIs and tightens contracts. There is no compatibility shim — update call sites.

Breaking changes

v1v2
ListStrictSelector(id)Selector(id) — identical locator
PageObject.waitProp(name, value)control.expect().toHaveAttribute("data-prop-{name}", value)
PageObject.waitPropAbsence(name, value)control.expect().not.toHaveAttribute("data-prop-{name}", value)
PageObject.waitNoValue()control.expect().not.toHaveAttribute("data-prop-$value")
list.filterByTestId(id)list.filterByItemTestId(id)
list.getItemByIdMask(mask)list.getItemByTestId(new RegExp(mask))
createFixtures({ x: MyClass })unchanged — factory functions now also accepted

Removed APIs

ListStrictSelector

Selector(id) already produces the same locator chain. Replace mechanically:

- @ListStrictSelector("CartItem")
+ @Selector("CartItem")
  accessor item!: Locator;

PageObject.waitProp / waitPropAbsence / waitNoValue

All three assumed an internal data-prop-* attribute convention — your framework was expected to mirror component props to data-prop-{name} attributes for testing. Both waitProp and waitNoValue used that same convention internally; waitNoValue specifically checked for the absence of data-prop-$value.

Express the same intent directly with Playwright's .expect():

- await checkout.PromoCode.waitProp("disabled", "true");
+ await checkout.PromoCode.expect().toHaveAttribute("data-prop-disabled", "true");

- await checkout.PromoCode.waitPropAbsence("disabled", "true");
+ await checkout.PromoCode.expect().not.toHaveAttribute("data-prop-disabled", "true");

- await checkout.PromoCode.waitNoValue();
+ await checkout.PromoCode.expect().not.toHaveAttribute("data-prop-$value");

If your project does not use the data-prop-* convention and you originally wanted to assert that a form control's value is empty, use Playwright's toHaveValue instead — but note this is a different assertion (DOM .value vs. an attribute):

await checkout.PromoCode.expect().toHaveValue("");

PageObject.expect() returns Playwright's assertion API bound to the underlying locator. If you prefer working with the raw locator directly, import Playwright's expect and pass control.$:

import { expect } from "@playwright/test";

await expect(checkout.PromoCode.$).toHaveAttribute("data-prop-disabled", "true");

Renamed APIs

filterByTestIdfilterByItemTestId

The rename clarifies the self-vs-descendant distinction. The behavior is unchanged.

- const item = list.filterByTestId("CartItem_2");
+ const item = list.filterByItemTestId("CartItem_2");

The companion filterByHasTestId(id) matches rows that contain a descendant with data-testid={id}. See Lists for the full distinction.

getItemByIdMaskgetItemByTestId(new RegExp(...))

The mask was always a regex pattern. The new form is explicit:

- const item = list.getItemByIdMask("CartItem_\\d+");
+ const item = list.getItemByTestId(/CartItem_\d+/);

Widened signatures

waitText and waitValue accept RegExp

await checkout.PromoCode.waitText(/applied/i);
await checkout.PromoCode.waitValue(/^\d{6}$/);
await checkout.PromoCode.waitValue(42);  // number → coerced to "42"

@ListSelector and @ListRootSelector accept string | RegExp

Strings are still used as regex patterns (escape metacharacters if needed). Pass a RegExp for full control:

@ListSelector("CartItem_")             // matches /CartItem_/
@ListSelector(/^CartItem_\d+$/)        // matches CartItem_1, CartItem_2, ...

New strictness

PageObject subclasses cannot be passed as factory arguments

In v1, @Selector("X", MyPageObjectSubclass) silently produced broken instances. In v2, it throws at decoration time:

- @Selector("PromoCode", PromoPageObject)
- accessor PromoCode!: PromoPageObject;

+ @Selector("PromoCode")
+ accessor PromoCode = new PromoPageObject();

Use the initializer form for PageObject subclasses. The factory-argument form is reserved for plain classes whose constructor accepts a Locator (see Custom Controls).

ListPageObject constructor validates itemType

Invalid item types throw at construction time instead of failing silently on access:

new ListPageObject("not a class");  // throws
new ListPageObject(42);              // throws
new ListPageObject(CartItemControl); // ok
new ListPageObject(new CartItemControl()); // ok

Update strategy

  1. Run a grep pass for the removed and renamed APIs:
    ListStrictSelector | waitNoValue | waitProp | waitPropAbsence
    filterByTestId | getItemByIdMask
  2. Apply the table above — each row is a one-line change.
  3. Run TypeScript. The compiler catches most of the rest.
  4. Run your test suite. Behavior of equivalent calls is unchanged.

See also

Last updated on

On this page