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

# Project tools

> Add tools for test APIs, data setup, and device operations.

A project tool is an AI SDK tool that the agent can call during a goal, such
as a request to your test API or a data setup step.

## Add a tool

Wrap the tool with `defineTool` and put it in the agent's `tools`:

```ts title="e2e.config.ts"

const APP_URL = process.env.APP_URL ?? 'http://localhost:3000';

const seedCart = defineTool(
  tool({
    description: 'Seed the cart with a SKU via the store test API',
    inputSchema: z.object({ sku: z.string() }),
    execute: async ({ sku }) => {
      const response = await fetch(`${APP_URL}/api/test/cart?sku=${encodeURIComponent(sku)}`);
      if (!response.ok) throw new Error(`Cart seeding failed: HTTP ${response.status}`);
      return response.text();
    },
  }),
  { mutates: true },
);

export default {
  targets: [{ engine: web(), app: { url: APP_URL } }],
  agents: { default: { model: gateway('openai/gpt-6-luna-fast'), tools: { seedCart } } },
} satisfies E2EConfig;
```

The agent can now seed a cart before it checks out:

```ts
await agent.act('seed the cart with SKU {sku}, then check out', {
  params: { sku: 'SHIRT-BLUE-M' },
});
await expect(screen.getByText('Order confirmed')).toBeVisible();
```

<Note>
The config load fails with `INVALID_CONFIG` for a tool named like a
grammar verb (`tap`, `screenshot`) or a tool the harness adds:
`complete_step`, `locate`, `test_lines`, `start_recording`,
`stop_recording`, or `report_finding`. It also fails for a value
`defineTool` didn't build, and for `tools` on an entry with a custom
`executor`.
</Note>

## Declare what a tool does

`defineTool` takes the AI SDK tool and these annotations:

| Annotation | Meaning |
| --- | --- |
| `mutates: true` | Changes state. The call takes a slot of the step's action budget before it runs, failed or not, and runs in order with the other actions. The [replay cache](https://e2e.tester.army/docs/cache) can't replay it, so the agent runs that part of the step live. |
| `mutates: false` | Read-only. Runs without an action slot and may observe the screen. |
| `platforms: ['ios', 'android']` | Offers the tool only on those platforms. Omit it to offer the tool on every target. |

## Observe from a tool

A read-only tool can observe the redacted screen through `getToolContext`.
This example saves a masked screenshot as report evidence when pixels are
available:

```ts

const captureEvidence = defineTool(
  tool({
    description: 'Save a screenshot as evidence in the report',
    inputSchema: z.object({}),
    execute: async (_input, executionOptions) => {
      const context = getToolContext(executionOptions);
      const observation = await context.observe({ pixels: true });
      const pixels = observation.pixels;
      if (pixels === undefined) {
        return { withheld: observation.pixelsWithheld ?? 'UNSUPPORTED_CAPABILITY' };
      }
      const id = await context.attachScreenshot(pixels, `evidence-${randomUUID()}`);
      return { evidence: id };
    },
  }),
  { mutates: false },
);
```

- `observe()` returns a redacted text snapshot. `tree: true` adds the node
  tree and `pixels: true` a masked screenshot, under the built-in agent's
  rules, so no pixels
  [after a secret fill](https://e2e.tester.army/docs/reference/security#screenshots-after-a-secret-fill).
- In a `mutates: true` tool, `observe()` fails with `POLICY_DENIED`.
  Observe in a separate read-only tool.
- `attachScreenshot(pixels, label)` adds the image to the step's artifacts
  and returns its artifact id. A reused label overwrites the earlier image.

## Device toolkits

`@e2e-dev/mobile` has a ready tool pack, `mobileTools`, with `open_app`,
`swipe`, and `alert`. Spread it to add your own tools beside it:

```ts
tools: { ...mobileTools(iphone, pixel), seedCart },
```

See [Agent tools](https://e2e.tester.army/docs/mobile#agent-tools) for the setup.

## Next

<CardGroup cols={2}>
  <Card title="Custom executors" icon="gears" href="/executors">
    Replace the prompt or decision loop.
  </Card>
  <Card title="How agent steps work" icon="stairs" href="/agent-steps">
    What the model sees and what it may do.
  </Card>
</CardGroup>
