# Checks

> Check an app outcome with retrying assertions, compare values and collect several failures in one test.

Source: https://rehearsal.dev/retest/docs/essentials/checks

After an action, check the outcome you care about, such as a task appearing with the title you saved. This page covers checks that wait for the app, immediate value comparisons and ways to collect several failures.

## Check an outcome

Pass a locator to `expect`, then choose a matcher for its expected state. Locator checks read the page again until they pass or their time runs out, so you do not need a fixed sleep for the save to finish.

```ts filename="tests/task.retest.ts"
import { expect, test } from '@rehearsal-labs/retest'

test('saves a task', async ({ page }) => {
  await page.goto('/')
  await page.getByTestId('task-title').fill('Release checklist')
  await page.getByTestId('save-task').click()

  await expect(page.getByTestId('saved-task')).toHaveText('Release checklist')
  await expect(page.getByTestId('task-title')).toHaveValue('Release checklist')
})
```

The first check waits for the saved task's text. The second checks the title still held by the field. Both are asynchronous, so await them; a plain value check such as `expect(2).toBe(2)` runs immediately.

## Choose a matcher

Choose a matcher for the fact your test needs to prove. Most locator matchers require exactly one element, while count and list checks can describe several matches.

| Matcher | What it checks |
| --- | --- |
| `toBeVisible()` | One element matches and is visible. |
| `toBeHidden()` | No element matches, or none of the matches is visible. |
| `toHaveText(text)` | One element has the expected whole text. |
| `toContainText(text)` | One element contains the expected text. |
| `toHaveValue(value)` | One field holds the expected value. |
| `toHaveCount(number)` | The locator has this many matches, hidden ones included. |
| `toBeChecked()` | One checkable control is checked. |
| `toBeEnabled()` or `toBeDisabled()` | One control has the expected enabled or disabled state. |

`toHaveText` compares the whole text, while `toContainText` checks for a substring. Both respect case, trim the ends and collapse runs of spaces or line breaks. `toHaveValue` compares a field's value without changing its whitespace.

```ts
await expect(page.getByTestId('saved-task')).toContainText('checklist')
await expect(page.getByTestId('saved-task')).toHaveText(/^Release /)
await expect(page.getByRole('listitem')).toHaveText([
  'Release checklist',
  'Groceries',
])
```

A regular expression searches the normalised text using its own flags. The list form of `toHaveText` checks every matched element in document order, with exactly as many matches as entries. Use the last example on a task list containing those two items.

Retest reads text and visibility for at most 100 matches. Above that limit, `toBeHidden()` and `toHaveText([...])` cannot pass, so narrow the locator to a smaller group first.

## Check the page

Pass the page itself to `expect` when you need to check navigation or the document title. These matchers retry too, so they can wait for a redirect or a title update.

```ts
await expect(page).toHaveURL('/')
await expect(page).toHaveTitle('Tasks')
```

A relative expected URL resolves against the app's base URL. `toHaveURL` compares the complete address, including its query and fragment, while `toHaveTitle` compares the whole title with whitespace normalised. Both also accept a regular expression.

## Check the opposite

Use `.not` when an element must no longer have a state or value. Retest needs a look that proves the opposite, so a missing element does not automatically make every negated matcher pass.

```ts
await expect(page.getByText('Could not save')).not.toBeVisible()
await expect(page.getByTestId('saved-task')).not.toHaveText('Draft')
```

The first check passes if the error is hidden or absent, but two or more matches fail with `ambiguous`. Use `toBeHidden()` when every match must be hidden. The second check still needs one matching saved task whose text differs from Draft. If that task never appears, it fails with `not_found` rather than treating absence as proof of the text.

## Set the wait

Locator and page checks have a five-second assertion budget by default. A matcher's `timeout` can shorten that budget, but a larger value cannot extend it or the time left in the test.

```ts
await expect(page.getByTestId('saved-task')).toHaveText(
  'Release checklist',
  { timeout: 2000 },
)
```

To give checks more time, change `timeouts.assertion` in your config or override it for a run. This command allows up to ten seconds per assertion, within the test's remaining time.

```sh
npx retest run --timeouts assertion=10000
```

When a check fails, the report shows what it expected, what it read and how long it waited. Increasing the budget helps only if the app can reach the expected state; otherwise use [Debugging a failure](https://rehearsal.dev/retest/docs/essentials/debugging.md) to investigate the mismatch.

## Compare values

Use value matchers for data already in your test, such as an object returned by a helper. They compare immediately because Retest has no page state to reread.

```ts
const task = { title: 'Release checklist', tags: ['smoke', 'release'] }

expect(task).toEqual({ title: 'Release checklist', tags: ['smoke', 'release'] })
expect(task.tags).toContain('release')
expect(task.title).toMatch(/^Release/)
```

`toEqual` compares the object's contents. `toBe` compares with `Object.is`, `toContain` looks in a string or array, and `toMatch` checks a string with a regular expression. Value matchers have no `.not`.

## Poll a value

If a helper reads something that changes over time, pass the function to `expect.poll`. For example, a server-side task count can confirm that one click caused one save, as in Retest's task examples.

```ts
const before = await savedTasks()
await page.getByTestId('task-title').fill('Release checklist')
await page.getByTestId('save-task').click()

await expect.poll(savedTasks, { timeout: 5000 }).toBe(before + 1)
```

Here `savedTasks` is your helper that reads the count from the app's API. Keep other tests from changing that counter while this test reads it, for example with a [lock](https://rehearsal.dev/retest/docs/scaling-up/parallel-runs.md). The polling function must only read; Retest refuses page actions inside it because polling would repeat them.

Unlike a locator matcher's timeout, `expect.poll`'s timeout sets its own polling budget, still limited by the test's remaining time. Without it, polling uses the assertion budget.

## Keep checking after a failure

Use `expect.soft` for independent checks when seeing several mismatches would help you diagnose the page. A state mismatch records a failure and allows the body to continue, but the test still fails at the end. A problem that prevents reading the page, such as a lost browser, still stops the test.

```ts
await expect.soft(page.getByTestId('saved-task')).toHaveText('Release checklist')
await expect.soft(page.getByTestId('task-title')).toHaveValue('Release checklist')
```

The report keeps the first failure and lists later ones beside it. Use an ordinary check for a prerequisite, because continuing after a failed sign-in or missing page can obscure the cause.

## Next steps

- [Test API](https://rehearsal.dev/retest/docs/reference/test-api.md): Look up every matcher and its options.
- [Layout checks](https://rehearsal.dev/retest/docs/guides/layout-checks.md): Check measured overlap, overflow and clipped text.
- [Screenshot comparisons](https://rehearsal.dev/retest/docs/guides/screenshots.md): Compare a page with a saved image.
- [AI checks](https://rehearsal.dev/retest/docs/guides/ai-checks.md): Ask a judge to assess evidence against a requirement.
