88 lines
3.4 KiB
Markdown
88 lines
3.4 KiB
Markdown
# page-object
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
## Purpose
|
|
|
|
`PageObject` is the abstract base class for Playwright Page Object Model implementations. It wires every subclass with a consistent `navigate` / `waitForReady` / `findByRole` / `findByLabel` / `findByText` surface so spec files stay declarative. Subclasses override the `path` and `waitForReady` specifics while inheriting the shared query conveniences.
|
|
|
|
## Features
|
|
|
|
- Abstract `path: string` — subclass declares the route
|
|
- `navigate(options?)` — delegates to `page.goto` with the configured path
|
|
- `waitForReady(options?)` — subclass overrides to assert page-specific readiness
|
|
- Query helpers: `findByRole`, `findByLabel`, `findByText` — thin wrappers over `page.getBy*`
|
|
- `exactOptionalPropertyTypes`-safe: `timeout` is forwarded only when set
|
|
|
|
## Usage: Basic Example
|
|
|
|
```ts
|
|
import { PageObject } from "@suaveplan/testing-e2e/page-objects";
|
|
|
|
class LoginPage extends PageObject {
|
|
public readonly path = "/login";
|
|
public async waitForReady(): Promise<void> {
|
|
await this.findByRole("heading", { name: /sign in/i });
|
|
}
|
|
}
|
|
|
|
// spec:
|
|
const login = new LoginPage(page);
|
|
await login.navigate();
|
|
await login.waitForReady();
|
|
```
|
|
|
|
## Usage: Advanced Example
|
|
|
|
```ts
|
|
import { PageObject } from "@suaveplan/testing-e2e/page-objects";
|
|
import { expect } from "@playwright/test";
|
|
|
|
class CheckoutPage extends PageObject {
|
|
public readonly path = "/checkout";
|
|
|
|
public async waitForReady({ timeoutMs }: { timeoutMs?: number } = {}): Promise<void> {
|
|
const headingFinder = this.findByRole("heading", { name: /checkout/i });
|
|
await expect(headingFinder).toBeVisible({
|
|
...(timeoutMs !== undefined ? { timeout: timeoutMs } : {}),
|
|
});
|
|
}
|
|
|
|
public async enterCardNumber(number: string): Promise<void> {
|
|
await this.findByLabel("Card number").fill(number);
|
|
}
|
|
|
|
public async submit(): Promise<void> {
|
|
await this.findByRole("button", { name: /pay now/i }).click();
|
|
}
|
|
}
|
|
|
|
// spec:
|
|
const checkout = new CheckoutPage(page);
|
|
await checkout.navigate({ timeoutMs: 10_000 });
|
|
await checkout.waitForReady();
|
|
await checkout.enterCardNumber("4242 4242 4242 4242");
|
|
await checkout.submit();
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### `abstract class PageObject`
|
|
|
|
- `abstract readonly path: string`
|
|
- `constructor(page: Page)`
|
|
- `navigate(options?: { timeoutMs?: number }): Promise<void>`
|
|
- `waitForReady(options?: { timeoutMs?: number }): Promise<void>` — subclass override
|
|
- `findByRole(role, options?)` → `Locator`
|
|
- `findByLabel(label)` → `Locator`
|
|
- `findByText(text)` → `Locator`
|
|
|
|
## Implementation Notes
|
|
|
|
`navigate` calls `page.waitForLoadState("domcontentloaded", waitOptions)` where `waitOptions` is `undefined` when no timeout is supplied and `{ timeout }` when one is — this conditional construction is required by `exactOptionalPropertyTypes` which forbids passing `{ timeout: undefined }`. The class deliberately stays small; subclass-specific assertions belong on the subclass, not baked into a common helper that tries to anticipate every scenario.
|
|
|
|
### Base Class
|
|
|
|
`PageObject` is the minimal base class for Playwright page-object-model helpers. It carries a `page: Page` reference, a `baseUrl: string`, and exposes `goto(path?)` / `expectVisible(locator)` / `expectUrlMatches(regex)` helpers that every subclass uses. Subclasses narrow the locator surface to the page's specific components and keep tests focused on flows rather than selectors.
|