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

# iOS Reference

> Reference for @qawolf/flows/ios, the entry point for defining iOS flows with simulator and device controls, launch options, and assertions.

`@qawolf/flows/ios` defines iOS flows and advanced simulator or device control.

## Primary Exports

* `flow(...)`
* `launch(...)`
* `device`
* `expect`
* `testContextDependencies`

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

Example:

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

const profile = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict></dict></plist>`;

export default flow(
  "Open iOS app",
  { target: "iOS - iPhone 15 (iOS 26)", launch: true },
  async ({ driver, test }) => {
    await test("install profile", async () => {
      await driver.pause(1000);
      await device.installConfigurationProfile(driver, profile);
    });
  },
);
```

## Flow Callback Context

All iOS flow callbacks receive:

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

Launch-enabled iOS flows also receive `driver`.

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

## Target Model

The current target input model is:

```ts theme={null}
type IosFlowTargetInput =
  | IosFlowTarget
  | {
      target: IosFlowTarget;
      launch?: false | undefined;
    }
  | {
      target: IosFlowTarget;
      launch: true | LaunchOptions;
    };
```

The implementation accepts either:

* a target directly for the common path
* `{ target, launch }` when startup behavior should be part of the flow

Example:

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

export const targetOnlyFlow = flow(
  "Target-only path",
  "iOS - iPhone 15 (iOS 26)",
  async () => {
    // call launch() explicitly when startup should happen in the callback
  },
);

export const launchedFlow = flow(
  "Launch-enabled path",
  { target: "iOS - iPhone 15 (iOS 26)", launch: true },
  async ({ driver, test }) => {
    await test("launch app", async () => {
      await driver.pause(1000);
    });
  },
);
```

## `flow(...)`

Use `flow(...)` for iOS authoring.

Behavior from the implementation:

* without launch, the callback receives `inputs`, `setOutput(...)`, and `test(...)`
* with `launch: true`, the flow calls `launch()` with default iOS startup
* with `launch: <options>`, the flow calls `launch(options)`
* when launch is enabled, the callback also receives `driver`

Example:

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

export default flow(
  "Launch in flow definition",
  { target: "iOS - iPhone 15 (iOS 26)", launch: true },
  async ({ driver, test }) => {
    await test("launch app", async () => {
      await driver.pause(1000);
    });
  },
);
```

## `launch(...)`

`launch()` starts iOS automation for the active flow and returns:

```ts theme={null}
type LaunchResult = {
  driver: Awaited<ReturnType<Dependencies["wdio"]["startIos"]>>;
};
```

This API is only available while a flow is running.

Example:

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

export default flow(
  "Launch explicitly",
  "iOS - iPhone 15 (iOS 26)",
  async () => {
    const { driver } = await launch();
    await driver.pause(1000);
  },
);
```

### Launch Shape

```ts theme={null}
type LaunchOptions = {
  app?: {
    path?: string;
    env?: string;
    url?: string;
  };
  autoAcceptAlerts?: boolean;
  autoDismissAlerts?: boolean;
  browserName?: string;
  bundleId?: string;
  capabilities?: Record<string, unknown>;
  noReset?: boolean;
  platformVersion?: string;
  respectSystemAlerts?: boolean;
  snapshotMaxDepth?: number;
  udid?: string;
  waitForIdleTimeout?: number;
  webDriverAgentUrl?: string;
};
```

Example:

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

export default flow(
  "Launch app bundle",
  "iOS - iPhone 15 (iOS 26)",
  async () => {
    const { driver } = await launch({
      app: { path: "ios/MyApp.app" },
      bundleId: "com.example.ios",
      snapshotMaxDepth: 500,
    });

    await driver.pause(1000);
  },
);
```

### Launch Defaults

The current implementation applies these defaults:

* when `app` is omitted, launch falls back to the runner-provided executable input path and then to installed-app startup through `bundleId`
* `respectSystemAlerts` defaults to `true`
* `snapshotMaxDepth` defaults to `999`
* `noReset` defaults to `false`

Example:

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

export default flow(
  "Use iOS defaults",
  "iOS - iPhone 15 (iOS 26)",
  async () => {
    const { driver } = await launch({
      bundleId: "com.example.ios",
    });

    await driver.pause(1000);
  },
);
```

### App Resolution

When your CI pipeline uploads an iOS build, QA Wolf sets `RUN_INPUT_PATH` to the uploaded file before the flow runs. Omit `app` in your launch call and QA Wolf will use that path automatically — you only need to provide the `bundleId`.

When `app` is provided, the current resolution order is:

1. `app.path`
2. `app.env`
3. `app.url`

When `app` is omitted, launch falls back to `RUN_INPUT_PATH`.

Relative paths are resolved against `RUN_INPUTS_EXECUTABLES_DIR` when that environment variable is present.

Explicit app source examples:

```ts theme={null}
await launch({ app: { path: "ios/MyApp.app" } });
await launch({ app: { env: "IOS_APP_PATH" } });
await launch({ app: { url: "https://example.com/MyApp.zip" } });
```

`RUN_INPUT_PATH` fallback example:

```ts theme={null}
// The runner sets RUN_INPUT_PATH before the flow starts.
await launch({
  bundleId: "com.example.ios",
});
```

<Note>
  If `app` is present but does not resolve to a value, launch does not fall back to `RUN_INPUT_PATH`.
</Note>

### Advanced Appium capabilities

The named launch options above cover the most common settings. For anything else, use the `capabilities` escape hatch: its entries are spread into the underlying [webdriver.io `remote`](https://webdriver.io/docs/api) capabilities and passed straight to the iOS driver.

`startIos` is a thin wrapper around webdriver.io's `remote`, so capabilities that QA Wolf does not set itself are forwarded as-is. See the Appium references for the full list of available keys:

* [Appium core capabilities](https://appium.io/docs/en/2.11/guides/caps/)
* [XCUITest (iOS) driver capabilities](https://github.com/appium/appium-xcuitest-driver/blob/v8.3.1/docs/reference/capabilities.md)

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

export default flow(
  "Launch with custom capabilities",
  "iOS - iPhone 15 (iOS 26)",
  async () => {
    const { driver } = await launch({
      // Named options cover the common knobs:
      respectSystemAlerts: true,
      waitForIdleTimeout: 10,
      // Anything else is passed through verbatim:
      capabilities: {
        "appium:resetOnSessionStartOnly": true,
        "appium:settings[useFirstMatch]": "true",
        "appium:waitForQuiescence": "false",
      },
    });

    await driver.pause(1000);
  },
);
```

<Note>
  Named options take precedence over `capabilities` for the same key. In particular, `noReset`, `respectSystemAlerts`, and `snapshotMaxDepth` are always applied (using their defaults when omitted), so configure those through the named options rather than `capabilities`.
</Note>

### Migrating from `wdio.startIos`

Older flows obtained a `wdio` handle from the test context and called `startIos` directly. With the flow API, pass the same configuration through the flow's `launch` options instead — map the settings that have a named option, and route the rest through `capabilities`. The `driver` you receive is the same webdriver.io `Browser` handle, so there is no separate "raw" driver to obtain.

```ts theme={null}
// Before: raw wdio.startIos
const driver = await wdio.startIos({
  "appium:app": process.env.IOS_PATH,
  "appium:settings[respectSystemAlerts]": true,
  "appium:resetOnSessionStartOnly": true,
  "appium:settings[useFirstMatch]": "true",
  "appium:waitForQuiescence": "false",
  "appium:waitForIdleTimeout": 10,
});
```

```ts theme={null}
// After: flow launch options
import { flow } from "@qawolf/flows/ios";

export default flow(
  "Open iOS app",
  {
    target: "iOS - iPhone 15 (iOS 26)",
    launch: {
      app: { env: "IOS_PATH" },
      respectSystemAlerts: true,
      waitForIdleTimeout: 10,
      capabilities: {
        "appium:resetOnSessionStartOnly": true,
        "appium:settings[useFirstMatch]": "true",
        "appium:waitForQuiescence": "false",
      },
    },
  },
  async ({ driver }) => {
    await driver.pause(1000);
  },
);
```

<Note>
  `app: { env: "IOS_PATH" }` reads the path from the environment variable named `IOS_PATH`, replacing the raw `"appium:app": process.env.IOS_PATH` form.
</Note>

## `device`

`device` is a runtime proxy over the iOS simulator or device API. Use it for device-level operations — such as installing configuration profiles, simulating sensors, or managing device state — that sit outside app UI interactions. Use `driver` for interacting with the app itself.

Example:

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

const profile = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict></dict></plist>`;

export default flow("Install profile", "iOS - iPhone 15 (iOS 26)", async () => {
  const { driver } = await launch();
  await device.installConfigurationProfile(driver, profile);
});
```

## `expect`

The exported `expect` is the assertion helper for iOS flows, backed by [`expect-webdriverio`](https://webdriver.io/docs/api/expect-webdriverio). All WebdriverIO matchers are available — including auto-retrying element matchers like `toBeDisplayed`, `toExist`, `toHaveText`, and `toHaveAttribute`. See the [expect-webdriverio matchers reference](https://webdriver.io/docs/api/expect-webdriverio) for the full list.

Always import `expect` from `@qawolf/flows/ios` rather than from `expect-webdriverio` directly, so that QA Wolf's defaults (timeout, `toHaveScreenshot`) stay in effect.

Example:

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

export default flow(
  "Verify welcome screen",
  { target: "iOS - iPhone 15 (iOS 26)", launch: true },
  async ({ driver, test }) => {
    await test("welcome heading is visible", async () => {
      const heading = driver.$("~welcome-heading");
      await expect(heading).toBeDisplayed();
      await expect(heading).toHaveText("Welcome");
    });
  },
);
```

For visual regression assertions with `expect(driver).toHaveScreenshot(...)`, see [Native mobile screenshots](/qawolf/visual-diffing-native-app).
