Migration v1 → v2
v2 removes deprecated APIs and tightens contracts. There is no compatibility shim — update call sites.
Breaking changes
Section titled “Breaking changes”| v1 | v2 |
|---|---|
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
Section titled “Removed APIs”ListStrictSelector
Section titled “ListStrictSelector”Selector(id) already produces the same locator chain. Replace mechanically:
@ListStrictSelector("CartItem")@Selector("CartItem")accessor item!: Locator;PageObject.waitProp / waitPropAbsence / waitNoValue
Section titled “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
Section titled “Renamed APIs”filterByTestId → filterByItemTestId
Section titled “filterByTestId → filterByItemTestId”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.
getItemByIdMask → getItemByTestId(new RegExp(...))
Section titled “getItemByIdMask → getItemByTestId(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
Section titled “Widened signatures”waitText and waitValue accept RegExp
Section titled “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
Section titled “@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
Section titled “New strictness”PageObject subclasses cannot be passed as factory arguments
Section titled “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
Section titled “ListPageObject constructor validates itemType”Invalid item types throw at construction time instead of failing silently on access:
new ListPageObject("not a class"); // throwsnew ListPageObject(42); // throwsnew ListPageObject(CartItemControl); // oknew ListPageObject(new CartItemControl()); // okUpdate strategy
Section titled “Update strategy”- Run a grep pass for the removed and renamed APIs:
ListStrictSelector | waitNoValue | waitProp | waitPropAbsencefilterByTestId | getItemByIdMask
- Apply the table above — each row is a one-line change.
- Run TypeScript. The compiler catches most of the rest.
- Run your test suite. Behavior of equivalent calls is unchanged.
See also
Section titled “See also”- Lists —
filterByItemTestIdvsfilterByHasTestId. - Custom Controls — factory-argument form.
- Built-In POM — initializer form for
PageObject.