> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qawolf.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Reference

> API reference for @qawolf/pom — page object base classes, the page registry, typed construction, and page hooks.

`@qawolf/pom` provides Page Object Model (POM) infrastructure for
[Playwright](https://playwright.dev): base classes for page objects, a central
registry that lets page objects construct one another without circular imports,
and automatic installation of popup and route-interception hooks.

## Requirements

* **Node.js** `>=22.22`
* **ES modules** — the package is ESM-only (`"type": "module"`). Import it from
  ESM code and use explicit `.js` specifiers in relative imports.
* **Peer dependencies**
  * `@qawolf/flows` — used by `EntryPointPageObject` to launch the browser.
  * `playwright` — for the `Page` / `Locator` types your page objects use.

## Install

```sh theme={null}
npm install @qawolf/pom
```

## Defining a page object

Extend `BasePageObject`. It stores the Playwright `Page` (available as
`this.page`) and provides `this.create(...)` for constructing other page
objects. Keep selectors in a private `locators` getter.

```ts theme={null}
import { BasePageObject } from "@qawolf/pom";

export class LoginPage extends BasePageObject {
  private get locators() {
    return {
      email: this.page.getByLabel("Email"),
      password: this.page.getByLabel("Password"),
      submit: this.page.getByRole("button", { name: "Sign in" }),
    } as const;
  }

  async signIn(email: string, password: string) {
    await this.locators.email.fill(email);
    await this.locators.password.fill(password);
    await this.locators.submit.click();
    return this.create("DashboardPage");
  }
}
```

## The page registry

### Registering

Create one module that registers every page object, and import it for its side
effects **before** you construct any page object. Register lazily with a module
loader so a page object's module is only loaded when it is first used:

```ts theme={null}
// register-pages.ts
import { registerPage } from "@qawolf/pom";

registerPage("LoginPage", () => import("./pages/login-page.js"));
registerPage("DashboardPage", () => import("./pages/dashboard-page.js"));
```

The loader must resolve to a module that exports the class under the **same
name** it was registered with. Eager registration also works when you already
hold the class value:

```ts theme={null}
import { LoginPage } from "./pages/login-page.js";

registerPage("LoginPage", LoginPage);
```

### Constructing

`createPage` builds a registered page object for a given `Page`. Import the
registration module first so the registry is populated:

```ts theme={null}
import "./register-pages.js";
import { createPage } from "@qawolf/pom";

const loginPage = await createPage("LoginPage", page);
await loginPage.signIn("user@example.com", "hunter2");
```

Inside a page object, use the protected `this.create(...)` instead — it shares
the current `Page` and routes through the same registry, which is what lets
page objects reference each other without importing each other's classes as
values (avoiding circular imports). Use `import type` only for annotations:

```ts theme={null}
import { BasePageObject } from "@qawolf/pom";
import type { SettingsPage } from "./settings-page.js";

export class DashboardPage extends BasePageObject {
  async openSettings(): Promise<SettingsPage> {
    await this.page.getByRole("link", { name: "Settings" }).click();
    return this.create("SettingsPage");
  }
}
```

`create` / `createPage` are async because lazily registered modules load on
first use.

### Typing `this.create(...)`

By default `this.create("LoginPage")` returns `any`. Augment the
`RegisteredPages` interface — declared for exactly this purpose — to make the
name-to-type mapping known, and `create` becomes fully typed. This is
incremental: names you don't list keep the `any` fallback.

```ts theme={null}
import type { LoginPage } from "./pages/login-page.js";
import type { DashboardPage } from "./pages/dashboard-page.js";

declare module "@qawolf/pom" {
  interface RegisteredPages {
    LoginPage: LoginPage;
    DashboardPage: DashboardPage;
  }
}
```

Now `await this.create("LoginPage")` is typed as `LoginPage`.

## Entry points and page hooks

An **entry point** is the page object that owns browser startup. Extend
`EntryPointPageObject` and expose a `create()` that launches the browser
(`initializeBrowser`), builds the instance (`createFromPage`), and installs
page hooks:

```ts theme={null}
import { EntryPointPageObject } from "@qawolf/pom";

export class AppEntryPoint extends EntryPointPageObject {
  static async create(): Promise<AppEntryPoint> {
    const page = await AppEntryPoint.initializeBrowser();
    const entryPoint = AppEntryPoint.createFromPage(page);
    await entryPoint.installPageHooks();
    return entryPoint;
  }

  async openLogin() {
    await this.goto("/login");
    return this.create("LoginPage");
  }
}
```

A page object can own popups to auto-dismiss or routes to intercept by
overriding `popupHandlers()` / `routeInterceptors()` on its class:

```ts theme={null}
import { BasePageObject, type PopupHandlerDef } from "@qawolf/pom";

export class CookieBannerPage extends BasePageObject {
  override popupHandlers(): PopupHandlerDef[] {
    return [
      {
        name: "cookie-banner",
        trigger: this.page.getByRole("button", { name: "Accept cookies" }),
        dismiss: async () => {
          await this.page
            .getByRole("button", { name: "Accept cookies" })
            .click();
        },
      },
    ];
  }
}
```

`installPageHooks()` collects these across **every** registered page object,
not just the entry point. Overrides are detected by an own-property check on the
registered class's prototype, so declare `popupHandlers()` /
`routeInterceptors()` directly on the class you register — an override inherited
from a base class is not picked up.

When registering lazily, pass `{ providesPageHooks: false }` for page objects
you know declare no hooks, so hook installation can skip loading their modules:

```ts theme={null}
registerPage("StaticPage", () => import("./pages/static-page.js"), {
  providesPageHooks: false,
});
```

## Exports

| Export                                                       | Description                                                                                                                 |
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `BasePageObject`                                             | Base class for page objects. Holds `this.page`; provides `this.create(name)` and the static `createFromPage(page)` factory. |
| `SubPageObject`                                              | Base class for a page object scoped to a parent region/component.                                                           |
| `EntryPointPageObject`                                       | Base class for the entry point; owns browser launch (`initializeBrowser`), `goto`, and `installPageHooks`.                  |
| `registerPage(name, classOrLoader, options?)`                | Register a page object by name, eagerly (class) or lazily (`() => import(...)`).                                            |
| `createPage(name, page)`                                     | Construct a registered page object for a `Page`. Returns a promise.                                                         |
| `PopupHandler`                                               | Manages add/remove of popup handlers on a page.                                                                             |
| `NetworkMonitor`                                             | Observes network activity and surfaces `NetworkError`s.                                                                     |
| `RegisteredPages`                                            | Empty interface you augment to type `create(name)`.                                                                         |
| `PopupHandlerDef`, `RouteInterceptorDef`, `PageSetupOptions` | Hook and setup types.                                                                                                       |
| `callPlatformAPI`                                            | Client for the QA Wolf platform API.                                                                                        |
| `assertPricesClose`, `moneyToNumber`, `numberToMoney`        | Money/price test-data helpers.                                                                                              |
| `reportCleanupFailed`, `reportCleanupFailure`                | Report failures from test cleanup steps.                                                                                    |
