# Writing tests

> Write a test that checks an outcome, then organise related tests with groups, hooks and rows of data.

Source: https://rehearsal.dev/retest/docs/essentials/writing-tests

Write one test for a complete user flow, such as saving a task and seeing its title. As the suite grows, use groups, hooks and data rows to organise related tests.

## Write a test

Put your tests in files ending in `.retest.ts` and import `test` and `expect` from Retest. The test below uses the task app from Retest's examples, with its address set in [Configuration](https://rehearsal.dev/retest/docs/essentials/configuration.md).

```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('saved-task')).toBeVisible()
})
```

`page` is the default app from your config. After opening the home page, the test enters and saves a title, then checks the saved text and visibility. `toHaveText` can read hidden elements, so `toBeVisible` checks that the saved task is visible too.

Each test gets fresh browser contexts and pages, so cookies and browser storage from another test do not carry over. This does not reset your server's data. If tests share an account or a server counter, see [Parallel runs](https://rehearsal.dev/retest/docs/scaling-up/parallel-runs.md).

## Await the work

Await each action and each asynchronous check so they finish in the order you wrote them. An app takes one command at a time, so starting two actions together on the same page fails the test.

An unawaited action or check fails the test even if it would succeed. Asynchronous checks start only when awaited, so leaving out await can leave the app unchecked. A test also fails if it makes no assertions or returns with work still running.

## Group related tests

Use `test.describe` when several tests cover the same part of your app. Its callback receives a `test` that inherits the group's options, and hooks on that `test` apply to the tests inside the group.

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

test.describe('tasks', (test) => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/')
    await expect(page.getByRole('heading', { name: 'Tasks' })).toBeVisible()
  })

  test.afterEach(async ({ page }) => {
    await expect(page.getByText('Could not save')).toBeHidden()
  })

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

`beforeEach` opens the app and checks that the task screen is ready before each test. `afterEach` checks for a save error after an ordinary failure too, but does not run when the whole test runs out of time. A failed `beforeEach` prevents the body from running; an `afterEach` failure stays beside the original failure rather than replacing it.

Groups can nest. Setup hooks run from the outer group inward, while teardown hooks run from the inner group outward. Reports identify the test as `tests/tasks.retest.ts > tasks > saves a task`; `--grep` searches only the title path, `tasks > saves a task`, without the file name.

## Test several values

When the same behaviour needs several inputs, `test.for` declares a separate test for each row. Put `$title` in the name to include that row's title in the report.

```ts
test.for([
  { title: 'Release checklist' },
  { title: 'Groceries' },
])('saves "$title"', async ({ page }, { title }) => {
  await page.goto('/')
  await page.getByTestId('task-title').fill(title)
  await page.getByTestId('save-task').click()
  await expect(page.getByTestId('saved-task')).toHaveText(title)
})
```

The callback receives the usual context first and the row second. Each row gets its own result and fresh page. Choose values that produce distinct names, because two rows with the same name stop the file from loading.

## Name a part of a test

For a longer flow, `test.step` gives a sequence of actions a name in the report. It returns the callback's result, so you can use a value from the step in a later check.

```ts
const title = await test.step('Create the task', async () => {
  await page.getByTestId('task-title').fill('Release checklist')
  await page.getByTestId('save-task').click()
  return 'Release checklist'
})

await expect(page.getByTestId('saved-task')).toHaveText(title)
```

This belongs inside a test after opening the page. Naming the step helps you find it in the timeline, but you still need the check afterwards to prove its outcome.

## Set test options

An options object between the name and callback can add tags or change the test's timeout. If your config lists allowed tags, include `smoke` there for this example.

```ts
test('saves a task', { tags: ['smoke'], timeout: 30_000 }, 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')
})
```

Use `apps` when a test needs named app handles, `state` to restore a saved sign-in, and `locks` to protect shared data. The [Test API](https://rehearsal.dev/retest/docs/reference/test-api.md) lists all options and which ones groups inherit.

`test.skip` declares a test that is reported as skipped, while `test.only` focuses a local run on the tests it marks. A run in continuous integration refuses `only` unless you pass `--allow-only`. To choose tests without editing them, use the filters in [Running tests](https://rehearsal.dev/retest/docs/essentials/running-tests.md).

## Next steps

- [Locators](https://rehearsal.dev/retest/docs/essentials/locators.md): Find the control your test needs.
- [Checks](https://rehearsal.dev/retest/docs/essentials/checks.md): Check that your actions produced the expected result.
- [Signing in and secrets](https://rehearsal.dev/retest/docs/guides/signing-in.md): Prepare a sign-in state for other tests.
