# Parallel runs

> Set worker and browser counts, and protect shared data while test files run in parallel.

Source: https://rehearsal.dev/retest/docs/scaling-up/parallel-runs

Run independent test files together to reduce the time your suite takes. This page shows how to set concurrency and keep tests from changing accounts or server data that another test needs.

## Set the worker count

Each worker runs one file at a time in its own process, with that file's tests in sequence. Use `--workers` to control how many files run together.

```sh
npx retest run --workers 4
npx retest run --workers 1
```

The first command allows four files at once, while the second runs files sequentially. The default is half the machine's available cores, rounded down and at least one. Sign-in setups run first, one after another, before tests restore their saved state.

Every browser test gets a fresh context and page for each app it uses. Cookies and browser storage are separate, although the browser process may be shared. Module state and unfinished callbacks remain shared by the tests of one file process.

## Spread work over browsers

If one browser process becomes a bottleneck, use `--browsers` to spread a target's work over several processes. Each worker stays on one browser.

```sh
npx retest run --workers 6 --browsers 2
```

For a target running the whole suite, this allows six file workers across two browsers. By default, Retest uses one browser per three workers that have files, rounded up. Targets running part of a matrix receive a share, capped by the files and workers that use them.

The human report says how many browsers each target used. Compare runs on your own app before raising the counts; more workers also increase load on the service those pages use.

## Protect shared state

A fresh context leaves shared backend data unchanged. If tests use the same inbox, account or counter, declare a lock and give it to every test that accesses that data.

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

const config = defineConfig({
  apps: { web: chrome({ baseUrl: 'http://127.0.0.1:4173' }) },
  locks: ['saves'],
})

export default config

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

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

test('saves a task', { locks: ['saves'] }, 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')
})
```

Tests holding `saves` take turns across files, workers and targets in this run. Other tests can continue alongside them. A `test.describe` block can pass locks to all its tests, and a test's own locks add to those inherited names.

Before launching its apps, a test takes all its locks together or waits with none. The wait uses none of its test budgets, and the report names the test that held the lock. Declare every lock name in the config.

> [!WARNING]
> Named locks belong to one run. Separate runs do not share them, so use separate accounts or coordinate outside Retest when several jobs access the same backend state.

## Native and Electron resources

Retest also reserves the desktop for a macOS app, the simulator for an iOS app and an Electron target's persistent data folder. A test acquires everything it needs before any app launches, and releases it after confirmed shutdown.

These reservations are shared across runs within one process. They do not coordinate every resource across separate processes, though the native desktop has its own machine lock. If shutdown cannot be confirmed, the resource remains held, and a waiting test may end with `setup_failed`.

## Limit sessions across host runs

A host calling `runFiles` can share a `SessionBudget` between runs. A session is one app session, such as a browser context and page; it is not a browser process.

```ts
import { SessionBudget } from '@rehearsal-labs/retest/runner'

const budget = new SessionBudget({ perOwner: 4, host: 8 })

// Add this option to each runFiles call that shares the budget.
const sessions = { owner: 'worker-1', budget }
```

Pass `sessions` in each run's options, using the appropriate owner name. An attempt reserves its apps together, so it never holds some sessions while waiting for the rest. The shared budget limits one owner to four and all owners together to eight in this process. [Use Retest from code](https://rehearsal.dev/retest/docs/scaling-up/from-code.md) shows the host call.

## Next steps

- [Best practices](https://rehearsal.dev/retest/docs/scaling-up/best-practices.md): Give tests their own data and explicit outcomes.
- [Continuous integration](https://rehearsal.dev/retest/docs/scaling-up/continuous-integration.md): Run the suite in a job and keep its evidence.
- [Use Retest from code](https://rehearsal.dev/retest/docs/scaling-up/from-code.md): Share session budgets between programmatic runs.
