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

# expect

> Locator assertions, fixture assertions, and plain value matchers.

`expect` dispatches on its argument; `expect.poll` re-reads a value until a
matcher holds.

```ts theme={"theme":"catppuccin-mocha"}
export interface Expect {
  (actual: Locator): AsyncExpectation;
  <E extends object>(actual: Expectable<E>): E;
  <T>(actual: T): ValueExpectation<T>;
  poll<T>(read: () => T | Promise<T>, options?: PollOptions): PollExpectation<T>;
}
```

An engine-contributed fixture may carry its own expectation surface
(`Expectable<E>`): `expect(web)` returns the `WebExpectation` the playwright
engine attaches. Anything that is neither a runner locator nor such a fixture
falls through to `ValueExpectation<T>`, including a promise. Await the value
first.

| Property              | Value                                                                                                                         |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Default timeout       | `config.assertionTimeout`, 5000 ms                                                                                            |
| Override              | `{ timeout }` in milliseconds, on every locator and `web` matcher                                                             |
| Negation grace window | A negated matcher passes after 1000 ms of continuous truth, or after the whole budget when `{ timeout }` is shorter than that |
| Failure code          | `ASSERTION_FAILED`, exit code 1                                                                                               |

`.not` is a getter that returns a new expectation with inverted polarity, on all
three kinds.

Locator strictness: except for `toHaveCount`, a positive matcher requires
exactly one match; zero matches keep polling until the timeout, and multiple
matches fail immediately with `LOCATOR_AMBIGUOUS`. `toBeHidden` and negated
visibility accept zero matches.

Text comparison is normalized: leading and trailing whitespace is trimmed and
each run of whitespace collapses to one space. A `TextMatch` string matches
exactly; a `RegExp` uses its own source and flags.

## Locator matchers

```ts theme={"theme":"catppuccin-mocha"}
export interface AsyncExpectation {
  readonly not: AsyncExpectation;
  toBeVisible(options?: { timeout?: number }): Promise<void>;
  toBeHidden(options?: { timeout?: number }): Promise<void>;
  toBeEnabled(options?: { timeout?: number }): Promise<void>;
  toBeDisabled(options?: { timeout?: number }): Promise<void>;
  toBeChecked(options?: { timeout?: number }): Promise<void>;
  toBeSelected(options?: { timeout?: number }): Promise<void>;
  toBeExpanded(options?: { timeout?: number }): Promise<void>;
  toBeFocused(options?: { timeout?: number }): Promise<void>;
  toHaveText(expected: TextMatch, options?: { timeout?: number }): Promise<void>;
  toContainText(expected: TextMatch, options?: { timeout?: number }): Promise<void>;
  toHaveValue(expected: TextMatch, options?: { timeout?: number }): Promise<void>;
  toHaveAttribute(name: string, options?: { timeout?: number }): Promise<void>;
  toHaveAttribute(name: string, value: TextMatch, options?: { timeout?: number }): Promise<void>;
  toHaveCount(expected: number, options?: { timeout?: number }): Promise<void>;
  toHaveAccessibleName(
    expected: TextMatch,
    options?: { timeout?: number },
  ): Promise<void>;
}
```

| Matcher                          | Waits for                              | Notes                                                |
| -------------------------------- | -------------------------------------- | ---------------------------------------------------- |
| `toBeVisible()`                  | One node, visible                      | Zero matches keep polling                            |
| `toBeHidden()`                   | Hidden or absent                       | Zero matches satisfy it                              |
| `toBeEnabled()`                  | Enabled                                | Enabled unless the node reports disabled             |
| `toBeDisabled()`                 | Disabled                               | -                                                    |
| `toBeChecked()`                  | Checked                                | Missing state is `false`                             |
| `toBeSelected()`                 | Selected                               | Missing state is `false`                             |
| `toBeExpanded()`                 | Expanded                               | Missing state is `false`                             |
| `toBeFocused()`                  | Focused                                | Single-node matcher                                  |
| `toHaveText(expected)`           | Full normalized text equals `expected` | -                                                    |
| `toContainText(expected)`        | Normalized text contains `expected`    | Substring or regexp                                  |
| `toHaveValue(expected)`          | Exposed input value                    | -                                                    |
| `toHaveAttribute(name)`          | Attribute is present                   | Single-node matcher                                  |
| `toHaveAttribute(name, value)`   | Attribute matches `value`              | Normalized text comparison, like `toHaveText`        |
| `toHaveCount(expected)`          | Exact match count                      | The only matcher that tolerates zero or many matches |
| `toHaveAccessibleName(expected)` | Accessible name                        | -                                                    |

```ts theme={"theme":"catppuccin-mocha"}
await expect(screen.getByRole('status')).toHaveText('Saved');
await expect(screen.getByTestId('todo')).toHaveCount(3);
await expect(screen.getByRole('dialog')).not.toBeVisible({ timeout: 10_000 });
```

## web matchers

Contributed by `@e2edev/playwright`; the type is exported from that package.
Every matcher call is a recorded `expect.<matcher>` assertion step.

```ts theme={"theme":"catppuccin-mocha"}
export interface WebExpectation {
  readonly not: WebExpectation;
  toHaveURL(expected: string | RegExp, options?: { timeout?: number }): Promise<void>;
  toHaveTitle(expected: TextMatch, options?: { timeout?: number }): Promise<void>;
  toHaveClass(target: Locator, expected: TextMatch, options?: { timeout?: number }): Promise<void>;
}
```

| Matcher                         | Waits for                        | Notes                                                                                                                   |
| ------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `toHaveURL(expected)`           | Current URL matches              | A relative string resolves against the base URL and matches exactly after normalization. A `RegExp` tests the full URL. |
| `toHaveTitle(expected)`         | Document title matches           | Normalized text comparison                                                                                              |
| `toHaveClass(target, expected)` | Target's class attribute matches | String comparison normalizes class-list whitespace                                                                      |

```ts theme={"theme":"catppuccin-mocha"}
await expect(web).toHaveURL('/dashboard');
await expect(web).toHaveTitle(/Dashboard/);
await expect(web).toHaveClass(web.locator('.card'), 'card active');
```

## Value matchers

```ts theme={"theme":"catppuccin-mocha"}
export interface ValueExpectation<T> {
  readonly not: ValueExpectation<T>;
  toBe(expected: T): void;
  toEqual(expected: unknown): void;
  toBeTruthy(): void;
  toBeFalsy(): void;
  toBeNull(): void;
  toBeUndefined(): void;
  toBeDefined(): void;
  toContain(expected: unknown): void;
  toMatch(expected: string | RegExp): void;
  toBeGreaterThan(expected: number): void;
  toBeLessThan(expected: number): void;
}
```

Synchronous, no options, no timeout. For a value the app is still writing,
see [Polling a value](#polling-a-value).

| Matcher                     | Requires                         |
| --------------------------- | -------------------------------- |
| `toBe(expected)`            | `Object.is(actual, expected)`    |
| `toEqual(expected)`         | Recursive structural equality    |
| `toBeTruthy()`              | Truthy value                     |
| `toBeFalsy()`               | Falsy value                      |
| `toBeNull()`                | Exactly `null`                   |
| `toBeUndefined()`           | Exactly `undefined`              |
| `toBeDefined()`             | Non-nullish value                |
| `toContain(expected)`       | String or collection containment |
| `toMatch(expected)`         | String or regexp match           |
| `toBeGreaterThan(expected)` | Numeric lower bound, exclusive   |
| `toBeLessThan(expected)`    | Numeric upper bound, exclusive   |

```ts theme={"theme":"catppuccin-mocha"}
const total = await screen.getByTestId('total').textContent();
expect(total).toContain('$');
expect(items.length).toBeGreaterThan(0);
expect(payload).toEqual({ ok: true });
```

## Polling a value

`expect.poll(read)` re-reads a value until a value matcher passes. An agent
step returns once the model has observed the app, which can be before the
write it triggered has landed; poll the read that proves the write instead of
sleeping.

```ts theme={"theme":"catppuccin-mocha"}
export interface PollOptions {
  timeout?: number;
  interval?: number;
  message?: string;
}

export type PollExpectation<T> = {
  readonly not: PollExpectation<T>;
} & {
  readonly [K in Exclude<keyof ValueExpectation<T>, 'not'>]: (
    ...args: Parameters<ValueExpectation<T>[K]>
  ) => Promise<void>;
};
```

Every value matcher is there under the same name and parameters, returning a
promise; `.not` flips the check. Each sample calls `read`, then runs the
matcher on the result. A matcher failure or a `read` that throws is one
failing sample and polling continues. A `read` that hangs is cut at the
deadline, so one stuck request cannot stretch the wait.

| Option     | Default                            | Meaning                                                                                                                                                                                                            |
| ---------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `timeout`  | `config.assertionTimeout`, 5000 ms | Deadline for the whole poll, in milliseconds. Capped by the attempt's own deadline: the test `timeout` in the body, `cleanupTimeout` in an `afterEach` hook. In a standalone script, outside any attempt, 5000 ms. |
| `interval` | 100 ms                             | Pause between samples.                                                                                                                                                                                             |
| `message`  | none                               | Extra line in the timeout error.                                                                                                                                                                                   |

The poll runs on the attempt's budget. When the attempt is cancelled or
times out, the loop and any read in flight stop at once with `CANCELLED`, so
a timed-out test never keeps reading through teardown. `timeout` must be a
finite number of milliseconds, 0 or more, and `interval` a finite number
above 0; anything else is `INVALID_CONFIG` when `expect.poll` is called,
before the first read.

At the deadline it throws `ASSERTION_FAILED` naming the matcher, then
`message` when given, then the last sample: the last matcher failure, the
last `read` error, or `no read completed`.

```text theme={"theme":"catppuccin-mocha"}
expect.poll(...).toBe(...) timed out after 5000 ms
the batch never finished
last: expected "running" to be "done"
```

```ts theme={"theme":"catppuccin-mocha"}
await agent.act('create a test named "AI checkout regression" described as {description}', {
  params: { description },
});
await expect
  .poll(() => getTest(workspace).then((row) => row?.title), { timeout: 15_000 })
  .toBe('AI checkout regression');
await expect.poll(async () => (await listTests(workspace)).length).toBeGreaterThan(1);
await expect.poll(() => readStatus(), { message: 'the batch never finished' }).not.toBe('running');
```

Unlike a locator matcher, `expect.poll` is not recorded as a report step: it
touches no fixture, so a timeout surfaces only as the test's failure.

<CardGroup cols={2}>
  <Card title="Writing tests" icon="pen-to-square" href="/writing-tests" />

  <Card title="screen and Locator" icon="crosshairs" href="/reference/screen" />

  <Card title="app and web" icon="masks-theater" href="/reference/playwright" />

  <Card title="Errors" icon="triangle-exclamation" href="/reference/errors" />
</CardGroup>
