# TypeScript

> Type-check your tests, register config names and write imports that Retest can load.

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

Retest runs TypeScript test files without a build step, but running a test does not check its types. Set up a separate type check to catch misspelt app names, wrong matcher arguments and methods your target cannot use.

## Run the type check

`retest init` writes `tests/tsconfig.json` and adds a `typecheck:e2e` script to `package.json`. With TypeScript and Node's type definitions installed as in the [Quick start](https://rehearsal.dev/retest/docs/quick-start.md), run that script before the tests.

**npm**

```sh
npm run typecheck:e2e
```

**pnpm**

```sh
pnpm run typecheck:e2e
```

**yarn**

```sh
yarn run typecheck:e2e
```

**bun**

```sh
bun run typecheck:e2e
```

The script runs `tsc -p tests/tsconfig.json`. If your project already has a root tsconfig, the generated file extends it while keeping the tests' compiler options and include list separate. It checks your test files and `retest.config.ts` without writing JavaScript.

## Register your config

Register the config's type once so every test in the same TypeScript program knows your names. Keep the `declare module` block that `init` writes, or add one after exporting your config.

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

const config = defineConfig({
  apps: {
    web: chrome({ baseUrl: 'http://127.0.0.1:4173' }),
  },
  tags: ['smoke'],
  secrets: { password: env('TASK_APP_PASSWORD') },
  testIds: ['task-title', 'save-task', 'saved-task'],
})

export default config

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

`defineConfig` preserves the literal names in this object. Registration makes `apps: ['web']`, the smoke tag, the password secret and these three test ids available to your tests. A typo in one of those names becomes a type error.

This config also declares a password source. Set `TASK_APP_PASSWORD` to at least four characters before running it; a shorter value stops the run with exit code 2. Leave out `secrets` if the suite needs no password. The type check does not read secret values; [Signing in and secrets](https://rehearsal.dev/retest/docs/guides/signing-in.md) explains how to use them.

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

test('saves a task', { apps: ['web'], tags: ['smoke'] }, async ({ web }) => {
  await web.goto('/')
  await web.getByTestId('task-title').fill('Release checklist')
  await web.getByTestId('save-task').click()
  await expect(web.getByTestId('saved-task')).toHaveText('Release checklist')
})
```

Because this test declares `web`, its callback receives `web` rather than `page`. It cannot use another app it did not declare. A test that omits `apps` uses the default app through `page`, as in [Writing tests](https://rehearsal.dev/retest/docs/essentials/writing-tests.md).

Without registration, tests can use `page`, but declaring `apps` or `state` produces an error explaining how to register. Registration applies to the whole TypeScript program, so give separate Retest projects separate tsconfigs rather than combining conflicting registrations.

## Decide which names to restrict

You do not have to list every test id or tag to start writing tests. In a registered config, omitting `testIds`, `tags` or `states` leaves those names open to strings; listing them restricts the accepted names.

Declare secrets, inboxes and locks before using their names in a registered project. `testIds` restricts only the type check; your app must still provide those attributes. Retest checks the other declared resources when it loads or runs the tests.

If you keep test ids in a separate object, preserve their literal types with `as const` before passing the object as `testIds`. Otherwise its values can widen to `string`, which loses the restriction.

## Follow the target capabilities

The page type follows the targets configured for its app. A web locator offers `tap` only when every target of that app emulates touch; `click` works for a web app with mixed mouse and touch targets.

An iOS app uses `tap`, a macOS app uses `click`, and neither has web navigation through `goto`. An Electron page is its app's first window and also has no `goto`. When a method does not apply, the type error tells you what to use instead.

## Write module imports

Write test files and the config as ES modules using `import` and `export`. Without a `"type"` field in `package.json`, Node loads them but warns that it had to infer the module format. Setting `"type": "module"` avoids that warning; `"type": "commonjs"` prevents `retest.config.ts` from loading.

Changing a package to `"type": "module"` also changes how Node reads its `.js` files, which breaks existing CommonJS `.js` files. Keep those files in a CommonJS package or give them a `.cjs` extension. Import types with `import type` so Node does not try to load them as runtime values.

```ts filename="tests/support/task.ts"
export type Task = { title: string }

export function releaseTask(): Task {
  return { title: 'Release checklist' }
}
```

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

test('saves a task', async ({ page }) => {
  const task: Task = releaseTask()
  await page.goto('/')
  await page.getByTestId('task-title').fill(task.title)
  await page.getByTestId('save-task').click()
  await expect(page.getByTestId('saved-task')).toHaveText(task.title)
})
```

The helper returns ordinary data, while `import type` keeps its type out of the runtime imports. Relative imports work with or without extensions. An import written as `./support/task.js` also loads `task.ts` when that TypeScript source exists.

## Use path aliases

Retest reads `paths` from the nearest `tsconfig.json` at or above the importing file. Add an alias to the tests' tsconfig when a larger suite needs shared helpers in several folders.

```json filename="tests/tsconfig.json"
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@support/*": ["./support/*"]
    }
  }
}
```

Merge these options into the generated file, keeping its other settings and include list. The explicit `baseUrl` makes `@support/task` resolve to `tests/support/task.ts`. Retest follows `extends`, including an inherited `baseUrl`; without the local setting, a root `"baseUrl": "."` would send this alias to `support/task.ts` at the project root. With no `baseUrl`, paths resolve from the tsconfig that declares them.

The config loads with the tsconfig nearest its own folder, so `retest.config.ts` cannot use an alias declared only in `tests/tsconfig.json`. The generated type check includes the config under the tests' compiler settings, so that import can pass the type check and still fail to load. Put shared aliases in the root tsconfig or use relative imports in the config.

Retest does not follow TypeScript project references or resolve bare imports from `baseUrl` alone.

## Know what can load

Node's TypeScript transformer handles types, enums, namespaces with values and parameter properties, while failures still point at your TypeScript lines. When importing app code into a test, check the restrictions below because its build tools may accept syntax Retest cannot load.

- JSX and `.tsx` or `.jsx` files do not load. Keep test helpers separate from components that require JSX.
- A decorator prevents its file and tests importing it from loading, so collection fails even when the tsconfig enables decorators.
- CommonJS test files, configs and TypeScript, including `.cts`, are refused. A CommonJS JavaScript helper such as `.cjs` can be imported using Node's normal rules.
- A type imported as a value can fail to load. Use `import type` for types and interfaces.

Retest reads import aliases from tsconfig, but it does not use settings such as `target`, `jsx` or `experimentalDecorators` to compile your code. A passing type check therefore complements a test run; it cannot guarantee that every imported file will load.

## Next steps

- [Configuration](https://rehearsal.dev/retest/docs/essentials/configuration.md): Choose the apps and targets the types describe.
- [Running tests](https://rehearsal.dev/retest/docs/essentials/running-tests.md): Run the suite after checking its types.
- [Test API](https://rehearsal.dev/retest/docs/reference/test-api.md): Look up the public methods and their arguments.
