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

# Playwright

> The browser engine, its options, and the web fixture it contributes.

`@e2edev/playwright` drives Chromium, Firefox, and WebKit through Playwright.
A web target names it as its engine, and it contributes the `web` fixture for
browser-only work. Import `test` from the package to have `web` typed.

Bring your own Playwright: the package declares `playwright` as a peer
dependency (`>=1.63.0 <2`) and does not install it. An app that already depends
on Playwright keeps its version, one copy in `node_modules`, and one browser
cache; `e2e init` adds `playwright` only to a project that has none. A version
outside the range may be rejected by your package manager as an unmet peer
(npm's `ERESOLVE`), so upgrade `playwright` within the range.

```ts title="e2e.config.ts" theme={"theme":"catppuccin-mocha"}
import type { E2EConfig } from '@e2edev/e2e';
import { playwright } from '@e2edev/playwright';

export default {
  targets: [{ engine: playwright({ url: 'http://localhost:3000' }) }],
} satisfies E2EConfig;
```

## Options

`playwright(options)` takes the app declaration every engine makes plus the
browser choices:

| Option                                      | Type                                  | Default            | Meaning                                                                                                                                                                                                                                                                      |
| ------------------------------------------- | ------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`                                       | `string`                              | unset              | The app's base URL; required once a test navigates. See [the app under test](/reference/config#the-app-under-test).                                                                                                                                                          |
| `command`, `readyUrl`, `services`           |                                       | unset              | The app process and its dependencies. See [command](/reference/config#command) and [services](/reference/config#services).                                                                                                                                                   |
| `allowedOrigins`, `environment`, `identity` |                                       | derived from `url` | Origin policy and cache/session identity. See [the app under test](/reference/config#the-app-under-test).                                                                                                                                                                    |
| `browser`                                   | `'chromium' \| 'firefox' \| 'webkit'` | `'chromium'`       | The browser to launch.                                                                                                                                                                                                                                                       |
| `viewport`                                  | `{ width, height }`                   | `1280` by `720`    | Initial viewport of every attempt's page.                                                                                                                                                                                                                                    |
| `connect`                                   | `PlaywrightConnectOptions`            | unset              | Attach to a remote browser over CDP instead of launching locally. Chromium only. `cdpEndpoint(signal)` resolves the DevTools URL; it is called once per worker and again when a session drops.                                                                               |
| `headers`                                   | `Record<string, string>`              | unset              | HTTP headers added to every request bound for an allowed origin and to none other. Names are case-insensitive. Routes every request through the runner, which turns the browser's HTTP cache off and blocks service workers for the target. A trace records request headers. |
| `basicAuth`                                 | `{ username, password }`              | unset              | Answers a `401` challenge from an allowed origin; a challenge from any other origin gets nothing. `:` is not allowed in the user name.                                                                                                                                       |

`headers` and `basicAuth` are how a test reaches a [protected preview](/starting-your-app#protected-previews).

## Web

```ts theme={"theme":"catppuccin-mocha"}
export interface Web extends Expectable<WebExpectation> {
  goto(
    url: string,
    options?: {
      waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';
      timeout?: number;
    },
  ): Promise<void>;
  reload(options?: ActionOptions): Promise<void>;
  back(options?: ActionOptions): Promise<void>;
  forward(options?: ActionOptions): Promise<void>;
  url(): Promise<string>;
  title(): Promise<string>;
  waitForURL(url: string | RegExp, options?: { timeout?: number }): Promise<void>;
  locator(selector: string): Locator;
  frameLocator(selector: string): Screen;
  evaluate<T extends JsonValue>(fn: string | (() => T | Promise<T>)): Promise<T>;
  evaluate<T extends JsonValue, Arg extends JsonValue>(
    fn: string | ((arg: Arg) => T | Promise<T>),
    arg: Arg,
  ): Promise<T>;
  route(
    pattern: string | RegExp,
    handler: (route: WebRoute) => void | Promise<void>,
  ): Promise<void>;
  unroute(pattern: string | RegExp): Promise<void>;
  waitForResponse(
    pattern: string | RegExp,
    options?: { timeout?: number },
  ): Promise<WebResponse>;
  cookies(): Promise<Cookie[]>;
  setCookies(cookies: readonly Cookie[]): Promise<void>;
  setViewport(size: { width: number; height: number }): Promise<void>;
  onDialog(
    handler: 'accept' | 'dismiss' | ((dialog: Dialog) => void | Promise<void>),
  ): Promise<() => Promise<void>>;
  waitForDownload(
    trigger: () => Promise<void>,
    options?: { timeout?: number },
  ): Promise<{ path: string; suggestedFilename: string }>;
  readonly keyboard: {
    press(key: string): Promise<void>;
    type(text: string): Promise<void>;
  };
  readonly mouse: {
    move(x: number, y: number): Promise<void>;
    wheel(deltaX: number, deltaY: number): Promise<void>;
    down(): Promise<void>;
    up(): Promise<void>;
  };
}
```

| Member                                                                                                                      | Default timeout                                                                                                      |
| --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `goto`, `reload`, `back`, `forward`                                                                                         | `config.timeout`, 120000 ms, the same budget `app.open()` uses; `{ timeout }` overrides, capped by the test deadline |
| `waitForResponse`, `waitForDownload`                                                                                        | `config.actionTimeout`, 30000 ms; `{ timeout }` overrides, capped by the test deadline                               |
| `waitForURL`                                                                                                                | `config.assertionTimeout`, 5000 ms                                                                                   |
| `url`, `title`, `evaluate`, `route`, `unroute`, `cookies`, `setCookies`, `setViewport`, `onDialog`, `keyboard.*`, `mouse.*` | `config.actionTimeout`, 30000 ms, not overridable                                                                    |

Every `web` call is a recorded `web.<method>` step. A relative URL or a URL
match needs the engine's declared `url`; without it the call fails with
`APP_URL_REQUIRED`. Declare `requires: ['web']` in a suite that also runs on
other platforms; acquiring `web` on a target whose engine does not contribute
it fails with `UNSUPPORTED_CAPABILITY`.

### Navigation

| Method                      | Behavior                                                                                                                                    | Throws                                                             |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `goto(url, options?)`       | Absolute URL or route relative to the base URL. `waitUntil` defaults to `'load'`.                                                           | `POLICY_DENIED` for a disallowed origin or scheme, `ACTION_FAILED` |
| `reload(options?)`          | One history reload                                                                                                                          | `ACTION_FAILED`                                                    |
| `back(options?)`            | One history entry back                                                                                                                      | `ACTION_FAILED`                                                    |
| `forward(options?)`         | One history entry forward                                                                                                                   | `ACTION_FAILED`                                                    |
| `url()`                     | Current serialized URL, immediate read                                                                                                      |                                                                    |
| `title()`                   | Current document title, immediate read                                                                                                      |                                                                    |
| `waitForURL(url, options?)` | Polls every 100 ms. A relative string resolves against the base URL and matches exactly after normalization; a `RegExp` tests the full URL. | `ASSERTION_FAILED` on timeout                                      |

```ts theme={"theme":"catppuccin-mocha"}
await web.goto('/dashboard', { waitUntil: 'networkidle' });
await web.waitForURL(/\/orders\/\d+$/, { timeout: 15_000 });
```

### Locators and frames

| Method                   | Returns   | Notes                                                                                                                                                                            |
| ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `locator(selector)`      | `Locator` | Web-only CSS or XPath locator, synchronous. Prefer semantic `screen` queries; a raw selector is not portable. Throws `INVALID_LOCATOR` for an empty selector.                    |
| `frameLocator(selector)` | `Screen`  | Query scope inside one iframe. You query inside it, you do not act on it. At resolve time it throws `LOCATOR_NOT_FOUND` for a missing frame and `LOCATOR_AMBIGUOUS` for several. |

```ts theme={"theme":"catppuccin-mocha"}
const payment = web.frameLocator('#payment-frame');
await payment.getByLabel('Card number').fill('4242424242424242');
```

### evaluate

Both overloads serialize the function and run it in the page, so it cannot
close over test-scope variables. Pass data through `arg`. Both `arg` and the
return value must be `JsonValue`; anything else, including a non-finite
number, throws `INVALID_ARGUMENT`.

Exceptions thrown by the evaluated code fail the test with `EVALUATE_FAILED`
and preserve the message. A Playwright timeout is reported as
`ACTION_FAILED`; page or browser closure, crashes, and lost execution
contexts are `ENGINE_FAILURE`.

```ts theme={"theme":"catppuccin-mocha"}
const count = await web.evaluate(() => document.querySelectorAll('li').length);
const flag = await web.evaluate(
  (name: string) => window.localStorage.getItem(name),
  'featureFlags',
);
```

### Routing

| Method                               | Behavior                                                                                                                                                                                                                                             | Throws                                                                      |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `route(pattern, handler)`            | Registers an attempt-scoped route. It may be registered before the first page opens and stays in force across `app.restart()`, `app.clearState()`, and session restore. The handler must decide exactly once with `fulfill`, `continue`, or `abort`. | `ACTION_FAILED` when the handler decides twice, or returns without deciding |
| `unroute(pattern)`                   | Removes every attempt-scoped route registered with a matching pattern                                                                                                                                                                                |                                                                             |
| `waitForResponse(pattern, options?)` | Resolves with a `WebResponse` whose `json()` and `text()` decode the buffered body                                                                                                                                                                   | `ACTION_FAILED` when nothing matches inside the deadline                    |

```ts theme={"theme":"catppuccin-mocha"}
await web.route('**/api/quote', async (route) => {
  await route.fulfill({ json: { cents: 4200 } });
});

const response = await web.waitForResponse('**/api/orders');
expect(response.status).toBe(201);
```

```ts theme={"theme":"catppuccin-mocha"}
export type RouteFulfillResponse = {
  status?: number;
  headers?: Record<string, string>;
} & (
  | { json: JsonValue; body?: never }
  | { body: string; json?: never }
  | { body?: never; json?: never }
);

export interface WebRoute {
  readonly request: {
    readonly url: string;
    readonly method: string;
    readonly headers: Readonly<Record<string, string>>;
    readonly postData?: string;
  };
  fulfill(response: RouteFulfillResponse): Promise<void>;
  continue(): Promise<void>;
  abort(): Promise<void>;
}

export interface WebResponse {
  readonly url: string;
  readonly status: number;
  readonly headers: Readonly<Record<string, string>>;
  json<T = unknown>(): Promise<T>;
  text(): Promise<string>;
}
```

`json` and `body` are mutually exclusive. Omitting both fulfills with an
empty body.

### Cookies and viewport

| Method                | Behavior                                                                                                                                        | Throws                                                                                                      |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `cookies()`           | Returns a mutable array copy of the cookies visible to the current context                                                                      |                                                                                                             |
| `setCookies(cookies)` | Validates every cookie's target origin before setting any. For the `domain` form the scheme follows the base URL and a leading `.` is stripped. | `POLICY_DENIED` for an unparseable target or a disallowed origin; nothing is set when any entry is rejected |
| `setViewport(size)`   | Resizes the viewport and records the new size on the report                                                                                     |                                                                                                             |

```ts theme={"theme":"catppuccin-mocha"}
export interface CookieFields {
  name: string;
  value: string;
  /** Unix timestamp in whole seconds. */
  expires?: number;
  httpOnly?: boolean;
  secure?: boolean;
  sameSite?: 'Strict' | 'Lax' | 'None';
}

export type Cookie = CookieFields &
  (
    | { url: string; domain?: never; path?: never }
    | { url?: never; domain: string; path?: string }
  );
```

Supply `url`, or `domain` with an optional `path`. Never both.

```ts theme={"theme":"catppuccin-mocha"}
await web.setCookies([{ url: 'http://localhost:3000', name: 'tz', value: 'UTC' }]);
```

### Dialogs and downloads

| Method                               | Behavior                                                                                                                                                                                                                                                                                                           | Throws                                                      |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
| `onDialog(handler)`                  | Registers an attempt-scoped handler and returns an idempotent unsubscribe function. Removed automatically at attempt end. A function handler must call `accept` or `dismiss`; one that returns without deciding has the dialog dismissed and fails the next step with `APP_NOT_OPEN`, as an unhandled dialog does. |                                                             |
| `waitForDownload(trigger, options?)` | Starts a download watch, runs `trigger`, waits for completion inside the same deadline, and registers the file as a `download` artifact. A failure in `trigger` keeps its own error.                                                                                                                               | `ACTION_FAILED` when the download does not complete in time |

```ts theme={"theme":"catppuccin-mocha"}
export interface Dialog {
  readonly message: string;
  accept(text?: string): Promise<void>;
  dismiss(): Promise<void>;
}
```

```ts theme={"theme":"catppuccin-mocha"}
const off = await web.onDialog('accept');
await screen.getByRole('button', { name: 'Delete' }).tap();
await off();

const file = await web.waitForDownload(() =>
  screen.getByRole('link', { name: 'Export CSV' }).tap(),
);
```

### keyboard and mouse

| Method                        | Behavior                                 |
| ----------------------------- | ---------------------------------------- |
| `keyboard.press(key)`         | Sends one key to whatever has focus      |
| `keyboard.type(text)`         | Types plain text into whatever has focus |
| `mouse.move(x, y)`            | Moves the pointer                        |
| `mouse.wheel(deltaX, deltaY)` | Scrolls the pointer wheel                |
| `mouse.down()`                | Presses the primary button               |
| `mouse.up()`                  | Releases the primary button              |

Both namespaces are viewport-level and unfocused. Prefer `locator.press` and
`locator.fill` when a target exists. `keyboard.type` takes plain text only;
use `locator.fill` with a `Secret` for secret material.

### JsonValue

```ts theme={"theme":"catppuccin-mocha"}
export type JsonPrimitive = string | number | boolean | null;
export type JsonValue =
  | JsonPrimitive
  | { readonly [key: string]: JsonValue }
  | readonly JsonValue[];
```

## Exported types

`PlaywrightOptions`, `PlaywrightConnectOptions`, `PlaywrightBasicAuth`,
`BrowserName`, `PlaywrightLiveSurface`, `Web`, `WebExpectation`, `WebRoute`,
`WebResponse`, `Cookie`, `Dialog`, and `DialogHandler` are exported from the
package for typing config helpers and fixtures.

## expect(web)

`expect(web)` returns the `WebExpectation` the engine attaches: `toHaveURL`,
`toHaveTitle`, and `toHaveClass`. See [web matchers](/reference/expect#web-matchers).

## surfaceOf

```ts theme={"theme":"catppuccin-mocha"}
import { playwright, surfaceOf } from '@e2edev/playwright';

const engine = playwright({ url: 'http://localhost:3000' });
const live = surfaceOf(engine);   // PlaywrightLiveSurface | undefined
live!.page();                     // the Page of the current attempt
live!.context();                  // its BrowserContext
```

`surfaceOf(handle)` returns the `PlaywrightLiveSurface` behind a handle this
package created, or `undefined` for any other handle. Both accessors throw
`INVALID_STATE` before an attempt is running or a page is open. It exists for a [custom executor](/executors#driving-the-page-yourself)
that brings its own browser tooling; what it does there is not recorded as
steps.

<CardGroup cols={2}>
  <Card title="app" href="/reference/app" />

  <Card title="screen and Locator" href="/reference/screen" />

  <Card title="expect" href="/reference/expect" />

  <Card title="Starting your app" href="/starting-your-app" />
</CardGroup>
