> ## 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.

# Web Reference

> Reference for @qawolf/flows/web, the entry point for defining web flows with launch behavior, page assertions, and browser context helpers.

`@qawolf/flows/web` defines web flows and optional explicit launch behavior.

## Primary Exports

* `flow(...)`
* `launch(...)`
* `expect`
* `isAnonymous(...)`
* `isPersistent(...)`
* `isElectron(...)`
* `testContextDependencies`

It also exports web-specific target, launch, callback context, and flow definition types.

Example:

```ts theme={null}
import { expect, flow } from "@qawolf/flows/web";

export default flow(
  "Open homepage",
  { target: "Web - Chrome", launch: true },
  async ({ page, test }) => {
    await test("open homepage", async () => {
      await page.goto("https://example.com");
      await expect(page).toBeDefined();
    });
  },
);
```

## Target Model

Web flows accept web targets only. Use the CLI entry point for `Basic`.

```ts theme={null}
type WebFlowTargetInput =
  | WebFlowTarget
  | {
      target: WebFlowTarget;
      launch?: false | undefined;
    }
  | {
      target: WebBrowserFlowTarget;
      launch: true | BrowserLaunchOptions;
    }
  | {
      target: WebElectronFlowTarget;
      launch: ElectronFlowLaunchOptions;
    };
```

Examples:

```ts theme={null}
import { flow, isElectron, isPersistent, launch } from "@qawolf/flows/web";

export const explicitLaunchFlow = flow("Open later", "Web - Chrome", async () => {
  const launchResult = await launch();
  if (isElectron(launchResult)) throw new Error("Expected browser launch");

  const page =
    isPersistent(launchResult)
      ? launchResult.page
      : await launchResult.context.newPage();

  await page.goto("https://example.com");
});

export const launchedFlow = flow(
  "Open immediately",
  { target: "Web - Chrome", launch: true },
  async ({ page, test }) => {
    await test("open page", async () => {
      await page.goto("https://example.com");
    });
  },
);
```

## Launch Shapes

Browser launch accepts the generated Playwright browser and context launch options, excluding Playwright's raw `persistentContext` and `userDataDir` fields. QA Wolf adds `browserContext`, reintroduces `userDataDir` for persistent launches, and maps those options back to the underlying Playwright shape.

```ts theme={null}
type BrowserLaunchOptions = {
  kind?: "browser";
  browser?: "chrome" | "chromium" | "firefox" | "msedge" | "webkit";
  browserContext?: "incognito" | "persistent";
  userDataDir?: string;

  // Generated Playwright browser and context options are also accepted,
  // except for Playwright's raw persistentContext and userDataDir fields.
  headless?: boolean;
  permissions?: string[];
  geolocation?: {
    latitude: number;
    longitude: number;
    accuracy?: number;
  };
  polyfills?: {
    intlListFormat?: boolean;
  };
  // ...plus other generated browser/context options.
};

type LaunchOptions =
  | BrowserLaunchOptions
  | {
      kind: "electron";
      executablePath: string;
    };

type ElectronFlowLaunchOptions = {
  executablePath: string;
};

type LaunchResult =
  | AnonymousLaunchResult
  | PersistentLaunchResult
  | ElectronLaunchResult;
```

QA Wolf maps `browserContext: "persistent"` to Playwright's persistent context mode. Pass `userDataDir` with `browserContext: "persistent"` when a profile directory should be reused.

Browser example:

```ts theme={null}
import { flow, launch } from "@qawolf/flows/web";

export default flow(
  "Open with persistent profile",
  "Web - Chrome",
  async () => {
    const { page } = await launch({
      browserContext: "persistent",
      userDataDir: "/tmp/qawolf-profile",
    });

    await page.goto("https://example.com");
  },
);
```

Electron declarative launch example:

```ts theme={null}
import { flow } from "@qawolf/flows/web";

export default flow(
  "Open Electron app",
  {
    target: "Electron",
    launch: {
      executablePath: "/Applications/MyApp.app/Contents/MacOS/MyApp",
    },
  },
  async ({ page, test }) => {
    await test("open app", async () => {
      await page.goto("about:blank");
    });
  },
);
```

Explicit `launch(...)` calls are not target-typed. Include `kind: "electron"` and use `firstWindowPage` from the result:

```ts theme={null}
import { flow, launch } from "@qawolf/flows/web";

export default flow("Open Electron app", "Web - Chrome", async () => {
  const { firstWindowPage } = await launch({
    kind: "electron",
    executablePath: "/Applications/MyApp.app/Contents/MacOS/MyApp",
  });

  await firstWindowPage.getByRole("button", { name: "Sign in" }).click();
});
```

Static launch options infer a narrow return type when they determine the result shape:

* `launch({ browserContext: "persistent" })` returns `PersistentLaunchResult`
* `launch({ browserContext: "incognito" })` returns `AnonymousLaunchResult`
* `launch({ kind: "electron", executablePath })` returns `ElectronLaunchResult`
* `launch()` returns the full `LaunchResult` union because startup depends on the active flow target

Use `isAnonymous(...)`, `isPersistent(...)`, and `isElectron(...)` to narrow dynamic results before reading shape-specific fields.

## Flow Callback Context

All web flow callbacks receive:

* `inputs`
* `setOutput(...)`
* `test(...)`

Launch-enabled browser flows also receive:

* `page`
* `context`
* optional `browser`

Electron launch-enabled flows receive the first Electron window as `page`.

<Note>
  `test(...)` can be omitted for simple flows where grouping steps into named sub-steps doesn't add value. For most flows, wrapping steps in `test(...)` is recommended — the label appears in your results and makes failures easier to locate.
</Note>

## `testContextDependencies`

`testContextDependencies` is exported for runner and tooling integration. Flow authors should usually use the public callback parameters above instead of depending on the raw runner dependency list.

## `expect`

The exported `expect` is the assertion helper for web flows, backed by [Playwright's `expect`](https://playwright.dev/docs/test-assertions). All Playwright matchers are available — including auto-retrying locator matchers like `toBeVisible`, `toHaveText`, `toHaveURL`, and `toHaveScreenshot`. See [Playwright's locator assertions reference](https://playwright.dev/docs/api/class-locatorassertions) for the full list.

Always import `expect` from `@qawolf/flows/web` rather than from `@playwright/test` directly, so that QA Wolf's defaults (custom timeout, visual diffing) stay in effect.

Example:

```ts theme={null}
import { expect, flow } from "@qawolf/flows/web";

export default flow(
  "Verify homepage",
  { target: "Web - Chrome", launch: true },
  async ({ page, test }) => {
    await test("hero heading is visible", async () => {
      await page.goto("https://example.com");
      await expect(page.getByRole("heading", { name: "Example Domain" })).toBeVisible();
    });
  },
);
```

For visual regression assertions with `expect(page).toHaveScreenshot(...)`, see [Web & Mobile web screenshots](/qawolf/visual-diffing).

## Defaults

* default `kind` is browser launch
* default `browserContext` is `"incognito"`
* default `browser` comes from the flow target
* GPU launch behavior is derived from the flow target
* Electron declarative launch requires `target: "Electron"` and `executablePath`

Example:

```ts theme={null}
import { flow, isElectron, isPersistent, launch } from "@qawolf/flows/web";

export default flow(
  "Use target browser defaults",
  "Web - Firefox",
  async () => {
    const launchResult = await launch();
    if (isElectron(launchResult))
      throw new Error("Expected browser launch");

    const page =
      isPersistent(launchResult)
        ? launchResult.page
        : await launchResult.context.newPage();

    await page.goto("https://example.com");
  },
);
```
