# Core concepts

> Build a test from apps, locators, actions and checks, then understand its result.

Source: https://rehearsal.dev/retest/docs/core-concepts

This page connects the parts of your first test, from the apps in its config to the result in its run folder. Use it to understand what Retest prepares for you and what your test must check.

## Apps and targets

An app is a named website, native app or Electron app your test uses. A target says how to run it. For a website, the target chooses the browser and any viewport or device emulation.

```ts filename="retest.config.ts"
import { app, chrome, chromium, defineConfig } from '@rehearsal-labs/retest'

const config = defineConfig({
  apps: {
    web: app({
      baseUrl: 'http://localhost:3000',
      targets: {
        chrome: chrome(),
        chromium: chromium(),
      },
    }),
  },
})

export default config

declare module '@rehearsal-labs/retest' {
  interface Register {
    config: typeof config
  }
}
```

This config runs each test once in Chrome and once in Chromium. Chrome uses the installed browser; Chromium needs its executable path in `RETEST_CHROMIUM` or `executablePath`. Each pairing, such as `web=chrome`, is a variant with its own result. The registration block lets TypeScript check your app names.

With one app, the test receives it as `page`. With several apps, you declare the names the test uses and receive a fixture for each. Browser apps get separate contexts, so two participants have separate cookies and storage. [Configuration](https://rehearsal.dev/retest/docs/essentials/configuration.md) explains how to pair their targets.

## Tests and fixtures

A test file ends in `.retest.ts`. Import `test` to declare a test and `expect` to check a result. The callback receives fixtures, the objects Retest prepares for that test:

```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')
})
```

Here, `page` is a fresh page in a new browser context for each test, and `goto` resolves `/` against the app's `baseUrl`. Await every action and asynchronous check so the next line runs after it settles. A missing await, unfinished work or no assertions fails the test.

If several tests need to sign in, a `test.setup` can save a named state that each test restores into its own context. See [Signing in and secrets](https://rehearsal.dev/retest/docs/guides/signing-in.md). Retest does not support custom fixtures or `test.extend` yet.

## Locators

A locator describes how to find an element. It keeps a query, not a reference to one node, so Retest finds the element again whenever you act on it or read it. In the task example, `getByTestId('save-task')` finds the element whose `data-testid` is `save-task`.

You can also find elements by role, accessible name, label, text, placeholder or CSS. An action needs one match. If nothing matches, it waits within its budget; if several elements match, it fails as `ambiguous`. [Locators](https://rehearsal.dev/retest/docs/essentials/locators.md) explains how to make the query specific.

## Actions and checks

Actions change the app; checks read it. Keeping those jobs separate means Retest can wait for a save to finish without pressing Save again.

| Kind | Example | What happens |
| --- | --- | --- |
| Navigation | `page.goto('/')` | Opens the page. |
| Action | `page.getByTestId('save-task').click()` | Waits until the element is ready, then sends input once. |
| Locator check | `expect(savedTask).toHaveText('Release checklist')` | Reads again until it passes or reaches its deadline. |
| Value check | `expect(title).toBe('Release checklist')` | Compares a value your test already holds, once. |
| AI check | `test.evaluate({ requirement, evidence })` | Asks a configured judge to assess the evidence. |

A click checks that the element is visible, stable, enabled and uncovered before sending mouse input. Once sent, the action is never repeated automatically. A successful click means input was sent; the following check establishes whether the app showed the result you wanted.

Locator checks have a five-second default budget. The saved-title check can see a loading message several times before the final title appears, without needing a sleep. Use [Checks](https://rehearsal.dev/retest/docs/essentials/checks.md) for exact conditions and [AI checks](https://rehearsal.dev/retest/docs/guides/ai-checks.md) when the requirement needs interpretation.

## Results

A test result says whether the test passed, failed or could not establish an answer. When something goes wrong, its failure class names the problem, such as `not_found` or `outcome_unknown`.

| Test status | Meaning |
| --- | --- |
| `passed` | The test met its required checks and finished successfully. |
| `failed` | A check, action or test-code rule failed, or the test ran out of time. |
| `inconclusive` | A required AI check could not decide from its evidence. |
| `error` | A problem prevented the test from completing its checks. |
| `not_run` | The test could not start, for example because its setup failed. |
| `skipped` | The test or its group was declared as skipped. |

If the browser disappears before input is sent, the failure class is `session_lost`. When input was sent but Retest cannot confirm what it did, it is `outcome_unknown`: the browser may have gone, a frame may have covered the element, or the page may have moved to a new document first. Both give the test an `error` status. Check the app's state before repeating an action whose outcome is unknown.

The run also has an exit code for your shell or CI system. A failed test normally gives 1; setup problems, incomplete checks and undecided results normally give 2. [Results and exit codes](https://rehearsal.dev/retest/docs/reference/results.md) explains how Retest combines different results.

## The run folder

Each run writes a new folder under `.retest/runs/`. It keeps events as they happen in `events.jsonl` and writes `result.json` when the run ends, alongside logs and captured evidence. The terminal prints the folder so you can inspect it later:

```sh
npx retest inspect .retest/runs/<time>
npx retest inspect .retest/runs/<time> --test "tests/task.retest.ts > saves a task"
```

Replace `<time>` with the printed folder name. Inspection reads the saved run and opens no browser. If `result.json` is missing, Retest rebuilds what it can from the events and marks the run incomplete.

## Next steps

- [Writing tests](https://rehearsal.dev/retest/docs/essentials/writing-tests.md): Organise tests with groups, hooks and named steps.
- [Locators](https://rehearsal.dev/retest/docs/essentials/locators.md): Choose a query that finds the intended element.
- [Debugging a failure](https://rehearsal.dev/retest/docs/essentials/debugging.md): Read the failed check and the evidence behind it.
