Skip to content

Verifying a registry

Playwright’s locators are lazy: a mistake in one surfaces only when an action uses it, possibly late in a test or in a test that rarely runs. Most mistakes in a registry are compile errors already, such as a path that is not in the union or a misspelt has reference. The rest can be found without a browser, and POMWright finds them all at once: verify() resolves every registered path and reports every failure in one error.

A page object verifies its registry right after defineLocators(), so a mistake fails the test in the fixture, before its body runs, with the page object’s name. A registry you create with createRegistryWithAccessors is verified when you call registry.verify().

registry.add("list").locator("ul");
registry.add("list.items").getByRole("listitem").filter({ has: "list.titel" }); // never registered
registry.add("menu").locator("nav");
registry.add("menu.items").locator("li").filter({ has: "menu.items" }); // refers to itself
registry.verify();
Locator registry verification failed for 2 of 4 paths:
"list.items": No locator schema registered for path "list.titel".
"menu.items": Detected cyclic filter reference while resolving "menu.items": "menu.items".

Each path is resolved on its own, as getLocator resolves it, which evaluates its has and hasNot references: a reference to a path that is never registered, a loop of references, a reference that is neither a Locator nor a path, and a reference to an any-frame segment all fail, each reported at the path that owns it. An any-frame frameLocator() is checked against its first-segment rule (see Frames). Registration mistakes, a duplicate path or an empty selector, throw earlier, at the call that makes them.

What it does not check: whether a selector parses (Playwright parses it at the first action), whether an element exists, whether a locator is unique, a query’s changes, and a path in the union that is never registered and never resolved, such as a slot no page object fills (see Locator modules). It never touches the page, and it costs a few microseconds per path (see Performance).

A page object runs defineLocators() and then verify() once, right after it is constructed. A failure is reported with the page object’s label and the original error as cause, at the line that constructed it. For a LoginPage whose form is filtered by its heading, main.form@login.heading, a path in its union that it never registers:

LoginPage: Locator registry verification failed for 1 of 5 paths:
"main.form@login": No locator schema registered for path "main.form@login.heading".

There is no way to turn it off: it fails only where a resolution would fail. See Page objects for what a test shows when the page object is built in a fixture, beforeEach or beforeAll.

By default a segment without a locator is a name that groups paths (see Resolving locators). A project that registers every segment can have verify() check that it did, with strictChains: true, so a forgotten ancestor is reported instead of silently leaving the chain shorter:

const { registry } = createRegistryWithAccessors<"main" | "main.form.button.save">(page, { strictChains: true });
registry.add("main").locator("main");
registry.add("main.form.button.save").getByRole("button", { name: "Save" });
registry.verify();
Locator registry verification failed for 1 of 2 paths:
"main.form.button.save": strictChains requires every ancestor to be registered; "main.form" and "main.form.button" are not.

A page object takes it as a constructor option, locators: { strictChains: true }, usually once, in the site’s base class. A path that fails both checks gets both messages on its line. skip works as before: it leaves a registered segment out of one query. Under the option an any-frame segment, which may have no registered ancestor, can only be a root path.