app - e2e

The app fixture opens, restarts, and resets the app under test, and takes screenshots and recordings. It is on every target. A method the engine can’t perform fails with UNSUPPORTED_CAPABILITY.

tests/billing.e2e.ts
import { expect, test } from 'e2e';

test('billing page', async ({ app, screen }) => {
  await app.open('/settings/billing');
  await expect(screen.getByRole('heading', { name: 'Billing' })).toBeVisible();
  await app.screenshot('billing');
});

Reference

open, restart, and clearState are bounded by timeout. The others are bounded by actionTimeout. Both come from the config: timeout defaults to 120000 ms and actionTimeout to 30000 ms. The remaining test timeout caps both. app methods take no per-call timeout option.

baseUrl

The resolved base URL of the target’s app.

const health = await fetch(`${app.baseUrl}/api/health`);
  • When app.url declares port 0, baseUrl carries the port the run allocated.
  • On a target that declares no app.url, it is undefined.

open

Opens the app: the declared URL, a path relative to it, or any absolute http(s) URL.

await app.open(); // the target's app.url
await app.open('/settings/billing'); // a path relative to app.url
await app.open('https://example.com/login'); // any http(s) URL

On a device target, which has no URL, open() launches the pinned app fresh and takes no path.

ThrowsWhen
APP_URL_REQUIREDA path, on a target that declares no app.url.
POLICY_DENIEDA URL whose scheme is not http: or https:. about:blank is allowed.
APP_UNREACHABLEThe app URL could not be reached.
ACTION_FAILEDThe operation failed or timed out.
UNSUPPORTED_CAPABILITYNo session.open on a target with a URL, or no session.restart on one without.

restart

Recreates the execution context and keeps persisted state. Then it reopens the app at its base URL when the target declares one.

await app.restart();
ThrowsWhen
ACTION_FAILED, ENGINE_FAILUREThe operation or the engine failed.
UNSUPPORTED_CAPABILITYNo session.restart.

clearState

Clears persisted client state and recreates the execution context. Then it reopens the app at its base URL when the target declares one.

test.beforeEach(async ({ app }) => {
  await app.clearState();
});
ThrowsWhen
ACTION_FAILED, ENGINE_FAILUREThe operation or the engine failed.
UNSUPPORTED_CAPABILITYNo session.reset.

clearState inside a serial group destroys the shared state the group depends on. Use it in beforeEach of an ordinary group instead.

back

Navigates back once.

await app.back();
ThrowsWhen
APP_NOT_OPENapp.open() has not run, or the engine reported invalid state.
ACTION_FAILEDThe operation failed or timed out.
UNSUPPORTED_CAPABILITYNo session.back.

screenshot

Captures a redacted evidence screenshot and returns its path.

const shot = await app.screenshot('after-checkout');
  • The file is an attempt artifact under screenshots/. The returned path is absolute. The report records the path relative to the artifact root.
  • label names the step and joins the file name.
ThrowsWhen
UNSUPPORTED_CAPABILITYThe engine takes no screenshots.
ENGINE_FAILUREThe capture failed.

The screenshot masks secure fields only, after a Secret fill too. A secret the app shows anywhere else is in the image. See screenshots after a secret fill.

startRecording

Starts recording the screen, until stopRecording.

test('the dialog slides in', { video: 'off' }, async ({ app, screen }) => {
  await app.open();
  await app.startRecording({ name: 'dialog' });
  await screen.getByRole('button', 'Open').tap();
  await expect(screen.getByRole('dialog')).toBeVisible();
  const [video] = await app.stopRecording();
});
  • One recording at a time. The engine has one recorder, so set video: 'off' on the test: a start fails while the test’s video records. That includes --video, retain-on-failure, and the retries on-first-retry records.
  • A recording still running when the test ends is stopped and kept.
  • Like the test’s video, a recording masks nothing.
ThrowsWhen
UNSUPPORTED_CAPABILITYThe test’s video records, or the engine records no video.
INVALID_ARGUMENTAnother recording is running.

stopRecording

Stops the recording and returns its videos, in play order.

const [video] = await app.stopRecording();
if ('path' in video) console.log(video.path);
  • A video is a file (path, absolute) under the attempt’s recordings/, named 001-<name>.<ext>, or a url when a hosted browser or device keeps the recording.
  • There is one video, except on the web when the page was replaced while it recorded (app.restart(), app.clearState()): then one per page.
  • The step links each video in the trace.

Types

export interface App {
  /**
   * The resolved base URL of the target's app, with the port the run
   * allocated when `app.url` declared port 0; undefined for a target that
   * declares no `app.url`.
   */
  readonly baseUrl: string | undefined;
  /**
   * Opens the app: the declared URL, a path relative to it, or any absolute
   * http(s) URL. On a device target, which has no URL, it launches the pinned
   * app fresh and takes no path.
   */
  open(path?: string): Promise<void>;
  /**
   * Recreates the execution context while preserving persisted state, then
   * reopens the app at its base URL when the target declares one.
   */
  restart(): Promise<void>;
  /**
   * Clears persisted client state, recreates the execution context, then
   * reopens the app at its base URL when the target declares one.
   */
  clearState(): Promise<void>;
  /** Navigates back once. */
  back(): Promise<void>;
  /** Captures a redacted evidence screenshot and returns its absolute path. */
  screenshot(label?: string): Promise<string>;
  /**
   * Starts recording the screen, until `stopRecording`. One recording at a
   * time, and none while the test's `video` records: set `{ video: 'off' }`
   * on the test.
   */
  startRecording(options?: RecordingOptions): Promise<void>;
  /**
   * Stops the recording and returns its videos in play order: one, or on the
   * web one per page when the page was replaced while it recorded.
   */
  stopRecording(): Promise<readonly [RecordedVideo, ...RecordedVideo[]]>;
}

/** How `app.startRecording` names its file. */
export interface RecordingOptions {
  /** File name stem, `recordings/001-<name>.<ext>`; defaults to `recording`. */
  readonly name?: string;
}

/** One video `app.stopRecording` returns: a file, or a link to a recording a hosted service keeps. */
export type RecordedVideo = RecordedVideoFile | RecordedVideoLink;

/** A video file under the attempt's `recordings/` directory. */
export interface RecordedVideoFile {
  /** Absolute path of the file. */
  readonly path: string;
  /** When it started recording, as an ISO timestamp. */
  readonly startedAt: string;
}

/** A recording a hosted browser or device service keeps, by URL. */
export interface RecordedVideoLink {
  /** `http(s)` URL of the recording. */
  readonly url: string;
  /** Media type of what the URL serves: `video/mp4`, or `text/html` for a player page. */
  readonly mediaType: string;
  /** When it started recording, as an ISO timestamp. */
  readonly startedAt: string;
}

Browser-only operations live on the browser fixture, and device operations on device.

Was this page helpful?