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

# Agents and personas

> Configure several agents in one suite, pin tests to them, and run one flow as each persona.

`agents` in your config is a record of agents by name. `default` is the one
tests run with. Other entries are other brains for the same suite: a model
that reads screens more carefully, a playbook for a UX review, a persona who
has never seen the app.

```ts title="e2e.config.ts" theme={"theme":"catppuccin-mocha"}
import type { E2EConfig } from '@e2edev/e2e';
import { createAgent } from '@e2edev/e2e/agent';
import { playwright } from '@e2edev/playwright';
import { gateway } from 'ai';

export default {
  targets: [{ engine: playwright({ url: 'http://localhost:3000' }) }],
  agents: {
    default: { model: gateway('openai/gpt-5.6-luna') },
    thorough: { model: gateway('anthropic/claude-opus-5'), context: 'Verify every claim on screen before concluding.' },
    buyer: { model: gateway('openai/gpt-5.6-luna'), context: 'You are a first-time buyer who has never seen this site.' },
    admin: { model: gateway('openai/gpt-5.6-luna'), context: 'You manage orders and approve refunds.' },
    ux: createAgent({ model: gateway('openai/gpt-5.6-luna'), system: 'Check that each control has a clear label and each action gives visible feedback.' }),
  },
} satisfies E2EConfig;
```

An entry takes one of three shapes:

* **An options block.** `model`, `context`, `vision`,
  `maxSteps`, `maxModelCalls`, `providerOptions`. This runs the built-in
  agent with those settings.
* **`createAgent(...)`.** The built-in agent with a `system` prompt and your
  own [tools](/tools).
* **An options block with `executor`.** Any [custom executor](/executors),
  composed with the model and budget options.

`context` is a sentence or two of project vocabulary that reaches every
`agent.*` call. `system` on `createAgent` shapes how the agent works. One
`model` serves every call, screenshots included, so it has to accept images.
The [config reference](/reference/config#agents) lists every key.

## Pick an agent per run

Without a flag, tests run as `default`. `--agent` re-points them:

```bash theme={"theme":"catppuccin-mocha"}
npx --no-install e2e run --agent thorough tests/checkout.e2e.ts
```

Several names run every unpinned test once per agent, a whole-suite sweep
without touching a file:

```bash theme={"theme":"catppuccin-mocha"}
npx --no-install e2e run --agent buyer,admin
```

## Pin an agent in the suite

A test, a describe block, or a single call can name its agent. Innermost
wins: a call's `agent` beats the test's, which beats its groups, which beat
the run.

The test bodies below are abbreviated to show agent selection; replace
the placeholder bodies with your app's checkout and refund steps.

```ts title="tests/checkout.e2e.ts" theme={"theme":"catppuccin-mocha"}
import { test } from '@e2edev/e2e';

test.describe('checkout as a first-time buyer', { agent: 'buyer' }, () => {
  test('pays for one item', async ({ app, agent }) => { /* ... */ });

  test.describe('refunds', { agent: 'admin' }, () => {
    test('refunds the order', async ({ agent }) => { /* ... */ });
  });

  test('reorders, and the admin approves', async ({ app, agent }) => {
    await app.open('/orders');
    await agent.act('reorder the most recent order');
    // One step under another brain; the ledger carries what the buyer did.
    await agent.act('approve the pending reorder', { agent: 'admin' });
    await agent.assert('the reorder shows as approved');
  });
});
```

`--agent` never overrides a pin it does not name. A persona stays itself
while you benchmark a model across everything that has no opinion. A pin
that names nothing in `agents` fails at collection; a call's unknown name is
`INVALID_ARGUMENT`. Every agent step in the report records the agent it ran
with.

## One flow, several personas

A pin can be a list. The test then runs once per agent named, as one result
each, in one run:

```ts title="tests/checkout.e2e.ts" theme={"theme":"catppuccin-mocha"}
import { test } from '@e2edev/e2e';

test.describe('checkout', { agent: ['buyer', 'admin'] }, () => {
  test('pays for one item', async ({ app, agent }) => { /* runs once per persona */ });
  test('applies a promo code', async ({ app, agent }) => { /* ... */ });
});
```

Every result carries the agent it ran as: `agent` in the report, `[admin]`
after the title in the terminal and in JUnit case names, its own result id,
and its own artifact directory. With a list pin, `--agent admin` narrows the
block to the admin alone, while `--agent thorough` leaves it running as both
personas.

A serial group runs as one unit per agent, so its members share the group's
pin. A setup test pins at most one agent.

A persona that must be signed in pairs its pin with a `session`. A session is
one name per block, so a sweep over signed-in personas is a loop over
describe blocks; [Signing in](/authentication#several-signed-in-users) shows
the setup test and the loop.

## Next

<CardGroup cols={2}>
  <Card title="Project tools" href="/tools">
    Give the agent a test API or a device toolkit.
  </Card>

  <Card title="Custom executors" href="/executors">
    Keep the loop with your own prompt, or replace it.
  </Card>
</CardGroup>
