Skip to content

Principles

These are the rules POMWright’s design follows. Knowing them makes its behaviour predictable. Where a page seems to break one of them, it says why.

POMWright adds to Playwright and changes none of it. A registered locator is a call of Playwright’s own method with Playwright’s own arguments (getByRole("button", { name: "Log in" })), steps are Playwright’s filter and nth, a resolved path is an ordinary Locator, URLs resolve as Playwright resolves baseURL, waits are Playwright’s waits with Playwright’s timeouts, steps are test.step, storage is page.sessionStorage. What POMWright adds is what Playwright does not have: names for locators, chains built from those names, page objects that check themselves, typed session storage that can be filled before an app loads, and a logger for the report. The docs explain each feature through the Playwright feature it builds on.

Each element gets a path that says where it is (main.form@login.button@login) and one locator that says what it is (getByRole("button", { name: "Log in" })). Resolving a path chains the locators of its segments, so a locator stays short and is still unique in its context, and a page whose structure changes fails the test instead of letting it find a similar element elsewhere. See Resolving locators.

A registered locator never changes after add(): a registration ends with its chain, at the next call on the registry, the registry keeps copies of the objects you pass, and every resolution works on its own copy. A query (getLocatorSchema(path)) is a private copy of a path’s chain, changed for one use. A seed from createReusable is a value: its methods return new seeds, as a Playwright Locator’s do. So code that tweaks a locator for one test cannot change it for the next. See Queries and Reusing locators.

A mistake is reported where it is made, as early as it can be found. A missing first argument, or an empty one where Playwright cannot use it, throws at the call that registers it. A page object checks its URL when it is constructed, and its locators right after, so a broken page object fails the test in the fixture, before the body runs, naming the page object and every path at fault. See Verifying a registry and Page objects.

Check at runtime; types where they cost nothing

Section titled “Check at runtime; types where they cost nothing”

Every rule is checked at runtime, so a value from a variable, an environment or JavaScript code is checked like a literal. TypeScript adds what it can check for free: paths that are not in the union, misspelt has references, a replace() without its first argument. POMWright does not add a type that would make ordinary code harder to write in exchange for an error the runtime reports anyway.

Each method does one thing and says so. update patches a definition and keeps its strategy; replace installs a new one. skip leaves a segment out of one query and deletes nothing. null deletes an option, and undefined means “not given”, as in Playwright. POMWright sets no timeout or wait of its own: an unset option means Playwright’s default.

It has no runner, no reporter and no assertions: tests use @playwright/test. It does not wait for anything Playwright would not wait for, retry anything, or catch errors to hide them. It does not decide how you structure a suite beyond the page object and the registry; Organising a project shows one way.