Troubleshooting
“Cannot resolve locator: the class does not implement the context protocol…”
Section titled ““Cannot resolve locator: the class does not implement the context protocol…””Cause: the host class has no @RootSelector(...), no locator property, and no page property. Child decorators have nowhere to chain from.
Fix: make the host fit one of the patterns:
// ✅ Option A — scoped root@RootSelector("Page")class HostA { constructor(readonly page: Page) {} }
// ✅ Option B — fragmentclass HostB { constructor(readonly locator: Locator) {} }
// ✅ Option C — page-only hostclass HostC { constructor(readonly page: Page) {} }See Context Resolution for the full priority list.
”X extends PageObject and cannot be passed as a factory argument…”
Section titled “”X extends PageObject and cannot be passed as a factory argument…””Cause: a PageObject subclass was passed as the second argument to @Selector(...). The decorator detects this at decoration time.
// ❌ throws at decoration time@Selector("PromoCode", PromoPageObject)accessor PromoCode!: PromoPageObject;Fix: use the initializer form:
// ✅ initialize the instance instead@Selector("PromoCode")accessor PromoCode = new PromoPageObject();Why: PageObject instances are cloned under the parent’s scope via cloneWithContext. The factory-argument form constructs a new instance with the locator, which bypasses that contract. Custom controls (plain classes with constructor(locator: Locator)) do use the factory-argument form — see Custom Controls.
”X must extend RootPageObject to use root decorators…”
Section titled “”X must extend RootPageObject to use root decorators…””Cause: @RootSelector(...) was applied to a class that extends PageObject (not RootPageObject). PageObject is for nested children only.
Fix: extend RootPageObject for root-level page objects:
// ❌ PageObject is for nested children only@RootSelector("CheckoutPage")class CheckoutPage extends PageObject {}
// ✅ RootPageObject is the root-level base class@RootSelector("CheckoutPage")class CheckoutPage extends RootPageObject {}“Decorated class must receive Playwright Page as the first constructor argument.”
Section titled ““Decorated class must receive Playwright Page as the first constructor argument.””Cause: the root-decorated class was instantiated with something other than a Page object as its first argument.
Fix: pass page directly, or use createFixtures() which wires page into every fixture:
// ✅ manual instantiationnew CheckoutPage(page);
// ✅ via fixturesconst test = base.extend(createFixtures({ checkoutPage: CheckoutPage }));TypeScript: “Property ‘X’ has no initializer…”
Section titled “TypeScript: “Property ‘X’ has no initializer…””Cause: missing the ! non-null assertion on a decorated accessor.
Fix:
// ❌ missing the non-null assertionaccessor PromoCode: Locator;
// ✅ decorator handles initializationaccessor PromoCode!: Locator;The !: tells TypeScript the decorator handles initialization.
TypeScript: “‘accessor’ modifier requires ‘ES2015’ or higher…”
Section titled “TypeScript: “‘accessor’ modifier requires ‘ES2015’ or higher…””Cause: the tsconfig.json target is below ES2015.
Fix:
{ "compilerOptions": { "target": "ES2022" }}See Installation for the full recommended config.
for await (const item of list.items) types as unknown
Section titled “for await (const item of list.items) types as unknown”Cause: ListPageObject was constructed without an itemType, so item type cannot be inferred.
Fix: pass the item control class:
// ❌ no item type — items are typed as unknownaccessor Items = new ListPageObject();
// ✅ items are typed as CartItemControlaccessor Items = new ListPageObject(CartItemControl);Fixture factory is treated as a constructor
Section titled “Fixture factory is treated as a constructor”Cause: the factory is a regular function (not arrow), so it has a prototype and is detected as a class.
Fix: use arrow function syntax:
// ❌ regular function — has a prototype, detected as a classcreateFixtures({ authPage: function (page) { return new AuthPage(page, config); },});
// ✅ arrow function — no prototype, called directlycreateFixtures({ authPage: (page) => new AuthPage(page, config),});