Initial Commit
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user