# page.ai() — Autonomous AI Flow

## The primary Donobu API for launching autonomous AI flows that navigate and interact with the browser to accomplish plain-English instructions.

Markdown

`page.ai` is the primary API in Donobu. Calling it launches an autonomous flow where the AI navigates and interacts with the browser to accomplish a plain-English instruction.

## Signature

```ts
await page.ai(
  instruction: string,
  options?: {
    schema?: z.ZodObject;         // Zod schema for structured output
    allowedTools?: string[];      // Restrict which tools the AI may use
    maxToolCalls?: number;        // Cap on tool invocations (default: 50)
    envVars?: string[];           // Environment variable names to expose to the AI
    cache?: boolean;              // Use the cache (default: true)
    gptClient?: GptClient | LanguageModel; // Override the AI provider for this call
    volatileElementIds?: boolean; // Treat id-only selectors as unstable (default: false)
    noSelectorFailover?: boolean; // Disable fallback selectors in the cache (default: false)
  }
): Promise<void | z.infer<Schema>>
```

## Basic usage

```ts
import { test } from 'donobu';

test('dismiss cookie banner and log in', async ({ page }) => {
  await page.goto('https://app.example.com');
  await page.ai('Dismiss any cookie banner, then log in with the test account');
});
```

## Returning structured data

Pass a [Zod](https://zod.dev/) schema as the `schema` option to receive typed output:

```ts
import { test } from 'donobu';
import { z } from 'zod';

test('read user profile', async ({ page }) => {
  await page.goto('https://app.example.com/profile');

const profile = await page.ai('Open the user profile and read the details', {
    schema: z.object({
      fullName: z.string(),
      email: z.string().email(),
      plan: z.string(),
    }),
  });

expect(profile.plan).toBe('Pro');
});
```

## Options

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `schema` | `z.ZodObject` | — | Zod schema for structured output. When provided, the return type is `z.infer<Schema>`. |
| `allowedTools` | `string[]` | Default tool set | Restrict which tools the AI may use. See [Available Tools Reference](/content/docs/playwright/api/tools-reference/index.html). |
| `maxToolCalls` | `number` | `50` | Maximum number of tool invocations the AI may make before the flow is aborted. |
| `envVars` | `string[]` | `[]` | Names of environment variables to expose to the AI for interpolation. See [Environment Variable Interpolation](/content/docs/playwright/env-var-interpolation/index.html). |
| `cache` | `boolean` | `true` | Whether to use the cache. Set to `false` to always run autonomously. |
| `gptClient` | `GptClient | LanguageModel` | Configured provider | Override the AI provider for this call. See [AI Provider Configuration](/content/docs/playwright/ai-providers/index.html). |
| `volatileElementIds` | `boolean` | `false` | When `true`, AI-generated selectors that rely solely on an `id` attribute are treated as unstable and deprioritised when writing to the cache. |
| `noSelectorFailover` | `boolean` | `false` | When `true`, only the primary AI-generated selector is stored in the cache — no fallback selectors are saved. See [`page.find`](/content/docs/playwright/api/page-find/index.html) for more on how Donobu generates and uses selectors. |

### `allowedTools` example

Narrowing the tool set improves reliability for flows where you know exactly what kind of interactions are needed:

```ts
await page.ai('Fill in and submit the sign-up form', {
  allowedTools: ['click', 'inputText', 'chooseSelectOption', 'pressKey'],
});
```

### `envVars` and interpolation

Use Donobu's `{{$.env.VAR_NAME}}` interpolation syntax instead of JavaScript template literals when the value changes between environments. This keeps the cache key stable:

```ts
// ✅ Correct — cache key stays constant across environments
await page.ai(
  'Log in as {{$.env.TEST_USERNAME}} with password {{$.env.TEST_PASSWORD}}',
  {
    envVars: ['TEST_USERNAME', 'TEST_PASSWORD'],
  },
);

// ❌ Avoid — JS interpolation bakes the value into the cache key
await page.ai(
  `Log in as ${process.env.TEST_USERNAME} with password ${process.env.TEST_PASSWORD}`,
);
```

See [Environment Variable Interpolation](/content/docs/playwright/env-var-interpolation/index.html) for full details.

## Return value
- **Without `schema`**: returns `Promise<void>`
- **With `schema`**: returns `Promise<z.infer<Schema>>`

## Error handling

`page.ai()` throws `PageAiException` when the autonomous flow ends in a non-success state — for example, if the AI cannot find a way to complete the instruction or hits the `maxToolCalls` limit.

```ts
import { PageAiException } from 'donobu';

try {
  await page.ai('Complete the checkout');
} catch (e) {
  if (e instanceof PageAiException) {
    console.log('Flow failed in state:', e.metadata.state);
    console.log('Original instruction:', e.originalInstruction);
  }
}
```

`PageAiException` properties:

| Property | Type | Description |
| --- | --- | --- |
| `originalInstruction` | `string` | The instruction that was passed to `page.ai()` |
| `metadata.state` | `string` | The terminal state (e.g. `'FAILED'`, `'NOT_COMPLETABLE'`) |
| `metadata.result` | `unknown` | Any partial result payload, if the AI produced one before failing |

## Caching behaviour

On the first run, the AI executes the flow and the resulting action sequence is written to `.cache-lock/<test-file>.cache.js`. On subsequent runs, Donobu replays the cached sequence without calling the AI.

Set `cache: false` on a call to skip cache lookup and always run autonomously. To invalidate all cache entries for an entire run, pass `--clear-ai-cache` to `npx donobu test` or set the `DONOBU_PAGE_AI_CLEAR_CACHE=1` environment variable.

See [Caching System](/content/docs/playwright/caching/index.html) for full details.
