Skip to content

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 — fragment
class HostB { constructor(readonly locator: Locator) {} }
// ✅ Option C — page-only host
class 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 instantiation
new CheckoutPage(page);
// ✅ via fixtures
const 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 assertion
accessor PromoCode: Locator;
// ✅ decorator handles initialization
accessor 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 unknown
accessor Items = new ListPageObject();
// ✅ items are typed as CartItemControl
accessor 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 class
createFixtures({
authPage: function (page) {
return new AuthPage(page, config);
},
});
// ✅ arrow function — no prototype, called directly
createFixtures({
authPage: (page) => new AuthPage(page, config),
});