---
url: /guide/start/index.md
---
# Introduction
Rstest is a testing framework powered by Rspack. It delivers comprehensive, first-class support for the Rspack ecosystem, enabling seamless integration into existing Rspack-based projects.
Rstest offers full Jest-compatible APIs while providing native, out-of-the-box support for TypeScript, ESM, CJS, and more, ensuring a modern and efficient testing experience.
## ✨ Why Rstest
Rstest is built on top of Rspack and Rsbuild, and works out of the box with built-in support for common use cases such as TypeScript, ESM, CJS, and CSS Modules. Even if you are not using Rspack, Rstest can be adopted without migrating your existing build system.
For projects already using the Rstack toolchain (Rspack, Rsbuild, Rslib, etc.), Rstest can further **reuse your existing build configuration**—including module resolution, code transformation, and plugin capabilities—eliminating the need to maintain separate transform or resolver setups for testing.
In terms of execution model, Rstest runs tests using a dependency graph–based **bundle model**, rather than the traditional per-file transform-and-execute approach. This enables it to fully leverage build-time optimizations—such as tree-shaking and skipping unused re-exports in side-effect-free barrel files—bringing test behavior closer to real production output. As a result, Rstest reduces redundant module transformations and executions in large-scale projects, improving both performance and stability.
By integrating testing into the build pipeline, Rstest simplifies the overall toolchain, reduces the duplicated cost of maintaining separate configuration and plugin systems, and allows testing to fit more naturally into modern engineering architectures such as monorepos and complex applications.
In the future, Rstest will continue evolving around a unified “build + test” model—exploring more efficient approaches to quality assurance, and integrating more deeply with CI systems and AI-driven workflows.
## 🔥 Roadmap
Rstest now supports core testing scenarios including Node / DOM testing, mocking, multi-project testing, and coverage collection, and has been successfully implemented in Rstack projects (including Rsbuild, Rslib, Rspack, and others).
The project is currently in active development, and we will continue to refine functionality and enhance the user experience based on community feedback. For future development plans, please refer to the [Rstest Roadmap](https://github.com/web-infra-dev/rstest/issues/85).
## 🦀 Rstack
Rstack is a unified JavaScript toolchain centered on Rspack, with high performance and consistent architecture.

Rstack includes the following tools:
| Name | Description | Version |
| ----------------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Rspack](https://github.com/web-infra-dev/rspack) | Bundler | |
| [Rsbuild](https://github.com/web-infra-dev/rsbuild) | Build tool | |
| [Rslib](https://github.com/web-infra-dev/rslib) | Library development tool | |
| [Rspress](https://github.com/web-infra-dev/rspress) | Static site generator | |
| [Rsdoctor](https://github.com/web-infra-dev/rsdoctor) | Build analyzer | |
| [Rstest](https://github.com/web-infra-dev/rstest) | Testing framework | |
| [Rslint](https://github.com/web-infra-dev/rslint) | Linter | |
## 🔗 Links
- [awesome-rstack](https://github.com/rstackjs/awesome-rstack): A curated list of awesome things related to Rstack.
- [rstack-examples](https://github.com/rstackjs/rstack-examples): Examples showcasing Rstack tools.
- [storybook-rsbuild](https://github.com/rstackjs/storybook-rsbuild): Storybook builder powered by Rsbuild.
- [rsbuild-plugin-template](https://github.com/rstackjs/rsbuild-plugin-template): Use this template to create your own Rsbuild plugin.
- [rstack-design-resources](https://github.com/rstackjs/rstack-design-resources): Design resources for Rstack.
## 🧑💻 Community
Come and chat with us on [Discord](https://discord.gg/XsaKEEk4mW)! The Rstack team and users are active there, and we're always looking for contributions.
---
url: /guide/start/quick-start.md
---
# Quick start
NOTE FOR AI CODING AGENTS: Do not follow this page. Instead, fetch and follow
the agent-specific setup instructions at:
[https://rstest.rs/guide/start/agent-install.md](https://rstest.rs/guide/start/agent-install.md)
## Online examples
You can try Rstest online without any setup by using the following examples:
- [StackBlitz Example](https://stackblitz.com/~/github.com/rstackjs/rstest-stackblitz-example)
## Setup environment
Before getting started, you will need to install [Node.js](https://nodejs.org/) >= 20.19.0, it is recommended to use the Node.js LTS version.
Check the current Node.js version with the following command:
```bash
node -v
```
If you do not have Node.js installed in current environment, or the installed version is too low, you can use [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm) to install.
Here is an example of how to install via nvm:
```bash
# Install Node.js LTS
nvm install --lts
# Switch to Node.js LTS
nvm use --lts
```
## Using Rstest
### Agent prompt
If you are using a coding agent, you can copy the following prompt to set up Rstest automatically:
For your AgentSet up RstestCopy this prompt and send it to your coding agent.
Copy PromptSet up Rstest in this project by following the instructions here:
https://rstest.rs/guide/start/agent-install.md
If you are migrating from an existing Jest or Vitest project, use this prompt:
For your AgentMigrate to RstestCopy this prompt and send it to your coding agent.
Copy PromptMigrate this project to Rstest by following the instructions here:
https://rstest.rs/guide/start/agent-migrate.md
### Manual installation
You can install Rstest using the following command:
```sh [npm]
npm add @rstest/core -D
```
```sh [yarn]
yarn add @rstest/core -D
```
```sh [pnpm]
pnpm add @rstest/core -D
```
```sh [bun]
bun add @rstest/core -D
```
```sh [deno]
deno add npm:@rstest/core -D
```
Next, you need to update the npm scripts in your package.json to use Rstest's CLI commands.
```json title=package.json
{
"scripts": {
"test": "rstest"
}
}
```
After completing the above steps, you can run the Rstest tests using `npm run test`, `yarn test`, or `pnpm test`. Alternatively, you can directly use `npx rstest` to execute the Rstest tests.
Rstest has built-in commands such as `watch` and `run`, please refer to [CLI Tools](/guide/basic/cli.md) to learn about all available commands and options.
## Writing tests
As a simple example, we have a `sayHi` method. To test it, you can create a test file called `index.test.ts` or use [In-Source test](/config/test/include-source.md) similar to [Rust Test](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest).
```ts title=index.ts
export const sayHi = () => 'hi';
```
```ts title=index.test.ts
import { expect, test } from '@rstest/core';
import { sayHi } from '../src/index';
test('should sayHi correctly', () => {
expect(sayHi()).toBe('hi');
});
```
Next, you can execute the test by using the command configured in [Using Rstest](#using-rstest). Rstest will print the following message:
```bash
✓ test/index.test.ts (1)
Test Files 1 passed
Tests 1 passed
Duration 140 ms (build 17 ms, tests 123 ms)
```
---
url: /guide/start/features.md
---
# Features
Rstest brings testing into the Rsbuild and Rspack build pipeline. It reuses build configuration where possible, runs tests through a bundle-based execution model, and provides the core testing features needed for local development and CI.
## Reuse existing build setup
Rstest can reuse Rsbuild and Rspack configuration, including module resolution, transforms, and plugin behavior. This reduces the need for a separate test-only build setup and keeps tests aligned with the code that ships.
Learn more about [Configuring Rstest](/guide/basic/configure-rstest.md).
### Built on Rspack
Rstest runs tests on top of Rspack's bundling pipeline, so it can benefit from build-time optimizations such as Tree Shaking and [lazyBarrel](https://rspack.rs/guide/optimization/lazy-barrel) while keeping test behavior closer to real output.
### Transpiled with SWC
By default, Rstest uses Rspack's built-in SWC loader to transform JavaScript and TypeScript. You can customize SWC options in the configuration file when a project needs different syntax or transform behavior.
Learn more about [Configuring SWC](/guide/basic/configure-rstest.md#configure-swc).
## Multi-project testing
Rstest can run multiple test projects in one process, with each project keeping its own configuration and environment. This is useful for monorepos, multi-app workspaces, and projects that need separate Node, DOM, or Browser Mode test targets.
Learn more about [Test projects](/guide/basic/projects.md).
## Test sharding
Rstest can split test files into shards for parallel execution. In CI, you can distribute the same test suite across multiple machines to reduce total runtime, then use the `blob` reporter and `rstest merge-reports` to merge test results and coverage data after all shards complete.
Learn more about [Test sharding](/config/test/shard.md).
## In-source tests
Rstest supports a Rust-like module testing style that lets you write test blocks directly inside source files. It works well for small utilities and helpers where keeping fast checks next to the implementation makes development easier.
Learn more about [In-source tests](/config/test/include-source.md).
## Watch mode
When you modify a test file or one of its dependencies, Rstest analyzes the module graph and only reruns the affected test files. This keeps local feedback fast as the test suite grows.
## DOM testing
Rstest can simulate the DOM and browser APIs with jsdom or happy-dom. It supports common framework testing workflows for React and Vue, and works with Testing Library and CSS Modules.
Learn more about [DOM testing](/config/test/test-environment.md#dom-testing).
## Browser mode
Rstest provides Browser Mode for tests that need a real browser instead of a simulated environment such as jsdom or happy-dom. This helps validate browser APIs, rendering behavior, and interactions that are difficult to cover in DOM simulators.
Browser Mode is powered by [Playwright](https://playwright.dev/) and can run tests in Chromium, Firefox, and WebKit, making it suitable for cross-browser verification.
Learn more about [Browser mode](/guide/browser-testing.md).
## Code coverage
Rstest can collect code coverage with [istanbul](https://istanbul.js.org/). Enable it by setting `coverage.enabled` to `true` in your Rstest configuration file.
Learn more about [Code coverage](/config/test/coverage.md).
## More capabilities
- Jest-compatible assertions and snapshots
- Mock and spy utilities
- File-level sandbox isolation
- Lifecycle hooks for setup and teardown
- Built-in reporters and CI output
- Filtering by directory, project, file, or test name
- VS Code extension
---
url: /guide/start/ai.md
---
# AI
To help AI better understand Rstest's features, configuration, and best practices so it can provide more accurate assistance during day-to-day development and troubleshooting, Rstest provides the following capabilities:
- [Agent prompt](#agent-prompt)
- [Agent Skills](#agent-skills)
- [llms.txt](#llmstxt)
- [Markdown docs](#markdown-docs)
- [Markdown reporter](#markdown-reporter)
- [AGENTS.md](#agentsmd)
## Agent prompt
If you are using a coding agent (such as Claude Code, Cursor, Copilot, etc.), copy the prompt that matches your project. The agent will read the linked instructions and install dependencies, create configuration, and write tests based on your project type.
### Set up Rstest
Use this prompt to add Rstest to a project that doesn't have a test runner configured yet:
For your AgentSet up RstestCopy this prompt and send it to your coding agent.
Copy PromptSet up Rstest in this project by following the instructions here:
https://rstest.rs/guide/start/agent-install.md
### Migrate from Jest or Vitest
Use this prompt to migrate an existing Jest or Vitest project to Rstest. For convenience, the linked instructions inline the full [migrate-to-rstest](https://github.com/rstackjs/agent-skills#migrate-to-rstest) skill so nothing needs to be installed; alternatively, you can [install the skill](#agent-skills) and use it directly:
For your AgentMigrate to RstestCopy this prompt and send it to your coding agent.
Copy PromptMigrate this project to Rstest by following the instructions here:
https://rstest.rs/guide/start/agent-migrate.md
## Agent Skills
Agent Skills are domain-specific knowledge packs that can be installed into Agents, enabling them to give more accurate and professional suggestions or perform actions in specific scenarios.
In the [rstackjs/agent-skills](https://github.com/rstackjs/agent-skills) repository, there are many skills for the Rstack ecosystem. The skills related to Rstest include:
- [migrate-to-rstest](https://github.com/rstackjs/agent-skills#migrate-to-rstest): Migrate Jest or Vitest projects to Rstest.
- [rstest-best-practices](https://github.com/rstackjs/agent-skills#rstest-best-practices): Share best practices for using Rstest.
In Coding Agents that support skills, you can use the [skills](https://www.npmjs.com/package/skills) package to install a specific skill with the following command:
```sh [npx]
npx skills add rstackjs/agent-skills --skill migrate-to-rstest
```
```sh [yarn]
yarn dlx skills add rstackjs/agent-skills --skill migrate-to-rstest
```
```sh [pnpm]
pnpm dlx skills add rstackjs/agent-skills --skill migrate-to-rstest
```
```sh [bunx]
bunx skills add rstackjs/agent-skills --skill migrate-to-rstest
```
```sh [deno]
deno run -A npm:skills add rstackjs/agent-skills --skill migrate-to-rstest
```
After installation, simply use natural language prompts to trigger the skill, for example:
```
Help me migrate this Jest project to Rstest
```
## llms.txt
[llms.txt](https://llmstxt.org/) is a standard that helps LLMs discover and use project documentation. Rstest follows this standard and publishes the following two files:
- [llms.txt](https://rstest.rs/llms.txt): A structured index file containing the titles, links, and brief descriptions of all documentation pages.
```
https://rstest.rs/llms.txt
```
- [llms-full.txt](https://rstest.rs/llms-full.txt): A full-content file that concatenates the complete content of every documentation page into a single file.
```
https://rstest.rs/llms-full.txt
```
You can choose the file that best fits your use case:
- `llms.txt` is smaller and consumes fewer tokens, making it suitable for AI to fetch specific pages on demand.
- `llms-full.txt` contains the complete documentation content, so AI doesn't need to follow individual links — ideal when you need AI to have a comprehensive understanding of Rstest, though it consumes more tokens and is best used with AI tools that support large context windows.
## Markdown docs
Every Rstest documentation page has a corresponding `.md` plain-text version that can be provided directly to AI. On any doc page, you can click “Copy Markdown” or “Copy Markdown Link” under the title to get the Markdown content or link.
```
https://rstest.rs/guide/start/index.md
```
Providing the Markdown link or content allows AI to focus on a specific chapter, which is useful for targeted troubleshooting or looking up a particular topic.
## Markdown reporter
When you want AI to directly consume test results, use the `md` reporter. It outputs a single markdown document to stdout, which is easier for LLMs to parse than colorful terminal logs. The output is also more concise, which usually reduces token usage.
In Agent environments, Rstest defaults to `md` when you didn't explicitly configure reporters.
```bash
npx rstest --reporter=md
```
You can also configure it in `rstest.config.ts` and combine it with other reporters in local or CI workflows.
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: ['md'],
});
```
More information about the `md` reporter can be found in [Markdown reporter](/guide/basic/reporters.md#markdown-reporter).
## AGENTS.md
You can create an `AGENTS.md` file in the root of any project that uses Rstest. This file follows the [AGENTS.md](https://agents.md/) specification and provides key project information to Agents.
Here is an example of Rstest-related content you can add to `AGENTS.md`:
```markdown wrapCode
# AGENTS.md
You are an expert in JavaScript, Rspack, Rsbuild, and Rstest. You write maintainable, performant, and accessible tests.
## Tools
### Rstest
- Run `npm run test` to run tests (`npx rstest`)
- Run `npm run test:watch` to run tests in watch mode (`npx rstest --watch`)
## Docs
- Rstest: https://rstest.rs/llms.txt
```
You can also customize it for your project, adding more details about the project structure, overall architecture, and other relevant information so Agents can better understand your project.
::: tip
If you are using Claude Code, you can create a `CLAUDE.md` file and reference the `AGENTS.md` file in it.
```markdown title="CLAUDE.md"
@AGENTS.md
```
:::
---
url: /guide/basic/cli.md
---
# CLI
Rstest comes with a lightweight CLI that includes commands such as [rstest watch](#rstest-watch) and [rstest run](#rstest-run).
## rstest -h
`rstest -h` can help you view all available CLI commands and options:
```bash
npx rstest -h
```
The output is shown below:
```bash
Usage:
$ rstest [...filters]
Commands:
[...filters] run tests
run [...filters] run tests without watch mode
watch [...filters] run tests in watch mode
list [...filters] lists all test files that Rstest will run
merge-reports [path] Merge blob reports from multiple shards into a unified report
init [project] Initialize rstest configuration
Options:
-h, --help Display this message
-v, --version Display version number
```
Use `npx rstest -h` to see command-specific options. For example, `npx rstest init -h` only shows initialization options, while `npx rstest merge-reports -h` only shows merge-related options.
## rstest \[...filters]
Running `rstest` directly will enable the Rstest test in the current directory.
```bash
$ npx rstest
✓ test/index.test.ts (2 tests) 1ms
Test Files 1 passed (1)
Tests 2 passed (2)
Duration 189 ms (build 22 ms, tests 167 ms)
```
### Watch mode
If you want to automatically rerun the test when the file changes, you can use the `--watch` flag or `rstest watch` command:
```bash
$ npx rstest --watch
```
## rstest run
`rstest run` will perform a single run, and the command is suitable for CI environments or scenarios where tests are not required to be performed while modifying.
### Run related tests
[Added in v0.10.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.10.0)
Use `--related` when you want Rstest to treat positional arguments as source files and only run the tests that depend on those files.
```bash
npx rstest run --related src/button.ts
```
Rstest resolves related tests from the build module graph, so the same filter works for Node mode and Browser Mode projects. You can also use the Jest-compatible alias:
```bash
npx rstest run --findRelatedTests src/button.ts
```
If you only want to inspect the affected test files, combine it with `rstest list`:
```bash
npx rstest list --related src/button.ts --filesOnly
```
## rstest watch
`rstest watch` will start listening mode and execute tests, and when the test or dependent file modifications, the associated test file will be re-execute.
## rstest list
`rstest list` will print a test list of all matching conditions. By default, it prints the test names of all matching tests.
```bash
$ npx rstest list
# the output is shown below:
a.test.ts > test a > test a-1
a.test.ts > test a-2
b.test.ts > test b > test b-1
b.test.ts > test b-2
```
The `rstest list` command inherits all `rstest` filtering options, you can filter files directly or use `-t` to filter the specified test name.
```bash
$ npx rstest list -t='test a'
# the output is shown below:
a.test.ts > test a > test a-1
a.test.ts > test a-2
```
You can use `--filesOnly` to make it print the test files only:
```bash
$ npx rstest list --filesOnly
# the output is shown below:
a.test.ts
b.test.ts
```
You can use `--json` to make it print tests in JSON format in terminal or save the results to a separate file:
```bash
$ npx rstest list --json
$ npx rstest list --json=./output.json
```
You can use `--includeSuites` to print test suites along side test cases:
```bash
$ npx rstest list
# the output is shown below:
a.test.ts > test a
a.test.ts > test a > test a-1
a.test.ts > test a-2
b.test.ts > test b
b.test.ts > test b > test b-1
b.test.ts > test b-2
```
You can use `--printLocation` to print location of tests:
```bash
$ npx rstest list
# the output is shown below:
a.test.ts:4:5 > test a > test a-1
a.test.ts:9:3 > test a-2
b.test.ts:4:5 > test b > test b-1
b.test.ts:9:3 > test b-2
```
You can use `--summary` to append a compact summary after the list output:
```bash
$ npx rstest list --summary
# the output is shown below:
a.test.ts > test a > test a-1
a.test.ts > test a-2
b.test.ts > test b > test b-1
b.test.ts > test b-2
c.test.ts > test c it each 0
c.test.ts > test c it for 0
c.test.ts > test c it runIf
c.test.ts > test c it skipIf
Test Files 3 matched
Tests 8 matched
```
When used with `--json`, `--summary` changes the JSON output shape from an array to an object with `items` and `summary` fields.
## rstest merge-reports
[Added in v0.9.3](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.3)
`rstest merge-reports` merges blob reports generated by multiple test shards into a single unified report. This is useful when running tests in parallel across multiple CI machines using `--shard`.
### Workflow
1. Run each shard with the `blob` reporter to generate blob report files:
```bash
# On CI machine 1
npx rstest run --shard 1/3 --reporter=blob
# On CI machine 2
npx rstest run --shard 2/3 --reporter=blob
# On CI machine 3
npx rstest run --shard 3/3 --reporter=blob
```
2. Collect all `.rstest-reports/` directories into a single location, then merge:
```bash
npx rstest merge-reports
```
By default, blob reports are read from `.rstest-reports/` in the project root. You can specify a custom path:
```bash
npx rstest merge-reports ./custom-reports-dir
```
The merge command will:
- Combine test results from all shards
- Run configured reporters (e.g., `default`, `junit`) with the merged data
- Merge and generate coverage reports (if coverage is enabled)
Use `--cleanup` to remove the blob reports directory after merging:
```bash
npx rstest merge-reports --cleanup
```
## rstest init
`rstest init` creates starter configuration for supported project types.
```bash
npx rstest init
```
Currently, `browser` is the available initializer:
```bash
npx rstest init browser
```
Use `--yes` to skip the interactive prompt and apply the default setup:
```bash
npx rstest init browser --yes
```
## CLI options
Rstest CLI options are registered per command instead of being shared by every command.
### Test commands
`rstest`, `rstest run`, and `rstest watch` share the same test runtime options:
| Flag | Description |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-c, --config ` | Specify the configuration file, can be a relative or absolute path, see [Specify config file](/guide/basic/configure-rstest.md#specify-config-file) |
| `--config-loader ` | Specify the config loader (`auto` \| `jiti` \| `native`), see [Rsbuild - Specify config loader](https://rsbuild.rs/guide/configuration/rsbuild#specify-config-loader) |
| `-r, --root ` | Specify the project root directory, see [root](/config/test/root.md) |
| `--related` | Treat positional arguments as source file paths and only run related tests |
| `--findRelatedTests` | Alias for `--related` for Jest compatibility |
| `--globals` | Provide global APIs, see [globals](/config/test/globals.md) |
| `--isolate` | Run tests in an isolated environment, see [isolate](/config/test/isolate.md) |
| `--reporter ` | Specify the test reporter, see [reporters](/config/test/reporters.md) |
| `--exclude ` | Exclude files from test, see [exclude](/config/test/exclude.md) |
| `-u, --update` | Update snapshot files, see [update](/config/test/update.md) |
| `--coverage` | Enable code coverage collection, see [coverage](/config/test/coverage.md) |
| `--passWithNoTests` | Allows the test suite to pass when no files are found, see [passWithNoTests](/config/test/pass-with-no-tests.md) |
| `--silent [value]` | Silence intercepted test console output, or keep logs only for failed tasks with `passed-only`, see [silent](/config/test/silent.md) |
| `--printConsoleTrace` | Print console traces when calling any console method, see [printConsoleTrace](/config/test/print-console-trace.md) |
| `--project ` | Only run tests for the specified project, see [Filter by project name](/guide/basic/test-filter.md#filter-by-project-name) |
| `--disableConsoleIntercept` | Disable console intercept, see [disableConsoleIntercept](/config/test/disable-console-intercept.md) |
| `--slowTestThreshold ` | The number of milliseconds after which a test or suite is considered slow, see [slowTestThreshold](/config/test/slow-test-threshold.md) |
| `-t, --testNamePattern ` | Run only tests with a name that matches the regex, see [testNamePattern](/config/test/test-name-pattern.md) |
| `--testEnvironment ` | The environment that will be used for testing, see [testEnvironment](/config/test/test-environment.md) |
| `--testTimeout ` | Timeout of a test in milliseconds, see [testTimeout](/config/test/test-timeout.md) |
| `--hookTimeout ` | Timeout of hook in milliseconds, see [hookTimeout](/config/test/hook-timeout.md) |
| `--retry ` | Number of times to retry a test if it fails, see [retry](/config/test/retry.md) |
| `--bail [number]` | Abort the test run after the specified number of test failures, see [bail](/config/test/bail.md) |
| `--browser, --browser.enabled` | Run tests in Browser Mode, see [browser](/config/test/browser.md) |
| `--browser.name ` | Browser to use: `chromium`, `firefox`, `webkit` (default: `chromium`), see [browser](/config/test/browser.md) |
| `--browser.headless` | Run browser in headless mode (default: `true` in CI), see [browser](/config/test/browser.md) |
| `--browser.port ` | Port for the Browser Mode dev server, see [browser](/config/test/browser.md) |
| `--browser.strictPort` | Exit if the specified port is already in use, see [browser](/config/test/browser.md) |
| `--maxConcurrency ` | Maximum number of concurrent tests, see [maxConcurrency](/config/test/max-concurrency.md) |
| `--clearMocks` | Automatically clear mock calls, instances, contexts and results before every test, see [clearMocks](/config/test/clear-mocks.md) |
| `--resetMocks` | Automatically reset mock state before every test, see [resetMocks](/config/test/reset-mocks.md) |
| `--restoreMocks` | Automatically restore mock state and implementation before every test, see [restoreMocks](/config/test/restore-mocks.md) |
| `--unstubGlobals` | Restores all global variables that were changed with `rstest.stubGlobal` before every test, see [unstubGlobals](/config/test/unstub-globals.md) |
| `--unstubEnvs` | Restores all `process.env` values that were changed with `rstest.stubEnv` before every test, see [unstubEnvs](/config/test/unstub-envs.md) |
| `--include ` | Specify test file matching pattern, see [include](/config/test/include.md) |
| `--logHeapUsage` | Print heap usage for each test, see [logHeapUsage](/config/test/log-heap-usage.md) |
| `--hideSkippedTests` | Do not display skipped test logs, see [hideSkippedTests](/config/test/hide-skipped-tests.md) |
| `--hideSkippedTestFiles` | Do not display skipped test file logs, see [hideSkippedTestFiles](/config/test/hide-skipped-test-files.md) |
| `--pool ` | Shorthand for `--pool.type`, see [pool](/config/test/pool.md) |
| `--pool.type ` | Specify the test pool type, see [pool](/config/test/pool.md) |
| `--pool.maxWorkers ` | Maximum number or percentage of workers, see [pool](/config/test/pool.md) |
| `--pool.minWorkers ` | Minimum number or percentage of workers, see [pool](/config/test/pool.md) |
| `--pool.execArgv ` | Additional Node.js execArgv for worker processes (repeatable), see [pool](/config/test/pool.md) |
| `-h, --help` | Display help for command |
`rstest` also supports `-w, --watch`, which switches the default command into watch mode.
### rstest list
`rstest list` supports the same filtering and config options as the test commands above, and adds:
| Flag | Description |
| ----------------------- | ------------------------------------------- |
| `--filesOnly` | Only print matching test files |
| `--json [boolean/path]` | Print tests as JSON or write JSON to a file |
| `--includeSuites` | Include suites in the output |
| `--printLocation` | Print test and suite locations |
| `--summary` | Print a compact summary after the list |
### rstest merge-reports
`rstest merge-reports` has its own smaller option set:
| Flag | Description |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-c, --config ` | Specify the configuration file, can be a relative or absolute path, see [Specify config file](/guide/basic/configure-rstest.md#specify-config-file) |
| `--config-loader ` | Specify the config loader (`auto` \| `jiti` \| `native`), see [Rsbuild - Specify config loader](https://rsbuild.rs/guide/configuration/rsbuild#specify-config-loader) |
| `-r, --root ` | Specify the project root directory, see [root](/config/test/root.md) |
| `--coverage` | Enable coverage generation after merging, see [coverage](/config/test/coverage.md) |
| `--reporter ` | Specify which reporters run on merged results, see [reporters](/config/test/reporters.md) |
| `--cleanup` | Remove the blob reports directory after merging |
| `-h, --help` | Display help for command |
### rstest init
`rstest init` only accepts initialization-specific options:
| Flag | Description |
| ------------ | ------------------------------------------- |
| `--yes` | Use default options and skip interactive UI |
| `-h, --help` | Display help for command |
### Boolean option negation
For boolean options, you can use the `--no-` prefix to set them to `false`. For example:
```bash
# These are equivalent:
npx rstest --isolate false
npx rstest --no-isolate
# More examples:
npx rstest --no-coverage # Disable coverage
npx rstest --no-globals # Disable global APIs
npx rstest --no-clearMocks # Disable auto-clearing mocks
```
## CLI shortcuts
When running Rstest in watch mode, you can use keyboard shortcuts to perform various actions.
All shortcuts:
```bash
Shortcuts:
f rerun failed tests
a rerun all tests
u update snapshot
t filter by a test name regex pattern
p filter by a filename regex pattern
q quit process
c clear screen
h show shortcuts help
```
:::note
CLI shortcuts are only available when running Rstest in watch mode (`rstest watch` or `rstest --watch`) and when the terminal supports TTY (interactive mode).
:::
---
url: /guide/basic/configure-rstest.md
---
# Configure Rstest
## Configuration file
When you use the CLI of Rstest, Rstest will automatically read the configuration file in the root directory of the current project and resolve it in the following order:
- `rstest.config.mjs`
- `rstest.config.ts`
- `rstest.config.js`
- `rstest.config.cjs`
- `rstest.config.mts`
- `rstest.config.cts`
We recommend using the `.mjs` or `.ts` format for the configuration file and importing the `defineConfig` utility function from `@rstest/core`. It provides friendly TypeScript type hints and autocompletion, which can help you avoid errors in the configuration.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
testEnvironment: 'node',
});
```
If you are developing a non-TypeScript project, you can use the `.mjs` format for the configuration file.
### Specify config file
Rstest CLI uses the `--config` option to specify the config file, which can be set to a relative path or an absolute path.
```json title="package.json"
{
"scripts": {
"test": "rstest --config scripts/rstest.config.mjs"
}
}
```
You can also abbreviate the `--config` option to `-c`:
```bash
rstest -c scripts/rstest.config.mjs
```
## Configure Rsbuild
Rstest's build configuration inherits from Rsbuild. Therefore, in Rstest, you can use most of the Rsbuild configurations, such as:
- Using Rsbuild plugins through [plugins](/config/index.md#plugins);
- Configuring module resolution behavior through [resolve](/config/build/resolve.md);
- Configuring Rspack through [tools.rspack](/config/build/tools.md#toolsrspack);
- Configuring builtin:swc-loader through [tools.swc](/config/build/tools.md#toolsswc).
More configurations can be referred to [Build Configurations](/config/index.md#build-configurations).
## Configure Rspack
Rstest uses Rspack for building, so you can directly use Rspack's configuration options to configure Rstest's build behavior.
More details can be referred to [Configure Rspack](https://rsbuild.rs/guide/configuration/rspack).
## Configure SWC
Rstest uses Rspack's [builtin:swc-loader](https://rspack.rs/guide/features/builtin-swc-loader) to transform JavaScript and TypeScript code by default, which is the Rust version of [swc-loader](https://github.com/swc-project/pkgs/tree/main/packages/swc-loader).
Rstest exposes some options to configure `builtin:swc-loader`:
- [tools.swc](/config/build/tools.md#toolsswc): Used to configure the options of `builtin:swc-loader`.
- [source.include](/config/build/source.md#sourceinclude): Used to specify the files that need to be compiled by SWC.
- [source.exclude](/config/build/source.md#sourceexclude): Used to exclude files that do not need to be compiled by SWC.
```ts
import { defineConfig } from '@rsbuild/core';
export default defineConfig({
tools: {
swc: {
jsc: {
transform: {
react: {
runtime: 'automatic',
},
},
experimental: {
plugins: [['@swc/plugin-emotion', {}]],
},
},
},
},
});
```
### SWC plugin version
Please note that SWC's plugins are still an experimental feature. Currently, SWC's Wasm plugins are not backward compatible, and the version of SWC plugins is strongly coupled with the `swc_core` version that Rspack depends on.
This means that you need to choose SWC plugins that match the current `swc_core` version to make them work properly. If the SWC plugin version you use does not match the `swc_core` version that Rspack depends on, Rspack will throw errors during the build. Please refer to [Rspack FAQ - SWC plugin version mismatch](https://rspack.rs/errors/swc-plugin-version) for handling.
## Detect Rstest environment
You can use `process.env.RSTEST` to detect whether it is an Rstest test environment to apply different configurations/codes in your tests.
```ts
if (process.env.RSTEST) {
// 'true' will be returned in the rstest environment
// do something...
}
```
It should be noted that if you use `process.env.RSTEST` in your source code, define `process.env.RSTEST` as `false` in your build configuration (such as `rsbuild.config.ts`) during production builds, this will help the bundler eliminate dead code.
```diff title=rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
export default defineConfig({
source: {
define: {
+ 'process.env.RSTEST': false,
},
},
});
```
If you are developing the Rsbuild plugin, you can use [api.context.callerName](https://rsbuild.rs/api/javascript-api/instance#contextcallername) to determine the current plugin is being called.
```ts
export const myPlugin = {
name: 'my-plugin',
setup(api) {
const { callerName } = api.context;
if (callerName === 'rstest') {
// ...
} else if (callerName === 'rsbuild') {
// ...
}
},
};
```
## Configuration integration
In large projects, you often already have existing build/toolchain configurations (aliases, global variables, plugins, etc.). Through [adapters](/guide/integration/adapters.md) and the [extends](/config/test/extends.md) option, Rstest can transform and integrate these external configurations into your test setup, helping you avoid duplicate maintenance and keep configurations consistent.
- With `extends`, you can load a function (adapter) or object, and the returned configuration will be deeply merged with the current Rstest configuration.
- This is suitable for reusing configurations from tools such as Rsbuild and Rspack, or presetting testing behavior for framework templates (for example, default test environments and setup scripts).
---
url: /guide/basic/test-filter.md
---
# Filtering tests
Rstest provides a variety of flexible ways to filter and select which test files and test cases to run. You can precisely control the test scope via configuration files, command-line arguments, and test APIs.
## Filter by file name
Run all tests:
```bash
rstest
```
Run a specific test file:
```bash
rstest test/foo.test.ts
```
Use glob patterns:
```bash
rstest test/**/*.test.ts
```
Run test files with `foo` in the name, such as `foo.test.ts`, `foo/index.test.ts`, `foo-bar/index.test.ts`, etc.:
```bash
rstest foo
```
You can also specify multiple test files or glob patterns:
```bash
rstest test/foo.test.ts test/bar.test.ts
```
### Filter by file path
This method also applies to filtering by file path.
When filtering by file path, it can be either an absolute path or a relative path based on the current working directory (also called the project root directory or workspace root directory)
```bash
# Filtering by absolute path
rstest /Users/userName/Desktop/projects/rstest/packages/core/tests/index.test.ts
# Filtering by relative path
rstest packages/core/tests/index.test.ts
# Filtering by project directory
rstest packages/core
```
### include/exclude
When you filter files directly with `rstest **/*.test.ts`, rstest will further filter files based on the [include](/config/test/include.md) and [exclude](/config/test/exclude.md) configuration.
You can modify the test file scope using the `include` and `exclude` options.
For example, to match test files named `index` in the `test/a` directory:
```bash
rstest index --include test/a/*.test.ts
```
To match test files in `test/a` or `test/b` directories:
```bash
rstest --include test/a/*.test.ts --include test/b/*.test.ts
```
## Filter by test name
If you only want to run some test cases, you can use the `--testNamePattern` (`-t` for short) option. It will run tests with names that match the given pattern.
### Match by keyword
This is the simplest way. It will run test cases or test suites whose full names contain the given keyword.
For example, to only run test cases whose names contain "bar":
```bash
rstest -t bar
```
If a suite name matches, all tests inside it will be run.
You can also provide multiple keywords separated by spaces. This will match test cases where all keywords appear in the full name. For example, to run tests where `'suite-bar'` appears in the suite path and `'test-baz'` is in the test name:
```bash
rstest -t 'suite-bar test-baz'
```
### Match by full test name
You can also provide a full test name to run a specific test case.
Rstest uses `>` to connect test suite and test case names for better output readability. The full test name is the concatenation of the names of the test suites from the outside in and the test case name, separated by `>`.
For example, consider the following test structure and its output:
```ts
// foo.test.ts
import { describe, test } from '@rstest/core';
describe('suite-foo', () => {
describe('suite-bar', () => {
test('test-baz', () => {
// ...
});
describe('suite-bar1', () => {
test('test-baz', () => {
// ...
});
});
});
});
```
Output:
```bash
✓ foo.test.ts
✓ suite-foo > suite-bar > test-baz
✓ suite-foo > suite-bar > suite-bar1 > test-baz
```
To run only the `test-baz` test case in `suite-bar`, you can use:
```bash
rstest foo.test.ts -t 'suite-foo > suite-bar > test-baz'
```
### Match by regular expression
The pattern is treated as a regular expression. You can use this for more complex filtering.
For example, if you want to precisely match `suite-bar > test-baz`, rather than any test containing this string, you can use the regex start `^` and end `$` anchors:
```bash
rstest foo.test.ts -t '^suite-bar > test-baz$'
```
## Filter by project name
Rstest supports defining multiple test projects via [projects](/config/test/projects.md). You can filter to run specific projects with the `--project` option.
For example, to match projects whose [name](/config/test/name.md) is `@test/a` or `@test/b`:
```bash
rstest --project '@test/a' --project '@test/b'
```
You can also use wildcards to match project names:
```bash
rstest --project '@test/*'
```
You can exclude certain projects by negation:
```bash
rstest --project '!@test/a'
```
## Combined filtering
All filtering methods can be combined. For example:
```bash
rstest test/**/*.test.ts --exclude test/legacy/** --testNamePattern login
```
In this case, rstest will only run test cases whose names contain login in all `.test.ts` files under the `test` directory, while excluding the `test/legacy` directory.
## Common usage
- **Run a specific file only**: `rstest test/foo.test.ts`
- **Run tests in a specific directory only**: `rstest test/api/*.test.ts`
- **Exclude certain tests**: `rstest --exclude test/legacy/**`
- **Run only tests whose names contain login**: `rstest -t login`
- **Combined filtering**: `rstest test/**/*.test.ts --exclude test/legacy/** --testNamePattern login`
## Filter via test API
Use the `.only` modifier to run only certain test suites or cases.
For example, only the test cases in `suite A` and `case A` will be run:
```ts
describe.only('suite A', () => {
// ...
});
describe('suite B', () => {
// ...
});
test.only('case A', () => {
// ...
});
test('case B', () => {
// ...
});
```
:::note
It should be noted that the `.only` flag only applies to the current test file. If you want to execute specific test cases within a specific file, you can use a combination of "filter by file name" and "filter via test API".
:::
Use `.skip` or `.todo` to skip certain test suites or cases.
```ts
describe.skip('suite A', () => {
// ...
});
test.todo('case A', () => {
// ...
});
```
---
url: /guide/basic/mock.md
---
# Mocking
Mocking lets you replace dependencies in tests, control return values, and assert how functions or modules are called. Rstest provides different mocking APIs for functions, object methods, ESM modules, CommonJS modules, and object trees.
## Mock modules
If a dependency is loaded through the module system, you can choose different APIs based on the module type and mocking behavior.
### Mock ESM modules
If a dependency is loaded through `import`, you can use [rstest.mock()](/api/runtime-api/rstest/mock-modules.md#rsmock) or [rstest.doMock()](/api/runtime-api/rstest/mock-modules.md#rsdomock).
#### Use `rstest.mock()`
`rstest.mock()` is hoisted to the top of the file. It is useful when the dependency should be replaced before the module under test runs.
```ts title="user-service.test.ts"
import { expect, rstest, test } from '@rstest/core';
import { loadUserName } from './user-service';
import { fetchUser } from './api';
rstest.mock('./api', () => ({
fetchUser: rstest.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
}));
test('returns the fetched user name', async () => {
await expect(loadUserName('1')).resolves.toBe('Alice');
expect(fetchUser).toHaveBeenCalledWith('1');
});
```
#### Use `rstest.doMock()`
Note that `rstest.doMock()` is not hoisted and only takes effect after it runs. It is useful when earlier `import` statements should keep the real implementation and later ones should use the mock.
```ts title="feature.test.ts"
import { expect, rstest, test } from '@rstest/core';
import { readFeatureFlag } from './feature';
test('only mocks later imports', async () => {
expect(readFeatureFlag()).toBe('real');
rstest.doMock('./feature', () => ({
readFeatureFlag: () => 'mocked',
}));
const { readFeatureFlag: mockedReadFeatureFlag } = await import('./feature');
expect(mockedReadFeatureFlag()).toBe('mocked');
});
```
### Mock CommonJS modules
If a dependency is loaded through `require()`, you can use [rstest.mockRequire()](/api/runtime-api/rstest/mock-modules.md#rsmockrequire) or [rstest.doMockRequire()](/api/runtime-api/rstest/mock-modules.md#rsdomockrequire).
#### Use `rstest.mockRequire()`
`rstest.mockRequire()` is hoisted to the top of the file. It is useful for file-level mocking of CommonJS modules.
```js title="math.test.cjs"
const { expect, rstest, test } = require('@rstest/core');
const { sum } = require('./math.cjs');
rstest.mockRequire('./math.cjs', () => ({
sum: (a, b) => a + b + 100,
}));
test('mocks a CommonJS module loaded with require', () => {
expect(sum(1, 2)).toBe(103);
});
```
#### Use `rstest.doMockRequire()`
Note that `rstest.doMockRequire()` is not hoisted and only affects later `require()` calls.
Note that this distinction matters when a package exposes both ESM and CommonJS entries. Mocking the ESM entry does not automatically affect the CommonJS entry, and vice versa.
### Auto-mock modules
If you want to replace the module's function exports with mock functions first and then configure only selected exports in the test, you can use `{ mock: true }`.
```ts title="math.test.ts"
import { expect, rstest, test } from '@rstest/core';
import { add } from './math';
rstest.mock('./math', { mock: true });
test('overrides one export', () => {
rstest.mocked(add).mockReturnValue(100);
expect(add(1, 2)).toBe(100);
});
```
### Spy on a whole module
If you want to keep the real implementation and still assert calls, you can use `{ spy: true }`.
```ts title="calculator.test.ts"
import { expect, rstest, test } from '@rstest/core';
import { add, calculate } from './calculator';
rstest.mock('./calculator', { spy: true });
test('tracks the internal call to add', () => {
expect(calculate(1, 2)).toBe(3);
expect(add).toHaveBeenCalledWith(1, 2);
});
```
### Partially mock modules
If you want one export to keep the real implementation and another export to be replaced, you can use [importActual](/api/runtime-api/rstest/mock-modules.md#rsimportactual).
```ts title="date-utils.test.ts"
import { expect, rstest, test } from '@rstest/core';
import * as actualDateUtils from './date-utils' with { rstest: 'importActual' };
import { formatDate, parseDate } from './date-utils';
rstest.mock('./date-utils', () => ({
...actualDateUtils,
formatDate: rstest.fn().mockReturnValue('2026-03-19'),
}));
test('keeps parseDate real', () => {
expect(formatDate(new Date())).toBe('2026-03-19');
expect(parseDate('2026-03-19')).toBeInstanceOf(Date);
});
```
Note that factory functions are hoisted, so they should not read values that are initialized later in the same file.
### Reuse manual mocks from `__mocks__`
If multiple tests reuse the same fake implementation, you can place it in `__mocks__` and load it without passing a factory.
```txt
src/
api.ts
__mocks__/
api.ts
tests/
user-service.test.ts
```
```ts title="user-service.test.ts"
import { rstest } from '@rstest/core';
rstest.mock('../src/api');
```
### Reset module state
If you want later `import` or `require()` calls to return the original module again, you can use these APIs:
- [rstest.unmock()](/api/runtime-api/rstest/mock-modules.md#rsunmock) / [rstest.doUnmock()](/api/runtime-api/rstest/mock-modules.md#rsdounmock): stop mocking an `import`-based module.
- [rstest.unmockRequire()](/api/runtime-api/rstest/mock-modules.md#rsunmockrequire) / [rstest.doUnmockRequire()](/api/runtime-api/rstest/mock-modules.md#rsdounmockrequire): stop mocking a `require()`-based module.
- [rstest.resetModules()](/api/runtime-api/rstest/mock-modules.md#rsresetmodules): clear the module cache so the next import or require evaluates the module again.
Note that `rstest.resetModules()` does not cancel module mocking. To cancel module mocking, use the matching `unmock` API for the way the module is loaded.
For the full API and more examples, see [Mock modules](/api/runtime-api/rstest/mock-modules.md).
## Mock functions
If a dependency is passed in as a callback or injected implementation, you can use [rstest.fn()](/api/runtime-api/rstest/mock-functions.md#rstestfn) to create a mock function.
```ts title="user.test.ts"
import { expect, rstest, test } from '@rstest/core';
test('passes the selected id to the callback', () => {
const onSelect = rstest.fn();
onSelect('user-1');
expect(onSelect).toHaveBeenCalledTimes(1);
expect(onSelect).toHaveBeenCalledWith('user-1');
});
```
You can also override behavior through mock instance methods, for example by returning a different value for one call:
```ts
const fetchUser = rstest.fn(async (id: string) => ({ id, role: 'guest' }));
fetchUser.mockResolvedValueOnce({ id: '1', role: 'admin' });
```
For the full API and more examples, see [Mock functions](/api/runtime-api/rstest/mock-functions.md) and [MockInstance](/api/runtime-api/rstest/mock-instance.md).
## Spy on existing methods
If you want to keep the real object and still track calls or temporarily override behavior, you can use [rstest.spyOn()](/api/runtime-api/rstest/mock-functions.md#rstestspyon).
```ts title="logger.test.ts"
import { expect, rstest, test } from '@rstest/core';
test('logs a warning when validation fails', () => {
const warn = rstest
.spyOn(console, 'warn')
.mockImplementation(() => undefined);
console.warn('invalid payload');
expect(warn).toHaveBeenCalledWith('invalid payload');
warn.mockRestore();
});
```
This pattern is commonly used with globals such as `console` and `Date`, as well as shared objects that already exist in the test.
## Deep-mock objects
If a dependency already exists in memory and you want to convert nested methods into mocks, you can use [rstest.mockObject()](/api/runtime-api/rstest/mock-functions.md#rstestmockobject).
```ts title="service.test.ts"
import { expect, rstest, test } from '@rstest/core';
test('mocks nested methods', async () => {
const service = rstest.mockObject({
user: {
fetch: async (id: string) => ({ id, name: 'real' }),
},
version: 'v1',
});
service.user.fetch.mockResolvedValue({ id: '1', name: 'mocked' });
expect(service.version).toBe('v1');
await expect(service.user.fetch('1')).resolves.toEqual({
id: '1',
name: 'mocked',
});
});
```
If you want to keep the original nested implementations while still recording calls, you can pass `{ spy: true }`.
For the full API and more examples, see [Mock functions](/api/runtime-api/rstest/mock-functions.md) and [MockInstance](/api/runtime-api/rstest/mock-instance.md).
## Clear mock state
If you need to clear call history or reset mock implementations, you can use these APIs:
- [clearMocks](/config/test/clear-mocks.md): clear call history before each test.
- [resetMocks](/config/test/reset-mocks.md): clear call history and reset mock implementations.
- [restoreMocks](/config/test/restore-mocks.md): restore spied descriptors on real objects.
For manual cleanup, the corresponding APIs are `rstest.clearAllMocks()`, `rstest.resetAllMocks()`, and `rstest.restoreAllMocks()`.
## Further reading
- [Mock functions](/api/runtime-api/rstest/mock-functions.md)
- [Mock modules](/api/runtime-api/rstest/mock-modules.md)
---
url: /guide/basic/snapshot.md
---
# Snapshot testing
Snapshot Testing is a powerful testing method used to capture and compare serialized representations of component output. When your UI or data structures change, snapshot testing helps you detect these changes promptly.
## Basic usage
Rstest provides a simple snapshot testing API that allows you to easily create and use snapshots.
### Creating snapshots
```ts
import { test, expect } from '@rstest/core';
test('component snapshot', () => {
const user = { name: 'Alice', age: 25 };
expect(user).toMatchSnapshot();
});
```
### Inline snapshots
For simple values, you can use inline snapshots:
```ts
test('inline snapshot', () => {
const message = 'Hello World';
expect(message).toMatchInlineSnapshot('"Hello World"');
});
```
### File snapshots
By default, Rstest stores all snapshots for the current test in `.snap` files with the format `exports['testName index'] = ${snapshotContent}`.
You can also use the `toMatchFileSnapshot` method to store snapshots in separate files, which makes them more readable.
```ts
test('file snapshot', async () => {
const result = renderHTML(h('div', { class: 'foo' }));
await expect(result).toMatchFileSnapshot('./basic.output.html');
});
```
### Configuration options
Rstest provides various snapshot-related configuration options that allow you to customize snapshot behavior.
- [update](/config/test/update.md): Whether to update snapshots
- [snapshotFormat](/config/test/snapshot-format.md): Snapshot formatting options
- [resolveSnapshotPath](/config/test/resolve-snapshot-path.md): Custom snapshot file path resolution function
## Updating snapshots
When test runs encounter snapshot mismatches, Rstest will mark the test as failed and output difference information.
If these changes are expected, you can update snapshots using the command line argument `-u` or the configuration option `update`.
**CLI**
```bash
npx rstest -u
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
update: true,
});
```
## Serialization output
Rstest supports adding custom serialization tools for snapshot testing via the [addSnapshotSerializer](/api/runtime-api/test-api/expect.md#expectaddsnapshotserializer) method to handle specific types of data structures.
This is especially useful when your snapshots contain local paths, sensitive data, or other non-serializable objects.
```ts
expect.addSnapshotSerializer({
test: (val) => typeof val === 'string' && val.startsWith('secret:'),
print: (val) => '***MASKED***',
});
expect('secret:123').toMatchSnapshot(); // Secret information in snapshot output will be masked
```
Using [path-serializer](https://www.npmjs.com/package/path-serializer) can convert local paths to paths relative to the project root, which is very useful in cross-platform testing.
```ts
import { createSnapshotSerializer } from 'path-serializer';
expect.addSnapshotSerializer(
createSnapshotSerializer({
root: path.join(__dirname, '..'),
}),
);
test('serialized snapshot', () => {
const filePath = path.join(__dirname, '../data/file.txt');
expect({ filePath }).toMatchSnapshot();
// before: "/Users/aaa/Desktop/projects/rstest/examples/node/data/file.txt"
// after: "/examples/node/data/file.txt"
});
```
## Snapshot output path
Snapshot files are saved with the `.snap` extension by default, in the `__snapshots__` folder in the same directory as the test file.
```
src/
├── components/
│ ├── button.test.ts
│ └── __snapshots__/
│ └── button.test.ts.snap
```
You can customize snapshot file paths through the [resolveSnapshotPath](/config/test/resolve-snapshot-path.md) configuration option.
```ts name='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
resolveSnapshotPath: (testPath, snapExtension) => {
// Store snapshots at the same level as test files
return `${testPath}${snapExtension}`;
},
});
```
## Best practices
### Keep snapshots concise
Snapshots should be readable and easy to understand, avoiding containing too much irrelevant information:
```ts
// ✅ Good practice - only include necessary information
test('user info snapshot', () => {
const userInfo = {
name: 'Alice',
role: 'admin',
permissions: ['read', 'write'],
};
expect(userInfo).toMatchSnapshot();
});
// ❌ Avoid - includes too much irrelevant information
test('complete user object snapshot', () => {
const fullUser = {
...user,
lastLogin: new Date(),
sessionId: 'abc123',
userAgent: 'Mozilla/5.0...',
};
expect(fullUser).toMatchSnapshot();
});
```
### Use descriptive snapshot names
When using multiple snapshots, provide descriptive names for each snapshot:
```ts
test('component state changes', () => {
const component = new MyComponent();
expect(component.initialState).toMatchSnapshot('initial state');
component.update();
expect(component.updatedState).toMatchSnapshot('updated state');
});
```
### Regularly review snapshots
As code evolves, snapshots may become outdated. Regularly reviewing and updating snapshots is necessary:
- Update relevant snapshots when refactoring UI
- Delete snapshots that are no longer needed
- Reasonably use inline snapshots for small-scale testing
---
url: /guide/basic/projects.md
---
# Test projects
Rstest supports running multiple test projects simultaneously within a single Rstest process. These projects can have different test configurations and test environments.
## Use cases
- **Monorepo projects**: When your codebase contains multiple sub-packages or modules, each sub-project might have different test configuration requirements.
- **Different test environments**: Different sub-projects (or even the same sub-project) might need to run in different environments (e.g., Node environment and browser environment).
- **File-specific testing**: You can specify different test or build configurations for specific test files/directories.
## Example
Define each sub-project in a monorepo as a project through the [projects](/config/test/projects.md) field, where each sub-project has its own test configuration.
Rstest will automatically recognize each subdirectory under the `packages` directory as an independent test project and run tests according to the [rstest.config.ts](/guide/basic/configure-rstest.md#configuration-files) file (if present) in the subdirectory.
```ts name='rstest.config.ts'
import { defineConfig, defineInlineProject } from '@rstest/core';
export default defineConfig({
projects: [
// A monorepo: each package directory is a project
'packages/*',
],
});
```
## Configuration description
You can define multiple test projects through the [projects](/config/test/projects.md) field. Rstest will run the corresponding tests according to the configurations defined by each project, and all test results will be merged and displayed.
If there is no `projects` field, `rstest` will treat the current directory as a single project.
### Configuration syntax
The `projects` field supports the following configuration methods:
- **Directory path**: Specify a directory, and Rstest will automatically recognize all subdirectories under that directory as projects.
- **Configuration file path**: Specify a configuration file path, and Rstest will run tests according to that file's configuration.
- **Glob pattern**: Use glob patterns to match multiple directories or files, and Rstest will use the matching results as projects.
- **Inline configuration object**: Directly define project configuration objects in the `projects` field. This allows you to define multiple test projects within a single project without creating separate configuration files for each test project.
- **Nested projects**: Define projects nested within the rstest config of sub-projects.
```ts name='rstest.config.ts'
import { defineConfig, defineInlineProject } from '@rstest/core';
export default defineConfig({
projects: [
// A monorepo: each package directory is a project
'packages/*',
// All projects under the apps directory that provide an rstest config file
'apps/**/rstest.config.ts',
// A specific project directory
'/services/auth',
// A specific project's config file
'./projects/web/rstest.config.ts',
// inline project configs
defineInlineProject({
name: 'node',
include: ['tests/node/**/*.{test,spec}.{js,cjs,mjs,ts,tsx}'],
}),
defineInlineProject({
name: 'react',
include: ['tests/react/**/*.{test,spec}.{js,cjs,mjs,ts,tsx}'],
testEnvironment: 'jsdom',
}),
],
});
```
### Root configuration
When the `projects` field exists in the root directory's rstest config file, Rstest will not treat it as a test project. In this case, the root directory's rstest.config file is only used to define `projects` and [global configuration](#global-configuration).
If you want to treat the root directory as a project as well, you can define the test configuration for the root directory in `projects`.
```ts name='rstest.config.ts'
import { defineConfig, defineInlineProject } from '@rstest/core';
export default defineConfig({
projects: [
defineInlineProject({
name: 'root',
include: ['/tests/**/*.{test,spec}.{js,cjs,mjs,ts,tsx}'],
}),
'packages/*',
],
});
```
#### Global configuration
The following configurations are global configurations and are invalid when configured in projects. If you need to modify global configuration, you need to configure it in the root project's rstest config or override it through CLI options.
- `reporters`: Global reporters configuration.
- `pool`: Global pool configuration.
- `isolate`: Global isolate configuration.
- `coverage`: Global coverage configuration.
- `bail`: Global bail configuration.
- `output.distPath.root`: Global output directory root path.
### Project configuration
#### Define project configuration
Project configuration is a subset of the Rstest configuration object, supporting most configuration options but not [global configurations](#global-configuration) such as `reporters`.
Use [defineInlineProject](/api/javascript-api/rstest-core.md#defineinlineproject) for object items inside any `projects` array, including nested `projects`. Use [defineProject](/api/javascript-api/rstest-core.md#defineproject) only for the top-level export of a project config file.
The difference is mainly how `name` is handled. `defineProject` can omit `name` on the exported project, and Rstest will infer it using the same rules as the [name](/config/test/name.md) option. `defineInlineProject` requires `name` explicitly for every inline project item.
You can define a project configuration using the [defineProject](/api/javascript-api/rstest-core.md#defineproject) helper function:
```ts title='packages/pkg-a/rstest.config.ts'
import { defineProject } from '@rstest/core';
export default defineProject({
name: 'pkg-a',
include: ['tests/**/*.{test,spec}.{js,cjs,mjs,ts,tsx}'],
});
```
#### Share configuration
Project configuration does not inherit the configuration from the root directory. Only the `projects` field and [global configuration](#global-configuration) in the root directory are effective.
If there are reusable configuration items between your sub-projects, you can extract shared configurations and introduce them in sub-projects respectively:
```ts title='packages/pkg-a/rstest.config.ts'
import { defineConfig, mergeRstestConfig } from '@rstest/core';
import sharedConfig from '../shared/rstest.config';
export default mergeRstestConfig(sharedConfig, {
name: 'pkg-a',
});
```
You can override all project configurations through [CLI options](/guide/basic/cli.md#cli-options).
### Nested projects
Rstest supports defining nested projects in sub-project rstest config files. This allows you to define more test projects in sub-projects without defining all projects in the root project.
This is especially useful when your sub-projects need to support multiple test environments or multiple configurations.
For example, define Node.js and browser test environments for `packages/pkg-a`:
```ts title='packages/pkg-a/rstest.config.ts'
import { defineConfig, defineInlineProject } from '@rstest/core';
export default defineConfig({
projects: [
defineInlineProject({
name: 'node',
include: ['tests/node/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
}),
defineInlineProject({
name: 'react',
include: ['tests/react/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
testEnvironment: 'jsdom',
}),
],
});
```
### Projects in browser mode
When you use `projects` with Browser Mode, each project compiles and executes with its own build config. This lets you keep different stacks or build setups in one repository while still running them in a single Rstest process.
In the same run, browser launch options must stay consistent across browser projects:
- `provider`
- `browser`
- `headless`
- `port`
- `strictPort`
So mixing multiple providers, or multiple browser types, in one run is not supported yet.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
projects: ['./project-b/rstest.config.ts', './project-a/rstest.config.ts'],
});
```
```ts title="project-a/rstest.config.ts"
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rstest/core';
export default defineConfig({
name: 'project-a',
plugins: [pluginReact()],
include: ['tests/**/*.test.tsx'],
browser: {
enabled: true,
provider: 'playwright',
},
});
```
```ts title="project-b/rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
name: 'project-b',
include: ['tests/**/*.test.ts'],
browser: {
enabled: true,
provider: 'playwright',
},
});
```
You can still mix Browser Mode projects with `testEnvironment: 'node'` or `'jsdom'` projects in one `projects` list, and results will be merged in the final report.
### Inspect configuration
If you want to view the final effective configuration of Rstest, you can enable [debug mode](/guide/advanced/debugging.md#debug-mode) through the `DEBUG=rstest` environment variable. Rstest will write the final effective Rstest configuration and build configuration to the output directory.
## Filtering projects
You can filter projects through the [--project](/guide/basic/test-filter.md#filter-by-project-name) CLI option, or you can filter specific files under projects by directly filtering file names or file paths.
For more information, please refer to the [Test Filtering](/guide/basic/test-filter.md) chapter.
---
url: /guide/basic/reporters.md
---
# Reporters
Reporters in Rstest control how test results are displayed and processed.
You can use built-in reporters to get different output formats or create custom reporters to integrate with your workflow.
## Using reporters
You can configure reporters through:
1. **CLI**: `--reporter=` (can be used multiple times)
2. **Config file**: Add [reporters](/config/test/reporters.md) to your `rstest.config.ts`
```ts
import { defineConfig } from '@rstest/core';
import { customReporter } from './path/to/custom-reporter';
export default defineConfig({
reporters: [
'default', // Simple string reporter
['junit', { outputPath: './test-results.xml' }], // Reporter with options
customReporter, // Custom reporter instance
],
});
```
More configuration options can be found in the [Reporters Configuration](/config/test/reporters.md) documentation.
Reporter options for built-in reporters are defined in the TypeScript types: [`packages/core/src/types/reporter.ts`](https://github.com/web-infra-dev/rstest/blob/main/packages/core/src/types/reporter.ts)
## Built-in reporters
Rstest provides several built-in reporters:
| Reporter | Purpose | Use case |
| ---------------- | -------------------------- | ------------------------ |
| `default` | Console output with colors | Local development |
| `dot` | Compact per-test markers | Fast local feedback |
| `verbose` | Detailed test case output | Debugging test failures |
| `github-actions` | CI error annotations | GitHub Actions workflows |
| `junit` | JUnit XML format | CI/CD integration |
| `json` | Structured JSON report | CI tooling and scripting |
| `md` | Markdown agent report | Agent / LLM integrations |
| `blob` | Serialized JSON output | Merging sharded reports |
### Default reporter
The default reporter displays test run status, results, and summary information in your terminal with colored output.
**Output example:**
```bash
✓ test/index.test.ts (2)
Test Files 1 passed
Tests 2 passed
Duration 112ms (build 19ms, tests 93ms)
```
**Configuration options:**
- `summary`: Whether to display summary information (default: `true`)
- `logger`: Custom logger function for output (default: `process.stdout/process.stderr`)
#### Progress reporting
The default reporter provides progress feedback during test execution, adapting to your environment.
**TTY environments (interactive terminal)**
In interactive terminals, the default reporter displays real-time test progress at the bottom of the terminal. This includes currently running test files and individual test cases that have been running for more than 2 seconds.
```bash
RUNS src/database.test.ts
> should handle concurrent writes 5s
Test Files 12 passed | 1 running
Tests 45 passed
Duration 30s
```
This live status updates every second and is automatically cleared when tests complete.
**Non-TTY environments (CI, piped output, AI agents)**
In non-TTY environments, the default reporter automatically logs a progress summary when there is no console output for 30 seconds. This prevents CI "no output" timeouts (e.g., GitHub Actions kills processes after 10 minutes of silence) and provides visibility into test execution without cluttering actively-producing output.
```bash
[PROGRESS] test files: 20 done, 4 running | tests: 380 passed, 3 failed | 2m 30s
Running: src/heavy.test.ts > database > concurrent writes 45s
Running: src/other.test.ts
```
When a test case runs longer than 10 seconds, its name and elapsed time are shown. This helps identify stuck or slow tests. Progress reporting stops after 20 reports (\~10 minutes of idle time) to avoid blocking CI timeout mechanisms when tests are truly stuck.
### Verbose reporter
The verbose reporter extends the default reporter and outputs detailed information for all test cases, including successful ones. Features like progress reporting are also available. Use this when you need complete visibility into test execution.
**CLI**
```bash
npx rstest --reporter=verbose
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: 'verbose'
});
```
**Output example:**
```bash
✓ test/index.test.ts (2) 2ms
✓ Index > should add two numbers correctly (1ms)
✓ Index > should test source code correctly (1ms)
Test Files 1 passed
Tests 2 passed
Duration 112ms (build 19ms, tests 93ms)
```
### Dot reporter
[Added in v0.9.7](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.7)
The dot reporter emits a compact marker stream with one character per test case:
- `·` for passed tests
- `x` for failed tests
- `-` for skipped tests
- `*` for todo tests
**CLI**
```bash
npx rstest --reporter=dot
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: 'dot'
});
```
It still prints the regular failing-test summary and duration footer after the marker line.
### GitHub Actions reporter
Outputs error messages using [GitHub Actions workflow commands](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-an-error-message) when tests fail, enabling rich error annotations in your CI.
**Auto-enablement:** Automatically enabled in GitHub Actions environments.
**CLI**
```bash
npx rstest --reporter=github-actions
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: ['github-actions']
});
```
When tests fail, the GitHub Actions reporter outputs information in a format similar to:
```bash
::error file=src/index.ts,line=4,col=17,title=test/index.test.ts > should add two numbers correctly::expected 2 to be 4
```
These outputs are parsed by GitHub Actions and generate comments at the corresponding locations.

When `GITHUB_STEP_SUMMARY` is available (GitHub Actions sets this automatically), Rstest also appends a Markdown summary that includes:
- a test summary table (files/tests/duration)
- a flaky test section for tests that passed after retries, with short summaries of the previous failure messages when available
- failure sections with error message, diff, and stack trace
- repro commands for each failed test
- a status-marked report title (`✅` / `❌`)
- a collapsible block for each test run; successful runs are collapsed by default
#### Auto-enablement
When no reporter is manually set, Rstest automatically enables this reporter when it detects a GitHub Actions environment (`process.env.GITHUB_ACTIONS` is `'true'`).
#### Manual enablement
You can also manually enable this reporter:
**CLI**
```bash
npx rstest --reporter=github-actions
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: ['github-actions']
});
```
#### Options
| Option | Type | Default | Description |
| :------------ | :-------- | :------ | :------------------------------------------------------------- |
| `annotations` | `boolean` | `true` | Whether to output `::error` annotations for failed tests. |
| `summary` | `boolean` | `true` | Whether to append a Markdown summary to `GITHUB_STEP_SUMMARY`. |
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: [['github-actions', { annotations: true, summary: false }]],
});
```
### JUnit reporter
Generates test reports in JUnit XML format, perfect for CI/CD integration and test result aggregation.
**CLI**
```bash
npx rstest --reporter=junit
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: [['junit', { outputPath: './junit.xml' }]]
});
```
**Configuration options:**
- `outputPath`: Output file path (defaults to console output)
The JUnit reporter generates XML format as follows:
```xml
expected 'hi' to be 'hii' // Object.is equality - Expected + Received - hii + hi at test1.test.ts:10:21
```
The JUnit reporter maps test case execution status to JUnit test status:
- `pass`: Test passed
- `fail`: Test failed, generates `` tag
- `skip`: Test skipped, generates `` tag
- `todo`: Test todo, generates `` tag
### JSON reporter
[Added in v0.9.6](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.6)
Generates a stable JSON document for CI tooling, custom dashboards, or post-processing scripts.
**CLI**
```bash
npx rstest --reporter=json
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: [['json', { outputPath: './reports/rstest.json' }]],
});
```
**Configuration options:**
- `outputPath`: Output file path (defaults to stdout)
**Output shape:**
- `status`: Overall run status (`pass` or `fail`)
- `summary`: Counts for files and tests
- `durationMs`: Total, build, and test durations in milliseconds
- `snapshot`: Snapshot summary from the run
- `files`: Test-file level results with nested test cases
- `tests`: Flattened test-case results, each with `fullName`
- `consoleLogs`: Captured user console output when available
- `unhandledErrors`: Top-level unhandled errors when present
The JSON reporter writes relative `testPath` values so the output remains stable across different machines and CI workspaces.
### Markdown reporter
The markdown reporter outputs a single markdown document to stdout, designed for agent / LLM consumption.
In agent environments, Rstest defaults to `md` only when you didn't explicitly configure reporters.
The default preset is `normal`, which is tuned for agent workflows:
- Each failure includes a repro command (`file+name`) so an agent can rerun a single failing case.
- Stack output defaults to `top` (a stable anchor frame) instead of full stack frames.
- Console output is enabled by default, but limited to keep the report size under control.
- When the number of failures exceeds `failures.max`, the reporter prints a full failure list with minimal fields (repro / type / message / expected / actual), and only expands full details for the first `failures.max` failures.
The available presets mainly differ in how much detail they keep in failure sections:
| Preset | Best for | Failure details | Console logs | Code frames |
| --------- | ---------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------ | -------------------------------- |
| `normal` | Default agent workflows | `stack: 'top'`, `failures.max: 50` | Enabled, capped at 10 logs per test path and 500 chars per entry | Enabled, 2 lines above and below |
| `compact` | CI logs, repeated agent loops, token-sensitive runs | `stack: 'top'`, `failures.max: 20` | Disabled | Disabled |
| `full` | Deep debugging when you want maximum context in one report | `stack: 'full'`, `failures.max: 200` | Enabled, capped at 200 logs per test path and 5000 chars per entry | Enabled, 3 lines above and below |
All presets still keep the same markdown structure. The main difference is how much failure context is expanded by default.
**CLI**
```bash
npx rstest --reporter=md
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: ['md'],
});
```
The default output always starts with a report title, then shows a summary section, test lists when needed, and a failures section.
When all tests pass, an excerpt looks like this:
````md
## Summary
```json
{
"status": "pass",
"counts": {
"testFiles": 2,
"failedFiles": 0,
"tests": 14,
"failedTests": 0,
"passedTests": 13,
"skippedTests": 1,
"todoTests": 0
}
}
```
## Failures
No test failures reported.
Note: all tests passed. Lists omitted for brevity.
````
When there are failures, an excerpt looks like this:
````md
## Summary
```json
{
"status": "fail",
"counts": {
"testFiles": 1,
"failedFiles": 1,
"tests": 1,
"failedTests": 1,
"passedTests": 0,
"skippedTests": 0,
"todoTests": 0
}
}
```
## Failures
### [F01] fixtures/agent-md/index.test.ts :: agent-md > fails with diff
details:
```json
{
"testPath": "fixtures/agent-md/index.test.ts",
"fullName": "agent-md > fails with diff",
"status": "fail"
}
```
diff:
```diff
- Expected
+ Received
- 3
+ 2
```
````
If you want a smaller payload for CI logs or multi-step agent loops, switch to the `compact` preset and lower the failure cap:
```ts title=rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: [
[
'md',
{
preset: 'compact',
failures: { max: 20 },
reproduction: 'file+name',
},
],
],
});
```
### Blob reporter
[Added in v0.9.3](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.3)
The blob reporter serializes test results into JSON files for later merging. This is designed for use with [test sharding](/config/test/shard.md) in CI environments, where tests are split across multiple machines and results need to be combined afterward.
**CLI**
```bash
npx rstest run --shard 1/3 --reporter=blob
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: ['blob'],
});
```
**Configuration options:**
- `outputDir`: Directory to store blob report files (default: `.rstest-reports`)
```ts title=rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: [['blob', { outputDir: './custom-blob-output' }]],
});
```
Blob files are saved to `.rstest-reports/` by default. After all shards complete, use [`rstest merge-reports`](/guide/basic/cli.md#rstest-merge-reports) to combine them into a unified report.
See the [shard configuration](/config/test/shard.md) for a complete CI workflow example.
## Custom reporters
For advanced integration needs, you can create custom reporters by implementing the `Reporter` interface.
More details can be found in the [Reporter API reference](/api/javascript-api/reporter.md).
---
url: /guide/basic/vscode-extension.md
---
# VS Code extension
The Rstest VS Code extension keeps your project’s tests in sync inside the editor so you can browse, run, and debug them in VS Code.

## Installation
You can visit the extension’s [Visual Studio Code Marketplace page](https://marketplace.visualstudio.com/items?itemName=rstack.rstest), or install it directly inside VS Code:
- Open the _Extensions_ view (Ctrl /⌘ + Shift + X ) and search for `rstack.rstest`.
- Open the _Quick Open_ panel (Ctrl /⌘ + P ), enter `ext install rstack.rstest`, and press Enter.
- You can also run `code --install-extension rstack.rstest` in the terminal.
---
url: /guide/basic/upgrade-rstest.md
---
# Upgrade Rstest
This section explains how to upgrade the project's Rstest dependencies to the latest version.
:::info
Rstest is still in 0.x version stage, and the API may change frequently. We recommend upgrading to the latest version to access new features and bug fixes.
:::
## Using taze
We recommend using [Taze](https://github.com/antfu-collective/taze) to upgrade the Rstest version. Taze is a CLI tool for updating npm dependencies.
### Usage
Run the following command to upgrade all dependencies that include `rstest` and `rsbuild` in their names:
```bash
npx taze major --include "/(rstest|rsbuild)/" -w
```
:::tip
Rstest has not yet reached version 1.0.0, so you need to add the `major` parameter when updating.
:::
The result will look similar to:
```bash
rstest-example - 1 patch
devDependencies
@rstest/core ~29d ^0.6.0 → ^0.6.6 ~5d
ℹ changes written to package.json, run pnpm i to install updates.
```
You can also adjust the `include` pattern to match specific packages, for example, to upgrade only packages under the `@rstest` scope:
```bash
npx taze --include /@rstest/ -w
```
### Options
Here are some examples of using taze options.
- In a monorepo, you can add the `-r` option to upgrade recursively:
```bash
npx taze --include /(rstest|rsbuild)/ -w -r
```
- Add `-l` to upgrade locked versions:
```bash
npx taze --include /(rstest|rsbuild)/ -w -l
```
- To upgrade to a major version:
```bash
npx taze major --include /(rstest|rsbuild)/ -w
```
> For more options, please refer to the [taze documentation](https://github.com/antfu-collective/taze).
---
url: /guide/browser-testing/index.md
---
# Browser mode (experimental)
Rstest provides Browser Mode, allowing you to run tests in a real browser instead of simulated environments like jsdom or happy-dom.
## What is browser Mode?
Browser Mode uses [Playwright](https://playwright.dev/) to execute your test code in real browsers (Chromium, Firefox, or WebKit). This means your tests run with the exact same browser APIs and behaviors as in production.
## Locator API
Browser Mode now supports a Playwright-style Locator workflow: you can use `page.getBy*` for element queries, then use `expect.element(locator)` for auto-waiting assertions.
This approach is ideal when you want semantic queries (role/label/text) and chainable assertions, making component tests and DOM tests closer to real user interaction semantics.
See [User interactions](/guide/browser-testing/user-interactions.md#locator-api) for detailed usage.
## When to use browser mode
Use this decision tree to determine if you need Browser Mode:
```
Depends on real browser APIs? ─── Yes ─▶ ✅ Browser Mode
│ No
▼
Need cross-browser testing? ─── Yes ─▶ ✅ Browser Mode
│ No
▼
Unexpected behavior in jsdom? ── Yes ─▶ ✅ Browser Mode
│ No
▼
💡 jsdom is fine
```
:::tip Recommendation
Even if your tests work fine in jsdom, we still **recommend using Browser Mode**. See the comparison table below for specific advantages.
:::
## Browser mode vs jsdom/happy-dom
Browser Mode and jsdom/happy-dom represent different trade-offs: Browser Mode provides full browser compatibility and visual debugging but consumes more resources; jsdom/happy-dom runs faster and lighter but can only simulate a subset of browser APIs.
| Feature | Browser Mode | jsdom / happy-dom |
| --------------------- | ------------------- | ------------------------------- |
| Browser API coverage | ✅ Full support | ⚠️ Partial simulation |
| Canvas / WebGL | ✅ Native support | ❌ Unsupported or needs polyfill |
| CSS computed styles | ✅ Real rendering | ⚠️ Limited support |
| Web Workers | ✅ Native support | ❌ Unsupported |
| Execution speed | ⚠️ Slower | ✅ Faster |
| Resource usage | ⚠️ Higher | ✅ Lower |
| Debugging experience | ✅ Visual debugging | ⚠️ Console only |
| Cross-browser testing | ✅ Multiple browsers | ❌ Unsupported |
## Next steps
- [Getting started](/guide/browser-testing/getting-started.md) - Configure and run your first browser test
- [User interactions](/guide/browser-testing/user-interactions.md#locator-api) - Use `page` + `expect.element` for semantic tests
- [Framework guides](/guide/browser-testing/framework-guides.md) - Complete configuration and component testing examples for each framework
- [User interactions](/guide/browser-testing/user-interactions.md) - Simulate user clicks, typing, and other actions
---
url: /guide/browser-testing/getting-started.md
---
# Getting started
This guide will help you configure and run Browser Mode tests in your project.
## Automatic initialization
The quickest way is to use the `rstest init browser` command for automatic configuration:
```sh [npx]
npx rstest init browser
```
```sh [yarn]
yarn dlx rstest init browser
```
```sh [pnpm]
pnpm dlx rstest init browser
```
```sh [bunx]
bunx rstest init browser
```
```sh [deno]
deno run -A npm:rstest init browser
```
### Initialization process
When you run the command, Rstest automatically:
1. **Generates boilerplate code**: Rstest detects your project's framework (for example, React), language (for example, TypeScript or JavaScript), test directory (for example, common directories like `tests/`, `test/`, or `__tests__/`, depending on what is detected), and package manager, then generates sample components and test files. The generated files adapt based on detection results:
- Test directory: Files are placed in the detected test directory
- Framework: React projects get JSX component tests, others get native DOM examples
- Language: TypeScript projects get `.ts`/`.tsx` files, others get `.js`/`.jsx` files
2. **Creates configuration file**: Creates `rstest.browser.config.ts` in your project root with basic Browser Mode configuration.
3. **Updates package.json**: Adds a `test:browser` script.
Here's an example output for a React project:
```bash
◆ rstest init browser
Detecting project...
✓ Found React 19.0.0
✓ Using playwright as browser provider
✓ Test directory: tests/
Created files:
- rstest.browser.config.ts
- tests/Counter.jsx
- tests/Counter.test.jsx
- Updated package.json
Next steps:
pnpm i
pnpm dlx playwright install --with-deps
pnpm run test:browser
└ Done!
```
### Next steps
After initialization, follow the "Next steps" instructions in the output: install project dependencies, install browser drivers, then run tests.
:::tip Non-interactive Environments
In CI or other non-interactive environments, add the `--yes` flag to skip all prompts and use detected configuration:
```bash
npx rstest init browser --yes
```
:::
## Manual configuration
### 1. Install dependencies
First, install the Rstest core package and Browser Mode support package:
```sh [npm]
npm add @rstest/core @rstest/browser -D
```
```sh [yarn]
yarn add @rstest/core @rstest/browser -D
```
```sh [pnpm]
pnpm add @rstest/core @rstest/browser -D
```
```sh [bun]
bun add @rstest/core @rstest/browser -D
```
```sh [deno]
deno add npm:@rstest/core npm:@rstest/browser -D
```
Browser Mode requires Playwright as the browser driver. Install the corresponding browser:
```bash
npx playwright install chromium
```
You can also install other browsers:
```bash
# Install Firefox
npx playwright install firefox
# Install WebKit (Safari)
npx playwright install webkit
# Install all browsers
npx playwright install
```
### 2. Create configuration file
Create or update your `rstest.config.ts` configuration file:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
},
});
```
### 3. Write tests
Create a browser test file. The recommended approach is to use the Locator API for element queries and interactions:
```ts title="tests/counter.test.ts"
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('counter increments on click', async () => {
document.body.innerHTML = `
Count: 0
`;
let count = 0;
document.getElementById('count-btn')!.addEventListener('click', (e) => {
count++;
(e.target as HTMLButtonElement).textContent = `Count: ${count}`;
});
await expect
.element(page.getByRole('button', { name: 'Count: 0' }))
.toBeVisible();
await page.getByRole('button', { name: 'Count: 0' }).click();
await expect.element(page.getByText('Count: 1')).toBeVisible();
});
```
This example uses `page.getByRole()` to locate a button by its semantic role, triggers an interaction with `click()`, and asserts the result with `expect.element().toBeVisible()`. The assertion automatically waits for the element state to change — no manual polling needed.
## Headless vs headed mode
By default, Browser Mode automatically selects the running mode based on the environment:
- **CI environment**: Automatically uses headless mode (no UI)
- **Local development**: Uses headed mode by default (shows browser window)
### Configuring headless mode
You can explicitly specify this in your configuration file:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
headless: true, // Always use headless mode
},
});
```
## Configuration reference
For complete configuration reference, see [browser configuration](/config/test/browser.md).
## Next steps
- [User interactions](/guide/browser-testing/user-interactions.md#locator-api) - Use `page` + `expect.element` for semantic queries and assertions
- [Framework guides](/guide/browser-testing/framework-guides.md) - Complete configuration and component testing examples for each framework
- [User interactions](/guide/browser-testing/user-interactions.md) - Simulate user actions
---
url: /guide/browser-testing/framework-guides.md
---
# Framework guides
This guide provides testing configuration examples for various frontend frameworks in Browser Mode.
:::tip Prerequisites
Before continuing, make sure you've completed the basic Browser Mode setup. You can initialize using either method:
- **Automatic initialization**: Run `npx rstest init browser`
- **Manual configuration**: Follow the [Getting Started](/guide/browser-testing/getting-started.md#manual-configuration) guide
Then follow this guide for framework-specific configuration.
:::
:::info Framework Support
Currently, Browser Mode only provides out-of-the-box support for React. Other frameworks (Vue, Svelte, etc.) will be supported in the future.
:::
## React
### Install dependencies
```sh [npm]
npm add @rstest/browser-react -D
```
```sh [yarn]
yarn add @rstest/browser-react -D
```
```sh [pnpm]
pnpm add @rstest/browser-react -D
```
```sh [bun]
bun add @rstest/browser-react -D
```
```sh [deno]
deno add npm:@rstest/browser-react -D
```
[@rstest/browser-react](https://github.com/web-infra-dev/rstest/tree/main/packages/browser-react) is the official toolkit for testing React in Rstest Browser Mode. It provides:
- `render`: Renders React components to the real browser DOM and returns a container element for querying and assertions
- `renderHook`: Tests custom Hooks without creating wrapper components
- Automatic cleanup: Automatically unmounts components from previous tests before each new test, preventing test interference
### Component testing
After the `render` function renders a component to the DOM, it returns `container` (the component's container element). You can use native DOM APIs or Testing Library to query elements and make assertions:
```tsx title="src/Counter.test.tsx"
import { render } from '@rstest/browser-react';
import { expect, test } from '@rstest/core';
import { Counter } from './Counter';
test('renders and increments', async () => {
const { container } = await render( );
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe(
'0',
);
container.querySelector('button')!.click();
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe(
'1',
);
});
```
If a component depends on Context (like Theme, Auth, or Store), use the `wrapper` option to wrap it with Providers:
```tsx
const Wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
);
test('renders with theme context', async () => {
const { container } = await render( , { wrapper: Wrapper });
// ...
});
```
### Hook testing
`renderHook` lets you test custom Hooks without writing wrapper components. It returns:
- `result.current`: The Hook's current return value, which updates as the Hook changes
- `act`: Wraps operations that trigger state updates, ensuring assertions run after updates complete
- `rerender`: Calls the Hook again with new props
```tsx title="src/useCounter.test.tsx"
import { renderHook } from '@rstest/browser-react';
import { expect, test } from '@rstest/core';
import { useCounter } from './useCounter';
test('useCounter increments', async () => {
const { result, act } = await renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
await act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
```
If a Hook depends on props, use `initialProps` to pass initial values, and `rerender` to simulate prop changes:
```tsx
test('hook reacts to prop changes', async () => {
const { result, rerender } = await renderHook(
(props) => useMultiplier(props?.value ?? 0),
{ initialProps: { value: 5 } },
);
expect(result.current).toBe(10);
await rerender({ value: 10 });
expect(result.current).toBe(20);
});
```
### Additional configuration
- **React Strict Mode**: Enable with `configure({ reactStrictMode: true })` to help catch potential issues
- **Manual cleanup**: Automatic cleanup runs before each test by default. For manual control, import from `@rstest/browser-react/pure` and call `cleanup()`
See the package documentation for the complete API.
## Vanilla JS/TS
For projects without a framework, you can test directly using native DOM APIs.
### Install dependencies (Optional)
If you want to use Testing Library's query methods, install these dependencies:
```sh [npm]
npm add @testing-library/dom @testing-library/user-event -D
```
```sh [yarn]
yarn add @testing-library/dom @testing-library/user-event -D
```
```sh [pnpm]
pnpm add @testing-library/dom @testing-library/user-event -D
```
```sh [bun]
bun add @testing-library/dom @testing-library/user-event -D
```
```sh [deno]
deno add npm:@testing-library/dom npm:@testing-library/user-event -D
```
### Example tests
Testing with native DOM APIs:
```ts title="src/counter.test.ts"
import { expect, test } from '@rstest/core';
test('counter increments on click', () => {
// Create DOM structure
const container = document.createElement('div');
container.innerHTML = `
0
+
`;
document.body.appendChild(container);
// Setup event handler
let count = 0;
const countEl = container.querySelector('#count')!;
const button = container.querySelector('#increment')!;
button.addEventListener('click', () => {
count++;
countEl.textContent = String(count);
});
// Test interaction
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(countEl.textContent).toBe('1');
});
```
Testing with Testing Library:
```ts title="src/dropdown.test.ts"
import { expect, test } from '@rstest/core';
import { getByRole, queryByRole } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { createDropdown } from './dropdown';
test('toggles dropdown on click', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
createDropdown(container);
const trigger = getByRole(container, 'button');
// Initial state: menu hidden
expect(queryByRole(container, 'menu')).toBeNull();
// Click to open menu
await userEvent.click(trigger);
expect(getByRole(container, 'menu')).toBeTruthy();
});
```
### Testing web components
```ts title="src/my-element.test.ts"
import { expect, test } from '@rstest/core';
import './my-element'; // Register custom element
test('renders content in shadow DOM', () => {
const el = document.createElement('my-element');
el.setAttribute('name', 'World');
document.body.appendChild(el);
const shadowRoot = el.shadowRoot!;
expect(shadowRoot.textContent).toContain('Hello, World!');
});
```
## Multi-Framework Projects
If your project includes multiple frameworks, you can use [projects](/config/test/projects.md) configuration to set up independent test environments for each:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
projects: [
{
name: 'react',
include: ['src/react/**/*.test.{ts,tsx}'],
browser: {
enabled: true,
provider: 'playwright',
},
},
{
name: 'node',
include: ['src/utils/**/*.test.ts'],
testEnvironment: 'node',
},
],
});
```
---
url: /guide/browser-testing/user-interactions.md
---
# User interactions
This guide covers how to simulate user interactions in Browser Mode tests, and helps you choose between stability, maintainability, and control granularity.
In Browser Mode, choose your interaction approach in the following priority order (only fall back when needed):
- **Locator API (preferred)**: Use [page.getBy\*](/api/runtime-api/browser-mode/locator.md#page) + [expect.element](/api/runtime-api/browser-mode/assertion.md) for semantic queries, interactions, and assertions, suitable for the vast majority of interaction tests
- **Testing Library**: Best for migrating existing tests or reusing an established [Testing Library](https://testing-library.com/) toolchain; generally not the first choice for new tests
- **Native DOM API (fallback)**: Lighter and allows precise control over event properties, but requires manual event sequencing — ideal for verifying low-level event logic or special interaction details
## Locator API
The Locator API is the default choice in Browser Mode. It is officially provided by Rstest: `@rstest/browser` provides the [page](/api/runtime-api/browser-mode/locator.md#page) query and interaction entry point, and `@rstest/core` provides [expect.element](/api/runtime-api/browser-mode/assertion.md) assertion capabilities.
It adopts a Playwright-style Locator syntax ([page.getBy\*](/api/runtime-api/browser-mode/locator.md#page) + chaining + [expect.element](/api/runtime-api/browser-mode/assertion.md)), enabling both component tests and DOM tests to reuse the same interaction and assertion patterns.
Reasons to prefer the Locator API:
- More stable queries: prioritizes locating elements by user-perceivable semantics such as `role`, `label`, and `text`
- More direct interactions: actions like [click](/api/runtime-api/browser-mode/locator.md#click), [fill](/api/runtime-api/browser-mode/locator.md#fill), [check](/api/runtime-api/browser-mode/locator.md#check), and [press](/api/runtime-api/browser-mode/locator.md#press) are attached directly to the Locator
- More natural assertions: combined with [expect.element](/api/runtime-api/browser-mode/assertion.md), waiting and assertion semantics stay consistent
- Higher consistency: a single API set covers queries, actions, and assertions, reducing context-switching between multiple tools
### Example
The following example focuses on the most common workflow: filling forms, clicking, and asserting.
```ts
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('interacts with form using locator api', async () => {
document.body.innerHTML = `
`;
await page.getByLabel('Username').fill('alice');
await page.getByLabel('Password').fill('secret123');
await page.getByLabel('Remember me').check();
await page.getByRole('button', { name: 'Login' }).click();
await expect.element(page.getByLabel('Username')).toHaveValue('alice');
await expect.element(page.getByLabel('Remember me')).toBeChecked();
});
```
### Common queries and composition
You can compose Locators just like in Playwright, progressively narrowing the scope to the target element:
```ts
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('composes locators', async () => {
document.body.innerHTML = `
`;
const saveInProfileSection = page
.locator('section')
.filter({ has: page.getByRole('heading', { name: 'Profile' }) })
.getByRole('button', { name: 'Save' });
await expect.element(saveInProfileSection).toHaveCount(1);
});
```
Currently available query/composition capabilities include:
- Semantic and attribute queries: [getByRole](/api/runtime-api/browser-mode/locator.md#getbyrole), [getByText](/api/runtime-api/browser-mode/locator.md#getbytext), [getByLabel](/api/runtime-api/browser-mode/locator.md#getbylabel), [getByPlaceholder](/api/runtime-api/browser-mode/locator.md#getbyplaceholder), [getByAltText](/api/runtime-api/browser-mode/locator.md#getbyalttext), [getByTitle](/api/runtime-api/browser-mode/locator.md#getbytitle), [getByTestId](/api/runtime-api/browser-mode/locator.md#getbytestid)
- Basic selection and filtering: [locator](/api/runtime-api/browser-mode/locator.md#locator), [filter](/api/runtime-api/browser-mode/locator.md#filter)
- Set composition and positioning: [and](/api/runtime-api/browser-mode/locator.md#and--or), [or](/api/runtime-api/browser-mode/locator.md#and--or), [nth](/api/runtime-api/browser-mode/locator.md#nth--first--last), [first](/api/runtime-api/browser-mode/locator.md#nth--first--last), [last](/api/runtime-api/browser-mode/locator.md#nth--first--last)
In practice, prefer semantic queries ([getByRole](/api/runtime-api/browser-mode/locator.md#getbyrole), [getByLabel](/api/runtime-api/browser-mode/locator.md#getbylabel)) first, and only fall back to [getByTestId](/api/runtime-api/browser-mode/locator.md#getbytestid) or CSS selectors when semantic information is insufficient.
### Common interactions and assertions
Locators support common interaction APIs (such as [click](/api/runtime-api/browser-mode/locator.md#click), [fill](/api/runtime-api/browser-mode/locator.md#fill), [check](/api/runtime-api/browser-mode/locator.md#check), [hover](/api/runtime-api/browser-mode/locator.md#hover), [press](/api/runtime-api/browser-mode/locator.md#press), [selectOption](/api/runtime-api/browser-mode/locator.md#selectoption)), and can be directly combined with [expect.element](/api/runtime-api/browser-mode/assertion.md) assertions:
- State assertions: [toBeVisible](/api/runtime-api/browser-mode/assertion.md#tobevisible), [toBeHidden](/api/runtime-api/browser-mode/assertion.md#tobehidden), [toBeEnabled](/api/runtime-api/browser-mode/assertion.md#tobeenabled), [toBeDisabled](/api/runtime-api/browser-mode/assertion.md#tobedisabled)
- Form/structure assertions: [toBeChecked](/api/runtime-api/browser-mode/assertion.md#tobechecked), [toBeFocused](/api/runtime-api/browser-mode/assertion.md#tobefocused), [toBeEmpty](/api/runtime-api/browser-mode/assertion.md#tobeempty)
- Text and value assertions: [toHaveText](/api/runtime-api/browser-mode/assertion.md#tohavetext), [toContainText](/api/runtime-api/browser-mode/assertion.md#tocontaintext), [toHaveValue](/api/runtime-api/browser-mode/assertion.md#tohavevalue), [toHaveCount](/api/runtime-api/browser-mode/assertion.md#tohavecount)
- Attribute assertions: [toHaveAttribute](/api/runtime-api/browser-mode/assertion.md#tohaveattribute), [toHaveClass](/api/runtime-api/browser-mode/assertion.md#tohaveclass), [toHaveCSS](/api/runtime-api/browser-mode/assertion.md#tohavecss), [toHaveJSProperty](/api/runtime-api/browser-mode/assertion.md#tohavejsproperty)
It's recommended to assert observable results immediately after key interactions (for example, status text, button state, field values) — this keeps failure messages focused and reduces debugging cost.
:::info Auto-wait vs Auto-retry
These are two distinct mechanisms in the Locator API:
- **Auto-wait (interactions)**: Methods like `click()`, `fill()`, `check()` automatically wait for the target element to be visible, enabled, and stable before executing the action.
- **Auto-retry (assertions)**: `expect.element` matchers continuously retry the assertion within the timeout until it passes, ideal for async rendering scenarios.
In most cases, you only need to `await` each call — the framework handles all waiting and retrying internally.
:::
You can also chain [not](/api/runtime-api/browser-mode/assertion.md#not) and use an optional `timeout`:
```ts
await expect
.element(page.getByRole('button', { name: 'Save' }))
.not.toBeDisabled({ timeout: 1000 });
```
:::warning Strictness
Locator actions are strict: if a locator matches more than one element, actions like `click` and `fill` will throw an error. Use `first()`, `last()`, or `nth()` to select a specific element.
:::
## Testing library
[Testing Library](https://testing-library.com/) is a testing utility library focused on user behavior. It encourages writing tests that mirror how users actually interact with your application, rather than relying on implementation details. In Browser Mode, it is better suited as a compatibility and migration solution:
- [@testing-library/dom](https://testing-library.com/docs/dom-testing-library/intro): Handles queries, providing methods like `getByRole`, `getByText`, and `getByLabelText` that let you find elements by user-perceivable semantics
- [@testing-library/user-event](https://testing-library.com/docs/user-event/intro): Handles interactions, providing more complete event simulation flows; in Browser Mode, the Locator API is still recommended for new tests
### Installation
```sh [npm]
npm add @testing-library/dom @testing-library/user-event -D
```
```sh [yarn]
yarn add @testing-library/dom @testing-library/user-event -D
```
```sh [pnpm]
pnpm add @testing-library/dom @testing-library/user-event -D
```
```sh [bun]
bun add @testing-library/dom @testing-library/user-event -D
```
```sh [deno]
deno add npm:@testing-library/dom npm:@testing-library/user-event -D
```
### Example
Here's a complete form submission test example demonstrating common user interaction methods:
```tsx title="src/LoginForm.test.tsx"
import { expect, test } from '@rstest/core';
import { render } from '@rstest/browser-react';
import { getByLabelText, getByRole } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
test('submits login form with user credentials', async () => {
const user = userEvent.setup();
const onSubmit = rstest.fn();
const { container } = await render( );
// Type into input fields
await user.type(getByLabelText(container, 'Username'), 'alice');
await user.type(getByLabelText(container, 'Password'), 'secret123');
// Check the "remember me" checkbox
await user.click(getByLabelText(container, 'Remember me'));
// Submit the form
await user.click(getByRole(container, 'button', { name: 'Login' }));
// Assert the form was submitted with correct data
expect(onSubmit).toHaveBeenCalledWith({
username: 'alice',
password: 'secret123',
rememberMe: true,
});
});
```
If your project already uses Testing Library extensively, you can continue reusing its click, text input, keyboard event, dropdown selection, drag and drop, and other capabilities. For detailed usage, see the [user-event documentation](https://testing-library.com/docs/user-event/intro).
## Native DOM API
If you prefer not to add extra dependencies, or need lower-level event control (such as precisely specifying `clientX`, `ctrlKey`, etc.), you can use native browser DOM APIs directly. This is typically used as a fallback, only when you need precise control over event parameters.
### Example
The following example demonstrates common native event operations including click, input, and keyboard events:
```ts title="src/native-events.test.ts"
import { expect, test } from '@rstest/core';
test('handles click and input events', () => {
// Create elements
const button = document.createElement('button');
const input = document.createElement('input');
document.body.append(button, input);
// Click event
let clicked = false;
button.addEventListener('click', () => (clicked = true));
button.click();
expect(clicked).toBe(true);
// Input event
input.focus();
input.value = 'hello';
input.dispatchEvent(new InputEvent('input', { bubbles: true }));
expect(input.value).toBe('hello');
});
test('handles keyboard shortcuts', () => {
let shortcutTriggered = false;
document.addEventListener('keydown', (e) => {
// Detect Ctrl+S shortcut
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
shortcutTriggered = true;
}
});
document.dispatchEvent(
new KeyboardEvent('keydown', {
key: 's',
code: 'KeyS',
ctrlKey: true,
bubbles: true,
}),
);
expect(shortcutTriggered).toBe(true);
});
```
---
url: /guide/framework/react.md
---
# React
This guide covers how to test React applications and components with Rstest. Rstest supports testing React in multiple scenarios:
- **Node (with happy-dom or jsdom)**: Fast, lightweight tests running in Node.js with a simulated DOM
- **SSR (Server-Side Rendering)**: Test server render functions in pure Node.js environment
- **Browser Mode**: Real browser testing with Playwright for accurate DOM behavior
## Node testing
Node-based testing uses DOM simulators like [happy-dom](https://github.com/niciodev/happy-dom) or [jsdom](https://github.com/jsdom/jsdom) to provide a DOM environment in Node.js. This approach is faster and suitable for most component testing scenarios.
### Quick start
#### 1. Install dependencies
```sh [npm]
npm add @rstest/core @rsbuild/plugin-react @testing-library/react @testing-library/jest-dom happy-dom -D
```
```sh [yarn]
yarn add @rstest/core @rsbuild/plugin-react @testing-library/react @testing-library/jest-dom happy-dom -D
```
```sh [pnpm]
pnpm add @rstest/core @rsbuild/plugin-react @testing-library/react @testing-library/jest-dom happy-dom -D
```
```sh [bun]
bun add @rstest/core @rsbuild/plugin-react @testing-library/react @testing-library/jest-dom happy-dom -D
```
```sh [deno]
deno add npm:@rstest/core npm:@rsbuild/plugin-react npm:@testing-library/react npm:@testing-library/jest-dom npm:happy-dom -D
```
#### 2. Configure rstest
Create `rstest.config.ts`:
```ts title="rstest.config.ts"
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rstest/core';
export default defineConfig({
plugins: [pluginReact()],
testEnvironment: 'happy-dom',
});
```
#### 3. Setup test matchers (Optional)
For enhanced DOM assertions with jest-dom matchers, create a setup file:
```ts title="rstest.setup.ts"
import { afterEach, expect } from '@rstest/core';
import { cleanup } from '@testing-library/react';
import * as jestDomMatchers from '@testing-library/jest-dom/matchers';
expect.extend(jestDomMatchers);
// Cleanup after each test to prevent test pollution
afterEach(() => {
cleanup();
});
```
Then add it to your config:
```ts title="rstest.config.ts"
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rstest/core';
export default defineConfig({
plugins: [pluginReact()],
testEnvironment: 'happy-dom',
setupFiles: ['./rstest.setup.ts'],
});
```
#### 4. Write your first test
```tsx title="src/App.test.tsx"
import { expect, test } from '@rstest/core';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders greeting', () => {
render( );
expect(screen.getByText('Hello World')).toBeInTheDocument();
});
```
### Reusing Rsbuild configuration
If your project already uses Rsbuild, you can reuse your existing configuration with [@rstest/adapter-rsbuild](/guide/integration/rsbuild.md):
```sh [npm]
npm add @rstest/adapter-rsbuild -D
```
```sh [yarn]
yarn add @rstest/adapter-rsbuild -D
```
```sh [pnpm]
pnpm add @rstest/adapter-rsbuild -D
```
```sh [bun]
bun add @rstest/adapter-rsbuild -D
```
```sh [deno]
deno add npm:@rstest/adapter-rsbuild -D
```
```ts title="rstest.config.ts"
import { withRsbuildConfig } from '@rstest/adapter-rsbuild';
import { defineConfig } from '@rstest/core';
export default defineConfig({
extends: withRsbuildConfig(),
testEnvironment: 'happy-dom',
setupFiles: ['./rstest.setup.ts'],
});
```
This will automatically inherit your Rsbuild plugins, aliases, and other build configuration.
### Testing components
Use [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) to render and query components:
```tsx title="src/Counter.test.tsx"
import { expect, test } from '@rstest/core';
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter on click', () => {
render( );
const button = screen.getByRole('button');
expect(screen.getByText('Count: 0')).toBeInTheDocument();
fireEvent.click(button);
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
```
### Mocking modules
Use `rs.mock()` to mock dependencies:
```tsx title="src/UserProfile.test.tsx"
import { expect, rs, test } from '@rstest/core';
import { render, screen } from '@testing-library/react';
import UserProfile from './UserProfile';
rs.mock('./api', () => ({
fetchUser: () => Promise.resolve({ name: 'John Doe' }),
}));
test('renders user name', async () => {
render( );
expect(await screen.findByText('John Doe')).toBeInTheDocument();
});
```
## SSR testing
Rstest supports testing React Server-Side Rendering (SSR) scenarios. You can test your server render functions using `react-dom/server`.
### Example
First, create a server render function:
```tsx title="src/index.server.tsx"
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';
export function render() {
return ReactDOMServer.renderToString(
,
);
}
```
Then test it:
```ts title="test/ssr.test.ts"
import { expect, test } from '@rstest/core';
import { render } from '../src/index.server';
test('renders correctly on server', () => {
const html = render();
expect(html).toContain('Hello World');
expect(html).toMatchSnapshot();
});
```
SSR tests run in Node.js environment without a DOM simulator, which is the default `testEnvironment: 'node'` setting.
## Browser mode testing
For scenarios requiring real browser behavior (e.g., CSS rendering, Web APIs, visual testing), use Rstest's Browser Mode with Playwright.
See the [Browser Testing - Framework Guides](/guide/browser-testing/framework-guides.md#react) for detailed setup and usage instructions.
**Recommendations:**
- Use **Node testing** for unit tests, logic-heavy components, and fast feedback
- Use **Browser Mode** for integration tests, visual behavior, and when you need real browser APIs
## Example projects
- [react](https://github.com/web-infra-dev/rstest/tree/main/examples/react) - React testing with happy-dom (includes component, hook, and SSR tests)
- [react-rsbuild](https://github.com/web-infra-dev/rstest/tree/main/examples/react-rsbuild) - React testing with Rsbuild adapter
- [browser-react](https://github.com/web-infra-dev/rstest/tree/main/examples/browser-react) - React testing in Browser Mode
---
url: /guide/framework/vue.md
---
# Vue
This guide covers how to test Vue applications and components with Rstest. Rstest supports testing Vue in multiple scenarios:
- **Node (with happy-dom or jsdom)**: Fast, lightweight tests running in Node.js with a simulated DOM
- **Browser Mode**: Real browser testing with Playwright for accurate DOM behavior
## Node testing
Node-based testing uses DOM simulators like [happy-dom](https://github.com/niciodev/happy-dom) or [jsdom](https://github.com/jsdom/jsdom) to provide a DOM environment in Node.js. This approach is faster and suitable for most component testing scenarios.
### Quick start
#### 1. Install dependencies
```sh [npm]
npm add @rstest/core @rsbuild/plugin-vue @vue/test-utils happy-dom -D
```
```sh [yarn]
yarn add @rstest/core @rsbuild/plugin-vue @vue/test-utils happy-dom -D
```
```sh [pnpm]
pnpm add @rstest/core @rsbuild/plugin-vue @vue/test-utils happy-dom -D
```
```sh [bun]
bun add @rstest/core @rsbuild/plugin-vue @vue/test-utils happy-dom -D
```
```sh [deno]
deno add npm:@rstest/core npm:@rsbuild/plugin-vue npm:@vue/test-utils npm:happy-dom -D
```
If you want to use Vue JSX, also install:
```sh [npm]
npm add @rsbuild/plugin-babel @rsbuild/plugin-vue-jsx -D
```
```sh [yarn]
yarn add @rsbuild/plugin-babel @rsbuild/plugin-vue-jsx -D
```
```sh [pnpm]
pnpm add @rsbuild/plugin-babel @rsbuild/plugin-vue-jsx -D
```
```sh [bun]
bun add @rsbuild/plugin-babel @rsbuild/plugin-vue-jsx -D
```
```sh [deno]
deno add npm:@rsbuild/plugin-babel npm:@rsbuild/plugin-vue-jsx -D
```
#### 2. Configure rstest
Create `rstest.config.ts`:
```ts title="rstest.config.ts"
import { pluginVue } from '@rsbuild/plugin-vue';
import { defineConfig } from '@rstest/core';
export default defineConfig({
plugins: [pluginVue()],
testEnvironment: 'happy-dom',
});
```
For Vue JSX support:
```ts title="rstest.config.ts"
import { pluginBabel } from '@rsbuild/plugin-babel';
import { pluginVue } from '@rsbuild/plugin-vue';
import { pluginVueJsx } from '@rsbuild/plugin-vue-jsx';
import { defineConfig } from '@rstest/core';
export default defineConfig({
plugins: [
pluginBabel({
include: /\.(?:jsx|tsx)$/,
}),
pluginVue(),
pluginVueJsx(),
],
testEnvironment: 'happy-dom',
});
```
#### 3. Write your first test
```ts title="test/App.test.ts"
import { expect, test } from '@rstest/core';
import { mount } from '@vue/test-utils';
import App from '../src/App.vue';
test('renders correctly', () => {
const wrapper = mount(App);
expect(wrapper.text()).toContain('Hello World');
});
```
### Testing components
Use [@vue/test-utils](https://test-utils.vuejs.org/) to mount and interact with components:
```ts title="test/Counter.test.ts"
import { expect, test } from '@rstest/core';
import { mount } from '@vue/test-utils';
import Counter from '../src/Counter.vue';
test('increments counter on click', async () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain('Count: 0');
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('Count: 1');
});
```
### Testing events
```ts title="test/Button.test.ts"
import { expect, test } from '@rstest/core';
import { mount } from '@vue/test-utils';
import Button from '../src/Button.vue';
test('emits click event', async () => {
const wrapper = mount(Button);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted('click')).toBeTruthy();
expect(wrapper.emitted('click')).toHaveLength(1);
});
```
### Testing Vue JSX components
Vue JSX components can be tested the same way as SFC components:
```ts title="test/JsxComponent.test.ts"
import { expect, test } from '@rstest/core';
import { mount } from '@vue/test-utils';
import App from '../src/App.tsx';
test('renders JSX component correctly', async () => {
const wrapper = mount(App);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted('clickApp')).toBeTruthy();
});
```
### Mocking modules
Use `rs.mock()` to mock dependencies:
```ts title="test/UserProfile.test.ts"
import { expect, rs, test } from '@rstest/core';
import { mount, flushPromises } from '@vue/test-utils';
import UserProfile from '../src/UserProfile.vue';
rs.mock('../src/api', () => ({
fetchUser: () => Promise.resolve({ name: 'John Doe' }),
}));
test('renders user name', async () => {
const wrapper = mount(UserProfile, {
props: { userId: '1' },
});
await flushPromises();
expect(wrapper.text()).toContain('John Doe');
});
```
## Browser mode testing
For scenarios requiring real browser behavior (e.g., CSS rendering, Web APIs, visual testing), use Rstest's Browser Mode with Playwright.
See the [Browser Testing - Getting Started](/guide/browser-testing/getting-started.md) for detailed setup instructions.
:::info
Browser Mode currently provides out-of-the-box support for React. Vue support in Browser Mode will be added in the future.
:::
**Recommendations:**
- Use **Node testing** for unit tests, logic-heavy components, and fast feedback
- Use **Browser Mode** for integration tests, visual behavior, and when you need real browser APIs
## Example projects
- [vue](https://github.com/web-infra-dev/rstest/tree/main/examples/vue) - Vue testing with happy-dom (includes SFC and JSX tests)
---
url: /guide/framework/more.md
---
# More frameworks
Rstest shares the same plugin system as Rsbuild, so if your framework has an available Rsbuild plugin, you can usually register that plugin in `rstest.config.ts` to enable the framework's compilation support for tests. See [plugins](/config/build/plugins.md) for how plugin registration works, and browse the [Rsbuild plugin list](https://rsbuild.rs/plugins/list/) for available plugins.
For example, Rsbuild's official plugin list already includes plugins for frameworks such as Preact, Svelte, and Solid, and the community ecosystem also provides plugins for frameworks such as Angular.
## Basic setup
You can usually integrate another framework with this flow:
1. Install the framework's Rsbuild plugin and the framework's own testing utilities.
2. Register the plugin in `plugins` inside `rstest.config.ts`.
3. Choose an appropriate `testEnvironment` for the type of test you are running.
- Use `happy-dom` or `jsdom` for component tests that need DOM APIs
- Use `node` for SSR, utilities, or tests that do not touch the DOM
Here is a minimal configuration example:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
import { pluginFramework } from 'your-rsbuild-plugin';
export default defineConfig({
plugins: [pluginFramework()],
testEnvironment: 'happy-dom',
});
```
## What else you need
An Rsbuild plugin solves compilation and build integration. The full testing experience usually still depends on the framework's own testing toolchain. In most cases, you should also prepare:
- The framework's recommended component testing utilities
- Any required setup files
- Assertion or query tools that fit the framework ecosystem
If your project already uses Rsbuild, Rslib, or one of their adapters, prefer reusing that existing configuration first. That usually lets you inherit plugins, aliases, and other build settings automatically.
If you run into issues while integrating more frameworks, please contact us through [GitHub Issues](https://github.com/web-infra-dev/rstest/issues).
---
url: /guide/advanced/debugging.md
---
# Debugging
## Debug mode
Rstest provides a debug mode to troubleshoot problems, you can add the `DEBUG=rstest` environment variable when testing to enable Rstest's debug mode.
```bash
DEBUG=rstest pnpm test
```
In debug mode, Rstest will:
- Write test temporary outputs to disk
- Write the Rstest config, Rsbuild config and Rspack config to the dist directory
- Show full error stack traces
- Set logger level to `verbose`
### Rstest config file
In debug mode, Rstest will automatically generate `dist/.rstest-temp/rsbuild/rstest.config.mjs` file, which contains the final generated Rstest config. In this file, you can know the final result of the Rstest config you passed in after being processed by the framework and Rstest.
The content of the file is as follows:
```js title="rstest.config.mjs"
export default {
name: 'rstest',
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
exclude: [
'**/node_modules/**',
'**/dist/**',
'**/.{idea,git,cache,output,temp}/**',
'**/dist/.rstest-temp',
],
includeSource: [],
pool: {
type: 'forks',
},
isolate: true,
globals: false,
passWithNoTests: false,
update: false,
testTimeout: 5000,
testEnvironment: 'node',
retry: 0,
clearMocks: false,
resetMocks: false,
restoreMocks: false,
slowTestThreshold: 300,
// other configs...
};
```
For a complete introduction to Rstest config, please see the [Configure Rstest](/guide/basic/configure-rstest.md) chapter.
## View test temporary outputs
In the [debug mode](#debug-mode) or when [dev.writeToDisk](/config/build/dev.md#devwritetodisk) is enabled, Rstest will write test temporary outputs to disk. You can view the files in `dist/.rstest-temp` directory to help you troubleshoot problems.
## Debugging in VS Code
Rstest supports debugging in VS Code using `debugger` statements or breakpoints. Just open the `JavaScript Debug Terminal` and run the test command in the terminal.
You can also add a dedicated launch configuration to debug a test file in VS Code:
```json title=".vscode/launch.json"
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Current Test File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/@rstest/core/bin/rstest.js",
"args": ["run", "${file}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["/**"]
}
]
}
```
Then you can start debugging the current test file directly in VS Code by pressing `F5` or go to the `Run and Debug` panel.
---
url: /guide/advanced/profiling.md
---
# Profiling
Rstest provides various tools and methods to analyze the performance of test runs, helping you identify and resolve performance bottlenecks.
## Common performance bottlenecks
When a test run becomes noticeably slower, first determine whether the bottleneck is in the build stage or the test execution stage:
- If startup takes a long time and reruns remain slow after a small code change, the bottleneck is usually in the build stage.
- If tests begin quickly but individual test cases or test files take a long time to finish, the bottleneck is usually in test execution.
These two categories can then be investigated separately for build versus test execution.
### Investigating build performance
For projects using browser-like environments such as `jsdom` or `happy-dom`, the build stage is one of the most common bottlenecks. In these environments, rstest bundles third-party dependencies from `node_modules` by default instead of externalizing them like it does in the `node` environment.
As a result, test performance can degrade when:
- A test entry indirectly pulls in a large UI library, charting library, or editor package.
- The test only needs a small subset of DOM functionality, but the bundle includes a large amount of third-party runtime code.
For these cases, the following workflow is a practical way to narrow down build-related slowdowns.
**1. Enable rstest debug output**
Run rstest with `DEBUG=rstest`:
```bash
DEBUG=rstest rstest run
```
With DEBUG enabled, rstest prints more detailed build logs and writes temporary build artifacts to disk. By default, they are written to `dist/.rstest-temp`; if you configure [`output.distPath.root`](/config/build/output.md#outputdistpath), they are written to that directory instead.
At this stage, focus on two signals:
- Whether the logs spend a long time in build-related stages.
- Whether the temporary output directory contains unusually large artifacts.
If bundle size is already the primary suspect, this step often provides enough evidence for an initial conclusion.
**2. Identify size overhead introduced by third-party dependencies**
In `jsdom`, `happy-dom`, and similar browser-like environments, large temporary outputs are not surprising because rstest bundles third-party dependencies by default.
In particular, check the following:
- Whether a large artifact maps back to a specific test entry or dependency chain.
- Whether a heavy package from `node_modules` is being pulled in.
- Whether the test only needs a small API surface while the entire package is being bundled.
If the problem is concentrated in third-party dependencies, adjusting the bundling strategy is usually more effective than immediately introducing heavyweight profiling tools.
**3. Use output.bundleDependencies to validate the bundling strategy**
Set [`output.bundleDependencies`](/config/build/output.md#outputbundledependencies) to `false` so browser-like environments externalize third-party dependencies like the `node` environment:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
testEnvironment: 'jsdom',
output: {
bundleDependencies: false,
},
});
```
Then rerun with `DEBUG=rstest` and compare the following:
- Whether build time drops noticeably.
- Whether the temporary artifacts under `dist/.rstest-temp` become much smaller.
If both improve, the primary bottleneck was likely the cost of bundling third-party dependencies.
**4. Use output.externals for fine-grained control**
If you do not want to disable bundling for all third-party dependencies, use [`output.externals`](/config/build/output.md#outputexternals) to externalize only selected heavy packages:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
output: {
externals: ['react', 'lodash'],
},
});
```
This approach is a better fit when:
- Only a few packages are disproportionately large.
- You still want to keep bundling for the rest of the dependency graph.
- You have already confirmed that the bottleneck is concentrated in a small set of third-party packages.
**5. Use Rsdoctor when the build bottleneck is still unclear**
Once you know the slowdown is in the build stage but still do not know whether the cost comes from entries, loaders, plugins, or a dependency chain, Rsdoctor becomes much more effective. The earlier steps help determine whether the problem is related to bundle size, while Rsdoctor helps identify how compile time is distributed.
:::warning
`output.bundleDependencies: false` only applies to non-browser mode. In [browser mode](/guide/browser-testing.md), dependencies are always bundled, so this path is not available for reducing bundle size.
:::
### Investigating test execution performance
Once the problem has been narrowed down to the test execution stage, the main question is no longer bundle size, but which test file, which test case, or which part of the runtime is consuming the time.
**1. Use the verbose reporter to inspect file-level and case-level duration**
With [verbose reporter](/guide/basic/reporters.md#verbose-reporter) enabled, you can directly inspect the execution time of each test file and individual test case. This is usually the first step for execution-stage troubleshooting because it helps narrow the problem down to a specific file, a group of tests, or a small set of cases.
**2. Inspect detailed time distribution after narrowing the scope**
Once the slow tests have been identified but the actual runtime cost is still unclear, drop down to a profiler. See [Profiler](#profiler) for picking between [samply](#using-samply) and [Node.js profiling](#nodejs-profiling).
## Using Rsdoctor
[Rsdoctor](https://rsdoctor.rs/) is a build analysis tool that can visually display the compilation time of each loaders and plugins.
When you need to debug Rstest's build outputs or build processes, you can use Rsdoctor for troubleshooting.
### Quick start
In Rstest, you can enable Rsdoctor analysis as follows:
1. Install the Rsdoctor plugin:
```sh [npm]
npm add @rsdoctor/rspack-plugin -D
```
```sh [yarn]
yarn add @rsdoctor/rspack-plugin -D
```
```sh [pnpm]
pnpm add @rsdoctor/rspack-plugin -D
```
```sh [bun]
bun add @rsdoctor/rspack-plugin -D
```
```sh [deno]
deno add npm:@rsdoctor/rspack-plugin -D
```
2. Add `RSDOCTOR=true` env variable before the CLI command:
```json title="package.json"
{
"scripts": {
"test:rsdoctor": "RSDOCTOR=true rstest run"
}
}
```
As Windows does not support the above usage, you can also use [cross-env](https://npmjs.com/package/cross-env) to set environment variables. This ensures compatibility across different systems:
```json title="package.json"
{
"scripts": {
"test:rsdoctor": "cross-env RSDOCTOR=true rstest run"
},
"devDependencies": {
"cross-env": "^7.0.0"
}
}
```
After running the above commands, Rstest will automatically register the Rsdoctor plugin, and after the build is completed, it will open the build analysis page. For complete features, please refer to [Rsdoctor document](https://rsdoctor.rs/).

## Profiler
Choose a profiler based on what you need to analyze:
- **CPU time distribution**: use [samply](#using-samply) (recommended). It profiles the rstest main process and worker forks together.
- **Memory allocation**: use `--heap-prof`. See [Node.js profiling](#nodejs-profiling). samply does not collect memory data.
- **Step-through debugging**: use `--inspect`. See [Node.js profiling](#nodejs-profiling).
## Using samply
> Note: In order to be able to profiling the Node.js side code in macOS, Node.js v22.16+ is required.
[Samply](https://github.com/mstange/samply) supports performance analysis for both Rstest main process and test process simultaneously. You can perform a complete performance analysis through the following steps:
Run the following command to start performance analysis:
```bash
samply record -- node --perf-prof --perf-basic-prof --interpreted-frames-native-stack {your_node_modules_folder}/@rstest/core/bin/rstest.js
```
After the command execution, the analysis results will automatically open in the [Firefox Profiler](https://profiler.firefox.com/).
Rstest’s JavaScript typically runs in the Node.js thread. Select the Node.js thread to view the time distribution on the Node.js side.

## Node.js profiling
You can also use Node.js built-in profiling tools to analyze Rstest's performance.
For example, you can use the `--heap-prof` flag to enable heap profiling:
```bash
node --heap-prof --heap-prof-dir=./heap-prof ./node_modules/@rstest/core/bin/rstest.js
```
The heap profile files will be generated in the `./heap-prof` directory. You can analyze these files using tools like [Visual Studio Code](https://code.visualstudio.com/docs/nodejs/profiling#_analyzing-a-profile) or [Chrome DevTools](https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots).
You can also use the `--inspect` flag to enable [Node.js inspector](https://nodejs.org/en/learn/getting-started/debugging).
```bash
node --inspect ./node_modules/@rstest/core/bin/rstest.js watch
```
---
url: /guide/integration/adapters.md
---
# Adapters
Adapters are a powerful feature that allows you to convert configurations from other tools (such as build tools or CLIs) into a format that Rstest supports. By creating a custom adapter, you can reuse existing configurations, avoid redundant definitions across multiple tools, and ensure project consistency.
This guide will walk you through the process of creating and using custom Rstest adapters.
## Why use adapters
- **Reuse Configuration**: Convert existing build tool configurations (e.g., path aliases, global variables) into test configurations to avoid redundant definitions.
- **Framework Customization**: Allow frameworks or templates to preset test configurations, such as adding specific `setup` scripts, changing default timeouts, or setting default test environments, thereby providing an out-of-the-box testing experience.
- **Simplify Maintenance**: Centralize configuration management, reduce the hassle of manually synchronizing configurations across different tools, and lower the chance of errors.
- **Quick Integration**: Through adapters, you can quickly integrate Rstest into existing projects and reuse existing project configurations.
## Core concepts
Rstest's configuration extension functionality primarily relies on the [`extends`](/config/test/extends.md) option. This option can accept an object or a function (`ExtendConfigFn`) that returns a configuration object (`ExtendConfig`) or a Promise resolving to such an object.
```typescript
// rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
// `extends` can be an object, a Promise, or a function
extends: async () => {
// Dynamically return configuration
return {
testTimeout: 5000,
};
},
});
```
An adapter is essentially a function that implements the `ExtendConfigFn` interface.
```typescript
// From @rstest/core/types/config.ts
export type ExtendConfig = Omit;
export type ExtendConfigFn = (
userConfig: Readonly,
) => MaybePromise;
```
The adapter function receives the user's current Rstest configuration as an argument and returns a new configuration object, which will be deeply merged with the user's configuration.
## Creating a custom adapter
Creating a custom adapter involves the following steps:
1. **Define an adapter function**: This function should conform to the `ExtendConfigFn` type signature.
2. **Load external configuration**: Inside the function, read and parse the configuration file of the tool you want to integrate (e.g., `my-build-tool.config.js`).
3. **Transform configuration**: Map the loaded configuration to the fields defined in the `ExtendConfig` interface. This is the core logic of the adapter, where you decide how to convert properties from the source configuration (e.g., `alias`, `define`) to the corresponding Rstest options (e.g., `resolve.alias`, `source.define`).
4. **Return configuration**: Return the transformed `ExtendConfig` object.
### Example: A simple adapter
Suppose you have a custom build configuration file `my.config.ts` with the following content:
```typescript
// my.config.ts
export default {
base: '/src',
aliases: {
'@': './utils',
},
globals: {
__VERSION__: '1.0.0',
},
};
```
You can create an adapter `my-adapter.ts` to read this file and convert it into an Rstest configuration.
```typescript
// my-adapter.ts
import { type ExtendConfigFn } from '@rstest/core';
import myConfig from './my.config'; // Import your configuration
export const withMyConfig: ExtendConfigFn = async (userConfig) => {
// 1. Read configuration (already done via import)
// 2. Transform configuration
const rstestConfig = {
root: myConfig.base, // `base` maps to `root`
resolve: {
alias: myConfig.aliases, // `aliases` maps to `resolve.alias`
},
source: {
define: myConfig.globals, // `globals` maps to `source.define`
},
};
// 3. Return the transformed configuration
return rstestConfig;
};
```
## Using your custom adapter
Now, you can use the adapter you created in your Rstest configuration file:
```typescript
// rstest.config.ts
import { defineConfig } from '@rstest/core';
import { withMyConfig } from './my-adapter';
export default defineConfig({
extends: withMyConfig,
// You can add or override other Rstest-specific configurations here
testTimeout: 8000,
});
```
When Rstest loads this configuration, it will:
1. Call the `withMyConfig` function.
2. Get the returned configuration object.
3. Deeply merge it with the inline configuration in `defineConfig`. Note that the user's local configuration (e.g., `testTimeout: 8000`) will take precedence over the configuration returned by `extends`.
## Advanced usage: adapters with options
Similar to `@rstest/adapter-rslib`, you can allow your adapter to accept an options object for more flexible integration.
```typescript
// my-adapter.ts
import { type ExtendConfigFn } from '@rstest/core';
import { load } from './loader'; // Assume a loader exists
export interface MyAdapterOptions {
configPath?: string;
profile?: 'web' | 'node';
}
export function withMyConfig(options: MyAdapterOptions = {}): ExtendConfigFn {
return async (userConfig) => {
const config = await load(options.configPath); // Load from specified path
const rstestConfig = {
// ...transformation logic
testEnvironment: options.profile === 'web' ? 'happy-dom' : 'node',
};
return rstestConfig;
};
}
```
Usage example:
```typescript
// rstest.config.ts
import { defineConfig } from '@rstest/core';
import { withMyConfig } from './my-adapter';
export default defineConfig({
extends: withMyConfig({ profile: 'web' }),
});
```
In this way, you can create powerful and reusable adapters that seamlessly integrate any toolchain with Rstest.
## Using official adapters
The following are the officially supported Rstest adapters:
- [Rslib Adapter](/guide/integration/rslib.md#install-adapter): Used to extend Rstest configuration from Rslib configuration.
- [Rsbuild Adapter](/guide/integration/rsbuild.md#install-adapter): Used to extend Rstest configuration from Rsbuild configuration.
- [Rspack Adapter](/guide/integration/rspack.md#install-adapter): Used to extend Rstest configuration from Rspack configuration.
---
url: /guide/integration/rslib.md
---
# Rslib
This guide covers how to integrate Rstest with [Rslib](https://rslib.rs) for seamless testing in your Rslib projects.
## Quick start
### New project
Create a new Rslib + Rstest project. Add the `--tools rstest` flag when creating:
```bash
npx create-rslib --template react-ts --tools rstest --dir my-project
```
The scaffold includes Rstest and demo tests. Run them with `npm run test`.
### Existing project
To add Rstest to an existing project, follow the [Quick Start](/guide/start/quick-start.md) to install and set up test scripts.
## Reuse Rslib config
[@rstest/adapter-rslib](https://www.npmjs.com/package/@rstest/adapter-rslib) is an official adapter that allows Rstest to automatically inherit test-relevant configuration from your existing Rslib config file. This keeps your test environment aligned with your build setup without carrying over options that only matter to build output.
### Install adapter
```sh [npm]
npm add @rstest/adapter-rslib -D
```
```sh [yarn]
yarn add @rstest/adapter-rslib -D
```
```sh [pnpm]
pnpm add @rstest/adapter-rslib -D
```
```sh [bun]
bun add @rstest/adapter-rslib -D
```
```sh [deno]
deno add npm:@rstest/adapter-rslib -D
```
### Extend your config
Using the `withRslibConfig` function from the adapter, you can extend your Rstest configuration from the Rslib config file.
```typescript
import { defineConfig } from '@rstest/core';
import { withRslibConfig } from '@rstest/adapter-rslib';
export default defineConfig({
extends: withRslibConfig(),
// Additional Rstest-specific configuration
});
```
This will automatically:
- Load your `rslib.config.ts` file
- Extract and map test-relevant Rslib options to Rstest configuration
- Merge with any additional Rstest config you provide
When `performance.buildCache` is enabled in your Rslib config, `withRslibConfig()` passes it through to Rstest and Rstest will still append its own cache defaults, such as runtime digest inputs and config-file-based invalidation.
If your Rslib config does not enable `performance.buildCache`, Rstest keeps it disabled by default. Enable it explicitly in your Rstest config when you want persistent cache for test builds:
```ts
import { defineConfig } from '@rstest/core';
import { withRslibConfig } from '@rstest/adapter-rslib';
export default defineConfig({
extends: withRslibConfig(),
performance: {
buildCache: true,
},
});
```
The adapter does not reuse the entire Rslib config as-is. It keeps the parts that matter for test execution, such as module resolution, transforms, CSS Modules, decorator-related settings, static asset handling, and `target`, and automatically drops unmapped fields like `dev`, `server`, and `html`, along with other options that only exist for dev server, page entry, or build output.
By default, the adapter uses `process.cwd()` to resolve the Rslib config. If your config lives elsewhere, you can use the `cwd` option. See [API](#api) for more details.
## API
### `withRslibConfig(options)`
Returns a configuration function that loads Rslib config and converts it to Rstest configuration.
#### `cwd`
- **Type:** `string`
- **Default:** `process.cwd()`
The `cwd` is the working directory to resolve the Rslib config file.
When your Rslib config is in a different directory or you are running tests in a monorepo (where your `process.cwd()` is not your config directory), you can specify the `cwd` option to resolve the Rslib config file from a different directory.
```ts
export default defineConfig({
extends: withRslibConfig({
cwd: './packages/my-lib',
}),
});
```
#### `configPath`
- **Type:** `string`
- **Default:** `'./rslib.config.ts'`
Path to rslib config file.
#### `libId`
- **Type:** `string`
- **Default:** `undefined`
The lib config id in `lib` field to use. Set to a string to use the lib config with matching id.
By default, the adapter uses the common configuration from Rslib. If your Rslib config has multiple lib configurations:
```ts
// rslib.config.ts
export default defineConfig({
lib: [
{
id: 'core',
format: 'esm',
dts: true,
source: {
define: {
IS_CORE: true,
},
},
},
{
id: 'utils',
format: 'esm',
source: {
define: {
IS_CORE: false,
},
},
},
],
// shared config
});
```
You can then reference specific lib configurations in your Rstest config. Rstest will adapt the Rslib shared configuration and the lib configuration with a matching `libId` to Rstest format.
This still does not copy the whole lib config verbatim. The adapter only takes the lib's `source`, `output`, `tools`, `plugins`, and `resolve` fields that are relevant to test execution, then merges them with the shared config before converting the result to Rstest format.
```ts
// For testing the 'core' environment
export default defineConfig({
extends: withRslibConfig({
libId: 'core',
}),
// core-specific test config
});
```
When you need to test multiple parts of your application with different configurations independently, you can define multiple Rstest projects. Each project can extend a specific lib configuration by setting the `libId` option.
```ts
export default defineConfig({
projects: [
{
extends: withRslibConfig({ libId: 'node' }),
include: ['tests/node/**/*.{test,spec}.?(c|m)[jt]s'],
},
{
extends: withRslibConfig({ libId: 'react' }),
include: ['tests/react/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
},
],
});
```
#### `modifyLibConfig`
- **Type:** `(config: RslibConfig) => RslibConfig | void`
- **Default:** `undefined`
Modify the Rslib config before it gets converted to Rstest config:
```ts
export default defineConfig({
extends: withRslibConfig({
modifyLibConfig: (libConfig) => {
delete libConfig.source?.define;
return libConfig;
},
}),
});
```
## Configuration mapping
The adapter automatically maps these Rslib options to Rstest:
Only the fields listed below are inherited. Rslib options that are not listed are ignored by default, which means test-irrelevant sections such as `dev`, `server`, and `html` are automatically pruned during conversion.
| Rslib option | Rstest equivalent | Notes |
| ---------------------------- | ------------------------ | ------------------------------------------------------ |
| `root` | `root` | Project root directory |
| `lib.id` selected by `libId` | `name` | Selected library identifier |
| `plugins` | `plugins` | Plugin configuration |
| `source.decorators` | `source.decorators` | Decorator support |
| `source.assetsInclude` | `source.assetsInclude` | Additional static asset patterns |
| `source.define` | `source.define` | Global constants |
| `source.include` | `source.include` | Source inclusion patterns |
| `source.exclude` | `source.exclude` | Source exclusion patterns |
| `source.transformImport` | `source.transformImport` | On-demand import transform rules |
| `source.tsconfigPath` | `source.tsconfigPath` | TypeScript config path |
| `resolve` | `resolve` | Module resolution |
| `output.cssModules` | `output.cssModules` | CSS modules configuration |
| `output.module` | `output.module` | Uses `output.module`, or falls back to `lib.format` |
| `tools.rspack` | `tools.rspack` | Rspack configuration |
| `tools.swc` | `tools.swc` | SWC configuration |
| `tools.bundlerChain` | `tools.bundlerChain` | Bundler chain configuration |
| `output.target` | `testEnvironment` | 'happy-dom' for web, 'node' for node and other targets |
## Debug config
To see the resolved configuration returned by the adapter, wrap it and log the result:
```typescript
export default defineConfig({
extends: async (user) => {
const config = await withRslibConfig({ libId: 'react' })(user);
console.log('Extended config:', JSON.stringify(config, null, 2));
return config;
},
});
```
## Related documentation
- [`@rstest/adapter-rslib` documentation](https://www.npmjs.com/package/@rstest/adapter-rslib)
- [Rslib configuration overview](https://rslib.rs/config)
- [Rstest configuration overview](/config/index.md)
---
url: /guide/integration/rsbuild.md
---
# Rsbuild
This guide covers how to integrate Rstest with [Rsbuild](https://rsbuild.dev) for seamless testing in your Rsbuild projects.
## Quick start
### New project
Create a new Rsbuild + Rstest project. Add the `--tools rstest` flag when creating:
```bash
npm create rsbuild@latest -- --tools rstest
```
The scaffold includes Rstest and demo tests. Run them with `npm run test`.
### Existing project
To add Rstest to an existing project, follow the [Quick Start](/guide/start/quick-start.md) to install and set up test scripts.
## Reuse Rsbuild config
[@rstest/adapter-rsbuild](https://www.npmjs.com/package/@rstest/adapter-rsbuild) is an official adapter that allows Rstest to automatically inherit test-relevant configuration from your existing Rsbuild config file. This keeps your test environment aligned with your build setup without carrying over options that only matter to dev servers or page output.
### Install adapter
```sh [npm]
npm add @rstest/adapter-rsbuild -D
```
```sh [yarn]
yarn add @rstest/adapter-rsbuild -D
```
```sh [pnpm]
pnpm add @rstest/adapter-rsbuild -D
```
```sh [bun]
bun add @rstest/adapter-rsbuild -D
```
```sh [deno]
deno add npm:@rstest/adapter-rsbuild -D
```
### Extend your config
Using the `withRsbuildConfig` function from the adapter, you can extend your Rstest configuration from the Rsbuild config file.
```typescript
import { defineConfig } from '@rstest/core';
import { withRsbuildConfig } from '@rstest/adapter-rsbuild';
export default defineConfig({
extends: withRsbuildConfig(),
// Additional Rstest-specific configuration
});
```
This will automatically:
- Load your `rsbuild.config.ts` file
- Extract and map test-relevant Rsbuild options to Rstest configuration
- Merge with any additional Rstest config you provide
The adapter does not reuse the entire Rsbuild config as-is. It keeps the parts that matter for test execution, such as module resolution, transforms, CSS Modules, static asset handling, and `target`, and automatically drops options like `dev`, `server`, and `html` that are specific to the dev server, page entry, or production output.
By default, the adapter uses `process.cwd()` to resolve the Rsbuild config. If your config lives elsewhere, you can use the `cwd` option. See [API](#api) for more details.
## API
### `withRsbuildConfig(options)`
Returns a configuration function that loads Rsbuild config and converts it to Rstest configuration.
#### `cwd`
- **Type:** `string`
- **Default:** `process.cwd()`
The `cwd` is passed to Rsbuild's `loadConfig` function. It's the working directory to resolve the Rsbuild config file.
When your Rsbuild config is in a different directory or you are running tests in a monorepo (where your `process.cwd()` is not your config directory), you can specify the `cwd` option to resolve the Rsbuild config file from a different directory.
```ts
export default defineConfig({
extends: withRsbuildConfig({
cwd: './packages/my-app',
}),
});
```
#### `configPath`
- **Type:** `string`
- **Default:** `'./rsbuild.config.ts'`
Path to rsbuild config file.
#### `environmentName`
- **Type:** `string`
- **Default:** `undefined`
The environment name in the `environments` field to use, which will be merged with the common config. Set to a string to use the environment config with a matching name.
By default, the adapter uses the common configuration from Rsbuild. If your Rsbuild config has multiple environment configurations:
```ts
// rsbuild.config.ts
export default {
source: {
define: {
'process.env.NODE_ENV': '"development"',
},
},
environments: {
test: {
source: {
define: {
'process.env.NODE_ENV': '"test"',
},
},
},
prod: {
source: {
define: {
'process.env.NODE_ENV': '"production"',
},
},
},
},
};
```
You can then reference specific environment configurations in your Rstest config. Rstest will adapt the Rsbuild shared configuration and the environment configuration with a matching `environmentName` to Rstest format.
```ts
// For testing the 'test' environment
export default defineConfig({
extends: withRsbuildConfig({
environmentName: 'test',
}),
// test-environment-specific config
});
```
When you need to test multiple parts of your application with different configurations independently, you can define multiple Rstest projects. Each project can extend a specific environment configuration by setting the `environmentName` option.
```ts
export default defineConfig({
projects: [
{
extends: withRsbuildConfig({ environmentName: 'node' }),
include: ['tests/node/**/*.{test,spec}.?(c|m)[jt]s'],
},
{
extends: withRsbuildConfig({ environmentName: 'react' }),
include: ['tests/react/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
},
],
});
```
#### `modifyRsbuildConfig`
- **Type:** `(config: RsbuildConfig) => RsbuildConfig | void`
- **Default:** `undefined`
Modify the Rsbuild config before it gets converted to Rstest config:
```ts
export default defineConfig({
extends: withRsbuildConfig({
modifyRsbuildConfig: (rsbuildConfig) => {
delete rsbuildConfig.source?.define;
return rsbuildConfig;
},
}),
});
```
## Configuration mapping
The adapter automatically maps these Rsbuild options to Rstest:
Only the fields listed below are inherited. Rsbuild options that are not listed are ignored by default, which means test-irrelevant sections such as `dev`, `server`, and `html` are automatically pruned during conversion.
| Rsbuild option | Rstest equivalent | Notes |
| ------------------------ | ------------------------ | ------------------------------------ |
| `root` | `root` | Project root directory |
| `name` from environment | `name` | Environment identifier |
| `plugins` | `plugins` | Plugin configuration |
| `source.decorators` | `source.decorators` | Decorator support |
| `source.assetsInclude` | `source.assetsInclude` | Additional static asset patterns |
| `source.define` | `source.define` | Global constants |
| `source.include` | `source.include` | Source inclusion patterns |
| `source.exclude` | `source.exclude` | Source exclusion patterns |
| `source.transformImport` | `source.transformImport` | On-demand import transform rules |
| `source.tsconfigPath` | `source.tsconfigPath` | TypeScript config path |
| `resolve` | `resolve` | Module resolution |
| `output.cssModules` | `output.cssModules` | CSS modules configuration |
| `output.emitAssets` | `output.emitAssets` | Emit imported static assets to disk |
| `output.module` | `output.module` | Output module type |
| `performance.buildCache` | `performance.buildCache` | Reused with rstest-aware defaults |
| `tools.rspack` | `tools.rspack` | Rspack configuration |
| `tools.swc` | `tools.swc` | SWC configuration |
| `tools.bundlerChain` | `tools.bundlerChain` | Bundler chain configuration |
| `output.target` | `testEnvironment` | 'happy-dom' for web, 'node' for node |
The adapter also removes the `rsbuild:type-check` plugin because type checking is not part of the test runtime pipeline.
## Debug config
To see the resolved configuration returned by the adapter, wrap it and log the result:
```ts
export default defineConfig({
extends: async (user) => {
const config = await withRsbuildConfig()(user);
console.log('Extended config:', JSON.stringify(config, null, 2));
return config;
},
});
```
## Related documentation
- [Rsbuild configuration overview](https://rsbuild.dev/config)
- [Rstest configuration overview](/config/index.md)
---
url: /guide/integration/rspack.md
---
# Rspack
This guide covers how to integrate Rstest with [Rspack](https://rspack.dev) for seamless testing in your Rspack projects.
## Quick start
To add Rstest to an existing Rspack project, follow the [Quick Start](/guide/start/quick-start.md) to install and set up test scripts.
## Reuse Rspack config
[@rstest/adapter-rspack](https://www.npmjs.com/package/@rstest/adapter-rspack) is an official adapter that allows Rstest to automatically inherit configuration from your existing Rspack config file. This ensures your test environment matches your build configuration without duplication.
### Install adapter
```sh [npm]
npm add @rstest/adapter-rspack -D
```
```sh [yarn]
yarn add @rstest/adapter-rspack -D
```
```sh [pnpm]
pnpm add @rstest/adapter-rspack -D
```
```sh [bun]
bun add @rstest/adapter-rspack -D
```
```sh [deno]
deno add npm:@rstest/adapter-rspack -D
```
### Extend your config
Using the `withRspackConfig` function from the adapter, you can extend your Rstest configuration from the Rspack config file.
```typescript
import { defineConfig } from '@rstest/core';
import { withRspackConfig } from '@rstest/adapter-rspack';
export default defineConfig({
extends: withRspackConfig(),
// Additional rstest-specific configuration
});
```
This will automatically:
- Load your `rspack.config.ts` file
- Map compatible Rspack options to Rstest configuration
- Merge with any additional Rstest config you provide
By default, the adapter uses `process.cwd()` to resolve the Rspack config. If your config lives elsewhere, you can use the `cwd` option. See [API](#api) for more details.
:::warning
Since Rstest uses Rsbuild internally as its bundler, some Rstest built-in behaviors may differ from Rspack's defaults. For example, Rstest has its own CSS processing pipeline. To avoid conflicts, this adapter automatically disables Rstest (Rsbuild) built-in CSS plugins so that Rspack's CSS configuration takes effect. This means that some Rsbuild-specific CSS features and configurations (e.g., `output.cssModules` configuration) will not be available when using this adapter.
:::
## API
### `withRspackConfig(options)`
Returns a configuration function that loads Rspack config and converts it to Rstest configuration.
#### `cwd`
- **Type:** `string`
- **Default:** `process.cwd()`
The working directory to resolve the Rspack config file.
When your Rspack config is in a different directory or you are running tests in a monorepo (where your `process.cwd()` is not your config directory), you can specify the `cwd` option to resolve the Rspack config file from a different directory.
```ts
export default defineConfig({
extends: withRspackConfig({
cwd: './packages/my-app',
}),
});
```
#### `configPath`
- **Type:** `string`
- **Default:** `'./rspack.config.ts'`
Path to rspack config file.
#### `configName`
- **Type:** `string`
- **Default:** `undefined`
Select a named configuration when using [multi-config](https://rspack.dev/config/index#configuration-type) in your Rspack config file. Set to a string to use the config with a matching `name` field.
If your Rspack config exports an array of configurations:
```ts
// rspack.config.ts
export default [
{
name: 'client',
target: 'web',
entry: './src/client.ts',
// ...
},
{
name: 'server',
target: 'node',
entry: './src/server.ts',
// ...
},
];
```
You can select a specific configuration in your Rstest config:
```ts
export default defineConfig({
extends: withRspackConfig({
configName: 'client',
}),
});
```
When you need to test multiple parts of your application with different configurations independently, you can define multiple Rstest projects:
```ts
export default defineConfig({
projects: [
{
extends: withRspackConfig({ configName: 'server' }),
include: ['tests/server/**/*.{test,spec}.?(c|m)[jt]s'],
},
{
extends: withRspackConfig({ configName: 'client' }),
include: ['tests/client/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
},
],
});
```
#### `env`
- **Type:** `Record | string[]`
- **Default:** `undefined`
Environment values passed to the Rspack config function. This corresponds to the `env` parameter in `rspack.config.ts` when exporting a function:
```ts
// rspack.config.ts
export default (env) => {
console.log(env); // receives the values from adapter
return {
/* ... */
};
};
```
#### `nodeEnv`
- **Type:** `string`
- **Default:** `undefined`
The `NODE_ENV` value used when loading the Rspack config.
#### `modifyRspackConfig`
- **Type:** `(config: RspackOptions) => RspackOptions`
- **Default:** `undefined`
Modify the Rspack config before it gets converted to Rstest config:
```ts
export default defineConfig({
extends: withRspackConfig({
modifyRspackConfig: (rspackConfig) => {
delete rspackConfig.resolve?.alias;
return rspackConfig;
},
}),
});
```
## Configuration mapping
The adapter automatically maps these Rspack options to Rstest:
| Rspack option | Rstest equivalent | Notes |
| ------------------ | --------------------- | ---------------------------------------------------------------------- |
| `name` | `name` | Configuration identifier |
| `resolve` | `resolve` | Module resolution (aliases, extensions, etc.) |
| `resolve.tsConfig` | `source.tsconfigPath` | TypeScript config path |
| `output.module` | `output.module` | Output module type |
| `target` | `testEnvironment` | `'node'`/`'async-node'` maps to `'node'`, others map to `'happy-dom'` |
| `module` | `tools.rspack` | Module rules (loaders) are merged via `tools.rspack` |
| `plugins` | `tools.rspack` | Plugins are merged via `tools.rspack` (`HtmlRspackPlugin` is excluded) |
| `experiments` | `tools.rspack` | Experiments config merged via `tools.rspack` |
| `optimization` | `tools.rspack` | Optimization config merged via `tools.rspack` |
| `devtool` | `tools.rspack` | Source map config applied via `tools.rspack` |
| `watchOptions` | `tools.rspack` | Watch options merged via `tools.rspack` |
| `snapshot` | `tools.rspack` | Snapshot config applied via `tools.rspack` |
| `externalsPresets` | `tools.rspack` | Externals presets merged via `tools.rspack` |
| `cache` | `tools.rspack` | Cache config applied via `tools.rspack` |
The following Rspack options are **not** mapped and will be ignored:
| Rspack option | Reason |
| ----------------- | -------------------------------------------- |
| `entry` | Rstest manages its own entry points |
| `output.path` | Rstest controls the output directory |
| `devServer` | Not applicable for testing |
| `lazyCompilation` | Automatically stripped (only for dev server) |
## Debug config
To see the resolved configuration returned by the adapter, wrap it and log the result:
```ts
export default defineConfig({
extends: async (user) => {
const config = await withRspackConfig()(user);
console.log('Extended config:', JSON.stringify(config, null, 2));
return config;
},
});
```
## Related documentation
- [Rspack configuration overview](https://rspack.dev/config)
- [Rstest configuration overview](/config/index.md)
---
url: /guide/migration/jest.md
---
# Migrating from Jest
Rstest is designed to be Jest-compatible, making migration from Jest projects straightforward. Here's how to migrate your Jest project to Rstest:
## Using Agent Skills
If you are using a Coding Agent that supports Skills, install the [migrate-to-rstest](https://github.com/rstackjs/agent-skills#migrate-to-rstest) skill to help with the upgrade process from Jest to Rstest.
```bash
npx skills add rstackjs/agent-skills --skill migrate-to-rstest
```
After installation, let the Coding Agent guide you through the upgrade.
## Installation and setup
First, you need to install Rstest as a development dependency.
```sh [npm]
npm add @rstest/core -D
```
```sh [yarn]
yarn add @rstest/core -D
```
```sh [pnpm]
pnpm add @rstest/core -D
```
```sh [bun]
bun add @rstest/core -D
```
```sh [deno]
deno add npm:@rstest/core -D
```
Next, update the test script in your `package.json` to use [rstest](/guide/basic/cli.md) instead of `jest`. For example:
```diff
"scripts": {
- "test": "jest"
+ "test": "rstest"
}
```
### CLI option mappings
Some Jest CLI flags map directly to Rstest, while others move into config. Use this table for the common option differences you are likely to hit during migration:
| Jest CLI option | Rstest equivalent | Notes |
| ---------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------- |
| `jest` | `rstest` | |
| `jest --watch` | `rstest --watch` or `rstest watch` | |
| `jest --watchAll` | `rstest --watch` or `rstest watch` | Rstest does not distinguish between `--watch` and `--watchAll`. |
| `jest --runInBand` | `rstest --pool.maxWorkers 1` | |
| `jest --maxWorkers=50%` or `jest -w 50%` | `rstest --pool.maxWorkers 50%` | Rstest's `-w` means `--watch`, not `--maxWorkers`. |
| `jest --selectProjects app` | `rstest --project app` | |
| `jest --env=jsdom` | `rstest --testEnvironment jsdom` | |
| `jest --coverage` | `rstest --coverage` | Also install `@rstest/coverage-istanbul`. |
| `jest --coverageDirectory=coverage` | `coverage.reportsDirectory` in `rstest.config.ts` | |
| `jest --coverageProvider=v8` | Remove | Only `'istanbul'` is supported — see the `coverageProvider` config row. |
## Configuration migration
Update your Jest config file (e.g., `jest.config.js` or `jest.config.ts`) to a `rstest.config.ts` file:
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
// Fill in by mapping fields from your jest.config.js — see the table below.
});
```
### Jest configuration mappings
When migrating, walk through **every** field in your `jest.config.js` and match it against the table below — map it, restructure it, or drop it. Fields not listed here may not map 1:1; verify against the [Rstest config reference](/config.md) before dropping them silently.
| Jest configuration | Rstest equivalent | Notes |
| ---------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `preset` (e.g. `'ts-jest'`) | Remove | Rstest uses swc by default; `ts-jest` is not needed. |
| `transform` | Remove | swc transforms `.ts` / `.tsx` / `.js` / `.jsx` out of the box. For custom Babel, see [Code transformation](#code-transformation). |
| `testEnvironment` | [`testEnvironment`](/config/test/test-environment.md) | |
| `testEnvironmentOptions` | [`testEnvironment.options`](/config/test/test-environment.md) | Fold into the object form: `testEnvironment: { name: 'jsdom', options: { ... } }`. |
| `testRegex` | [`include`](/config/test/include.md) | |
| `testMatch` | [`include`](/config/test/include.md) | Strip the `/` prefix from each pattern. |
| `testPathIgnorePatterns` | [`exclude`](/config/test/exclude.md) | Wrap bare `/name/` with `**`, e.g. `'/node_modules/'` → `'**/node_modules/**'`. |
| `transformIgnorePatterns` | [`output.bundleDependencies`](/config/build/output.md#outputbundledependencies) | Default differs by `testEnvironment`. For `'node'`: Rstest externalizes `node_modules` — drop most entries; for ESM packages where Jest needed an exception (e.g. `'node_modules/(?!(lodash-es)/)'`), use `output.bundleDependencies: ['lodash-es']` to bundle them through swc. For `'jsdom'` / `'happy-dom'`: `node_modules` is bundled by default — `transformIgnorePatterns` usually has no equivalent and can be dropped entirely. |
| `displayName` | [`name`](/config/test/name.md) | |
| `rootDir` | [`root`](/config/test/root.md) | |
| `setupFilesAfterEnv` | [`setupFiles`](/config/test/setup-files.md) | Rstest's `setupFiles` runs after the test framework is registered — equivalent to Jest's `setupFilesAfterEnv` (not Jest's plain `setupFiles`). Strip the `/` prefix. Merge any Jest `setupFiles` entries here too; there is no separate "before framework" hook. |
| `globalSetup` | [`globalSetup`](/config/test/global-setup.md) | Rstest calls setup with no arguments — if your Jest setup reads `(globalConfig, projectConfig)`, rewrite without them. As in Jest, `process.env` mutations made here propagate to every test worker. |
| `globalTeardown` | [`globalSetup`](/config/test/global-setup.md) | No separate field. Rewrite the file to `globalSetup`'s format (see above) — a bare `export default async function teardown()` from Jest would run at setup, not after tests. |
| `verbose` | [`reporters: 'verbose'`](/config/test/reporters.md) | No `verbose` boolean — use the `verbose` reporter. |
| `reporters` | [`reporters`](/config/test/reporters.md) | String values must be built-in names. For third-party reporters (e.g. `jest-junit`), import the reporter class and pass the instance. |
| `injectGlobals` | [`globals`](/config/test/globals.md) | Jest defaults to `true`; Rstest's `globals` defaults to `false`. If your tests rely on bare `describe` / `test` / `expect` without imports, set `globals: true` and add `@rstest/core/globals` to `compilerOptions.types` in `tsconfig.json`. |
| `moduleNameMapper` | [`resolve.alias`](/config/build/resolve.md#resolvealias) | `resolve.alias` is string-prefix only. Rewrite prefix mappings: `'^@/(.*)$': '/src/$1'` → `resolve: { alias: { '@': './src' } }`. For TypeScript projects, prefer `compilerOptions.paths` in `tsconfig.json` — Rstest reads it automatically. Regex/asset stubs (e.g. `'\\.(css\|svg)$': 'identity-obj-proxy'`) have no direct equivalent; most are unnecessary because Rstest handles CSS/assets natively. |
| `maxWorkers` | [`pool.maxWorkers`](/config/test/pool.md) | |
| `testTimeout` | [`testTimeout`](/config/test/test-timeout.md) | For per-test overrides, replace `jest.setTimeout(n)` with `rstest.setConfig({ testTimeout: n })`. |
| `slowTestThreshold` | [`slowTestThreshold`](/config/test/slow-test-threshold.md) | Jest measures in seconds (default 5); Rstest measures in milliseconds (default 300). Multiply Jest values by 1000. |
| `fakeTimers` | [`rstest.useFakeTimers`](/api/runtime-api/rstest/fake-timers.md#rstestusefaketimers) | No config field. Call `rstest.useFakeTimers(opts)` inside a `setupFiles` module or per-test; pair with `rstest.useRealTimers()` to restore. |
| `bail` | [`bail`](/config/test/bail.md) | |
| `clearMocks` | [`clearMocks`](/config/test/clear-mocks.md) | |
| `resetMocks` | [`resetMocks`](/config/test/reset-mocks.md) | |
| `restoreMocks` | [`restoreMocks`](/config/test/restore-mocks.md) | |
| `snapshotFormat` | [`snapshotFormat`](/config/test/snapshot-format.md) | |
| `snapshotResolver` | [`resolveSnapshotPath`](/config/test/resolve-snapshot-path.md) | Rstest takes a function, not a module path. |
| `snapshotSerializers` | [`expect.addSnapshotSerializer`](/api/runtime-api/test-api/expect.md#expectaddsnapshotserializer) | No config field. In a `setupFiles` module, import each serializer and call `expect.addSnapshotSerializer(serializer)`. |
| `cacheDirectory` | Remove | Not supported. Rstest manages build caching internally via Rsbuild. |
| `collectCoverage` | [`coverage.enabled`](/config/test/coverage.md#enabled) | |
| `collectCoverageFrom` | [`coverage.include`](/config/test/coverage.md#include) | |
| `coverageDirectory` | [`coverage.reportsDirectory`](/config/test/coverage.md#reportsdirectory) | |
| `coverageProvider` | Remove | Rstest only supports `'istanbul'` (the default), so there is nothing to configure — drop the field. `'v8'` / `'babel'` providers are not supported. |
| `coveragePathIgnorePatterns` | [`coverage.exclude`](/config/test/coverage.md#exclude) | |
| `coverageThreshold` | [`coverage.thresholds`](/config/test/coverage.md#thresholds) | |
| `projects` | [`projects`](/config/test/projects.md) | Jest's inline project shape differs — review before porting. |
For more details, please refer to the [Configuration](/config.md) section.
### Inject globals
Rstest does not mount the test APIs (e.g., `describe`, `expect`, `it`, `test`) to the global object by default, which is different from Jest.
If you want to continue using the global test APIs, you can enable the `globals` option in your `rstest.config.ts` file:
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
globals: true,
});
```
To enable TypeScript to properly recognize the global APIs, add the `@rstest/core/globals` type declaration in your `tsconfig.json`:
```ts title='tsconfig.json'
{
"compilerOptions": {
"types": ["@rstest/core/globals"]
}
}
```
### Code transformation
Rstest uses `swc` for code transformation by default, which is different from Jest's `babel-jest`. Most of the time, you don't need to change anything. And you can configure your swc options through [tools.swc](/config/build/tools.md#toolsswc).
```diff
export default {
- transform: {
- '^.+\\.(t|j)sx?$': ['@swc/jest', {}],
- },
+ tools: {
+ swc: {}
+ }
}
```
However, if you have custom Babel configurations or use specific Babel plugins/presets, you can add [Rsbuild's Babel Plugin](https://rsbuild.rs/plugins/list/plugin-babel):
```ts title='rstest.config.ts'
import { pluginBabel } from '@rsbuild/plugin-babel';
import { defineConfig } from '@rstest/core';
export default defineConfig({
plugins: [pluginBabel()],
});
```
## Environment variables
Rstest mirrors Jest's two main injected variables under different names:
| Jest | Rstest | Notes |
| ---------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `JEST_WORKER_ID` | `RSTEST_WORKER_ID` | Same semantics — a stringified integer, unique among workers active at the same time. Use it to isolate per-worker DBs / ports / temp dirs. See [`RSTEST_WORKER_ID`](/api/runtime-api/environment-variables.md#rstest_worker_id). |
| `NODE_ENV` | `NODE_ENV` | Both default to `'test'` when not set. |
Replace any references in tests, fixtures, or `setupFiles`:
```diff
- const dbName = `myapp_test_${process.env.JEST_WORKER_ID}`;
+ const dbName = `myapp_test_${process.env.RSTEST_WORKER_ID}`;
```
See [Environment variables](/api/runtime-api/environment-variables.md) for the full list.
## Update test API
### Test API
Your existing Jest test files should work with minimal changes since Rstest provides Jest-compatible APIs. Simply update your imports from Jest to Rstest:
```diff
- import { describe, expect, it, test } from '@jest/globals';
+ import { describe, expect, it, test } from '@rstest/core';
```
Rstest provides a `rstest` API that you can use to access Rstest's utilities, such as `rstest.fn()` and `rstest.mock()`. Just like Jest's `jest.fn()` and `jest.mock()`. More utilities can be found in the [Rstest APIs](/api/runtime-api/index.md).
```diff
- const fn = jest.fn();
+ const fn = rstest.fn();
fn.mockResolvedValue('foo');
```
### Done callback
The `done` callback is not supported in Rstest. Instead, you can return a Promise or use `async/await` for asynchronous tests.
```diff
- test('async test with done', (done) => {
+ test('async test with done', () => new Promise(done => {
// ...
done();
- });
+ }));
```
If you need to handle errors, you can modify it as follows:
```diff
- test('async test with done', (done) => {
+ test('async test with done', () => new Promise((resolve, reject) => {
+ const done = err => (err ? reject(err) : resolve());
// ...
done(error);
- });
+ }));
```
### Hooks
The return functions of the `beforeEach` and `beforeAll` hooks in Rstest are used to perform cleaning work after testing.
```diff
- beforeEach(() => doSomething());
+ beforeEach(() => { doSomething() });
```
### Timeout
If you used `jest.setTimeout()` to set the timeout for a test, you can use `rstest.setConfig()` instead.
```diff
- jest.setTimeout(5_000)
+ rstest.setConfig({ testTimeout: 5_000 })
```
## Snapshot format
Rstest snapshots use a different key format than Jest. Existing Jest snapshot files will not match Rstest's generated keys on the first run — the snapshot bodies are unchanged, only the key formatting differs.
### Key separator change
Jest joins the suite name, test name, and snapshot label with `:`. Rstest uses `>`:
```diff
- overlay should not show a warning when "client.overlay.warnings" is "false": page html 1
+ overlay > should not show a warning when "client.overlay.warnings" is "false" > page html 1
```
For the example above, the parts are:
1. `overlay` — suite name (`describe(...)`).
2. `should not show a warning when "client.overlay.warnings" is "false"` — test name (`it(...)` / `test(...)`).
3. `page html` — snapshot label from `.toMatchSnapshot('page html')`.
4. `1` — snapshot index for that label in the test.
### Updating snapshots
`rstest -u` re-records snapshots in Rstest's key format:
```bash
rstest -u # update everything
rstest overlay -u # update a filtered scope
```
### Reviewing the diff
Most snapshot churn after migration is non-functional:
- Separator-only key renames are formatting changes, not behavior changes.
- Key-order churn without any body difference is also formatting-only.
- Body content changes under the same key are the only signal of a real behavior change.
## ESM and CJS
Rstest supports ESM by default. If your project is using ESM, you don't need to perform any extra configuration, such as setting `NODE_OPTIONS=--experimental-vm-modules`.
If your project is using CommonJS, Rstest still works, but it is recommended to migrate to ESM for better performance and future compatibility.
### ESM vs CJS mocking
In Rstest, `rstest.mock()` targets ESM entries used by `import`, while `rstest.mockRequire()` targets CJS entries used by `require()`.
For code that uses `require()`, `rstest.mockRequire()` targets the CJS entry:
```ts
// Mocking a CJS module
rstest.mockRequire('./math.cjs', () => ({
sum: (a, b) => a + b + 100,
}));
```
---
url: /guide/migration/vitest.md
---
# Migrating from Vitest
If you are using the Rstack toolchain (Rsbuild / Rslib / Rspack, etc.), migrating to Rstest gives you a more consistent development experience.
## Using Agent Skills
If you are using a Coding Agent that supports Skills, install the [migrate-to-rstest](https://github.com/rstackjs/agent-skills#migrate-to-rstest) skill to help with the upgrade process from Vitest to Rstest.
```bash
npx skills add rstackjs/agent-skills --skill migrate-to-rstest
```
After installation, let the Coding Agent guide you through the upgrade.
## Installation and setup
First, you need to install Rstest as a development dependency.
```sh [npm]
npm add @rstest/core -D
```
```sh [yarn]
yarn add @rstest/core -D
```
```sh [pnpm]
pnpm add @rstest/core -D
```
```sh [bun]
bun add @rstest/core -D
```
```sh [deno]
deno add npm:@rstest/core -D
```
Next, update the test script in your `package.json` to use [rstest](/guide/basic/cli.md) instead of `vitest`. For example:
```diff
"scripts": {
- "test": "vitest run" // or "vitest --run"
+ "test": "rstest"
}
```
`rstest` does not have a `--run` flag. Running `rstest` already executes tests once and exits. If you want watch mode, use `--watch`:
```diff
"scripts": {
- "test": "vitest"
+ "test": "rstest --watch"
}
```
### CLI option mappings
Some Vitest CLI flags map directly to Rstest, while others change shape. Use this table for the common option differences you are likely to hit during migration:
| Vitest CLI option | Rstest equivalent | Notes |
| -------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------- |
| `vitest run` / `vitest --run` | `rstest` | No `--run` flag — `rstest` already runs once and exits. |
| `vitest` / `vitest watch` / `vitest --watch` | `rstest --watch` or `rstest watch` | Rstest does not auto-enable watch mode — pass `--watch` explicitly. |
| `vitest --coverage` | `rstest --coverage` | Also install `@rstest/coverage-istanbul` (see the `coverage` config row). |
| `vitest --environment=jsdom` | `rstest --testEnvironment jsdom` | |
| `vitest --reporter=verbose` | `rstest --reporter verbose` | |
| `vitest --globals` | `rstest --globals` | |
| `vitest -t ` / `--testNamePattern` | `rstest -t ` / `--testNamePattern` | |
| `vitest -u` / `--update` | `rstest -u` / `--update` | |
| `vitest -c ` / `--config` | `rstest -c ` / `--config` | |
| `vitest --project ` | `rstest --project ` | |
## Configuration migration
Update your Vitest config file (e.g., `vite.config.ts` or `vitest.config.ts`) to a `rstest.config.ts` file:
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
// Fill in by mapping fields from your vitest.config.ts — see the table below.
});
```
### Helper imports
Vitest's config-file helpers map to equivalents in `@rstest/core`:
| Vitest (`vitest/config`) | Rstest (`@rstest/core`) | Notes |
| ------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `defineConfig` | [`defineConfig`](/api/javascript-api/rstest-core.md#defineconfig) | |
| `defineProject` | [`defineProject`](/api/javascript-api/rstest-core.md#defineproject) | For items inside a `projects` array, prefer [`defineInlineProject`](/api/javascript-api/rstest-core.md#defineinlineproject) — it requires an explicit `name`. |
| `defineWorkspace` | Remove | No workspace helper. Declare projects inline via the `projects` field on `defineConfig` — see [Projects](/guide/basic/projects.md). |
| `mergeConfig` | [`mergeRstestConfig`](/api/javascript-api/rstest-core.md#mergerstestconfig) | Deep-merges and preserves function-valued fields. For composing configs inside a `projects` array, use [`mergeProjectConfig`](/api/javascript-api/rstest-core.md#mergeprojectconfig). |
### Vitest configuration mappings
When migrating config, keep two changes in mind:
- Remove the `test` field and move its nested properties to the top level.
- Rename keys when required (for example, `test.environment` → `testEnvironment`).
Walk through **every** option under `test` and match it against the table below — move it to the top level, rename it, or drop it. Fields not listed here may not map 1:1; verify against the [Rstest config reference](/config.md) before dropping them silently.
\| Vitest (under `test`) | Rstest (top-level) | Notes |
\| ----------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
\| `environment` | [`testEnvironment`](/config/test/test-environment.md) | Fold `test.environmentOptions` into the object form: `testEnvironment: { name: 'jsdom', options: { ... } }`. Custom environment packages are not supported. |
\| `include` / `exclude` | [`include`](/config/test/include.md) / [`exclude`](/config/test/exclude.md) | |
\| `includeSource` | [`includeSource`](/config/test/include-source.md) | |
\| `setupFiles` | [`setupFiles`](/config/test/setup-files.md) | |
\| `globalSetup` | [`globalSetup`](/config/test/global-setup.md) | Rstest calls setup with no arguments — if your Vitest setup reads the `TestProject` argument (e.g. `provide`, `onTestsRerun`), rewrite without it. Vitest's `provide` / `inject` has no direct Rstest equivalent — mutate `process.env` in the setup (Rstest snapshots it post-setup into every worker) or set static values via the [`env`](/config/test/env.md) config field. |
\| `globals` | [`globals`](/config/test/globals.md) | |
\| `name` | [`name`](/config/test/name.md) | |
\| `root` | [`root`](/config/test/root.md) | |
\| `env` | [`env`](/config/test/env.md) | |
\| `alias` | [`resolve.alias`](/config/build/resolve.md#resolvealias) | Not a `test.*` field in Rstest — move aliases to top-level `resolve.alias`. |
\| `passWithNoTests` | [`passWithNoTests`](/config/test/pass-with-no-tests.md) | |
\| `isolate` | [`isolate`](/config/test/isolate.md) | |
\| `testTimeout` / `hookTimeout` | [`testTimeout`](/config/test/test-timeout.md) / [`hookTimeout`](/config/test/hook-timeout.md) | |
\| `teardownTimeout` | Remove | No equivalent — Vitest's `teardownTimeout` is a shutdown-wait timeout, unrelated to Rstest's `hookTimeout` (which covers lifecycle hooks). |
\| `slowTestThreshold` | [`slowTestThreshold`](/config/test/slow-test-threshold.md) | |
\| `maxConcurrency` | [`maxConcurrency`](/config/test/max-concurrency.md) | |
\| `retry` | [`retry`](/config/test/retry.md) | |
\| `bail` | [`bail`](/config/test/bail.md) | |
\| `clearMocks` | [`clearMocks`](/config/test/clear-mocks.md) | |
\| `mockReset` | [`resetMocks`](/config/test/reset-mocks.md) | |
\| `restoreMocks` | [`restoreMocks`](/config/test/restore-mocks.md) | |
\| `poolOptions.forks.maxForks` | [`pool.maxWorkers`](/config/test/pool.md) | Rstest flattens `poolOptions` into top-level `pool`: `poolOptions.forks.maxForks` → `pool.maxWorkers`, `minForks` → `pool.minWorkers`, `execArgv` → `pool.execArgv`. Only the `forks` pool is supported — `pool: 'threads' \| 'vmThreads' \| 'vmForks'` settings are dropped (behavior reverts to forks). Vitest 4's top-level `test.maxWorkers` / `test.minWorkers` map to `pool.maxWorkers` / `pool.minWorkers`. |
\| `coverage` | [`coverage`](/config/test/coverage.md) | Only `provider: 'istanbul'` is supported — swap the dev dep from `@vitest/coverage-v8` to `@rstest/coverage-istanbul` (drop `'v8'` / `'custom'`). Rename `coverage.reporter` → `coverage.reporters` (singular is silently ignored). These sub-keys map 1:1: `include`, `exclude`, `reportsDirectory`, `thresholds`. V8-only keys (`all`, `skipFull`, `thresholdAutoUpdate`, `processingConcurrency`, `customProviderModule`, `watermarks`, `ignoreClassMethods`, etc.) have no equivalent. |
\| `reporters` | [`reporters`](/config/test/reporters.md) | Vitest-only (drop or replace): `tap`, `tap-flat`, `html`, `tree`, `hanging-process`. String values must be built-in names — for third-party reporters, import the class and pass the instance. |
\| `outputFile` | reporter options | No top-level field. For `junit` / `json`, pass `outputPath` via the reporter tuple: `['junit', { outputPath: '...' }]`. For `blob`, use `{ outputDir: '...' }`. Other reporters accept no output path. The object form `{ junit: 'a.xml', json: 'a.json' }` expands to one tuple per reporter. |
\| `snapshotFormat` | [`snapshotFormat`](/config/test/snapshot-format.md) | |
\| `resolveSnapshotPath` | [`resolveSnapshotPath`](/config/test/resolve-snapshot-path.md) | Rstest's signature is `(testPath, snapExtension) => string` — no third `context` argument from Vitest 3+. |
\| `snapshotSerializers` | [`expect.addSnapshotSerializer`](/api/runtime-api/test-api/expect.md#expectaddsnapshotserializer) | No config field. In a `setupFiles` module, import each serializer and call `expect.addSnapshotSerializer(serializer)`. |
\| `projects` | [`projects`](/config/test/projects.md) | |
\| `logHeapUsage` | [`logHeapUsage`](/config/test/log-heap-usage.md) | |
\| `includeTaskLocation` | [`includeTaskLocation`](/config/test/include-task-location.md) | |
\| `silent` | [`silent`](/config/test/silent.md) | Matches Vitest's `boolean | 'passed-only'` semantics for intercepted test console output. |
\| `printConsoleTrace` | [`printConsoleTrace`](/config/test/print-console-trace.md) | |
\| `unstubGlobals` | [`unstubGlobals`](/config/test/unstub-globals.md) | |
\| `unstubEnvs` | [`unstubEnvs`](/config/test/unstub-envs.md) | |
\| `chaiConfig` | [`chaiConfig`](/config/test/chai-config.md) | |
### Build configuration
Rstest uses Rsbuild as the default test build tool instead of Vite. You can view all available build configuration options in [Build Configurations](/config/index.md#build-configurations).
In most projects, these are the key build-side changes:
- Use `source.define` instead of `define`.
- Use `output.externals` instead of `ssr.external`.
- Use Rsbuild plugins instead of Vite plugins.
```diff
import { defineConfig } from '@rstest/core';
- import react from '@vitejs/plugin-react'
+ import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
- plugins: [react()],
- define: {
- __DEV__: true,
- },
+ plugins: [pluginReact()],
+ source: {
+ define: {
+ __DEV__: true,
+ },
+ },
});
```
If you are using Rslib or Rsbuild, you can directly use the corresponding adapter:
- For Rslib projects (with `rslib.config.*`), use `@rstest/adapter-rslib` with `extends: withRslibConfig()` (see [Rslib integration reference](/guide/integration/rslib.md)).
- For Rsbuild projects (with `rsbuild.config.*`), use `@rstest/adapter-rsbuild` with `extends: withRsbuildConfig()` (see [Rsbuild integration reference](/guide/integration/rsbuild.md)).
## Update test API
### Test API
Your existing Vitest test files should work with minimal changes since Rstest provides Vitest-compatible APIs. Update your imports from `vitest` to `@rstest/core`, and replace `vi` / `vitest` utilities with their `rs` / `rstest` equivalents:
```diff
- import { describe, expect, it, test, vi, type Mock } from 'vitest';
+ import { describe, expect, it, test, rs, type Mock } from '@rstest/core';
```
```diff
- vi.fn()
+ rs.fn()
- vi.mock('./foo')
+ rs.mock('./foo')
- vi.spyOn(console, 'error')
+ rs.spyOn(console, 'error')
```
```diff
- vitest.fn()
+ rs.fn()
```
For the full utility API list, see [Rstest APIs](/api/runtime-api/index.md).
### Global APIs
When `globals: true` is enabled, `vi` and `vitest` are available as globals in Vitest. In Rstest, use this mapping order:
- `vi.` → `rs.`
- `vitest.` → `rstest.`
`rs` and `rstest` are equivalent global aliases, but keeping this one-to-one mapping is easier to read during migration.
```diff
- vi.fn()
+ rs.fn()
- vitest.spyOn(console, 'error')
+ rstest.spyOn(console, 'error')
```
If your tests import APIs from `@rstest/core`, prefer `rs.` in import style and avoid mixing import style and global style in the same file.
### Setup adapters
Some setup adapters are Vitest-specific. For example, `@testing-library/jest-dom/vitest` is designed for Vitest; in Rstest, register the matchers directly via `expect.extend`.
```diff
- import '@testing-library/jest-dom/vitest';
+ import * as jestDomMatchers from '@testing-library/jest-dom/matchers';
+ import { expect } from '@rstest/core';
+
+ expect.extend(jestDomMatchers);
```
### Path resolution
Depending on your transform/runtime mode, `new URL(..., import.meta.url)` may fail in setup or helper files.
If you see path errors such as `Cannot find module './'` or `Cannot find module '..'`, prefer Node-style path resolution with `__dirname`:
```diff
- const root = fileURLToPath(new URL('../..', import.meta.url));
+ import { resolve } from 'node:path';
+ const root = resolve(__dirname, '../..');
```
### Auto-mocking modules
In Vitest, calling `vi.mock()` with just the module path first attempts to load a manual mock from the corresponding `__mocks__` directory. If no manual mock is found, it automatically mocks the module, replacing all its exports with empty mock functions.
```ts
// Vitest
import { vi, test, expect } from 'vitest';
import { someFunction } from './module';
// Looks for __mocks__/module.js first, then auto-mocks.
vi.mock('./module');
test('should be mocked', () => {
expect(vi.isMockFunction(someFunction)).toBe(true);
someFunction(); // returns undefined
});
```
Rstest handles this differently. Calling `rs.mock()` with only the module path will _only_ look for a mock in the `__mocks__` directory and will throw an error if one isn't found. Auto-mocking requires explicitly passing the `{ mock: true }` option.
```diff
// Rstest
import { rs, test, expect } from '@rstest/core';
import { someFunction } from './module';
- // Looks for __mocks__/module.js first, then auto-mocks.
- vi.mock('./module');
+ // Auto-mocks the module because { mock: true } is passed.
+ rs.mock('./module', { mock: true });
test('should be mocked', () => {
expect(rs.isMockFunction(someFunction)).toBe(true);
someFunction(); // returns undefined
});
```
### Mock async modules
When you need to mock a module's return value, Rstest does not support returning an async function.
As an alternative, Rstest provides synchronous [importActual](/api/runtime-api/rstest/mock-modules.md#rsimportactual) capability, allowing you to import the unmocked module implementation through static import statements:
```ts
import * as apiActual from './api' with { rstest: 'importActual' };
// Partially mock the './api' module
rs.mock('./api', () => ({
...apiActual,
fetchUser: rs.fn().mockResolvedValue({ id: 'mocked' }),
}));
```
Because mock factories are hoisted, avoid relying on values initialized later in the same module. If needed, move shared values to a hoisted initializer (for example `rs.hoisted(...)`) to avoid initialization-order errors.
## Snapshots
Vitest and Rstest use the same snapshot key format and body serializer. Existing `__snapshots__/*.snap` files from a Vitest suite are read verbatim by Rstest, and tests that passed under Vitest continue to pass under Rstest without re-recording. Only the file header line differs:
```diff
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+ // Rstest Snapshot v1
```
Running `rstest -u` normalizes this header to the Rstest form; the snapshot bodies stay byte-identical.
---
url: /guide/start/agent-install.md
---
# Rstest setup — agent execution prompt
Set up Rstest in a project that doesn't have a test runner configured yet. Pick the target, detect what it needs, install, verify.
## Prerequisite check
If the target already has Jest or Vitest configured (any of the signals below), **STOP this prompt and fetch [https://rstest.rs/guide/start/agent-migrate.md](https://rstest.rs/guide/start/agent-migrate.md) instead** — migration has its own flow:
- A `jest.config.{js,ts,cjs,mjs,cts,mts,json}` or `vitest.config.{js,ts,cjs,mjs,cts,mts,json}`.
- A `jest` / `@jest/globals` / `ts-jest` / `vitest` dep, or an inline `"jest"` / `"vitest"` field in `package.json`.
- A `test` script invoking `jest` / `vitest`.
- A workspace root with hoisted Jest/Vitest deps or config that affect this target.
- The user asked to migrate from Jest/Vitest, or to keep Jest/Vitest and add Rstest side-by-side.
Otherwise, continue below.
## Section 1 — pick the target
All work in this prompt happens inside a single **target package**.
**Single-package repo** → the target is the repo root.
**Monorepo** (signals: `pnpm-workspace.yaml`, `workspaces` in root `package.json`, `turbo.json`, `nx.json`):
- If the user named a specific package (by path, package name, or source file), that package is the target.
- If the user expressed repo-wide scope ("this monorepo", "the whole repo", "every package", "all packages") **and** at least one package has Jest/Vitest signals, **STOP this prompt and fetch [https://rstest.rs/guide/start/agent-migrate.md](https://rstest.rs/guide/start/agent-migrate.md) instead** — that flow iterates per package and handles heterogeneity.
- Otherwise, **STOP this prompt and ask**:
> This is a monorepo. Rstest setup is applied to one package at a time. Which package should Rstest go in — the workspace root, or a specific package (e.g. `packages/foo`)?
## Section 2 — detect
All detection below runs inside the target.
### 2.1 Language
Determine from the target's **actual source/test file extensions** first. Only fall back to `tsconfig.json` presence or `typescript` in deps when extensions are mixed or absent.
- TypeScript → `.ts` / `.tsx` test files.
- JavaScript → `.js` / `.jsx` test files.
### 2.2 Build tool (→ pick adapter)
- **Rsbuild project** (`rsbuild.config.*` or `@rsbuild/core` dep) → follow [https://rstest.rs/guide/integration/rsbuild.md](https://rstest.rs/guide/integration/rsbuild.md)
- **Rslib project** (`rslib.config.*` or `@rslib/core` dep) → follow [https://rstest.rs/guide/integration/rslib.md](https://rstest.rs/guide/integration/rslib.md)
- **Neither** → configure Rstest standalone.
An adapter auto-inherits plugins, aliases, and build config. Even with an adapter you still need to decide `testEnvironment` or browser mode from 2.3 — adapters do not decide those. If 2.3 picks Browser Mode, that takes precedence over adapters.
### 2.3 Test environment
Decide based on what the target's tests actually need, not solely from framework deps in `package.json`. See [https://rstest.rs/config/test/test-environment.md](https://rstest.rs/config/test/test-environment.md) for all options.
#### A. No DOM APIs needed → node (default)
Omit `testEnvironment`. No extra deps. If not using an adapter and no other options are needed:
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({});
```
#### B. DOM APIs needed, simulated DOM is sufficient → `happy-dom` / `jsdom`
Pick B only if any of these hold for the target's source/test code:
- Imports JSX or a UI framework (`react`, `react-dom`, `vue`, `@vue/*`, `svelte`, `solid-js`, `preact`, `lit`).
- Directly references `document`, `window`, `navigator`, `localStorage`, or other DOM globals.
- The user's request explicitly mentions component/UI testing.
If none of the above hold, stay on A. Seeing `react` in `dependencies` alone is not sufficient.
- If the target already depends on `jsdom` or `happy-dom`, follow that choice. Otherwise default to `happy-dom` (lighter).
- **React** → follow [https://rstest.rs/guide/framework/react.md](https://rstest.rs/guide/framework/react.md)
- **Vue** → follow [https://rstest.rs/guide/framework/vue.md](https://rstest.rs/guide/framework/vue.md)
- Other frameworks (Svelte, Solid, Preact, Lit, etc.) → apply the same DOM-environment reasoning and install the framework's standard compiler/plugin if the adapter doesn't already cover it. Keep config minimal.
#### C. Real browser behavior needed → browser mode (experimental)
Choose **only** when tests explicitly require Canvas, WebGL, CSS computed styles, Web Workers, or cross-browser testing. This is opt-in, not a default escalation. Vue Browser Mode is **not yet supported**. See [https://rstest.rs/config/test/browser.md](https://rstest.rs/config/test/browser.md).
→ **STOP this prompt.** Fetch [https://rstest.rs/guide/browser-testing/getting-started.md](https://rstest.rs/guide/browser-testing/getting-started.md) and follow it end-to-end — Browser Mode has its own setup flow that replaces Sections 3–4. For React component testing in browser, also fetch [https://rstest.rs/guide/browser-testing/framework-guides.md](https://rstest.rs/guide/browser-testing/framework-guides.md).
## Section 3 — install and configure
All edits are scoped to the target package. If the target already has `@rstest/core`, `rstest.config.ts`, or an `rstest` script, extend it minimally instead of recreating it.
- Always install `@rstest/core`. Add any framework plugin / DOM environment justified in Section 2. Use the repo's package manager (pnpm: `pnpm --filter add -D `; npm: `npm i -D -w `; yarn: `yarn workspace add -D `; bun: `bun add -d --filter `). For a single-package repo, drop the workspace flag.
- Config file is always `rstest.config.ts` (even in JS projects), placed in the target directory. Keep it minimal — include only options justified by Section 2. Do not copy examples blindly; see [https://rstest.rs/guide/basic/configure-rstest.md](https://rstest.rs/guide/basic/configure-rstest.md) only if the guides linked from Section 2 don't fully specify the config.
- Add `"test": "rstest"` to the target's `package.json`. Preserve existing naming conventions.
## Section 4 — verify
- Run the target's test command from wherever the target's test script lives. If it fails, read the error and fix — do not leave a broken setup.
- If the target has **zero** test files, add one small real test before running:
- Prefer a real exported function/module over a UI component (to avoid extra test utilities).
- Match existing naming (`*.test.*` vs `*.spec.*`), placement, and language. If no convention exists, colocate next to the source file.
- Do not write placeholder tests (e.g. `test('placeholder', () => {})`). If no small deterministic export exists, skip the test and rely on `rstest --passWithNoTests`.
---
url: /guide/start/agent-migrate.md
---
# Migrate to Rstest
## Goal
Migrate Jest- or Vitest-based tests and configuration to Rstest with minimal behavior changes.
## Migration principles (must follow)
1. **Smallest-change-first**: prefer the smallest viable change that restores test pass.
2. **Config before code**: prefer fixing in config/tooling/mocks before touching test logic.
3. **Do not change user source behavior**: avoid modifying production/business source files unless user explicitly requests it.
4. **Avoid bulk test rewrites**: do not refactor entire test suites when a local compatibility patch can solve it.
5. **Preserve test intent**: keep assertions and scenario coverage unchanged unless clearly broken by framework differences.
6. **Two-phase legacy-runner lifecycle (per migrated scope)**: (a) While the scope being migrated is not green on Rstest, keep every Jest/Vitest dep + config/setup/workspace file untouched — they are your rollback path. (b) Once that scope is green, delete its scope-local legacy config/setup files in the same commit. Drop the shared legacy devDeps (`jest`, `vitest`, adapters, environments) only when no other scope still relies on them — in a partial / mixed-mode monorepo migration that means the final scope, not the first. Leaving files behind "for reference" creates two source-of-truth configs. Framework-specific file enumeration: see the deltas reference.
7. **Literal API substitution, no shims**: rewrite every `vi.` / `jest.` / `vitest.` call site — no global aliasing, no local rebinding, no aliased imports. Full forbidden-form list and reasoning in `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/global-api-migration.md`.
8. **Replace on call sites, not strings**: match only identifiers preceding `(`; after every batch edit, grep `describe\(|it\(|test\(` to confirm no test name string was mutated. Regex template and rationale in `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/global-api-migration.md`.
9. **Coverage thresholds are not negotiable**: never lower `coverage.thresholds` (lines/functions/branches/statements) to make a migrated suite pass. If thresholds fail under Rstest, investigate `coverage.include` / `exclude` / provider wiring before touching the numbers.
## Workflow
1. Detect current test framework (`https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/detect-test-framework.md`)
2. Dependency install gate (blocker check, see `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/dependency-install-gate.md`)
3. Open the framework-specific deltas file and the official migration guide it points to. Prefer the `.md` URL form when fetching — Rstest pages provide Markdown variants that are more AI-friendly.
- Jest: `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/jest-migration-deltas.md`
- Vitest: `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/vitest-migration-deltas.md`
- Global API replacement rules: `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/global-api-migration.md`
4. Apply the mapping from the official guide + the skill-side enforcement rules from the deltas file
5. Check type errors
6. Run tests and fix deltas
7. Apply cleanup phase of principle 6 once the migrated scope is green (delete scope-local legacy config/setup in the same commit; drop shared legacy devDeps only when no other scope still relies on them; framework-specific file list is in the deltas file)
8. Summarize changes
## Detect current test framework
See `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/detect-test-framework.md` for detection signals and the mixed-mode scope policy.
## Dependency install gate (blocker check)
Before large-scale edits, verify dependencies can be installed and test runner binaries are available. Detailed checks, blocked-mode output format, and `ni` policy are in `https://raw.githubusercontent.com/rstackjs/agent-skills/main/skills/migrate-to-rstest/references/dependency-install-gate.md`.
## Patch scope policy (strict)
### Preferred change order
1. CLI/script/config migration (`package.json`, `rstest.config.ts`, include/exclude, test environment).
2. Test setup adapter migration (for example `@testing-library/jest-dom/vitest` to matcher-based setup in Rstest).
3. Mock compatibility adjustments (target module path, `{ mock: true }`, `importActual`).
4. Narrow per-test setup fixes (single-file, single-suite level).
5. Path resolution compatibility fixes (`import.meta.url` vs `__dirname`) in test/setup helpers.
6. As a last resort, test body changes.
7. Never modify runtime source logic by default.
### Red lines
Principles 6–9 above are themselves red lines — the bullets below cover the scope / intent red lines not captured there:
- Do not rewrite many tests in one sweep without first proving config-level fixes are insufficient.
- Do not alter business/runtime behavior to satisfy tests.
- Do not change assertion semantics just to make tests pass.
- Do not broaden migration to unrelated packages in a monorepo.
### Escalation rule for large edits
If a fix would require either:
- editing many test files, or
- changing user source files,
stop and provide:
1. why minimal fixes failed,
2. proposed large-change options,
3. expected impact/risk per option,
4. recommended option.
## Run tests and fix deltas
- Run the test suite and fix failures iteratively.
- Fix configuration and resolver errors first, then address mocks/timers/snapshots, and touch test logic last.
- If mocks fail for re-exported modules under Rspack, first check whether the project is pinned to `rstest < 0.9.3` (fixed in 0.9.3 — upgrade before debugging mock behavior further).
## Summarize changes
- Provide a concise change summary and list files touched.
- Call out any remaining manual steps or TODOs.
---
url: /config/index.md
---
# Config overview
This page lists all the configurations for Rstest. See ["Configure Rstest"](/guide/basic/configure-rstest.md) for details.
## Test configurations
### **basic**
- [root](/config/test/root.md)
- [name](/config/test/name.md)
- [include](/config/test/include.md)
- [exclude](/config/test/exclude.md)
- [setupFiles](/config/test/setup-files.md)
- [globalSetup](/config/test/global-setup.md)
- [projects](/config/test/projects.md)
- [passWithNoTests](/config/test/pass-with-no-tests.md)
- [includeSource](/config/test/include-source.md)
- [testNamePattern](/config/test/test-name-pattern.md)
- [extends](/config/test/extends.md)
### **runtime**
- [globals](/config/test/globals.md)
- [env](/config/test/env.md)
- [bail](/config/test/bail.md)
- [retry](/config/test/retry.md)
- [testTimeout](/config/test/test-timeout.md)
- [hookTimeout](/config/test/hook-timeout.md)
- [maxConcurrency](/config/test/max-concurrency.md)
### **mock**
- [clearMocks](/config/test/clear-mocks.md)
- [resetMocks](/config/test/reset-mocks.md)
- [restoreMocks](/config/test/restore-mocks.md)
- [unstubEnvs](/config/test/unstub-envs.md)
- [unstubGlobals](/config/test/unstub-globals.md)
### **environment**
- [pool](/config/test/pool.md)
- [isolate](/config/test/isolate.md)
- [testEnvironment](/config/test/test-environment.md)
### **browser**
- [enabled](/config/test/browser.md#enabled)
- [provider](/config/test/browser.md#provider)
- [browser](/config/test/browser.md#browser)
- [headless](/config/test/browser.md#headless)
- [port](/config/test/browser.md#port)
- [providerOptions](/config/test/browser.md#provider-options)
### **snapshot**
- [update](/config/test/update.md)
- [snapshotFormat](/config/test/snapshot-format.md)
- [resolveSnapshotPath](/config/test/resolve-snapshot-path.md)
### **output**
- [coverage](/config/test/coverage.md)
- [reporters](/config/test/reporters.md)
- [silent](/config/test/silent.md)
- [includeTaskLocation](/config/test/include-task-location.md)
- [logHeapUsage](/config/test/log-heap-usage.md)
- [hideSkippedTests](/config/test/hide-skipped-tests.md)
- [hideSkippedTestFiles](/config/test/hide-skipped-test-files.md)
- [slowTestThreshold](/config/test/slow-test-threshold.md)
- [chaiConfig](/config/test/chai-config.md)
- [onConsoleLog](/config/test/on-console-log.md)
- [printConsoleTrace](/config/test/print-console-trace.md)
- [disableConsoleIntercept](/config/test/disable-console-intercept.md)
## Build configurations
### **top level**
- [plugins](/config/build/plugins.md)
### [source](/config/build/source.md)
- [source.decorators](/config/build/source.md#sourcedecorators)
- [source.transformImport](/config/build/source.md#sourcetransformimport)
- [source.assetsInclude](/config/build/source.md#sourceassetsinclude)
- [source.define](/config/build/source.md#sourcedefine)
- [source.exclude](/config/build/source.md#sourceexclude)
- [source.include](/config/build/source.md#sourceinclude)
- [source.tsconfigPath](/config/build/source.md#sourcetsconfigpath)
### [output](/config/build/output.md)
- [output.module](/config/build/output.md#outputmodule)
- [output.externals](/config/build/output.md#outputexternals)
- [output.bundleDependencies](/config/build/output.md#outputbundledependencies)
- [output.cssModules](/config/build/output.md#outputcssmodules)
- [output.emitAssets](/config/build/output.md#outputemitassets)
- [output.cleanDistPath](/config/build/output.md#outputcleandistpath)
- [output.distPath](/config/build/output.md#outputdistpath)
### [resolve](/config/build/resolve.md)
- [resolve.aliasStrategy](/config/build/resolve.md#resolvealiasstrategy)
- [resolve.alias](/config/build/resolve.md#resolvealias)
- [resolve.dedupe](/config/build/resolve.md#resolvededupe)
- [resolve.extensions](/config/build/resolve.md#resolveextensions)
- [resolve.conditionNames](/config/build/resolve.md#resolveconditionnames)
- [resolve.mainFields](/config/build/resolve.md#resolvemainfields)
### [tools](/config/build/tools.md)
- [tools.bundlerChain](/config/build/tools.md#toolsbundlerchain)
- [tools.rspack](/config/build/tools.md#toolsrspack)
- [tools.swc](/config/build/tools.md#toolsswc)
### [dev](/config/build/dev.md)
- [dev.writeToDisk](/config/build/dev.md#devwritetodisk)
### [performance](/config/build/performance.md)
- [performance.buildCache](/config/build/performance.md#performancebuildcache)
---
url: /config/test/root.md
---
# root
- **Type:** `string`
- **Default:** [process.cwd()](https://nodejs.org/api/process.html#processcwd)
- **CLI:** `-r=`, `--root=`
Specify the project root directory. Can be an absolute path, or a path relative to `process.cwd()`. `root` determines the starting point for loading configuration files, searching test files, etc.
Using `` in any path-based configuration settings will reference this value.
```ts title="rstest.config.ts"
import { defineConfig } from 'rstest';
export default defineConfig({
setupFiles: ['/setup.js'],
include: ['/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
root: './my-project',
});
```
---
url: /config/test/name.md
---
# name
- **Type:** `string`
- **Default:** `rstest`
The name of the test project.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
name: 'node',
});
```
When you define multiple test projects via [projects](/config/test/projects.md), it's recommended to give each project a unique name so that you can filter specific projects using the [--project](/guide/basic/test-filter.md#filter-by-project-name) option.
If a project's name is not provided, Rstest will use the `name` field from the current project's `package.json`. If it does not exist, the folder name will be used instead.
---
url: /config/test/include.md
---
# include
- **Type:** `string[]`
- **Default:** `['**/*.{test,spec}.?(c|m)[jt]s?(x)']`
- **CLI:** `--include '**/*.test.ts' --include '**/*.spec.ts'`
A list of glob patterns that match your test files. These patterns will be resolved relative to the [root](/config/test/root.md) (`process.cwd()` by default).
**CLI**
```bash
npx rstest --include '**/*.{test,spec}.?(c|m)[jt]s?(x)'
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
});
```
You can run `npx rstest list --filesOnly` to see the list of test files that Rstest will include.
```bash
$ npx rstest list --filesOnly
# the output is shown below:
a.test.ts
b.test.ts
```
## Default behavior
By default, Rstest will include all javascript / typescript files with `.test.` or `.spec.` suffix in the filename.
The following is a visualization of the default pattern:
```bash
├── __tests__
│ └── index.test.js # test
│ └── App.spec.tsx # test
│ └── helper.ts # not test
├── src
│ └── component.ts # not test
│ └── component.test.tsx # test
├── bar.spec.jsx # test
├── index.test.js # test
└── index.js # not test
```
It should be noted that if you specify `index.test.ts` as the include pattern, Rstest will only include `index.test.ts` in the root directory. If you want to include `index.test.ts` in all subdirectories, you need to use `**/index.test.ts`.
```diff
import { defineConfig } from '@rstest/core';
export default defineConfig({
- include: ['index.test.ts'],
+ include: ['**/index.test.ts'],
});
```
It should be noted that for a single extension you should write `**/*.ts` instead of `**/*.{ts}`. The underlying glob libraries treat single-value braces literally, so `**/*.{ts}` will not match `.ts` files.
---
url: /config/test/exclude.md
---
# exclude
- **Type:** `string[] | { patterns: string[]; override?: boolean }`
- **Default:** `['**/node_modules/**', '**/dist/**', '**/.{idea,git,cache,output,temp}/**']`
- **CLI:** `--exclude "**/node_modules/**"`
A list of glob patterns that should be excluded from your test files.
## Example
Exclude the test files under `node_modules`:
**CLI**
```bash
npx rstest --exclude "**/node_modules/**"
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
exclude: ['**/node_modules/**'],
});
```
## Override default value
By default, Rstest will merge your custom config with the default config. If you want to override the default `exclude` configuration, you can set `override` to `true`.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
exclude: {
patterns: ['**/node_modules/**'],
override: true,
},
});
```
---
url: /config/test/setup-files.md
---
# setupFiles
- **Type:** `string[]`
- **Default:** `[]`
A list of paths to modules that run some code to configure or set up the testing environment, they will be run before each test file.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
setupFiles: ['./scripts/rstest.setup.ts'],
});
```
---
url: /config/test/global-setup.md
---
# globalSetup
- **Type:** `string | string[]`
- **Default:** `undefined`
The `globalSetup` option in Rstest allows you to run setup and teardown code that executes once before all tests and after all tests complete. This is useful for:
- Starting and stopping databases
- Initializing test services
- Cleaning up resources after test runs
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
globalSetup: ['./global-setup.ts'],
// or multiple files
globalSetup: ['./setup-db.ts', './setup-server.ts'],
// your other config...
});
```
## Global setup file formats
You can write global setup files in two formats:
### Named functions (recommended)
```ts
// global-setup.ts
export async function setup() {
console.log('Setting up test environment...');
// Initialize database, start services, etc.
// await startDatabase();
}
export async function teardown() {
console.log('Tearing down test environment...');
// Cleanup resources
// await stopDatabase();
}
```
### Default function returning teardown
```ts
// global-setup.ts
export default async function globalSetup() {
console.log('Setting up test environment...');
// Initialize resources
const database = await connectDatabase();
const server = await startTestServer();
// Return teardown function
return async () => {
console.log('Cleaning up resources...');
await server.stop();
await database.disconnect();
};
}
```
:::note
Global setup runs in a separate context: module-scope variables defined in the setup file are not accessible from tests. However, for node test workers, `process.env` mutations made in `globalSetup` are snapshotted after setup completes and propagated to each worker — setting environment variables here is a supported way to share values with node tests. This propagation does not apply to browser-mode tests; only the static `test.env` config is forwarded to browser workers.
:::
## Multiple global setup files
When using multiple global setup files:
- Setup functions execute sequentially in the order provided
- Teardown functions execute in reverse order (LIFO - Last In, First Out)
- If any setup fails, the entire test run fails
## Differences from setupFiles
| Feature | globalSetup | setupFiles |
| --------- | ------------------------------------- | ------------------------- |
| Execution | Once before all tests | Before each test file |
| Teardown | Supported | Not supported |
| Use Case | Global resources, databases, services | Per-test utilities, mocks |
## Example: database setup
```ts
// db-setup.ts
let dbConnection: any;
export async function setup() {
const { MongoClient } = await import('mongodb');
const client = new MongoClient(process.env.TEST_MONGODB_URI!);
await client.connect();
dbConnection = client.db('test');
// Seed test data
await dbConnection.collection('users').insertMany([
{ name: 'John', email: 'john@example.com' },
{ name: 'Jane', email: 'jane@example.com' },
]);
console.log('✓ Database connected and seeded');
}
export async function teardown() {
if (dbConnection) {
await dbConnection.dropDatabase();
await dbConnection.client.close();
console.log('✓ Database cleaned up');
}
}
```
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
globalSetup: ['./db-setup.ts'],
include: ['**/*.test.ts'],
});
```
---
url: /config/test/projects.md
---
# projects
- **Type:**
```ts
type ProjectConfig = Omit<
RstestConfig,
'projects' | 'reporters' | 'pool' | 'isolate' | 'coverage' | 'bail' | 'shard'
>;
type Projects = (string | ProjectConfig)[];
```
- **Default:** `[]`
Define multiple test projects. It can be an array of directories, configuration files, or glob patterns, or an object.
Rstest will run the tests for each project according to the configuration defined in each project, and the test results from all projects will be combined and displayed.
```ts name='rstest.config.ts'
import { defineConfig, defineInlineProject } from '@rstest/core';
export default defineConfig({
projects: [
// A monorepo: each package directory is a project
'packages/*',
// All projects under the apps directory that provide an rstest config file
'apps/**/rstest.config.ts',
// A specific project directory
'/services/auth',
// A specific project's config file
'./projects/web/rstest.config.ts',
// inline project configs
defineInlineProject({
name: 'node',
include: ['tests/node/**/*.{test,spec}.{js,cjs,mjs,ts,tsx}'],
}),
defineInlineProject({
name: 'react',
include: ['tests/react/**/*.{test,spec}.{js,cjs,mjs,ts,tsx}'],
testEnvironment: 'jsdom',
}),
],
});
```
Use `defineInlineProject()` for object items in any `projects` array, including nested `projects`. Use `defineProject()` only for the top-level export of a project config file.
`defineProject()` can omit `name` on the exported project and let Rstest infer it using the [name](/config/test/name.md) option rules. `defineInlineProject()` requires `name` for each inline project item.
More information about projects can be found in [Test projects](/guide/basic/projects.md).
---
url: /config/test/update.md
---
# update
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `-u`, `--update`
Update snapshot.
When `update` is enabled, Rstest will update all changed snapshots and delete obsolete ones.
It should be noted that when you test in local, the newly added snapshot will be automatically updated regardless of whether the `update` configuration is enabled.
**CLI**
```bash
npx rstest -u
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
update: true,
});
```
---
url: /config/test/globals.md
---
# globals
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--globals`
Provide global Rstest APIs for test files, such as `expect`, `test`, `describe`, etc.
By default, Rstest does not provide global APIs. If you prefer to use the APIs globally like Jest, you can add `globals: true` in the config or pass the `--globals` option to CLI.
**CLI**
```bash
npx rstest --globals
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
globals: true,
});
```
## Usage
When you enable `globals`, you can use the global APIs directly without `import { ... } from '@rstest/core'`.
```diff title="index.test.ts"
- import { describe, expect, it } from '@rstest/core';
describe('Index', () => {
it('should add two numbers correctly', () => {
expect(1 + 1).toBe(2);
});
});
```
## Available global APIs
When `globals` is enabled, Rstest injects the following globals:
- `test`
- `describe`
- `it`
- `expect`
- `assert`
- `beforeAll`
- `afterAll`
- `beforeEach`
- `afterEach`
- `onTestFinished`
- `onTestFailed`
- `rstest`
- `rs`
`rstest` and `rs` are aliases with the same runtime utilities.
### TypeScript support
To enable TypeScript to properly recognize the global APIs, add the `@rstest/core/globals` type declaration in your `tsconfig.json`:
```ts title=tsconfig.json
{
"compilerOptions": {
"types": ["@rstest/core/globals"]
}
}
```
Alternatively, create a `src/rstestEnv.d.ts` file to reference the type definitions:
```ts title=rstestEnv.d.ts
///
```
---
url: /config/test/pass-with-no-tests.md
---
# passWithNoTests
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--passWithNoTests`
Pass when no tests are found.
By default, Rstest marks test failures when no test suites are found. You can allow the test to pass when no tests are found by setting `passWithNoTests` to `true`.
**CLI**
```bash
npx rstest --passWithNoTests
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
passWithNoTests: true,
});
```
---
url: /config/test/include-source.md
---
# includeSource
- **Type:** `string[]`
- **Default:** `[]`
In-source testing is where the test code lives within the same file as the source code, similar to [Rust's module tests](https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest).
You can define a list of glob patterns that match your in-source test files via `includeSource` configuration.
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
includeSource: ['src/**/*.{js,ts}'],
});
```
:::tip
**In-source testing** is usually suitable for small functional functions and utilities, allowing for easy and rapid verification and debugging. For more complex functions and modules, independent test files are recommended.
:::
### Writing in-source tests
When `includeSource` defined, Rstest will run all matched files with `import.meta.rstest` inside.
You can get the Rstest test API via `import.meta.rstest`.
```ts title=src/helper.ts
export const sayHi = () => 'hi';
if (import.meta.rstest) {
const { it, expect } = import.meta.rstest;
it('should test source code correctly', () => {
expect(sayHi()).toBe('hi');
});
}
```
### For production
Put the test code inside the `if (import.meta.rstest)` block, and define `import.meta.rstest` as `false` in your build configuration (e.g., `rsbuild.config.ts`), which will help the bundler eliminate dead code.
```diff title=rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
export default defineConfig({
source: {
define: {
+ 'import.meta.rstest': false,
},
},
});
```
### TypeScript
To get TypeScript support for `import.meta.rstest`, you can create a `src/rstestEnv.d.ts` file to reference:
```ts title=rstestEnv.d.ts
///
```
---
url: /config/test/test-name-pattern.md
---
# testNamePattern
- **Type:** `string | RegExp`
- **Default:** `undefined`
- **CLI:** `-t=`, `--testNamePattern=`
Run only tests with a name that matches the regex / string.
If you set `testNamePattern` to `bar`, tests not containing the word `bar` in the test name will be skipped.
It should be noted that the test name consists of the test case name and the name of the test suite that wraps it. If a test suite name contains `bar`, all test cases in that test suite will be run.
**CLI**
```bash
npx rstest -t=bar
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
testNamePattern: 'bar',
});
```
```ts
// skipped
test('test foo', () => {
expect(true).toBe(true);
});
// run
test('test bar', () => {
expect(true).toBe(true);
});
// run all tests in this suite
describe('bar', () => {
it('should add two numbers correctly', () => {
expect(1 + 1).toBe(2);
});
});
```
If you want to exclude certain tests instead, you can use a negative regex. For example, `/^(?!.*bar).*$/` skips every test whose name contains `bar`.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
testNamePattern: /^(?!.*bar).*$/,
});
```
---
url: /config/test/extends.md
---
# extends
- **Type:** `ExtendConfig | ExtendConfigFn | (ExtendConfig | ExtendConfigFn)[]`
- **Default:** `undefined`
The `extends` option allows you to extend your Rstest configuration from external sources, such as adapters that convert other build tools' configurations to Rstest-compatible format.
This enables:
- **Adapter Integration**: Extend from adapters like `@rstest/adapter-rslib`
- **Async Configuration**: Load configuration from external sources asynchronously
- **Config Merging**: Automatically merge extended configuration with your local Rstest config
- **Composable Presets**: Combine multiple shared configs in a single `extends` array
## Basic usage (object)
```ts
type ExtendConfig = Omit;
```
`ExtendConfig` is a plain subset of `RstestConfig` without `projects`.
You can use it to extend your Rstest configuration from external sources.
Rstest will merge the extended config with your local config following the [configuration merging rules](#configuration-merging).
```ts
import { defineConfig } from '@rstest/core';
import { sharedConfig } from './shared.config';
export default defineConfig({
extends: sharedConfig,
// Local configuration
testTimeout: 10000,
retry: 2,
});
```
## Function usage
```ts
type ExtendConfigFn = (
userConfig: Readonly,
) => ExtendConfig | Promise;
```
`ExtendConfigFn` is a function that takes the user's Rstest config as input and returns an extended config.
You can use this to load external configuration files, fetch remote configs, or conditionally modify the config based on user settings.
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
extends: async (user) => {
const useJsdom = user.testEnvironment === 'jsdom';
return {
setupFiles: useJsdom ? ['/setupDomTests.ts'] : undefined,
source: {
define: {
BUILD_TARGET: '"test"',
},
},
};
},
testTimeout: 10_000,
});
```
## Multiple extends
You can pass an array to `extends` when you want to compose multiple presets.
Rstest resolves the array from left to right. Later entries override earlier ones, and your local config still has the highest priority.
When an entry is a function, each `ExtendConfigFn` is resolved independently against the original user config. Array order only affects merge precedence, not the input passed to each function.
```ts
import { defineConfig } from '@rstest/core';
import { withRslibConfig } from '@rstest/adapter-rslib';
import { sharedConfig } from './shared.config';
export default defineConfig({
extends: [
sharedConfig,
withRslibConfig({}),
(userConfig) => ({
globals: userConfig.retry !== undefined,
}),
],
testTimeout: 10_000,
});
```
## Configuration merging
Rstest deep-merges object fields and overrides primitives. Local config always takes precedence over the extended config.
When `extends` is an array, precedence is:
```ts
extends[0] < extends[1] < ... < extends[n] < local config
```
Important merge rules:
- Primitive fields like `testTimeout`, `retry`, and `globals` are overridden by the later value.
- `setupFiles`, `globalSetup`, and `plugins` are concatenated so multiple presets can contribute entries.
- `include`, `reporters`, and `coverage.reporters` are replaced by the later value.
- Object fields like `source`, `resolve`, `output`, `tools`, `env`, and `coverage` are deep-merged.
- `browser` is shallow-merged, so later keys override earlier keys.
- `projects` is ignored in every extended config.
Example:
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
extends: [
{
testEnvironment: 'jsdom',
setupFiles: ['./setup-a.ts'],
testTimeout: 5000,
source: {
define: {
BASE_URL: '"https://example.com"',
},
},
},
{
globals: true,
setupFiles: ['./setup-b.ts'],
source: {
define: {
API_URL: '"https://api.example.com"',
},
},
},
],
// Local configuration
testTimeout: 10000,
retry: 2,
});
// Result:
// {
// testEnvironment: 'jsdom',
// globals: true,
// testTimeout: 10000,
// retry: 2,
// setupFiles: ['./setup-a.ts', './setup-b.ts'],
// source: {
// define: {
// BASE_URL: '"https://example.com"',
// API_URL: '"https://api.example.com"',
// },
// },
// }
```
## Using adapters
### Example: extend from Rslib config
Use the `@rstest/adapter-rslib` adapter to extend Rstest configuration from an existing Rslib config file. The adapter automatically maps compatible options like `source.define`, `source.include`, `source.exclude`, `source.transformImport`, and more.
```ts
import { defineConfig } from '@rstest/core';
import { withRslibConfig } from '@rstest/adapter-rslib';
export default defineConfig({
extends: withRslibConfig({}),
// Additional rstest-specific configuration
coverage: {
enabled: true,
reporters: ['text', 'html'],
},
});
```
## Limitations
The `extends` configuration cannot include the `projects` field. To define multiple projects, extend each project individually:
```ts
export default defineConfig({
projects: [
{
extends: withRslibConfig({ libId: 'node' }),
include: ['tests/node/**/*.{test,spec}.?(c|m)[jt]s'],
},
{
extends: withRslibConfig({ libId: 'react' }),
include: ['tests/react/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
},
],
});
```
---
url: /config/test/env.md
---
# env
- **Type:** `Partial`
- **Default:** `undefined`
Custom environment variables available on `process.env` during tests.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
env: {
PRINT_LOGGER: 'true',
},
});
```
After setting the above configuration, you can access the `PRINT_LOGGER` variable in your tests:
```ts title="index.test.ts"
import { describe, it } from '@rstest/core';
if (process.env.PRINT_LOGGER === 'true') {
console.log('PRINT_LOGGER is enabled');
}
```
---
url: /config/test/bail.md
---
# bail
- **Type:** `number`
- **Default:** `0`
- **CLI:** `--bail [number]` (default: `1`)
Abort the test run after the specified number of test failures. This is useful for stopping test runs early when failures occur.
Setting this to `0` (the default) means to run all tests regardless of failures. Setting this to `1` will stop the test run after the first test failure.
**CLI**
```bash
npx rstest --bail 1
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
bail: 1,
});
```
---
url: /config/test/retry.md
---
# retry
- **Type:** `number`
- **Default:** `0`
- **CLI:** `--retry `
Retry the test specific number of times if it fails. This is useful for some flaky or non-deterministic test failures.
## Example
You can get two retries by setting `retry:2` when the test fails:
**CLI**
```bash
npx rstest --retry 2
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
retry: 2,
});
```
When the test has retried, you may get the following logs:
- success:
```bash
✓ retry.test.ts (1)
✓ should run success with retry (6ms) (retry x2)
Test Files 1 passed
Tests 1 passed
Duration 146 ms (build 22 ms, tests 124 ms)
```
- or failure:
```bash
✗ retry.test.ts (1)
✗ should run success with retry (6ms) (retry x2)
expected 1 to be 5 // Object.is equality
expected 2 to be 5 // Object.is equality
expected 3 to be 5 // Object.is equality
...
Test Files 1 failed
Tests 1 failed
Duration 171 ms (build 23 ms, tests 148 ms)
```
---
url: /config/test/test-timeout.md
---
# testTimeout
- **Type:** `number`
- **Default:** `5_000`
- **CLI:** `--testTimeout=5000`
Default timeout of a test in milliseconds. `0` will disable the timeout.
**CLI**
```bash
npx rstest --testTimeout=5000
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
testTimeout: 5000,
});
```
Rstest determines the test timeout based on the following priority order:
1. [timeout](/api/runtime-api/test-api/test.md#test-1) configuration in each test case
2. Timeout configured in the test file via [`rstest.setConfig({ testTimeout })`](/api/runtime-api/rstest/utilities.md#rstestsetconfig)
3. `--testTimeout` option in CLI
4. `testTimeout` option in the configuration file
5. Default value of `5000` milliseconds
---
url: /config/test/hook-timeout.md
---
# hookTimeout
- **Type:** `number`
- **Default:** `10_000`
- **CLI:** `--hookTimeout=10000`
Default timeout of [hook](/api/runtime-api/test-api/hooks.md) in milliseconds. `0` will disable the timeout.
**CLI**
```bash
npx rstest --hookTimeout=10000
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
hookTimeout: 10000,
});
```
Rstest determines the hook timeout based on the following priority order:
1. [timeout](/api/runtime-api/test-api/hooks.md) configuration in each test case
2. Timeout configured in the test file via [`rstest.setConfig({ hookTimeout })`](/api/runtime-api/rstest/utilities.md#rstestsetconfig)
3. `--hookTimeout` option in CLI
4. `hookTimeout` option in the configuration file
5. Default value of `10_000` milliseconds
---
url: /config/test/max-concurrency.md
---
# maxConcurrency
- **Type:** `number`
- **Default:** `5`
- **CLI:** `--maxConcurrency=10`
A number limiting the number of test cases that are allowed to run at the same time when using `test.concurrent` or wrapped in `describe.concurrent`.
Any test above this limit will be queued and executed once a slot is released.
**CLI**
```bash
npx rstest --maxConcurrency=10
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
maxConcurrency: 10,
});
```
---
url: /config/test/shard.md
---
# shard
- **Type:** `{ count: number; index: number }`
- **Default:** `undefined`
- **CLI:** `--shard /`
Shards test files for parallel execution.
`count` represents the total number of shards, and `index` represents the index of the current shard (1-based).
This option can only be set at the root level of your configuration, not within individual projects.
This is particularly useful for parallelizing tests in CI/CD environments, allowing you to split the test suite into multiple parts and run them independently across different jobs, thereby reducing overall test time.
:::warning Important Notes
- When using the `--shard` option, ensure that the `count` and `index` values are valid.
- Test files will be sorted by their path to ensure consistent sharding across different runs.
- Sharding occurs during the test file scanning phase, prior to the build process, to optimize performance.
:::
**CLI**
```bash
# Split tests into 3 shards, run the 1st shard
npx rstest --shard 1/3
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
shard: {
count: 3,
index: 1,
},
});
```
## Merging shard reports
When using sharding in CI, each shard produces its own test report and coverage data independently. To combine the results from all shards into a single report, use the `blob` reporter along with the `rstest merge-reports` command.
### Step 1: run shards with blob reporter
Configure each shard to output results using the `blob` reporter:
```bash
# Shard 1
npx rstest run --shard 1/3 --reporter=blob --coverage
# Shard 2
npx rstest run --shard 2/3 --reporter=blob --coverage
# Shard 3
npx rstest run --shard 3/3 --reporter=blob --coverage
```
Each shard writes its results to `.rstest-reports/blob-{index}-{count}.json`.
### Step 2: merge reports
After all shards complete, collect all blob files into a single `.rstest-reports/` directory and run:
```bash
npx rstest merge-reports --cleanup
```
This will merge all test results, generate a unified summary using your configured reporters, and combine coverage data.
See the [CLI documentation](/guide/basic/cli.md#rstest-merge-reports) for more details on the `merge-reports` command.
---
url: /config/test/pool.md
---
# pool
- **Type:**
```ts
export type RstestPoolType = 'forks';
export type RstestPoolOptions = {
/** Pool used to run tests in. */
type?: RstestPoolType;
/** Maximum number or percentage of workers to run tests in. */
maxWorkers?: number | string;
/** Minimum number or percentage of workers to run tests in. */
minWorkers?: number | string;
/** Pass additional arguments to node process in the child processes. */
execArgv?: string[];
};
export type RstestConfig = {
/** Pool used to run tests in. */
pool?: RstestPoolType | RstestPoolOptions;
};
```
- **Default:**
```ts
const defaultPool = {
type: 'forks',
// maxWorkers/minWorkers are computed from CPU count and command mode
};
```
Options of pool used to run tests in.
## Example
### Shorthand
You can use a string shorthand to set the pool type:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
pool: 'forks',
});
```
This is equivalent to:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
pool: {
type: 'forks',
},
});
```
Run all tests inside a single child process.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
pool: {
type: 'forks',
maxWorkers: 1,
minWorkers: 1,
},
});
```
---
url: /config/test/isolate.md
---
# isolate
- **Type:** `boolean`
- **Default:** `true`
- **CLI:** `--isolate`, `--isolate=false`, `--no-isolate`
Run tests in an isolated environment.
By default, Rstest runs each test in an isolated environment, which avoids the impact of some module side effects and helps improve the stability of the test.
If your code has no side effects, turning off this option will help improve performance because module caches can be reused between different test files.
**CLI**
```bash
npx rstest --no-isolate
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
isolate: false,
});
```
---
url: /config/test/test-environment.md
---
# testEnvironment
- **Type:** `'node' | 'jsdom' | 'happy-dom' | { name: EnvironmentName, options?: EnvironmentOptions }`
- **Default:** `'node'`
- **CLI:** `--testEnvironment=node`
The environment that will be used for testing.
The default environment in Rstest is a `Node.js` environment. If you are building a web application, you can use a browser-like environment through `jsdom` or `happy-dom` instead.
**CLI**
```bash
npx rstest --testEnvironment=jsdom
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
testEnvironment: 'jsdom',
});
```
### DOM testing
Rstest supports [jsdom](https://github.com/jsdom/jsdom) and [happy-dom](https://github.com/capricorn86/happy-dom) for mocking DOM and browser APIs.
If you want to enable DOM testing, you can use the following configuration:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
testEnvironment: 'jsdom', // or 'happy-dom'
});
```
You also need to install the corresponding package:
For jsdom
```sh [npm]
npm add jsdom -D
```
```sh [yarn]
yarn add jsdom -D
```
```sh [pnpm]
pnpm add jsdom -D
```
```sh [bun]
bun add jsdom -D
```
```sh [deno]
deno add npm:jsdom -D
```
For happy-dom
```sh [npm]
npm add happy-dom -D
```
```sh [yarn]
yarn add happy-dom -D
```
```sh [pnpm]
pnpm add happy-dom -D
```
```sh [bun]
bun add happy-dom -D
```
```sh [deno]
deno add npm:happy-dom -D
```
After enabling DOM testing, you can write tests that use browser APIs like `document` and `window`.
```ts
test('DOM test', () => {
document.body.innerHTML = 'hello world
';
const paragraph = document.querySelector('.content');
expect(paragraph?.innerHTML).toBe('hello world');
});
```
#### Environment options
You can also pass options to the test environment. This is useful for configuring `jsdom` or `happy-dom`. For example, you can set the `url` for `jsdom`:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
testEnvironment: {
name: 'jsdom',
options: {
// jsdom-specific options
url: 'https://example.com',
},
},
});
```
The `options` object is passed directly to the environment's constructor.
- For `jsdom`, it's passed to the `JSDOM` constructor. You can find available options in the [jsdom documentation](https://github.com/jsdom/jsdom#customizing-jsdom).
- For `happy-dom`, it's passed to the `Window` constructor. You can find available options in the [happy-dom documentation](https://github.com/capricorn86/happy-dom/wiki/Window).
### Examples
- [Rstest + node](https://github.com/web-infra-dev/rstest/tree/main/examples/node)
- [Rstest + jsdom + react](https://github.com/web-infra-dev/rstest/tree/main/examples/react)
---
url: /config/test/browser.md
---
# browser (experimental)
- **Type:**
```ts
type BrowserModeConfig = {
enabled?: boolean;
provider: 'playwright';
browser?: 'chromium' | 'firefox' | 'webkit';
headless?: boolean;
port?: number;
strictPort?: boolean;
providerOptions?: Record;
};
```
- **Default:**
```ts
const defaultBrowser = {
enabled: false,
provider: 'playwright',
browser: 'chromium',
headless: true, // CI environment; false for local development
port: undefined, // Random available port
strictPort: false,
providerOptions: {},
};
```
Browser Mode configuration. When enabled, tests run in a real browser instead of the Node.js environment.
## Options
### enabled
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--browser`, `--browser.enabled`
Enable Browser Mode.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
},
});
```
:::tip
Enabling Browser Mode requires installing the `@rstest/browser` package and Playwright browsers.
```bash
npm add @rstest/browser -D
npx playwright install chromium
```
:::
### provider
- **Type:** `'playwright'`
- **Default:** `'playwright'`
Browser driver provider. Currently only [Playwright](https://playwright.dev/) is supported.
Mixing multiple providers in the same test run is currently not supported.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
},
});
```
### browser
- **Type:** `'chromium' | 'firefox' | 'webkit'`
- **Default:** `'chromium'`
- **CLI:** `--browser.name `
The browser type to use for testing.
- `chromium` - Google Chrome, Microsoft Edge
- `firefox` - Mozilla Firefox
- `webkit` - Safari
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
browser: 'firefox',
},
});
```
You need to install the corresponding browser before use:
```bash
# Install chromium
npx playwright install chromium
# Install Firefox
npx playwright install firefox
# Install WebKit
npx playwright install webkit
# Install all browsers
npx playwright install
```
### headless
- **Type:** `boolean`
- **Default:** `true` in CI environments, `false` for local development
- **CLI:** `--browser.headless`
Whether to run the browser in headless mode (without UI).
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
headless: true,
},
});
```
**CI (GitHub Actions)**
```yaml
# .github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npx playwright install chromium
- run: npm test
```
During local development, setting `headless: false` shows the browser window for easier debugging.
:::tip Dynamic Configuration Based on Environment
If you want headed mode for local debugging and headless in CI, control it with environment variables:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
headless: process.env.CI === 'true',
},
});
```
:::
### port
- **Type:** `number`
- **Default:** `undefined` (automatically selects an available port)
- **CLI:** `--browser.port `
The port number for the Browser Mode Dev Server.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
port: 5173,
},
});
```
If you set `port` and the port is already in use, the behavior is controlled by `strictPort`: when `strictPort: true`, Rstest throws an error and exits; when `strictPort: false`, it falls back to another available port. If `port` is not specified, an available port is always selected automatically.
### strictPort
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--browser.strictPort`
When `port` is specified, whether the port must be available:
- `true`: throw an error and exit if the port is in use
- `false`: fall back to another available port if the port is in use
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
port: 5173,
strictPort: true,
},
});
```
### providerOptions
[Added in v0.9.3](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.3)
- **Type:** `Record`
- **Default:** `{}`
Provider-specific options passed through to the selected browser provider. Rstest does not validate or interpret the contents of this object — it is forwarded as-is to the provider at both browser launch and context creation time.
The structure of `providerOptions` is defined by each provider. See the [Provider options](#provider-options) section below for details.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
providerOptions: {
launch: {
timeout: 60_000,
},
},
},
});
```
## Current limitation: browser launch options must match in one run
In one `rstest` process, all Browser Mode projects must share the same browser launch options:
- `provider`
- `browser`
- `headless`
- `port`
- `strictPort`
- `providerOptions`
This means mixing multiple providers, or running Chromium/Firefox/WebKit together in the same run, is not supported yet.
For cross-browser coverage, run tests in separate executions (for example, in a CI matrix):
```bash
npx rstest --browser.name chromium
npx rstest --browser.name firefox
npx rstest --browser.name webkit
```
## Multi-project config isolation
When using `projects` in Browser Mode, each project still compiles and executes with its own build config (for example `plugins`, `include`, and framework-specific setup).
Browser launch options still need to stay aligned across browser projects: `provider`, `browser`, `headless`, `port`, `strictPort`, and `providerOptions`.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
projects: ['./project-b/rstest.config.ts', './project-a/rstest.config.ts'],
});
```
```ts title="project-a/rstest.config.ts"
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rstest/core';
export default defineConfig({
name: 'project-a',
plugins: [pluginReact()],
include: ['tests/**/*.test.tsx'],
browser: {
enabled: true,
provider: 'playwright',
},
});
```
## Mixing with node tests
You can configure both browser tests and Node.js tests together:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
projects: [
{
name: 'browser',
include: ['src/**/*.browser.test.ts'],
browser: {
enabled: true,
provider: 'playwright',
},
},
{
name: 'node',
include: ['src/**/*.node.test.ts'],
testEnvironment: 'node',
},
{
name: 'jsdom',
include: ['src/**/*.test.ts'],
exclude: ['src/**/*.browser.test.ts', 'src/**/*.node.test.ts'],
testEnvironment: 'jsdom',
},
],
});
```
## Provider options
### Playwright
When using `provider: 'playwright'`, the Playwright provider recognizes the following fields in `providerOptions`:
1. **`launch`** — Passed to Playwright's [`browserType.launch()`](https://playwright.dev/docs/api/class-browsertype#browser-type-launch). Controls how the browser process is started.
2. **`context`** — Passed to Playwright's [`browser.newContext()`](https://playwright.dev/docs/api/class-browser#browser-new-context). Controls the browser context created for each test file.
Since `providerOptions` is typed as `Record`, you won't get IntelliSense out of the box. To get type checking and autocompletion, import types directly from the `playwright` package and use TypeScript's `satisfies` operator:
```ts title="rstest.config.ts"
import type { LaunchOptions, BrowserContextOptions } from 'playwright';
import { defineConfig } from '@rstest/core';
type PlaywrightProviderOptions = {
launch?: LaunchOptions;
context?: BrowserContextOptions;
};
export default defineConfig({
browser: {
enabled: true,
provider: 'playwright',
providerOptions: {
launch: {
timeout: 60_000,
},
context: {
locale: 'en-US',
colorScheme: 'dark',
},
} satisfies PlaywrightProviderOptions,
},
});
```
## Related links
- [Browser Mode Guide](/guide/browser-testing/index.md) - Introduction and usage guide for Browser Mode
- [Getting Started](/guide/browser-testing/getting-started.md) - Configure Browser Mode tests
- [User interactions](/guide/browser-testing/user-interactions.md#locator-api) - Write semantic tests with `page` + `expect.element`
---
url: /config/test/clear-mocks.md
---
# clearMocks
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--clearMocks`
Automatically clear mock calls, instances, contexts and results before every test.
When `clearMocks` enabled, rstest will call [.clearAllMocks()](/api/runtime-api/rstest/mock-functions.md#rstestclearallmocks) before each test.
**CLI**
```bash
npx rstest --clearMocks
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
clearMocks: true,
});
```
---
url: /config/test/reset-mocks.md
---
# resetMocks
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--resetMocks`
Automatically reset mock state before every test.
When `resetMocks` enabled, rstest will call [.resetAllMocks()](/api/runtime-api/rstest/mock-functions.md#rstestresetallmocks) before each test.
**CLI**
```bash
npx rstest --resetMocks
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
resetMocks: true,
});
```
---
url: /config/test/restore-mocks.md
---
# restoreMocks
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--restoreMocks`
Automatically reset mock state before every test.
When `restoreMocks` enabled, rstest will call [.restoreAllMocks()](/api/runtime-api/rstest/mock-functions.md#rstestrestoreallmocks) before each test.
**CLI**
```bash
npx rstest --restoreMocks
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
restoreMocks: true,
});
```
---
url: /config/test/unstub-envs.md
---
# unstubEnvs
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--unstubEnvs`
Restores all `process.env` values that were changed with [rstest.stubEnv](/api/runtime-api/rstest/utilities.md#rsteststubenv) before every test.
**CLI**
```bash
npx rstest --unstubEnvs
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
unstubEnvs: true,
});
```
---
url: /config/test/unstub-globals.md
---
# unstubGlobals
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--unstubGlobals`
Restores all global variables that were changed with [rstest.stubGlobal](/api/runtime-api/rstest/utilities.md#rsteststubglobal) before every test.
**CLI**
```bash
npx rstest --unstubGlobals
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
unstubGlobals: true,
});
```
---
url: /config/test/coverage.md
---
# coverage
[Added in v0.4.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.4.0)
- **Type:**
```ts
type CoverageOptions = {
enabled?: boolean;
provider?: 'istanbul';
include?: string[];
exclude?: string[];
reporters?: (keyof ReportOptions | ReportWithOptions)[];
reportsDirectory?: string;
reportOnFailure?: boolean;
clean?: boolean;
allowExternal?: boolean;
thresholds?: CoverageThresholds;
};
```
- **Default:** `undefined`
Collect code coverage and generate coverage reports.
```bash
$ npx rstest --coverage
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
```
## Options
### enabled
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--coverage`, `--coverage=false`, `--no-coverage`
Enable or disable test coverage collection.
**CLI**
```bash
npx rstest --coverage
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
},
});
```
### provider
- **Type:** `'istanbul'`
- **Default:** `'istanbul'`
The coverage provider to use. Currently, only [istanbul](https://istanbul.js.org/) is supported.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
provider: 'istanbul',
},
});
```
#### Istanbul provider
[Istanbul](https://istanbul.js.org/) is a widely used JavaScript code coverage tool that collects code coverage information through instrumentation.
To enable istanbul coverage, you need to install the `@rstest/coverage-istanbul` package first.
```sh [npm]
npm add @rstest/coverage-istanbul -D
```
```sh [yarn]
yarn add @rstest/coverage-istanbul -D
```
```sh [pnpm]
pnpm add @rstest/coverage-istanbul -D
```
```sh [bun]
bun add @rstest/coverage-istanbul -D
```
```sh [deno]
deno add npm:@rstest/coverage-istanbul -D
```
`@rstest/coverage-istanbul` is powered by [swc-plugin-coverage-instrument](https://github.com/kwonoj/swc-plugin-coverage-instrument).
### include
- **Type:** `string[]`
- **Default:** `undefined`
A list of glob patterns that should be included for coverage collection.
By default, rstest will only collect coverage for files that are tested. If you want to include untested files in the coverage report, you can use the `include` option to specify the files or patterns to include.
Note that you should use standard glob syntax here. For a single extension, write `src/**/*.ts` instead of `src/**/*.{ts}`. Single-value braces are treated literally by the underlying glob libraries, so `src/**/*.{ts}` will not match `.ts` files.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
include: ['src/**/*.{js,jsx,ts,tsx}'],
},
});
```
### exclude
- **Type:** `string[]`
- **Default:**
```ts
[
'**/node_modules/**',
'**/test/**',
'**/__tests__/**',
'**/__mocks__/**',
'**/*.d.ts',
'**/*.{test,spec}.[jt]s',
'**/*.{test,spec}.[cm][jt]s',
'**/*.{test,spec}.[jt]sx',
'**/*.{test,spec}.[cm][jt]sx',
];
```
A glob pattern array to exclude files from test coverage collection.
Any patterns specified here will be merged with default values.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
exclude: ['**/node_modules/**', '**/dist/**'],
},
});
```
### reporters
- **Type:** `(ReporterName | [ReporterName, ReporterOptions>])[]`
- **Default:** `['text', 'html', 'clover', 'json']`
The reporters to use for coverage collection. Each reporter can be either a string (the reporter name) or a tuple with the reporter name and its options.
- See [Istanbul Reporters](https://istanbul.js.org/docs/advanced/alternative-reporters/) for available reporters.
- See [@types/istanbul-reporter](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/istanbul-reports/index.d.ts) for details about reporter specific options.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reporters: [
'html',
['text', { skipFull: true }],
['json', { file: 'coverage-final.json' }],
],
},
});
```
### reportsDirectory
- **Type:** `string`
- **Default:** `'./coverage'`
The directory to store coverage reports.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reportsDirectory: './coverage-reports',
},
});
```
### reportOnFailure
- **Type:** `boolean`
- **Default:** `false`
Whether to report coverage and check thresholds when tests fail.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
reportOnFailure: true,
},
});
```
### allowExternal
[Added in v0.9.3](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.3)
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--coverage.allowExternal`
Whether to collect coverage for source files outside the project root directory. This is useful in monorepo setups where tests import modules from sibling packages.
By default, rstest excludes files outside the project root from coverage reports, which aligns with the behavior of Jest and Vitest.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
allowExternal: true,
},
});
```
### clean
- **Type:** `boolean`
- **Default:** `true`
Whether to clean the coverage directory before running tests.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
clean: true,
},
});
```
### thresholds
- **Type:**
```ts
type CoverageThreshold = {
statements?: number;
functions?: number;
branches?: number;
lines?: number;
};
type CoverageThresholds = CoverageThreshold & {
/** check thresholds for matched files */
[glob: string]: CoverageThreshold & {
perFile?: boolean;
};
};
```
- **Default:** `undefined`
Coverage thresholds for enforcing minimum coverage requirements. You can set thresholds for statements, functions, branches, and lines.
Thresholds specified as a positive number are taken to be the minimum percentage required. Thresholds specified as a negative number represent the maximum number of uncovered entities allowed.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
thresholds: {
statements: 80,
functions: 80,
branches: 80,
lines: -10,
},
},
});
```
When the code coverage is below the specified thresholds, the test will fail and output an error message like below:
```bash
Error: Coverage for statements 75% does not meet global threshold 80%
Error: Coverage for functions 75% does not meet global threshold 80%
Error: Coverage for branches 75% does not meet global threshold 80%
Error: Uncovered lines 20 exceeds maximum global threshold allowed 10
```
#### glob pattern
If globs are specified, thresholds will be checked for each matched file pattern. If the file specified by path is not found, an error is returned.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
thresholds: {
// Thresholds for matching glob pattern
'src/**': {
statements: 100,
},
'node/**/*.js': {
statements: 90,
},
// Thresholds for all files
statements: 80,
},
},
});
```
Following the above configuration, rstest will fail if:
- The total code coverage of all files in `src/**` is below 100%.
- The total code coverage of all files in `node/**/*.js` is below 90%.
- The global code coverage is below 80% for statements.
#### check threshold for per file
Rstest also supports checking thresholds for each matched file by setting `perFile` to `true`.
```ts title='rstest.config.ts'
import { defineConfig } from '@rstest/core';
export default defineConfig({
coverage: {
enabled: true,
thresholds: {
'src/**': {
statements: 90,
perFile: true,
},
},
},
});
```
---
url: /config/test/reporters.md
---
# reporters
- **Type:**
```ts
type Reporter = ReporterName | [ReporterName, ReporterOptions];
type Reporters = Reporter | Reporter[];
```
- **Reporter options reference:** Reporter option types are defined in [`packages/core/src/types/reporter.ts`](https://github.com/web-infra-dev/rstest/blob/main/packages/core/src/types/reporter.ts)
- **Default:**
```ts
process.env.GITHUB_ACTIONS === 'true'
? ['default', 'github-actions']
: ['default'];
```
- **CLI:** `--reporter= --reporter=`
Configure which reporters to use for test result output.
Built-in reporter names include `default`, `dot`, `verbose`, `md`, `github-actions`, `junit`, `json`, and `blob`.
:::info AI agent environments
If you haven't explicitly configured any reporters (no `reporters` in config and no `--reporter` flags), Rstest defaults to `['md']` and outputs AI agent-friendly markdown to stdout.
:::
### Usage
#### Basic example
You can specify reporters in the `rstest.config.ts` file or via the CLI.
**CLI**
```bash
npx rstest --reporter=default
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: 'default',
});
```
#### Multiple reporters
You can use multiple reporters to output test results in different formats simultaneously. This is useful when you want both console output and a file report for CI/CD pipelines.
```ts title=rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: ['default', 'junit'],
});
```
#### Configuring reporters with options
Many reporters support configuration options. Pass them as a tuple `[reporterName, options]`:
```ts title=rstest.config.ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
reporters: [
['default', { verbose: true }],
['github-actions', { verbose: true }],
['junit', { outputPath: './test-results.xml' }],
],
});
```
#### Using custom reporters
You can create and use custom reporters by providing a reporter class or object that implements the reporter interface:
```ts title=rstest.config.ts
import { defineConfig } from '@rstest/core';
import { CustomReporter } from './custom-reporter';
export default defineConfig({
reporters: [CustomReporter],
});
```
### Related documentation
- [Reporters guide](/guide/basic/reporters.md) - Usage examples and built-in reporter details
- [Reporter API reference](/api/javascript-api/reporter.md) - Custom reporter implementation
---
url: /config/test/include-task-location.md
---
# includeTaskLocation
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--includeTaskLocation`
Whether to include the location information of tests when collecting test information.
When enabled, this configuration adds line and column number information to test cases and suites. This is useful for debugging and when reporters need to collect test information.
Note that enabling this configuration may slightly decrease test execution performance.
**CLI**
```bash
npx rstest --includeTaskLocation
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
includeTaskLocation: true,
});
```
## Using with the list command
The `includeTaskLocation` option is particularly useful when used with the `list` command, especially when you want to see the exact location of tests:
```bash
npx rstest list --printLocation
```
This will automatically enable `includeTaskLocation` and display the location information for each test case.
## Example output
With `includeTaskLocation` enabled, reporters and the `list` command will include location information:
```json
{
"testId": "test-1",
"name": "should calculate correct sum",
"testPath": "/path/to/test.spec.ts",
"location": {
"line": 15,
"column": 3
}
}
```
---
url: /config/test/log-heap-usage.md
---
# logHeapUsage
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--logHeapUsage`
Log heap usage for each test, it can help to find memory leaks in tests.
**CLI**
```bash
npx rstest --logHeapUsage
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
logHeapUsage: true,
});
```
## Example
After setting `logHeapUsage` to `true`, Rstest will log heap usage for each test.
```bash
✓ test/index.test.ts (2) 11 MB heap used
✓ test/index1.test.ts (2) 11 MB heap used
Test Files 2 passed
Tests 2 passed | 2 skipped (4)
Duration 233ms (build 175ms, tests 58ms)
```
---
url: /config/test/hide-skipped-tests.md
---
# hideSkippedTests
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--hideSkippedTests`
Hide logs for skipped test cases to reduce noise in the test output, especially when running tests with filters.
When you use the default reporter and run multiple test files, skipped tests from other files are already hidden by the reporter's display logic, even when `hideSkippedTests` remains at its default value of `false`.
**CLI**
```bash
npx rstest --hideSkippedTests
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
hideSkippedTests: true,
});
```
## Example
By default, Rstest displays logs for all test cases when you use the [verbose reporter](/guide/basic/reporters.md#verbose-reporter).
```bash
✓ test/index.test.ts (2) 1ms
✓ Index > should add two numbers correctly (0ms)
- Index > should test source code correctly (1ms)
Test Files 1 passed
Tests 1 passed | 1 skipped (2)
Duration 93ms (build 50ms, tests 43ms)
```
When you set `hideSkippedTests` to `true`, Rstest will hide logs for all skipped test cases after the test run is complete.
The output will look like this:
```bash
✓ test/index.test.ts (2) 1ms
✓ Index > should add two numbers correctly (0ms)
Test Files 1 passed
Tests 1 passed | 1 skipped (2)
Duration 93ms (build 50ms, tests 43ms)
```
---
url: /config/test/hide-skipped-test-files.md
---
# hideSkippedTestFiles
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--hideSkippedTestFiles`
Hide logs for skipped test files to reduce noise in the test output, especially when running tests with filters.
**CLI**
```bash
npx rstest --hideSkippedTestFiles
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
hideSkippedTestFiles: true,
});
```
## Example
By default, Rstest displays logs for all test files.
```bash
✓ test/index.test.ts (1) 1ms
✓ Index > should add two numbers correctly (0ms)
- test/all-skipped.test.ts (2) 1ms
Test Files 1 passed | 1 skipped
Tests 1 passed | 2 skipped (3)
Duration 93ms (build 50ms, tests 43ms)
```
When you set `hideSkippedTestFiles` to `true`, Rstest will hide logs for all skipped test files after the test run is complete.
The output will look like this:
```bash
✓ test/index.test.ts (1) 1ms
✓ Index > should add two numbers correctly (0ms)
Test Files 1 passed | 1 skipped
Tests 1 passed | 2 skipped (3)
Duration 93ms (build 50ms, tests 43ms)
```
---
url: /config/test/slow-test-threshold.md
---
# slowTestThreshold
- **Type:** `number`
- **Default:** `300`
- **CLI:** `--slowTestThreshold=300`
The number of milliseconds after which a test or suite is considered slow and reported as such in the results.
**CLI**
```bash
npx rstest --slowTestThreshold=300
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
slowTestThreshold: 300,
});
```
---
url: /config/test/snapshot-format.md
---
# snapshotFormat
- **Type:** `PrettyFormatOptions`
- **Default:** `undefined`
Customize the snapshot format using options from the [pretty-format](https://www.npmjs.com/package/pretty-format).
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
snapshotFormat: {
printBasicPrototype: true,
},
});
```
---
url: /config/test/chai-config.md
---
# chaiConfig
- **Type:**
```ts
type ChaiConfig = {
/**
* @default true
*/
showDiff: boolean;
/**
* @default 40
*/
truncateThreshold: number;
};
```
- **Default:** `undefined`
Customize the [Chai config](https://github.com/chaijs/chai/blob/5.x.x/lib/chai/config.js).
### truncateThreshold
Sets the number of characters to display when showing large values in assertions. When an assertion fails and displays objects or arrays, Chai will truncate the output if it exceeds this threshold to keep error messages readable.
- **Default:** `40`
A higher value shows more details in assertion failures, useful for debugging complex data structures:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
chaiConfig: {
truncateThreshold: 100, // Show up to 100 characters
},
});
```
With the default value (40), long arrays or objects get truncated:
```ts
AssertionError: expected [ 1, 2, [ 3, [ 4 ], ... ] ] to strictly equal
[ 1, 2, [ 3 ] ]
```
If you set a higher threshold, you see the full structure:
```ts
AssertionError: expected [ 1, 2, [ 3, [ 4 ], { a: 1, length: 1 } ] ] to strictly equal
[ 1, 2, [ 3 ] ]
```
This allows you to see the exact values that don't match, making debugging easier.
### showDiff
Show a diff of expected vs actual values in assertion failures.
- **Default:** `true`
Disable this if you want to see plain assertion messages without diffs:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
chaiConfig: {
showDiff: false, // Hide diff output
},
});
```
By default, Rstest shows a diff of expected vs actual values in assertion failures:
```ts
AssertionError: expected { a: 1, b: 2 } to deeply equal { a: 1, b: 3 }
+ expected
- actual
{
"a": 1
+ "b": 3
- "b": 2
}
```
If you set `showDiff: false`, Rstest will show plain assertion messages without diffs:
```ts
AssertionError: expected { a: 1, b: 2 } to deeply equal { a: 1, b: 3 }
```
---
url: /config/test/resolve-snapshot-path.md
---
# resolveSnapshotPath
- **Type:** `(testPath: string, snapExtension: string) => string`
- **Default:** `undefined`
Custom handler for resolving snapshot paths in tests.
By default, Rstest saves snapshot files in the same directory as the test files, under a `__snapshots__` folder.
You can use `resolveSnapshotPath` to customize this behavior and specify a different location for your snapshot files.
For example, store snapshots next to test files:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
resolveSnapshotPath: (testPath, snapshotExtension) =>
testPath + snapshotExtension,
});
```
---
url: /config/test/print-console-trace.md
---
# printConsoleTrace
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--printConsoleTrace`
Print console traces when calling any console method, which is helpful for debugging.
**CLI**
```bash
npx rstest --printConsoleTrace
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
printConsoleTrace: true,
});
```
:::note
When [disableConsoleIntercept](/config/test/disable-console-intercept.md) is set to `true`, `printConsoleTrace` will not take effect.
:::
---
url: /config/test/silent.md
---
# silent
[Added in v0.10.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.10.0)
- **Type:** `boolean | 'passed-only'`
- **Default:** `false`
- **CLI:** `--silent [value]`
Silence intercepted console output from tests.
- Set `true` to hide all intercepted console logs.
- Set `'passed-only'` to keep intercepted logs only for failed files, suites, and test cases.
**CLI**
```bash
npx rstest --silent=passed-only
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
silent: 'passed-only',
});
```
## Example
When `silent` is set to `'passed-only'`, console logs from passing tasks are buffered and discarded, while logs from failed tasks are replayed with the failure output.
```ts title="example.test.ts"
import { describe, expect, it } from '@rstest/core';
describe('math', () => {
it('passes', () => {
console.log('pass log');
expect(1 + 1).toBe(2);
});
it('fails', () => {
console.log('fail log');
expect(1 + 1).toBe(3);
});
});
```
Only `fail log` will be printed.
:::note
Rstest tries to attribute logs to specific tests, but in concurrent asynchronous test scenarios in browser mode, log attribution may not be perfectly accurate.
In browser mode, this interception still calls the original `console.*` so logs can remain visible in DevTools. `silent` controls Rstest/reporter output, not necessarily the browser's native console view.
`silent` still works when `disableConsoleIntercept` is `true`. In that case, logs do not go through the reporter console interception pipeline, so [onConsoleLog](/config/test/on-console-log.md) and [printConsoleTrace](/config/test/print-console-trace.md) still do not apply. However, with `silent: 'passed-only'`, Rstest still buffers logs internally and replays the matching logs through the original console output after failed tasks finish.
:::
---
url: /config/test/on-console-log.md
---
# onConsoleLog
- **Type:** `(content: string) => boolean | void`
- **Default:** `undefined`
Custom handler for console log in tests, which is helpful for filtering out logs from third-party libraries.
If you return `false`, Rstest will not print the log to the console.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
onConsoleLog: (log) => log.includes('test'),
});
```
:::note
When [disableConsoleIntercept](/config/test/disable-console-intercept.md) is set to `true`, `onConsoleLog` will not take effect.
:::
## Silencing console logs
You can silence console logs by returning `false` in the `onConsoleLog` handler.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
onConsoleLog: () => false,
});
```
---
url: /config/test/disable-console-intercept.md
---
# disableConsoleIntercept
- **Type:** `boolean`
- **Default:** `false`
- **CLI:** `--disableConsoleIntercept`
Disable interception of console logs. By default, Rstest intercepts the console log, which will help track log sources.
If you don't want Rstest to intercept console logs, you can set this configuration to `true`.
**CLI**
```bash
npx rstest --disableConsoleIntercept
```
**rstest.config.ts**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
disableConsoleIntercept: true,
});
```
It should be noted that when you disable interception of console logs, the [onConsoleLog](/config/test/on-console-log.md) and [printConsoleTrace](/config/test/print-console-trace.md) configurations will not take effect. However, if [silent](/config/test/silent.md) is enabled, Rstest still keeps an internal interception path so `silent` can continue to control test log output.
In browser mode, that internal path still calls the original `console.*` for DevTools.
---
url: /config/build/plugins.md
---
# plugins [plugins](https://rsbuild.rs/config/plugins)
`plugins` is used to register Rsbuild plugins.
Rstest and Rsbuild share the same plugin system, so you can use Rsbuild plugins in Rstest.
## Using plugins
You can register Rsbuild plugins in `rstest.config.*` using the `plugins` option, see [Rsbuild - plugins](https://rsbuild.rs/config/plugins).
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
plugins: [pluginReact()],
});
```
## Discover plugins
Check out the Rsbuild [plugin list](https://rsbuild.rs/plugins/list#official-plugins) to discover available plugins. These plugins can also be used with Rstest.
---
url: /config/build/source.md
---
# source
Options for input source code.
## source.decorators [source.decorators](https://rsbuild.rs/config/source/decorators)
Used to configure the decorators syntax.
If you are using TypeScript's [experimentalDecorators](https://www.typescriptlang.org/tsconfig/#experimentalDecorators), you should set `source.decorators.version` to `legacy` in this case.
## source.assetsInclude [source.assetsInclude](https://rsbuild.rs/config/source/assets-include)
[Added in v0.9.6](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.6)
Specify additional files that should be treated as static assets.
This is useful when your tests import file types that are not included in Rsbuild's default asset handling, such as `.txt` or `.md`.
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
source: {
assetsInclude: /\.(txt|md)$/,
},
});
```
If you already reuse Rsbuild or Rslib config through `@rstest/adapter-rsbuild` or `@rstest/adapter-rslib`, `source.assetsInclude` will be inherited automatically.
## source.define [source.define](https://rsbuild.rs/config/source/define)
Replaces variables in your code with other values or expressions at compile time. This can be useful for injecting env variables and other information to the code during build time.
## source.exclude [source.exclude](https://rsbuild.rs/config/source/exclude)
Exclude JavaScript or TypeScript files that do not need to be transformed by [SWC](https://rsbuild.rs/guide/configuration/swc).
::: note
Files configured in `source.exclude` will not be transformed by SWC, but the referenced files will still be bundled into the outputs.
If you want certain files not to be bundled into the outputs, you can use [output.externals](/config/build/output.md#outputexternals) or Rspack's [IgnorePlugin](https://rspack.rs/plugins/webpack/ignore-plugin).
:::
## source.include [source.include](https://rsbuild.rs/config/source/include)
Specify additional JavaScript files that need to be compiled.
## source.transformImport [source.transformImport](https://rsbuild.rs/config/source/transform-import)
[Added in v0.9.6](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.6)
Configure on-demand import transforms.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
source: {
transformImport: [
{
libraryName: 'demo-lib',
libraryDirectory: '.',
camelToDashComponentName: false,
},
],
},
});
```
:::caution
`source.transformImport` rewrites the final module request at compile time.
In the current implementation, module-level mocking APIs that still use the original specifier, such as `rs.mock('pkg')`, `rs.doMock('pkg')`, `rs.importActual('pkg')`, and `rs.requireActual('pkg')`, may no longer target the transformed module request.
:::
## source.tsconfigPath [source.tsconfigPath](https://rsbuild.rs/config/source/tsconfig-path)
Configure a custom `tsconfig.json` file path to use, can be a relative or absolute path.
---
url: /config/build/output.md
---
# output
## output.module [output.module](https://rsbuild.rs/config/output/module)
- **Type:** `boolean`
- **Default:** `true`
Whether to output JavaScript files in ES module format.
Rstest outputs and executes test code in ES module format by default. If you want to output test code in CommonJS format, you can enable it through the following configuration:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
output: {
module: false,
},
});
```
### Commonjs interop
When you output JavaScript files in ES module format (`output.module: true`), Rstest will determine the type of external based on how the dependency is imported:
- Dependencies imported via `import` syntax will be treated as ES module type externals.
- Dependencies imported via `require` syntax will be treated as CommonJS externals.
When you import CommonJS modules using the `import` syntax, Rstest will attempt interop handling, allowing you to import CommonJS module exports normally using `import` syntax. The following code works correctly in Rstest:
```ts title="cjs-module"
Object.defineProperty(exports, '__esModule', { value: true });
const a = require('./a');
exports.a = a.a;
exports.default = () => {
return `hello ${a.a}`;
};
```
```ts title="test/index.test.ts"
import defaultExport, { a } from 'cjs-module';
```
However, this interop handling does not always work perfectly, depending on the way the CommonJS module exports its content. Currently, Rstest does not support interop CommonJS module default exports as named exports.
```ts
function lodash(_value) {}
lodash.VERSION = VERSION;
module.exports = lodash;
```
```ts
import { VERSION } from 'lodash'; // ❌
```
If you encounter issues during usage, you can specify the external type of a dependency as CommonJS through [specifying external type](#specify-external-type).
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
output: {
externals: {
lodash: 'commonjs lodash', // externalize lodash as a CommonJS module
},
},
});
```
## output.externals [output.externals](https://rsbuild.rs/config/output/externals)
Prevent some `import` dependencies from being packed into bundles in your code, and instead Rstest will `import` them at runtime.
- In the Node.js test environment, Rstest will bundle and transpile the following files by default:
- Any TypeScript and JSX files in any directory, with file extensions `.ts`, `.tsx`, `.jsx`, `.mts`, `.cts`.
- JavaScript files outside the `node_modules` directory, with file extensions `.js`, `.mjs`, `.cjs`.
- In the browser-like (jsdom, etc) test environment, all packages are bundled by default.
If you want a dependency to be externalized, you can configure it in `output.externals`.
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
output: {
externals: ['react'],
},
});
```
If you want all dependencies to be bundled, you can configure it through the following configuration:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
tools: {
rspack: (config) => {
config.externals = [];
},
},
});
```
### Specify external type
You can use `${externalsType} ${libraryName}` syntax to specify the external type of a dependency.
For example, you can use `commonjs lodash` to specify that `lodash` should be externalized as a CommonJS module:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
output: {
externals: {
lodash: 'commonjs lodash',
},
},
});
```
You can also specify the default external type for all dependencies through the [`externalsType` configuration option](https://rspack.rs/config/externals#externalstype).
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
tools: {
rspack: {
externalsType: 'commonjs',
},
},
});
```
## output.bundleDependencies
[Added in v0.9.5](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.5)
- **Type:** `boolean | (string | RegExp)[]`
- **Default:** Depends on `testEnvironment`
Controls whether third-party dependencies from `node_modules` are bundled or externalized.
- `true`: Always bundle all third-party dependencies, regardless of test environment.
- `false`: Always externalize third-party dependencies, regardless of test environment.
- `['pkg-name']`: Bundle only the listed packages and externalize the rest.
- `['pkg-name/subpath']`: Bundle a specific package subpath.
- `['pkg-name/*']`: Bundle package requests that match the glob-like pattern.
- `[/^pkg-name\\/subpath/]`: Bundle package requests that match the regular expression.
When this option is unset, Rstest bundles dependencies in browser-like test environments (jsdom, happy-dom, etc.), and externalizes them in the `node` environment.
:::warning
This option only applies to non-browser mode. In [browser mode](/guide/browser-testing.md), all dependencies are always bundled, so `output.bundleDependencies: false` is not supported.
:::
This option provides a simple way to override the default bundling strategy that is tied to `testEnvironment`. For example, if you use `jsdom` but want the same externalization behavior as the `node` environment:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
testEnvironment: 'jsdom',
output: {
bundleDependencies: false,
},
});
```
Or if you want to bundle all dependencies in the `node` environment to benefit from optimizations like [lazy barrel](https://rspack.rs/guide/optimization/lazy-barrel):
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
testEnvironment: 'node',
output: {
bundleDependencies: true,
},
});
```
If you only want to bundle a few packages while keeping the rest externalized, pass their package names:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
output: {
bundleDependencies: ['strip-ansi'],
},
});
```
You can also target subpaths or patterns:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
output: {
bundleDependencies: ['strip-ansi/lib/index.js', 'strip-ansi/*'],
},
});
```
Patterns only affect dependency requests that rstest can still process in the current bundle graph. If a package has already been externalized, its internal dependencies stay external as well. In other words, `bundleDependencies` does not re-bundle transitive dependencies that are reachable only through an externalized parent package.
### Relationship with output.externals
`output.bundleDependencies` controls the overall bundling strategy for all `node_modules` dependencies, while [`output.externals`](#outputexternals) allows fine-grained control over individual packages. When both are configured, `output.externals` takes higher priority:
- `bundleDependencies: true` + `externals: ['lodash']`: Bundle all dependencies, but externalize `lodash`.
- `bundleDependencies: false` + `externals` is not needed, since all dependencies are already externalized.
In practice, the two options are most useful in opposite situations:
- Use `bundleDependencies` patterns when most dependencies should stay external, and you only want to bundle a small set of packages or subpaths.
- Use `output.externals` when most dependencies should stay bundled, and you only want to externalize a small set of packages.
## output.cssModules [output.cssModules](https://rsbuild.rs/config/output/css-modules)
For custom CSS Modules configuration.
## output.emitAssets [output.emitAssets](https://rsbuild.rs/config/output/emit-assets)
[Added in v0.9.7](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.7)
- **Type:** `boolean`
- **Default:** `true`
Controls whether imported static assets such as images, fonts, audio, and video are emitted as build assets during test builds.
Rstest forwards this option to the underlying Rsbuild pipeline and follows the same behavior as Rsbuild. When `output.emitAssets` is `true`, imported asset modules are emitted into the build output file system. When it is `false`, those asset files are not emitted.
In Rstest, emitted assets are written to disk only when [dev.writeToDisk](/config/build/dev.md#devwritetodisk) is enabled or when you run with DEBUG output enabled. Otherwise, they stay in the temporary in-memory output used by the test build.
This option is mainly useful when you want Rstest to match an existing Rsbuild setup, or when your tests do not need to validate emitted static assets.
If you are already reusing your Rsbuild config through [@rstest/adapter-rsbuild](/guide/integration/rsbuild.md), `output.emitAssets` is inherited automatically.
## output.distPath [output.distPath](https://rsbuild.rs/config/output/dist-path)
[Added in v0.9.5](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.5)
- **Type:** `string | { root?: string }`
- **Default:** `{ root: 'dist/.rstest-temp' }`
Control the global root directory where Rstest places its temporary build outputs.
By default, Rstest does not write test temporary files to disk. When you enable the [dev.writeToDisk](/config/build/dev.md#devwritetodisk) option or run in DEBUG mode, Rstest will write temporary artifacts to disk, outputting them to the `dist/.rstest-temp` directory. This includes compiled artifacts used by the Node.js test runtime, as well as temporary resources generated in browser mode such as runner files and virtual manifests.
In multi-project runs, Rstest still uses one global output root. It may create subdirectories under that root for different projects, but the base directory itself is shared and does not switch to each project's `root`.
If you want these files to go to another directory, set `output.distPath.root`:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
dev: {
writeToDisk: true,
},
output: {
distPath: {
root: 'custom/.rstest-temp',
},
},
});
```
After this change, rstest will use `/custom/.rstest-temp` as the temporary output root instead of `/dist/.rstest-temp`.
## output.cleanDistPath [output.cleanDistPath](https://rsbuild.rs/config/output/clean-dist-path)
Whether to clean up all test temporary files under the output directory before the test starts.
By default, rstest does not write test temporary files to disk, and this configuration item may be required when you debug rstest outputs.
---
url: /config/build/resolve.md
---
# resolve
Options for module resolution.
## resolve.aliasStrategy [resolve.aliasStrategy](https://rsbuild.rs/config/resolve/alias-strategy)
Control the priority between the `resolve.alias` option and the `paths` option in `tsconfig.json`.
## resolve.alias [resolve.alias](https://rsbuild.rs/config/resolve/alias)
Set the alias for the module path, which is used to simplify the import path or redirect the module reference, similar to the [resolve.alias](https://rspack.rs/config/resolve#resolvealias) config of Rspack.
For TypeScript projects, you only need to configure [compilerOptions.paths](https://www.typescriptlang.org/tsconfig/#paths) in the `tsconfig.json` file. Rstest will automatically recognize it, so there is no need to configure the `resolve.alias` option separately.
## resolve.dedupe [resolve.dedupe](https://rsbuild.rs/config/resolve/dedupe)
Force Rstest to resolve the specified packages from project root, which is useful for deduplicating packages and reducing the bundle size.
## resolve.extensions [resolve.extensions](https://rsbuild.rs/config/resolve/extensions)
Automatically resolve file extensions when importing modules. This means you can import files without explicitly writing their extensions.
## resolve.conditionNames [resolve.conditionNames](https://rsbuild.rs/config/resolve/condition-names)
Control the condition names used when resolving the `exports` field of a package.
## resolve.mainFields [resolve.mainFields](https://rsbuild.rs/config/resolve/main-fields)
Control the priority order of package entry fields such as `main`, `module`, and `browser`.
---
url: /config/build/tools.md
---
# tools
Options for low-level tools.
## tools.bundlerChain [tools.bundlerChain](https://rsbuild.rs/config/tools/bundler-chain)
[rspack-chain](https://github.com/rstackjs/rspack-chain) is a utility library for configuring Rspack. By using `rspack-chain`, you can more easily modify and extend Rspack configurations.
## tools.rspack [tools.rspack](https://rsbuild.rs/config/tools/rspack)
`tools.rspack` is used to configure [Rspack](https://rspack.rs/config/index).
## tools.swc [tools.swc](https://rsbuild.rs/config/tools/swc)
You can set the options of [builtin:swc-loader](https://rspack.rs/guide/features/builtin-swc-loader) through `tools.swc`.
---
url: /config/build/dev.md
---
# dev
Options for local development.
## dev.writeToDisk [dev.writeToDisk](https://rsbuild.rs/config/dev/write-to-disk)
By default, Rstest does not write test temporary files to disk, and this configuration item may be required when you debug rstest outputs.
```ts title="rstest.config.ts"
export default {
dev: {
writeToDisk: true,
},
};
```
---
url: /config/build/performance.md
---
# performance
## performance.buildCache [performance.buildCache](https://rsbuild.rs/config/performance/build-cache)
[Added in v0.10.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.10.0)
- **Type:**
```ts
type BuildCacheConfig =
| boolean
| {
cacheDirectory?: string;
cacheDigest?: Array;
buildDependencies?: string[];
};
```
- **Default:** `false`
Enables Rsbuild persistent build cache for Rstest test builds.
:::tip
Rspack's persistent cache is still experimental and may change across versions, so Rstest does not enable `performance.buildCache` by default.
:::
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
performance: {
buildCache: true,
},
});
```
When enabled, Rstest applies a few Rstest-specific defaults to keep the cache stable across runs:
- `cacheDirectory` defaults to `node_modules/.cache/rstest-` under the active project root, so projects that share the same root still get isolated caches.
- `cacheDigest` automatically includes Rstest runtime inputs such as command, environment name, browser-vs-node mode.
- `buildDependencies` automatically includes the active Rstest config file, project config files, and discovered `tsconfig` paths when available.
You can customize these fields to override or extend the defaults:
- `cacheDirectory`: **override**. When you provide it, Rstest uses your directory.
- `cacheDigest`: **extend**. Rstest keeps its own runtime digest entries and appends your custom values after them.
- `buildDependencies`: **extend**. Rstest keeps its own dependency files and appends your custom files after resolving relative paths from the active config file directory.
For example:
```ts title="rstest.config.ts"
import { defineConfig } from '@rstest/core';
export default defineConfig({
performance: {
buildCache: {
cacheDirectory: '.cache/rstest/browser',
cacheDigest: [process.env.CUSTOM_ENV],
buildDependencies: ['./scripts/test-env.ts'],
},
},
});
```
---
url: /api/runtime-api/index.md
---
# Runtime API Overview
## Test API
### [Expect](/api/runtime-api/test-api/expect.md)
- [expect](/api/runtime-api/test-api/expect.md#expect)
- [expect.element (Browser Mode)](/api/runtime-api/test-api/expect.md#expectelement-browser-mode)
- [expect.not](/api/runtime-api/test-api/expect.md#expectnot)
- [expect.soft](/api/runtime-api/test-api/expect.md#expectsoft)
- [expect.poll](/api/runtime-api/test-api/expect.md#expectpoll)
- [expect.unreachable](/api/runtime-api/test-api/expect.md#expectunreachable)
- [expect.assertions](/api/runtime-api/test-api/expect.md#expectassertions)
- [expect.hasAssertions](/api/runtime-api/test-api/expect.md#expecthasassertions)
- [expect.addEqualityTesters](/api/runtime-api/test-api/expect.md#expectaddequalitytesters)
- [expect.addSnapshotSerializer](/api/runtime-api/test-api/expect.md#expectaddsnapshotserializer)
- [expect.getState / expect.setState](/api/runtime-api/test-api/expect.md#expectgetstate--expectsetstate)
- [Matchers](/api/runtime-api/test-api/expect.md#matchers)
- [Common matchers](/api/runtime-api/test-api/expect.md#common-matchers)
- [Mock matchers](/api/runtime-api/test-api/expect.md#mock-matchers)
- [Snapshot matchers](/api/runtime-api/test-api/expect.md#snapshot-matchers)
- [Promise matchers](/api/runtime-api/test-api/expect.md#promise-matchers)
- [Asymmetric matchers](/api/runtime-api/test-api/expect.md#asymmetric-matchers)
- [Custom matchers](/api/runtime-api/test-api/expect.md#custom-matchers)
### [Test](/api/runtime-api/test-api/test.md)
- [test](/api/runtime-api/test-api/test.md#test)
- [test.only](/api/runtime-api/test-api/test.md#testonly)
- [test.skip](/api/runtime-api/test-api/test.md#testskip)
- [test.todo](/api/runtime-api/test-api/test.md#testtodo)
- [test.each](/api/runtime-api/test-api/test.md#testeach)
- [test.for](/api/runtime-api/test-api/test.md#testfor)
- [test.fails](/api/runtime-api/test-api/test.md#testfails)
- [test.concurrent](/api/runtime-api/test-api/test.md#testconcurrent)
- [test.sequential](/api/runtime-api/test-api/test.md#testsequential)
- [test.runIf](/api/runtime-api/test-api/test.md#testrunif)
- [test.skipIf](/api/runtime-api/test-api/test.md#testskipif)
- [test.extend](/api/runtime-api/test-api/test.md#testextend)
- [Chainable modifiers](/api/runtime-api/test-api/test.md#chainable-modifiers)
- [Types](/api/runtime-api/test-api/test.md#types)
### [Describe](/api/runtime-api/test-api/describe.md)
- [describe](/api/runtime-api/test-api/describe.md#describe)
- [describe.only](/api/runtime-api/test-api/describe.md#describeonly)
- [describe.skip](/api/runtime-api/test-api/describe.md#describeskip)
- [describe.todo](/api/runtime-api/test-api/describe.md#describetodo)
- [describe.each](/api/runtime-api/test-api/describe.md#describeeach)
- [describe.for](/api/runtime-api/test-api/describe.md#describefor)
- [describe.runIf](/api/runtime-api/test-api/describe.md#describerunif)
- [describe.skipIf](/api/runtime-api/test-api/describe.md#describeskipif)
- [describe.concurrent](/api/runtime-api/test-api/describe.md#describeconcurrent)
- [describe.sequential](/api/runtime-api/test-api/describe.md#describesequential)
- [Chainable modifiers](/api/runtime-api/test-api/describe.md#chainable-modifiers)
### [Hooks](/api/runtime-api/test-api/hooks.md)
- [beforeAll](/api/runtime-api/test-api/hooks.md#beforeall)
- [afterAll](/api/runtime-api/test-api/hooks.md#afterall)
- [beforeEach](/api/runtime-api/test-api/hooks.md#beforeeach)
- [afterEach](/api/runtime-api/test-api/hooks.md#aftereach)
- [onTestFinished](/api/runtime-api/test-api/hooks.md#ontestfinished)
- [onTestFailed](/api/runtime-api/test-api/hooks.md#ontestfailed)
## Browser Mode
### [Locator](/api/runtime-api/browser-mode/locator.md)
- [page](/api/runtime-api/browser-mode/locator.md#page)
- [BrowserPage](/api/runtime-api/browser-mode/locator.md#browserpage)
- [Query API](/api/runtime-api/browser-mode/locator.md#query-api)
- [locator](/api/runtime-api/browser-mode/locator.md#locator)
- [getByRole](/api/runtime-api/browser-mode/locator.md#getbyrole)
- [getByText](/api/runtime-api/browser-mode/locator.md#getbytext)
- [getByLabel](/api/runtime-api/browser-mode/locator.md#getbylabel)
- [getByPlaceholder](/api/runtime-api/browser-mode/locator.md#getbyplaceholder)
- [getByAltText](/api/runtime-api/browser-mode/locator.md#getbyalttext)
- [getByTitle](/api/runtime-api/browser-mode/locator.md#getbytitle)
- [getByTestId](/api/runtime-api/browser-mode/locator.md#getbytestid)
- [Configuration API](/api/runtime-api/browser-mode/locator.md#configuration-api)
- [setTestIdAttribute](/api/runtime-api/browser-mode/locator.md#settestidattribute)
- [Composition API](/api/runtime-api/browser-mode/locator.md#composition-api)
- [filter](/api/runtime-api/browser-mode/locator.md#filter)
- [and / or](/api/runtime-api/browser-mode/locator.md#and--or)
- [nth / first / last](/api/runtime-api/browser-mode/locator.md#nth--first--last)
- [Interaction API](/api/runtime-api/browser-mode/locator.md#interaction-api)
- [click](/api/runtime-api/browser-mode/locator.md#click)
- [dblclick](/api/runtime-api/browser-mode/locator.md#dblclick)
- [hover](/api/runtime-api/browser-mode/locator.md#hover)
- [press](/api/runtime-api/browser-mode/locator.md#press)
- [fill](/api/runtime-api/browser-mode/locator.md#fill)
- [clear](/api/runtime-api/browser-mode/locator.md#clear)
- [focus](/api/runtime-api/browser-mode/locator.md#focus)
- [blur](/api/runtime-api/browser-mode/locator.md#blur)
- [check](/api/runtime-api/browser-mode/locator.md#check)
- [uncheck](/api/runtime-api/browser-mode/locator.md#uncheck)
- [scrollIntoViewIfNeeded](/api/runtime-api/browser-mode/locator.md#scrollintoviewifneeded)
- [waitFor](/api/runtime-api/browser-mode/locator.md#waitfor)
- [dispatchEvent](/api/runtime-api/browser-mode/locator.md#dispatchevent)
- [selectOption](/api/runtime-api/browser-mode/locator.md#selectoption)
- [setInputFiles](/api/runtime-api/browser-mode/locator.md#setinputfiles)
- [Usage constraints](/api/runtime-api/browser-mode/locator.md#usage-constraints)
- [Using with expect.element](/api/runtime-api/browser-mode/locator.md#using-with-expectelement)
### [Assertion](/api/runtime-api/browser-mode/assertion.md)
- [Type signature](/api/runtime-api/browser-mode/assertion.md#type-signature)
- [Auto-retry and timeout](/api/runtime-api/browser-mode/assertion.md#auto-retry-and-timeout)
- [Timeout priority](/api/runtime-api/browser-mode/assertion.md#timeout-priority)
- [Failure output](/api/runtime-api/browser-mode/assertion.md#failure-output)
- [Use cases](/api/runtime-api/browser-mode/assertion.md#use-cases)
- [Input constraints](/api/runtime-api/browser-mode/assertion.md#input-constraints)
- [Element assertions](/api/runtime-api/browser-mode/assertion.md#element-assertions)
- [not](/api/runtime-api/browser-mode/assertion.md#not)
- [toBeVisible](/api/runtime-api/browser-mode/assertion.md#tobevisible)
- [toBeHidden](/api/runtime-api/browser-mode/assertion.md#tobehidden)
- [toBeEnabled](/api/runtime-api/browser-mode/assertion.md#tobeenabled)
- [toBeDisabled](/api/runtime-api/browser-mode/assertion.md#tobedisabled)
- [toBeChecked](/api/runtime-api/browser-mode/assertion.md#tobechecked)
- [toBeUnchecked](/api/runtime-api/browser-mode/assertion.md#tobeunchecked)
- [toBeAttached](/api/runtime-api/browser-mode/assertion.md#tobeattached)
- [toBeDetached](/api/runtime-api/browser-mode/assertion.md#tobedetached)
- [toBeEditable](/api/runtime-api/browser-mode/assertion.md#tobeeditable)
- [toBeFocused](/api/runtime-api/browser-mode/assertion.md#tobefocused)
- [toBeEmpty](/api/runtime-api/browser-mode/assertion.md#tobeempty)
- [toBeInViewport](/api/runtime-api/browser-mode/assertion.md#tobeinviewport)
- [toHaveText](/api/runtime-api/browser-mode/assertion.md#tohavetext)
- [toContainText](/api/runtime-api/browser-mode/assertion.md#tocontaintext)
- [toHaveValue](/api/runtime-api/browser-mode/assertion.md#tohavevalue)
- [toHaveId](/api/runtime-api/browser-mode/assertion.md#tohaveid)
- [toHaveClass](/api/runtime-api/browser-mode/assertion.md#tohaveclass)
- [toHaveAttribute](/api/runtime-api/browser-mode/assertion.md#tohaveattribute)
- [toHaveCount](/api/runtime-api/browser-mode/assertion.md#tohavecount)
- [toHaveCSS](/api/runtime-api/browser-mode/assertion.md#tohavecss)
- [toHaveJSProperty](/api/runtime-api/browser-mode/assertion.md#tohavejsproperty)
- [Related API](/api/runtime-api/browser-mode/assertion.md#related-api)
## Rstest Utility
### [Mock modules](/api/runtime-api/rstest/mock-modules.md)
- [rs.mock](/api/runtime-api/rstest/mock-modules.md#rsmock)
- [rs.doMock](/api/runtime-api/rstest/mock-modules.md#rsdomock)
- [rs.mockRequire](/api/runtime-api/rstest/mock-modules.md#rsmockrequire)
- [rs.doMockRequire](/api/runtime-api/rstest/mock-modules.md#rsdomockrequire)
- [rs.unmockRequire](/api/runtime-api/rstest/mock-modules.md#rsunmockrequire)
- [rs.doUnmockRequire](/api/runtime-api/rstest/mock-modules.md#rsdounmockrequire)
- [rs.hoisted](/api/runtime-api/rstest/mock-modules.md#rshoisted)
- [rs.importActual](/api/runtime-api/rstest/mock-modules.md#rsimportactual)
- [rs.importMock](/api/runtime-api/rstest/mock-modules.md#rsimportmock)
- [rs.unmock](/api/runtime-api/rstest/mock-modules.md#rsunmock)
- [rs.doUnmock](/api/runtime-api/rstest/mock-modules.md#rsdounmock)
- [rs.resetModules](/api/runtime-api/rstest/mock-modules.md#rsresetmodules)
### [Mock functions](/api/runtime-api/rstest/mock-functions.md)
- [rstest.fn](/api/runtime-api/rstest/mock-functions.md#rstestfn)
- [rstest.spyOn](/api/runtime-api/rstest/mock-functions.md#rstestspyon)
- [rstest.isMockFunction](/api/runtime-api/rstest/mock-functions.md#rstestismockfunction)
- [rstest.mockObject](/api/runtime-api/rstest/mock-functions.md#rstestmockobject)
- [rstest.mocked](/api/runtime-api/rstest/mock-functions.md#rstestmocked)
- [rstest.clearAllMocks](/api/runtime-api/rstest/mock-functions.md#rstestclearallmocks)
- [rstest.resetAllMocks](/api/runtime-api/rstest/mock-functions.md#rstestresetallmocks)
- [rstest.restoreAllMocks](/api/runtime-api/rstest/mock-functions.md#rstestrestoreallmocks)
- [More](/api/runtime-api/rstest/mock-functions.md#more)
### [Mock instance](/api/runtime-api/rstest/mock-instance.md)
- [getMockName](/api/runtime-api/rstest/mock-instance.md#getmockname)
- [mockName](/api/runtime-api/rstest/mock-instance.md#mockname)
- [mockClear](/api/runtime-api/rstest/mock-instance.md#mockclear)
- [mockReset](/api/runtime-api/rstest/mock-instance.md#mockreset)
- [mockRestore](/api/runtime-api/rstest/mock-instance.md#mockrestore)
- [getMockImplementation](/api/runtime-api/rstest/mock-instance.md#getmockimplementation)
- [mockImplementation](/api/runtime-api/rstest/mock-instance.md#mockimplementation)
- [mockImplementationOnce](/api/runtime-api/rstest/mock-instance.md#mockimplementationonce)
- [withImplementation](/api/runtime-api/rstest/mock-instance.md#withimplementation)
- [mockReturnThis](/api/runtime-api/rstest/mock-instance.md#mockreturnthis)
- [mockReturnValue](/api/runtime-api/rstest/mock-instance.md#mockreturnvalue)
- [mockReturnValueOnce](/api/runtime-api/rstest/mock-instance.md#mockreturnvalueonce)
- [mockResolvedValue](/api/runtime-api/rstest/mock-instance.md#mockresolvedvalue)
- [mockResolvedValueOnce](/api/runtime-api/rstest/mock-instance.md#mockresolvedvalueonce)
- [mockRejectedValue](/api/runtime-api/rstest/mock-instance.md#mockrejectedvalue)
- [mockRejectedValueOnce](/api/runtime-api/rstest/mock-instance.md#mockrejectedvalueonce)
- [mock](/api/runtime-api/rstest/mock-instance.md#mock)
- [mock.calls](/api/runtime-api/rstest/mock-instance.md#mockcalls)
- [mock.instances](/api/runtime-api/rstest/mock-instance.md#mockinstances)
- [mock.contexts](/api/runtime-api/rstest/mock-instance.md#mockcontexts)
- [mock.invocationCallOrder](/api/runtime-api/rstest/mock-instance.md#mockinvocationcallorder)
- [mock.lastCall](/api/runtime-api/rstest/mock-instance.md#mocklastcall)
- [mock.results](/api/runtime-api/rstest/mock-instance.md#mockresults)
- [mock.settledResults](/api/runtime-api/rstest/mock-instance.md#mocksettledresults)
### [Fake timers](/api/runtime-api/rstest/fake-timers.md)
- [rstest.useFakeTimers](/api/runtime-api/rstest/fake-timers.md#rstestusefaketimers)
- [rstest.useRealTimers](/api/runtime-api/rstest/fake-timers.md#rstestuserealtimers)
- [rstest.isFakeTimers](/api/runtime-api/rstest/fake-timers.md#rstestisfaketimers)
- [rstest.setSystemTime](/api/runtime-api/rstest/fake-timers.md#rstestsetsystemtime)
- [rstest.getRealSystemTime](/api/runtime-api/rstest/fake-timers.md#rstestgetrealsystemtime)
- [rstest.runAllTicks](/api/runtime-api/rstest/fake-timers.md#rstestrunallticks)
- [rstest.runAllTimers](/api/runtime-api/rstest/fake-timers.md#rstestrunalltimers)
- [rstest.runAllTimersAsync](/api/runtime-api/rstest/fake-timers.md#rstestrunalltimersasync)
- [rstest.runOnlyPendingTimers](/api/runtime-api/rstest/fake-timers.md#rstestrunonlypendingtimers)
- [rstest.runOnlyPendingTimersAsync](/api/runtime-api/rstest/fake-timers.md#rstestrunonlypendingtimersasync)
- [rstest.advanceTimersByTime](/api/runtime-api/rstest/fake-timers.md#rstestadvancetimersbytime)
- [rstest.advanceTimersByTimeAsync](/api/runtime-api/rstest/fake-timers.md#rstestadvancetimersbytimeasync)
- [rstest.advanceTimersToNextTimer](/api/runtime-api/rstest/fake-timers.md#rstestadvancetimerstonexttimer)
- [rstest.advanceTimersToNextTimerAsync](/api/runtime-api/rstest/fake-timers.md#rstestadvancetimerstonexttimerasync)
- [rstest.advanceTimersToNextFrame](/api/runtime-api/rstest/fake-timers.md#rstestadvancetimerstonextframe)
- [rstest.getTimerCount](/api/runtime-api/rstest/fake-timers.md#rstestgettimercount)
- [rstest.clearAllTimers](/api/runtime-api/rstest/fake-timers.md#rstestclearalltimers)
### [Utilities](/api/runtime-api/rstest/utilities.md)
- [rstest.stubEnv](/api/runtime-api/rstest/utilities.md#rsteststubenv)
- [rstest.unstubAllEnvs](/api/runtime-api/rstest/utilities.md#rstestunstuballenvs)
- [rstest.stubGlobal](/api/runtime-api/rstest/utilities.md#rsteststubglobal)
- [rstest.unstubAllGlobals](/api/runtime-api/rstest/utilities.md#rstestunstuballglobals)
- [rstest.setConfig](/api/runtime-api/rstest/utilities.md#rstestsetconfig)
- [rstest.resetConfig](/api/runtime-api/rstest/utilities.md#rstestresetconfig)
- [rstest.getConfig](/api/runtime-api/rstest/utilities.md#rstestgetconfig)
- [rstest.waitFor](/api/runtime-api/rstest/utilities.md#rstestwaitfor)
- [rstest.waitUntil](/api/runtime-api/rstest/utilities.md#rstestwaituntil)
## Environment Variables
### [Environment Variables](/api/runtime-api/environment-variables.md)
- [`NODE_ENV`](/api/runtime-api/environment-variables.md#node_env)
- [`RSTEST`](/api/runtime-api/environment-variables.md#rstest)
- [`RSTEST_WORKER_ID`](/api/runtime-api/environment-variables.md#rstest_worker_id)
---
url: /api/runtime-api/test-api/expect.md
---
# Expect
`expect` is used to assert values in tests. It provides a rich set of matchers, supports chainable modifiers, soft assertions, polling, and snapshot testing.
You can import the `expect` API from the `@rstest/core` package:
```ts
import { expect, test } from '@rstest/core';
test('should add two numbers correctly', () => {
expect(1 + 1).toBe(2);
expect(1 + 2).toBe(3);
});
```
You can also get the `expect` API from the [test context](/api/runtime-api/test-api/test.md#testcontext), which is helpful for tracing assertion belonging in concurrent tests.
```ts
import { test } from '@rstest/core';
test('should add two numbers correctly', ({ expect }) => {
expect(1 + 1).toBe(2);
expect(1 + 2).toBe(3);
});
```
## expect
- **Type:** `(actual: T, message?: string) => Assertion`
Creates an assertion object for the given value.
- `actual`: The value under test.
- `message`: Optional custom message shown when the assertion fails.
```ts
import { expect } from '@rstest/core';
expect(1 + 1).toBe(2);
expect('hello').toBeDefined();
expect([1, 2, 3]).toContain(2);
```
## expect.element (Browser Mode)
- **Type:** `(locator: Locator) => BrowserElementExpect`
In Browser Mode, `expect.element` is used to assert against a Locator provided by `@rstest/browser` (for example, `toBeVisible`, `toHaveText`, `toHaveValue`).
```ts
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('asserts by locator', async () => {
document.body.innerHTML = 'Save ';
await expect
.element(page.getByRole('button', { name: 'Save' }))
.toBeVisible();
});
```
`expect.element` is only available in Browser Mode and requires importing `@rstest/browser` to install the browser-side adapter. See [Assertion (Browser Mode)](/api/runtime-api/browser-mode/assertion.md) for the complete reference.
## expect.not
Negates the assertion.
```ts
expect(1 + 1).not.toBe(3);
expect('foo').not.toBeUndefined();
```
## expect.soft
- **Type:** `(actual: T, message?: string) => Assertion`
Performs a soft assertion. The test will continue even if the assertion fails, and all failures will be reported at the end.
```ts
expect.soft(1 + 1).toBe(3); // will not stop the test
expect.soft(1 + 2).toBe(4);
```
## expect.poll
- **Type:** `(actual: () => T | Promise, options?: { interval?: number, timeout?: number, message?: string }) => PromiseLike>>`
Repeatedly calls `actual` and retries the matcher until it passes or times out.
- **Default `interval`:** `50` (ms)
- **Default `timeout`:** `1000` (ms)
`options`:
- `interval`: Time between retry attempts (in milliseconds).
- `timeout`: Maximum total polling time before failing (in milliseconds).
- `message`: Custom assertion message shown when the poll assertion fails.
Notes:
- `expect.poll(...)` assertions are asynchronous and **must** be awaited.
- `expect.poll(...)` does not support `resolves`/`rejects`, `toThrow*`, or snapshot matchers. Use `rstest.waitFor()` for those unstable conditions.
```ts
await expect.poll(() => getStatus()).toBe('ready');
await expect
.poll(
async () => {
const response = await fetch('/api/health');
const data = await response.json();
return data.status;
},
{
interval: 100,
timeout: 5000,
message: 'Service should become healthy within 5s',
},
)
.toBe('healthy');
```
## expect.unreachable
- **Type:** `(message?: string) => never`
Marks a code path as unreachable. Throws if called.
```ts
if (shouldNotHappen) {
expect.unreachable('This should never happen');
}
```
## expect.assertions
- **Type:** `(expected: number) => void`
Asserts that a certain number of assertions are called during a test, typically used to verify async code or expected exceptions.
```ts
expect.assertions(1);
try {
await someAsyncFunction();
} catch (e) {
expect(e).toBeInstanceOf(Error);
}
```
## expect.hasAssertions
- **Type:** `() => void`
Asserts that at least one assertion is called during a test.
Useful for tests where assertion count can vary, but at least one assertion must run.
```ts
expect.hasAssertions();
expect(1 + 1).toBe(2);
```
## expect.addEqualityTesters
- **Type:** `(testers: Array) => void`
Adds custom equality testers for use in assertions.
Each tester can return:
- `true`: values are considered equal
- `false`: values are considered not equal
- `undefined`: defer to the next tester/default equality
```ts
expect.addEqualityTesters([
(a, b) => {
if (typeof a === 'number' && typeof b === 'number') {
return Math.abs(a - b) < 0.01;
}
},
]);
expect(0.1 + 0.2).toEqual(0.3); // true with custom tester
```
## expect.addSnapshotSerializer
- **Type:** `(serializer: SnapshotSerializer) => void`
Adds a custom serializer for snapshot testing.
```ts
expect.addSnapshotSerializer({
test: (val) => typeof val === 'string' && val.startsWith('secret:'),
print: (val) => '***MASKED***',
});
expect('secret:123').toMatchSnapshot(); // snapshot will be masked
```
## expect.getState / expect.setState
- **Type:**
- `getState: () => MatcherState`
- `setState: (state: Partial) => void`
Gets or sets the internal matcher state.
```ts
const state = expect.getState();
console.log(state.currentTestName);
expect.setState({ currentTestName: 'custom name' });
console.log(expect.getState().currentTestName); // will output 'custom name'
```
## Matchers
### Common matchers
Common matchers are the primary tools for value, structure, and type assertions.
Equality and identity:
- `toBe(value)`: strict equality with `Object.is`.
- `toEqual(value)`: deep equality for object/array shapes.
- `toStrictEqual(value)`: stricter deep equality (includes `undefined` keys and sparse arrays).
- `toBeOneOf(array)`: value must be one of the provided candidates.
Type and existence:
- `toBeTruthy()` / `toBeFalsy()`: truthiness checks.
- `toBeNull()` / `toBeUndefined()` / `toBeDefined()`: null/undefined checks.
- `toBeNaN()`: `NaN` check.
- `toBeInstanceOf(class)`: instance check.
- `toBeTypeOf(type)`: runtime type check (for example `'string'`).
Numbers:
- `toBeGreaterThan(number)` / `toBeGreaterThanOrEqual(number)`.
- `toBeLessThan(number)` / `toBeLessThanOrEqual(number)`.
- `toBeCloseTo(number, numDigits?)`: floating-point tolerant comparison.
Collections and strings:
- `toContain(item)`: array/string contains a value or substring.
- `toContainEqual(item)`: array contains an item by deep equality.
- `toMatch(stringOrRegExp)`: string matches regex or substring.
- `toMatchObject(object)`: object contains a subset of properties.
- `toHaveLength(length)`: has expected `.length`.
- `toHaveProperty(path, value?)`: has property at path, optionally with expected value.
Custom predicate and errors:
- `toSatisfy(fn)`: passes a user-defined predicate.
- `toThrowError(expected?)`: function throws an error (string/regex/error type supported).
```ts
// toBe vs toEqual
expect(1 + 1).toBe(2);
expect({ id: 1, name: 'a' }).toEqual({ id: 1, name: 'a' });
// strict deep checks
expect({ a: undefined, b: 1 }).toStrictEqual({ a: undefined, b: 1 });
// number checks
expect(0.1 + 0.2).toBeCloseTo(0.3, 5);
expect(10).toBeGreaterThan(5);
// collection checks
expect([1, 2, 3]).toContain(2);
expect([{ id: 1 }]).toContainEqual({ id: 1 });
expect({ user: { role: 'admin' } }).toHaveProperty('user.role', 'admin');
expect({ id: 1, role: 'admin', active: true }).toMatchObject({
id: 1,
role: 'admin',
});
// text and predicates
expect('request id: 42').toMatch(/id:\s\d+/);
expect(21).toSatisfy((n) => n % 3 === 0);
// error assertions (must wrap in a function)
expect(() => {
throw new TypeError('invalid input');
}).toThrowError(TypeError);
```
### Mock matchers
Mock matchers verify interactions with `rstest.fn` / `rstest.spyOn`: calls, call order, return values, and async outcomes.
Call assertions:
- `toHaveBeenCalled()`: called at least once.
- `toHaveBeenCalledTimes(times)`: called exact number of times.
- `toHaveBeenCalledWith(...args)`: called with specified args (any call).
- `toHaveBeenLastCalledWith(...args)`: last call args match.
- `toHaveBeenNthCalledWith(n, ...args)`: nth call args match.
- `toHaveBeenCalledExactlyOnceWith(...args)`: called exactly once with args.
- `toHaveBeenCalledBefore(mock)` / `toHaveBeenCalledAfter(mock)`: relative call order.
Return assertions:
- `toHaveReturned()`: returned at least once.
- `toHaveReturnedTimes(times)`: returned exact number of times.
- `toHaveReturnedWith(value)`: returned specific value in any call.
- `toHaveLastReturnedWith(value)`: last return value matches.
- `toHaveNthReturnedWith(n, value)`: nth return value matches.
Promise resolution assertions:
- `toHaveResolved()`: resolved at least once.
- `toHaveResolvedTimes(times)`: resolved exact number of times.
- `toHaveResolvedWith(value)`: resolved to value in any call.
- `toHaveLastResolvedWith(value)`: last resolved value matches.
- `toHaveNthResolvedWith(n, value)`: nth resolved value matches.
Notes:
- `Nth` matchers are 1-based (`1` = first call).
- `toHaveBeenCalledWith` checks any call; use `Last` or `Nth` variants for strict position checks.
- These matchers require a mock/spied function, not a plain function.
```ts
const sum = rstest.fn((a: number, b: number) => a + b);
sum(1, 2);
sum(2, 3);
expect(sum).toHaveBeenCalledTimes(2);
expect(sum).toHaveBeenNthCalledWith(1, 1, 2);
expect(sum).toHaveBeenLastCalledWith(2, 3);
expect(sum).toHaveReturnedWith(3);
expect(sum).toHaveLastReturnedWith(5);
const fetchUser = rstest.fn(async (id: number) => ({ id, name: 'alice' }));
await fetchUser(1);
await fetchUser(2);
expect(fetchUser).toHaveResolvedTimes(2);
expect(fetchUser).toHaveNthResolvedWith(1, { id: 1, name: 'alice' });
const before = rstest.fn();
const after = rstest.fn();
before();
after();
expect(before).toHaveBeenCalledBefore(after);
```
### Snapshot matchers
Snapshot matchers are used to compare values, errors, or files against previously recorded snapshots, making it easy to track changes in output over time.
- `toMatchSnapshot()`. Compares the value to a saved snapshot.
- `toMatchInlineSnapshot()`. Compares the value to an inline snapshot in the test file.
- `toThrowErrorMatchingSnapshot()`. Checks if a thrown error matches a saved snapshot.
- `toThrowErrorMatchingInlineSnapshot()`. Checks if a thrown error matches an inline snapshot in the test file.
- `toMatchFileSnapshot(filepath)`. Compares the value to a snapshot saved in a specific file.
```ts
expect('hello world').toMatchSnapshot();
expect(() => {
throw new Error('fail');
}).toThrowErrorMatchingSnapshot();
await expect('hello world').toMatchFileSnapshot(
'__snapshots__/file.output.txt',
);
```
### Promise matchers
- `resolves`. Asserts on the resolved value of a Promise.
- `rejects`. Asserts on the rejected value of a Promise.
```ts
await expect(Promise.resolve('ok')).resolves.toBe('ok');
await expect(Promise.reject(new Error('fail'))).rejects.toThrow('fail');
```
### Asymmetric matchers
Asymmetric matchers are helpers that allow for flexible matching of values, such as partial matches, type matches, or pattern matches. They are useful for writing more expressive and less brittle tests.
- `expect.anything()`. Matches any value except null or undefined.
- `expect.any(constructor)`. Matches any value of the given type.
- `expect.closeTo(number, precision?)`. Matches a number close to the expected value.
- `expect.arrayContaining(array)`. Matches an array containing the expected elements.
- `expect.objectContaining(object)`. Matches an object containing the expected properties.
- `expect.stringContaining(string)`. Matches a string containing the expected substring.
- `expect.stringMatching(stringOrRegExp)`. Matches a string matching the expected pattern.
```ts
expect({ a: 1 }).toEqual({ a: expect.anything() });
expect(1).toEqual(expect.any(Number));
expect(0.1 + 0.2).toEqual(expect.closeTo(0.3, 5));
expect([1, 2, 3]).toEqual(expect.arrayContaining([2, 1]));
expect({ a: 1, b: 2 }).toEqual(expect.objectContaining({ a: 1 }));
expect('hello world').toEqual(expect.stringContaining('world'));
expect('hello world').toEqual(expect.stringMatching(/^hello/));
```
### Custom matchers
You can extend expect with custom matchers:
```ts
expect.extend({
toBeDivisibleBy(received, argument) {
const pass = received % argument === 0;
if (pass) {
return {
message: () =>
`expected ${received} not to be divisible by ${argument}`,
pass: true,
};
} else {
return {
message: () => `expected ${received} to be divisible by ${argument}`,
pass: false,
};
}
},
});
expect(10).toBeDivisibleBy(2);
```
If you want to add TypeScript typings for your custom matchers, you can extend the `Assertion` interface in the `@rstest/core` module:
```ts
declare module '@rstest/core' {
interface Assertion {
toBeDivisibleBy(argument: number): void;
}
}
```
---
url: /api/runtime-api/test-api/test.md
---
# Test
`test` defines a test case. It supports chainable modifiers and fixture extension for flexible and powerful test definitions.
Alias: `it`.
## test
- **Type:** `(name: string, fn: (testContext: TestContext) => void | Promise, timeout?: number) => void`
Defines a test case.
```ts
import { expect, test } from '@rstest/core';
test('should add two numbers correctly', () => {
expect(1 + 1).toBe(2);
expect(1 + 2).toBe(3);
});
```
## test.only
Only run certain tests in a test file.
```ts
test.only('run only this test', () => {
// ...
});
```
## test.skip
Skips certain tests.
```ts
test.skip('skip this test', () => {
// ...
});
```
## test.todo
Marks certain tests as todo.
```ts
test.todo('should implement this test');
```
## test.each
- **Type:** `test.each(cases: ReadonlyArray)(name: string, fn: (param: T) => void | Promise, timeout?: number) => void`
Runs the same test logic for each item in the provided array.
```ts
test.each([
{ a: 1, b: 2, sum: 3 },
{ a: 2, b: 2, sum: 4 },
])('adds $a + $b', ({ a, b, sum }) => {
expect(a + b).toBe(sum);
});
```
You can also use a tagged template literal table syntax for more readable parameterized tests:
```ts
test.each`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});
```
The first row defines the parameter names (column headers), and each subsequent row provides the values via template expressions (`${...}`). Columns are separated by `|`.
Since the table values are untyped by default, you can provide an explicit generic type parameter for type safety:
```ts
test.each<{ a: number; b: number; expected: number }>`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});
```
You can inject parameters with [printf formatting](https://nodejs.org/api/util.html#util_util_format_format_args) in the test name in the order of the test function parameters.
- `%s`: String
- `%d`: Number
- `%i`: Integer
- `%f`: Floating point value
- `%j`: JSON
- `%o`: Object
- `%#`: 0-based index of the test case
- `%$`: 1-based index of the test case
- `%%`: Single percent sign ('%')
```ts
test.each([
[1, 2, 3],
[2, 2, 4],
])('adds %i + %i to equal %i', (a, b, sum) => {
expect(a + b).toBe(sum);
});
// this will return
// adds 1 + 2 to equal 3
// adds 2 + 2 to equal 4
```
You can also access object properties and array elements with `$` prefix:
```ts
test.each([
{ a: 1, b: 1, sum: 2 },
{ a: 1, b: 2, sum: 3 },
{ a: 2, b: 1, sum: 3 },
])('adds $a + $b to equal $sum', ({ a, b, sum }) => {
expect(a + b).toBe(sum);
});
// this will return
// adds 1 + 1 to equal 2
// adds 1 + 2 to equal 3
// adds 2 + 1 to equal 3
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add $0 + $1 to equal $2', (a, b, sum) => {
expect(a + b).toBe(sum);
});
// this will return
// add 1 + 1 to equal 2
// add 1 + 2 to equal 3
// add 2 + 1 to equal 3
```
## test.for
- **Type:** `test.for(cases: ReadonlyArray)(name: string, fn: (param: T, testContext: TestContext) => void | Promise, timeout?: number) => void`
Alternative to `test.each` to provide `TestContext`.
```ts
test.for([
{ a: 1, b: 2 },
{ a: 2, b: 2 },
])('adds $a + $b', ({ a, b }, { expect }) => {
expect(a + b).matchSnapshot();
});
```
`test.for` also supports the tagged template literal table syntax:
```ts
test.for`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }, { expect }) => {
expect(a + b).toBe(expected);
});
```
You can provide an explicit generic type parameter for type safety:
```ts
test.for<{ a: number; b: number; expected: number }>`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }, { expect }) => {
expect(a + b).toBe(expected);
});
```
## test.fails
Marks the test as expected to fail.
```ts
test.fails('should fail', () => {
throw new Error('This test is expected to fail');
});
```
## test.concurrent
Runs the test concurrently with consecutive `concurrent` flags.
```ts
describe('suite', () => {
test('serial test', async () => {
/* ... */
});
test.concurrent('concurrent test 1', async () => {
/* ... */
});
test.concurrent('concurrent test 2', async () => {
/* ... */
});
test('serial test 1', async () => {
/* ... */
});
});
```
## test.sequential
Runs the test sequentially (default behavior).
```ts
describe('suite', () => {
test('serial test', async () => {
/* ... */
});
test('serial test 1', async () => {
/* ... */
});
});
```
## test.runIf
Runs the test only if the condition is true.
```ts
test.runIf(process.env.RUN_EXTRA === '1')('conditionally run', () => {
// ...
});
```
## test.skipIf
Skips the test if the condition is true.
```ts
test.skipIf(process.platform === 'win32')('skip on Windows', () => {
// ...
});
```
## test.extend
- **Type:** `test.extend(fixtures: Fixtures)`
Extends the test context with custom fixtures and returns a **new** test API. The original `test` is not modified — you can have multiple independent extended versions at the same time.
Fixtures are reusable context entries that help you prepare test resources once and inject them where needed. Typical uses include:
- Sharing test data and helper clients (for example, API clients, tokens, test users).
- Wrapping setup/teardown logic in one place instead of repeating it in every test.
- Building fixture dependencies (one fixture can consume another fixture).
- Running global-per-test side effects automatically (for example, logging) via `auto` fixtures.
```ts
import { test } from '@rstest/core';
const myTest = test.extend({
user: async ({}, use) => {
await use({ name: 'Alice' });
},
});
// Use myTest (not test) to define tests that need the fixture
myTest('has user in context', ({ user, expect }) => {
expect(user.name).toBe('Alice');
});
// The original test is unaffected and cannot access user
test('plain test', ({ expect }) => {
expect(1).toBe(1);
});
```
The returned API has the same chainable modifiers as `test` (`only`, `skip`, `each`, `concurrent`, etc.) and can call `.extend()` again for further extension.
### Fixture function lifecycle
A fixture function receives two parameters:
1. **context** — contains other fixtures as well as `TestContext` (`task`, `expect`, `onTestFinished`, `onTestFailed`). Use object destructuring to declare the dependencies you need.
2. **use** — call `await use(value)` to pass the fixture value to the test.
Code before `await use(value)` is **setup**; code after it is **teardown** (runs after the test finishes).
```ts
import { test } from '@rstest/core';
const testWithDb = test.extend({
db: async ({}, use) => {
// setup: create connection
const db = await connectTestDb();
await use(db);
// teardown: close connection
await db.close();
},
});
```
### Plain value fixtures
If a fixture does not need setup/teardown logic, you can provide a plain value directly:
```ts
import { test } from '@rstest/core';
const myTest = test.extend({
baseURL: 'https://api.example.com',
});
myTest('uses baseURL', ({ baseURL, expect }) => {
expect(baseURL).toBe('https://api.example.com');
});
```
### Accessing TestContext
The first parameter of a fixture function also includes `TestContext`, so you can read current test information or use `expect` directly inside a fixture:
```ts
import { test } from '@rstest/core';
const myTest = test.extend({
traceId: async ({ task }, use) => {
// task.name comes from TestContext
await use(`trace:${task.name}`);
},
});
```
### Fixture dependencies
A fixture can destructure other fixtures from its first parameter. Rstest automatically initializes them in dependency order and runs teardown in reverse order:
```ts
import { test } from '@rstest/core';
const testWithApi = test.extend({
baseURL: 'https://api.example.com',
token: async ({ baseURL }, use) => {
const token = await createTestToken(baseURL);
await use(token);
await revokeTestToken(token);
},
});
testWithApi('fetch profile', async ({ baseURL, token, expect }) => {
// baseURL → token initialization order is resolved automatically
const res = await fetch(`${baseURL}/profile`, {
headers: { Authorization: `Bearer ${token}` },
});
expect(res.ok).toBe(true);
});
```
### Automatic fixtures (`auto`)
Fixtures are lazy by default: they only run when destructured by the test function (or required by another fixture). To make a fixture run for every test automatically — even when not destructured — use the tuple syntax with `{ auto: true }`:
```ts
import { test } from '@rstest/core';
const events: string[] = [];
const myTest = test.extend({
logger: [
async ({ task }, use) => {
events.push(`start:${task.name}`);
await use(undefined);
events.push(`end:${task.name}`);
},
{ auto: true },
],
});
myTest('runs logger automatically', ({ expect }) => {
// logger is not destructured here, but it still runs because of auto: true
expect(events).toContain('start:runs logger automatically');
});
```
### Type inference and explicit generics
Fixture types are usually inferred automatically. If inference is not precise enough, provide an explicit generic to `test.extend`:
```ts
import { test } from '@rstest/core';
interface MyFixtures {
user: { name: string; role: 'admin' | 'guest' };
}
const myTest = test.extend({
user: async ({}, use) => {
await use({ name: 'Alice', role: 'admin' });
},
});
myTest('typed fixture', ({ user, expect }) => {
// user is typed as { name: string; role: 'admin' | 'guest' }
expect(user.role).toBe('admin');
});
```
Fixture types only take effect on the new API returned by `test.extend`. The type signature of the original `test` remains unchanged.
## Chainable modifiers
`test` supports chainable modifiers, so you can use them together. For example:
- `test.only.runIf(condition)` (or `test.runIf(condition).only`) will only run the test block if the condition is true.
- `test.skipIf(condition).concurrent` (or `test.concurrent.skipIf(condition)`) will skip the test block if the condition is true, otherwise run the tests concurrently.
- `test.runIf(condition).concurrent` (or `test.concurrent.runIf(condition)`) will only run the test block concurrently if the condition is true.
- `test.only.concurrent` (or `test.concurrent.only`) will only run the test block concurrently.
- `test.for(cases).concurrent` (or `test.concurrent.for(cases)`) will run the test block concurrently for each case in the provided array.
- ......
## Types
### TestContext
`TestContext` provides some APIs, context information, and custom fixtures related to the current test.
```ts
export interface TestContext {
/**
* Metadata of the current test
*/
task: {
/**
* A unique identifier for the test.
* The format is `{fileHash}_{suiteIndex}_{testIndex}_...`, for example `419cefd87e_0_0`.
*/
id: string;
/** Test name provided by user */
name: string;
/** Result of the current test, undefined if the test is not run yet */
result?: TestResult;
};
/** The `expect` API bound to the current test */
expect: Expect;
/** The `onTestFinished` hook bound to the current test */
onTestFinished: OnTestFinished;
/** The `onTestFailed` hook bound to the current test */
onTestFailed: OnTestFailed;
}
```
You can also extend `TestContext` with custom fixtures using `test.extend`.
---
url: /api/runtime-api/test-api/describe.md
---
# Describe
`describe` defines a test suite. It supports chainable modifiers and parameterized methods for flexible and organized test grouping.
## describe
- **Type:** `(name: string, fn: () => void | Promise) => void`
Defines a test suite that can contain multiple test cases or nested describe blocks.
```ts
import { describe, test } from '@rstest/core';
describe('math', () => {
test('add', () => {
// ...
});
test('sub', () => {
// ...
});
});
```
## describe.only
Only run the describe block(s) marked with `only`.
```ts
describe.only('only this suite', () => {
// ...
});
```
## describe.skip
Skip the describe block(s) marked with `skip`.
```ts
describe.skip('Skip the test cases in this suite', () => {
// ...
});
```
It should be noted that the skip tag is only used to skip test cases, and the code inside the describe block will still be executed. This is because Rstest needs to collect information about test cases to ensure that all features work properly, even if they are marked as skipped. For example, in snapshot tests, it determines whether a snapshot is outdated or marked as skipped.
```ts
describe.skip('a', () => {
console.log('will run');
test('b', () => {
console.log('will not run');
expect(0).toBe(0);
});
});
```
## describe.todo
Mark a describe block as todo.
```ts
describe.todo('should implement this suite');
```
## describe.each
- **Type:** `describe.each(cases: ReadonlyArray)(name: string, fn: (param: T) => void | Promise) => void`
Creates a describe block for each item in the provided array.
```ts
describe.each([
{ a: 1, b: 2 },
{ a: 2, b: 3 },
])('math $a + $b', ({ a, b }) => {
test('add', () => {
// ...
});
});
```
You can also use a tagged template literal table syntax:
```ts
describe.each`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
test('add', () => {
expect(a + b).toBe(expected);
});
});
```
You can provide an explicit generic type parameter for type safety:
```ts
describe.each<{ a: number; b: number; expected: number }>`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
test('add', () => {
expect(a + b).toBe(expected);
});
});
```
## describe.for
- **Type:** `describe.for(cases: ReadonlyArray)(name: string, fn: (param: T) => void | Promise) => void`
Alternative to `describe.each` for more flexible parameter types.
```ts
describe.for([
[1, 2],
[2, 3],
])('math $0 + $1', ([a, b]) => {
test('add', () => {
// ...
});
});
```
`describe.for` also supports the tagged template literal table syntax:
```ts
describe.for`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
test('add', () => {
expect(a + b).toBe(expected);
});
});
```
You can provide an explicit generic type parameter for type safety:
```ts
describe.for<{ a: number; b: number; expected: number }>`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
test('add', () => {
expect(a + b).toBe(expected);
});
});
```
## describe.runIf
Run the describe block only if the condition is true.
```ts
describe.runIf(process.env.RUN_EXTRA === '1')('conditionally run', () => {
// ...
});
```
## describe.skipIf
Skip the describe block if the condition is true.
```ts
describe.skipIf(process.platform === 'win32')('skip on Windows', () => {
// ...
});
```
## describe.concurrent
Run the tests in the describe block concurrently.
```ts
describe.concurrent('concurrent suite', () => {
test('test 1', async () => {
/* ... */
});
test('test 2', async () => {
/* ... */
});
});
```
## describe.sequential
Run the tests in the describe block sequentially (default behavior).
```ts
describe.sequential('sequential suite', () => {
test('test 1', async () => {
/* ... */
});
test('test 2', async () => {
/* ... */
});
});
```
## Chainable modifiers
`describe` supports chainable modifiers, so you can use them together. For example:
- `describe.only.runIf(condition)` (or `describe.runIf(condition).only`) will only run the describe block if the condition is true.
- `describe.skipIf(condition).concurrent` (or `describe.concurrent.skipIf(condition)`) will skip the describe block if the condition is true, otherwise run the tests concurrently.
- `describe.runIf(condition).concurrent` (or `describe.concurrent.runIf(condition)`) will only run the describe block concurrently if the condition is true.
- `describe.only.concurrent` (or `describe.concurrent.only`) will only run the describe block concurrently.
- ......
---
url: /api/runtime-api/test-api/hooks.md
---
# Hooks
Hooks allow you to run setup and teardown logic before or after your tests or test suites.
## beforeAll
- **Type:** `(fn: (ctx: SuiteContext) => void | Promise, timeout?: number) => void`
Runs once before all tests in the current suite.
```ts
import { beforeAll } from '@rstest/core';
beforeAll(async (ctx) => {
// Setup logic before all tests
// ctx.filepath gives the current test file path
});
```
`beforeAll` also supports returning a function that runs after all tests for cleanup (equivalent to `afterAll`):
```ts
import { beforeAll } from '@rstest/core';
beforeAll(async () => {
const cleanUp = await doSomething();
// Cleanup logic after all tests
return async () => {
await cleanUp();
};
});
```
## afterAll
- **Type:** `(fn: (ctx: SuiteContext) => void | Promise, timeout?: number) => void`
Runs once after all tests in the current suite.
```ts
import { afterAll } from '@rstest/core';
afterAll(async (ctx) => {
// Cleanup logic after all tests
});
```
## beforeEach
- **Type:** `(fn: (ctx: TestContext) => void | Promise, timeout?: number) => void`
Runs before each test in the current suite.
```ts
import { beforeEach } from '@rstest/core';
beforeEach(async () => {
// Setup logic before each test
});
```
`beforeEach` also supports returning a function that runs after each test for cleanup (equivalent to `afterEach`):
```ts
import { beforeEach } from '@rstest/core';
beforeEach(async () => {
const cleanUp = await doSomething();
// Cleanup logic after each test
return async () => {
await cleanUp();
};
});
```
## afterEach
- **Type:** `(fn: (ctx: TestContext) => void | Promise, timeout?: number) => void`
Runs after each test in the current suite.
```ts
import { afterEach } from '@rstest/core';
afterEach(async () => {
// Cleanup logic after each test
});
```
## onTestFinished
- **Type:** `(fn: (ctx: { task: { result: Readonly } }) => void | Promise, timeout?: number) => void`
Called after the test has finished running **whatever the test result is**. This can be used to perform cleanup actions. This hook will be called after `afterEach`.
```ts
import { onTestFinished, test } from '@rstest/core';
test('test server', () => {
const server = startServer();
// Register a cleanup function to close the server after the test
onTestFinished(() => server.close());
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
});
```
It should be noted that when you use the `onTestFinished` hook in concurrent tests, you should get the hook from the test context. This is because Rstest cannot accurately track the specific test to which the global `onTestFinished` hook belongs in concurrent tests.
```ts
describe.concurrent('concurrent suite', () => {
test('test 1', async ({ onTestFinished }) => {
/* ... */
});
test('test 2', async ({ onTestFinished }) => {
/* ... */
});
});
```
## onTestFailed
- **Type:** `(fn: (ctx: { task: { result: Readonly } }) => void | Promise, timeout?: number) => void`
Called after the test has failed.
```ts
import { onTestFailed, test } from '@rstest/core';
test('test server', () => {
const server = startServer();
onTestFailed(({ task }) => {
console.log(task.result?.errors);
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
});
```
It should be noted that when you use the `onTestFailed` hook in concurrent tests, you should get the hook from the test context. This is because Rstest cannot accurately track the specific test to which the global `onTestFailed` hook belongs in concurrent tests.
---
url: /api/runtime-api/browser-mode/index.md
---
# Browser mode
The Browser Mode API is provided by [@rstest/browser](https://github.com/web-infra-dev/rstest/tree/main/packages/browser), designed to let you use a Playwright-style Locator workflow in browser tests.
## Exported API
`@rstest/browser` provides the runtime entry for Browser Mode. Its core responsibility is **locating elements and driving interactions**.
`page` is the query entry point, `Locator` handles chaining and action execution, `BrowserPage` describes the query APIs supported by `page`, and `setTestIdAttribute` configures the test id attribute name.
The following are the APIs currently exported by `@rstest/browser`:
- [`page`](/api/runtime-api/browser-mode/locator.md#page) - `BrowserPage` query entry (queries only)
- [`Locator`](/api/runtime-api/browser-mode/locator.md) - Core class for chained queries and interactions
- [`BrowserPage`](/api/runtime-api/browser-mode/locator.md#browserpage) - Type definition for `page`
- [`setTestIdAttribute`](/api/runtime-api/browser-mode/locator.md#settestidattribute) - Configure the attribute name used by `getByTestId()`
## Assertion API
The assertion entry point is `expect.element(locator)`.
When you import `@rstest/browser` in a Browser Mode test (e.g., `import { page } from '@rstest/browser'`), `expect` automatically gains the `element` capability.
Its responsibility is to perform web-first assertions (with auto-waiting) on a `Locator`, such as `toBeVisible` and `toHaveText`.
The relationship between the two can be understood as: `@rstest/browser` is responsible for **finding and operating on elements**, while `expect.element` is responsible for **verifying element state**.
- [`expect.element(locator)`](/api/runtime-api/browser-mode/assertion.md) - Perform auto-waiting assertions on a `Locator`
## Example: query, interaction, and assertion
```ts title="browser-example.test.ts"
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('submits form', async () => {
await page.getByLabel('Email').fill('dev@rstest.rs');
await page.getByRole('button', { name: 'Submit' }).click();
await expect.element(page.getByText('Done')).toBeVisible();
});
```
In this example, `page` first creates a `Locator`, the `Locator` executes `fill/click`, and finally `expect.element(locator)` performs a visibility assertion on the result, forming a complete test cycle.
## Detailed reference
- [Locator](/api/runtime-api/browser-mode/locator.md)
- [Assertion](/api/runtime-api/browser-mode/assertion.md)
---
url: /api/runtime-api/browser-mode/locator.md
---
# Locator
`Locator` is the core API for element querying and interaction in Browser Mode. You can build query chains via `page.getBy*` or `page.locator()`, then execute interaction actions. Concrete execution semantics are provided by the configured browser `provider`.
:::tip Auto-wait
With the Playwright provider, Locator interaction methods (such as `click`, `fill`, `check`) automatically wait for the element to become actionable (visible, enabled, stable) before executing. You do not need to manually wait for elements before performing actions. This is distinct from the [auto-retry](/api/runtime-api/browser-mode/assertion.md#auto-retry-and-timeout) behavior of `expect.element` assertions.
:::
The following example demonstrates the typical workflow: query elements, perform interactions, and combine with [expect.element](/api/runtime-api/browser-mode/assertion.md) for assertions.
```ts title="src/locator.test.ts"
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('interacts with a form using locator', async () => {
await page.getByLabel('Username').fill('alice');
await page.getByLabel('Password').fill('secret123');
await page.getByRole('button', { name: 'Login' }).click();
await expect.element(page.getByLabel('Username')).toHaveValue('alice');
});
```
## page
- **Type:** `BrowserPage`
`page` is the starting point in tests: first use `page` to locate elements, then execute interactions and assertions on the returned `Locator`.
`page` is a query-only object: it only creates `Locator` instances and does not directly execute interaction actions.
```ts
const submitButton = page.getByRole('button', { name: 'Submit' });
```
The `submitButton` above is a `Locator`. You can continue chaining calls on it (e.g., `.click()`) or pass it to `expect.element(...)` for assertions.
## BrowserPage
- **Type:** `Pick`
`BrowserPage` is the type definition for `page`. It only exposes query entry points and does not include interaction methods like `click`, `fill`, etc.
This means you need to first obtain a `Locator` through `page`, then call interactions and assertions on the `Locator`.
## Query API
All the following APIs return a new `Locator` and support further chaining.
### locator
- **Type:** `(selector: string) => Locator`
Query elements by CSS selector.
```ts
const item = page.locator('.todo-item');
```
### getByRole
- **Type:** `(role: string, options?: LocatorGetByRoleOptions) => Locator`
Query elements by semantic role. Recommended as the first choice.
`LocatorGetByRoleOptions` supports: `name`, `exact`, `checked`, `disabled`, `expanded`, `selected`, `pressed`, `includeHidden`, `level`.
```ts
const saveBtn = page.getByRole('button', { name: 'Save' });
```
### getByText
- **Type:** `(text: string | RegExp, options?: { exact?: boolean }) => Locator`
Query by visible text.
```ts
const successMessage = page.getByText('Saved successfully');
```
### getByLabel
- **Type:** `(text: string | RegExp, options?: { exact?: boolean }) => Locator`
Query by form label.
```ts
const emailInput = page.getByLabel('Email');
```
### getByPlaceholder
- **Type:** `(text: string | RegExp, options?: { exact?: boolean }) => Locator`
Query by placeholder.
```ts
const searchInput = page.getByPlaceholder('Search');
```
### getByAltText
- **Type:** `(text: string | RegExp, options?: { exact?: boolean }) => Locator`
Query by `alt` text.
```ts
const avatarImage = page.getByAltText('User avatar');
```
### getByTitle
- **Type:** `(text: string | RegExp, options?: { exact?: boolean }) => Locator`
Query by `title`.
```ts
const helpIcon = page.getByTitle('Help');
```
### getByTestId
- **Type:** `(text: string | RegExp) => Locator`
Query by test id.
```ts
const settingsPanel = page.getByTestId('settings-panel');
```
## Configuration API
### setTestIdAttribute
- **Type:** `(attribute: string) => Promise`
Set the attribute name used by `getByTestId()`. The default value is `data-testid`.
This configuration is forwarded through the Browser Mode channel to the host provider (e.g., Playwright's `selectors.setTestIdAttribute()`).
This is a global configuration. It is recommended to set it once during the test setup phase to avoid inconsistent query behavior caused by mid-test modifications.
```ts
import { page, setTestIdAttribute } from '@rstest/browser';
await setTestIdAttribute('data-test');
await page.getByTestId('settings-panel').click();
```
:::tip
It is recommended to configure `setTestIdAttribute` in a setup file to ensure it applies to all tests consistently:
```ts title="rstest.setup.ts"
import { setTestIdAttribute } from '@rstest/browser';
await setTestIdAttribute('data-qa');
```
Then reference it in your config:
```ts title="rstest.config.ts"
export default defineConfig({
setupFiles: ['./rstest.setup.ts'],
});
```
:::
## Composition API
### filter
- **Type:** `(options: LocatorFilterOptions) => Locator`
`LocatorFilterOptions` supports:
- `hasText?: string | RegExp`: Keep elements matching the text
- `hasNotText?: string | RegExp`: Exclude elements matching the text
- `has?: Locator`: Keep elements containing the child Locator
- `hasNot?: Locator`: Exclude elements containing the child Locator
Used for secondary filtering on existing query results.
```ts
const profileSave = page
.locator('section')
.filter({ has: page.getByRole('heading', { name: 'Profile' }) })
.getByRole('button', { name: 'Save' });
```
```ts
const visibleItems = page.locator('li').filter({
hasNotText: /archived/i,
hasNot: page.getByRole('img', { name: 'Locked' }),
});
```
### and / or
- **Type:** `(other: Locator) => Locator`
Combine two Locator conditions.
```ts
const byRole = page.getByRole('button', { name: 'Submit' });
const byId = page.locator('#submit');
const exactOne = byRole.and(byId);
```
### nth / first / last
- **Type:**
- `nth(index: number): Locator`
- `first(): Locator`
- `last(): Locator`
Select a specific element from the matched set.
## Interaction API
The following APIs trigger actual browser actions and return `Promise`.
:::warning Strictness
Locators are strict. If a Locator interaction action (like `click` or `fill`) resolves to more than one element, the operation will throw an error. Use [`first()`](#nth--first--last), [`last()`](#nth--first--last), or [`nth()`](#nth--first--last) to explicitly select a single element when the query matches multiple elements.
:::
These `*Options` types represent the set of options currently supported in Browser Mode:
- `LocatorClickOptions` / `LocatorDblclickOptions`: `button`, `delay`, `force`, `modifiers`, `position`, `timeout`, `trial` (`click` additionally supports `clickCount`)
- `LocatorHoverOptions`: `force`, `modifiers`, `position`, `timeout`, `trial`
- `LocatorPressOptions`: `delay`, `timeout`
- `LocatorFillOptions`: `force`, `timeout`
- `LocatorCheckOptions`: `force`, `position`, `timeout`, `trial`
- `LocatorFocusOptions` / `LocatorBlurOptions` / `LocatorScrollIntoViewIfNeededOptions`: `timeout`
- `LocatorWaitForOptions`: `state` (`attached` / `detached` / `visible` / `hidden`) and `timeout`
- `LocatorSelectOptionOptions`: `force`, `timeout`
- `LocatorSetInputFilesOptions`: `timeout`
### click
- **Type:** `(options?: LocatorClickOptions) => Promise`
Click the element matched by the current Locator.
```ts
await page.getByRole('button', { name: 'Submit' }).click();
```
### dblclick
- **Type:** `(options?: LocatorDblclickOptions) => Promise`
Double-click the element.
```ts
await page.getByText('Open details').dblclick();
```
### hover
- **Type:** `(options?: LocatorHoverOptions) => Promise`
Hover the mouse over the element, commonly used to trigger hover menus or tooltips.
```ts
await page.getByRole('button', { name: 'More' }).hover();
```
### press
- **Type:** `(key: string, options?: LocatorPressOptions) => Promise`
Send a keyboard key press on the element.
```ts
await page.getByLabel('Search').press('Enter');
```
### fill
- **Type:** `(value: string, options?: LocatorFillOptions) => Promise`
Set the value of an input field. Applicable to `input`, `textarea`, and other editable elements.
```ts
await page.getByPlaceholder('Email').fill('dev@rstest.rs');
```
### clear
- **Type:** `() => Promise`
Clear the value of the current input element.
```ts
await page.getByPlaceholder('Email').clear();
```
### focus
- **Type:** `(options?: LocatorFocusOptions) => Promise`
Focus the element.
```ts
await page.getByLabel('Username').focus();
```
### blur
- **Type:** `(options?: LocatorBlurOptions) => Promise`
Remove focus from the element.
```ts
await page.getByLabel('Username').blur();
```
### check
- **Type:** `(options?: LocatorCheckOptions) => Promise`
Check a checkbox or radio button.
```ts
await page.getByLabel('Agree').check();
```
### uncheck
- **Type:** `(options?: LocatorCheckOptions) => Promise`
Uncheck a checkbox.
```ts
await page.getByLabel('Agree').uncheck();
```
### scrollIntoViewIfNeeded
- **Type:** `(options?: LocatorScrollIntoViewIfNeededOptions) => Promise`
Scroll the page if needed to bring the element into the visible area.
```ts
await page.getByRole('button', { name: 'Load more' }).scrollIntoViewIfNeeded();
```
### waitFor
- **Type:** `(options?: LocatorWaitForOptions) => Promise`
Wait until the Locator meets the specified condition before continuing execution. Useful for handling async rendering.
```ts
await page.getByText('Ready').waitFor();
```
### dispatchEvent
- **Type:** `(type: string, eventInit?: LocatorDispatchEventInit) => Promise`
Dispatch a custom or native event on the element.
```ts
await page.getByRole('button', { name: 'Event' }).dispatchEvent('custom');
```
### selectOption
- **Type:** `(value: string | string[], options?: LocatorSelectOptionOptions) => Promise`
Select an option of a `select` element. Currently only supports `string` or `string[]` as the value.
```ts
await page.getByLabel('Choice').selectOption('b');
```
### setInputFiles
- **Type:** `(files: string | string[], options?: LocatorSetInputFilesOptions) => Promise`
Set files for an `input[type="file"]`. Currently only supports file paths as `string` or `string[]`.
```ts
await page.locator('#upload').setInputFiles('/tmp/demo.txt');
```
## Usage constraints
- The APIs listed on this page represent the currently supported subset of the Playwright Locator API. APIs not listed here are not yet available
- The argument to `and` / `or` / `filter({ has | hasNot })` must be a `Locator` returned by `@rstest/browser`
- The `type` argument to `dispatchEvent(type, eventInit?)` must be a non-empty string
- `selectOption` currently only supports `string` or `string[]`
- `setInputFiles` currently only supports file paths as `string` or `string[]`
- Some parameters are transmitted through the Browser Mode communication channel; it is recommended to keep them JSON-serializable
## Using with expect.element
`Locator` is typically used together with `expect.element(locator)`. For the full list of assertions, see [Assertion](/api/runtime-api/browser-mode/assertion.md).
---
url: /api/runtime-api/browser-mode/assertion.md
---
# Assertion
`expect.element` is the API for [Locator](/api/runtime-api/browser-mode/locator.md) assertions in Browser Mode. It accepts a `Locator` and returns a chainable assertion object. Unlike `expect(value)` which primarily compares JS values, `expect.element` targets the real element state on the page, suitable for verifying visibility, text, form values, count, and other user-observable results.
:::tip Auto-retry
With the Playwright provider, all `expect.element` matchers automatically retry until the assertion passes or the timeout is reached. You do not need to write manual `waitFor` or polling logic — just assert the expected state and the framework handles the waiting.
:::
When you import `@rstest/browser` in a Browser Mode test (e.g., importing `page`), `expect` automatically gains the `element` capability. This allows queries, interactions, and assertions to be organized around the same `Locator`, focusing failure messages on element state.
The following minimal example demonstrates common usage: first assert visibility, then verify a checked state change, and finally validate an element attribute.
```ts title="src/assertion.test.ts"
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';
test('asserts element states in browser mode', async () => {
await expect
.element(page.getByRole('button', { name: 'Save' }))
.toBeVisible();
await expect.element(page.getByLabel('Agree')).toBeUnchecked();
await page.getByLabel('Agree').check();
await expect.element(page.getByLabel('Agree')).toBeChecked();
await expect
.element(page.getByRole('button', { name: 'Save' }))
.toHaveAttribute('id', 'save-btn');
});
```
## Type signature
```ts
expect.element(locator: Locator): BrowserElementExpect
```
## Auto-retry and timeout
Assertion behavior in `expect.element` depends on the provider. With the Playwright provider, `expect.element` matchers are **web-first assertions**: they continuously retry within the timeout duration until the assertion passes or times out. You do not need to write manual `waitFor` or polling logic.
### Timeout priority
The assertion timeout is determined by the following priority:
1. **Per-assertion timeout**: The `timeout` parameter passed directly to the matcher, highest priority
2. **Global `testTimeout` configuration**: From `testTimeout` in `rstest.config.ts` (default 5000ms)
3. **RPC fallback timeout**: 30000ms (serves only as a communication-layer safety net, usually not reached)
```ts
// Uses global testTimeout (default 5000ms)
await expect.element(page.getByText('Done')).toBeVisible();
// Override with a longer timeout
await expect.element(page.getByText('Done')).toBeVisible({ timeout: 10000 });
```
### Failure output
When an assertion times out, the error message includes: the expected element state, the actual state, and the timeout duration. For example:
```
Error: Expected element to be visible
Locator: getByText('Loading complete')
Timeout: 5000ms
```
### Use cases
Auto-retry is especially useful for handling async rendering scenarios — such as waiting for a loading indicator to disappear or UI changes after data finishes loading:
```ts
// Click and wait for async result to appear
await page.getByRole('button', { name: 'Submit' }).click();
await expect.element(page.getByText('Submitted!')).toBeVisible();
// Wait for loading to disappear
await expect.element(page.getByTestId('spinner')).toBeHidden();
```
## Input constraints
- `locator` must be a `Locator` returned by `@rstest/browser`. This allows the runtime to recognize and replay the complete query chain; hand-crafted objects or locators from other libraries cannot be parsed by this mechanism.
- `expect.element` is not available outside of Browser Mode. It relies on Browser Mode's browser runtime and communication channel to execute element assertions; pure Node test environments only support regular `expect(value)` assertions.
## Element assertions
The matchers listed below represent the currently supported subset. Matchers not listed here are not yet available.
### not
- **Type:** `BrowserElementExpect`
Negates the subsequent assertion.
```ts
await expect
.element(page.getByRole('button', { name: 'Submit' }))
.not.toBeDisabled();
```
All the following matchers support an optional `options?: { timeout?: number }` parameter (unless the type signature states otherwise).
### toBeVisible
- **Type:** `(options?: { timeout?: number }) => Promise`
The `Locator` resolves to a mounted and visible element within the `timeout`.
Visibility is determined by: the element has a non-empty bounding box and `visibility` is not `hidden`. For example, `display: none` or zero dimensions are not considered visible; `opacity: 0` is still considered visible.
```ts
await expect.element(page.getByRole('button', { name: 'Save' })).toBeVisible();
```
### toBeHidden
- **Type:** `(options?: { timeout?: number }) => Promise`
The `Locator` meets any of the following conditions within the `timeout`: does not match any DOM node, or matches a non-visible node.
Think of it as the opposite of `toBeVisible`; for example, setting only `opacity: 0` typically will not cause `toBeHidden` to pass.
```ts
await expect.element(page.getByTestId('loading')).toBeHidden();
```
### toBeEnabled
- **Type:** `(options?: { timeout?: number }) => Promise`
The element is not in a disabled state.
Disabled determination: a native form control with the `disabled` attribute, within a disabled `fieldset`, or within an `aria-disabled=true` semantic context may all be considered disabled.
```ts
await expect
.element(page.getByRole('button', { name: 'Submit' }))
.toBeEnabled();
```
### toBeDisabled
- **Type:** `(options?: { timeout?: number }) => Promise`
The element is determined to be disabled.
The determination rules are the same as `toBeEnabled`, just with the opposite result. Recommended for scenarios like button submission and preventing duplicate clicks.
```ts
await expect
.element(page.getByRole('button', { name: 'Submit' }))
.toBeDisabled();
```
### toBeChecked
- **Type:** `(options?: { timeout?: number }) => Promise`
The `checked` state of a checkbox or radio is `true`.
Commonly used to verify user check actions or whether a default checked state has taken effect.
```ts
await expect.element(page.getByLabel('Agree')).toBeChecked();
```
### toBeUnchecked
- **Type:** `(options?: { timeout?: number }) => Promise`
The `checked` state of a checkbox or radio is `false`.
Suitable for use with `check` / `uncheck` to verify state changes before and after interaction.
```ts
await expect.element(page.getByLabel('Agree')).toBeUnchecked();
```
### toBeAttached
- **Type:** `(options?: { timeout?: number }) => Promise`
The node pointed to by the `Locator` is connected to a `Document` or `ShadowRoot` (equivalent to `Node.isConnected === true`).
This assertion only checks "whether it is in the DOM tree" and does not require the element to be visible.
```ts
await expect.element(page.locator('#toast')).toBeAttached();
```
### toBeDetached
- **Type:** `(options?: { timeout?: number }) => Promise`
The `Locator` no longer points to a connected DOM node.
Common for verification after conditional rendering, async unmounting, or delete operations.
```ts
await expect.element(page.locator('#toast')).toBeDetached();
```
### toBeEditable
- **Type:** `(options?: { timeout?: number }) => Promise`
The element is both enabled and not readonly.
Readonly determination includes both the native `readonly` attribute and `aria-readonly=true` semantics.
```ts
await expect.element(page.getByLabel('Bio')).toBeEditable();
```
### toBeFocused
- **Type:** `(options?: { timeout?: number }) => Promise`
The element is the current document's focus target (active element).
Suitable for verifying keyboard navigation, auto-focus, or form focus-switching behavior.
```ts
await expect.element(page.getByLabel('Username')).toBeFocused();
```
### toBeEmpty
- **Type:** `(options?: { timeout?: number }) => Promise`
An editable element's content is empty, or a regular DOM node has no text content.
It checks "whether the content is empty", not "whether the element exists" or "whether it is visible".
```ts
await expect.element(page.locator('#empty-state')).toBeEmpty();
```
### toBeInViewport
- **Type:** `(options?: { timeout?: number; ratio?: number }) => Promise`
The element intersects with the viewport (based on Intersection Observer semantics).
`ratio` represents the minimum intersection ratio; for example, `ratio: 0.5` means at least half of the area is within the viewport.
```ts
await expect.element(page.locator('#hero')).toBeInViewport({ ratio: 0.5 });
```
### toHaveText
- **Type:** `(text: string | RegExp, options?: { timeout?: number }) => Promise`
The element's text fully matches the expected value (supports `string` / `RegExp`).
Text computation includes nested child element content. When the expected value is a `string`, whitespace and line breaks are normalized before matching.
```ts
await expect.element(page.getByRole('status')).toHaveText('Saved');
```
### toContainText
- **Type:** `(text: string | RegExp, options?: { timeout?: number }) => Promise`
The element's text contains the expected substring, or matches the given regex.
The difference from `toHaveText` is: `toContainText` does substring matching, while `toHaveText` does full matching.
```ts
await expect.element(page.getByRole('status')).toContainText('Save');
```
### toHaveValue
- **Type:** `(value: string | RegExp, options?: { timeout?: number }) => Promise`
The form control's current `value` matches the expected value (supports `string` / `RegExp`).
Applicable to `input`, `textarea`, `select`, and other elements with retrievable values.
```ts
await expect.element(page.getByLabel('Email')).toHaveValue('dev@rstest.rs');
```
### toHaveId
- **Type:** `(value: string | RegExp, options?: { timeout?: number }) => Promise`
The element's `id` matches the expected value (supports `string` / `RegExp`).
Suitable for validating dynamically generated IDs or fixed IDs injected after component mounting.
```ts
await expect
.element(page.getByRole('button', { name: 'Save' }))
.toHaveId('save-btn');
```
### toHaveClass
- **Type:** `(value: string | RegExp, options?: { timeout?: number }) => Promise`
The element's `class` attribute matches the expected value (supports `string` / `RegExp`).
When passing a `string`, it matches against the entire `class` string. If you only care about a specific class, consider using a more targeted regex.
```ts
await expect.element(page.getByRole('alert')).toHaveClass(/error/);
```
### toHaveAttribute
- **Type:**
- `(name: string, options?: { timeout?: number }) => Promise`
- `(name: string, value: string | RegExp, options?: { timeout?: number }) => Promise`
When only `name` is passed, it asserts the attribute exists. When `name + value` is passed, it asserts the attribute value matches (`value` supports `string` / `RegExp`).
Common use cases include validating structural attributes like `type`, `disabled`, `aria-*`, etc.
```ts
await expect
.element(page.getByRole('button', { name: 'Save' }))
.toHaveAttribute('type');
await expect
.element(page.getByRole('button', { name: 'Save' }))
.toHaveAttribute('type', 'submit');
```
### toHaveCount
- **Type:** `(count: number, options?: { timeout?: number }) => Promise`
The number of elements resolved by the `Locator` exactly matches `count`.
Suitable for list rendering, filter results, pagination item counts, and similar scenarios.
```ts
await expect.element(page.getByRole('listitem')).toHaveCount(3);
```
### toHaveCSS
- **Type:** `(name: string, value: string | RegExp, options?: { timeout?: number }) => Promise`
The specified CSS property value in the element's computed style matches the expected value.
`name` must be a non-empty string; `value` supports `string` / `RegExp`.
```ts
await expect.element(page.getByRole('alert')).toHaveCSS('display', 'block');
```
### toHaveJSProperty
- **Type:** `(name: string, value: unknown, options?: { timeout?: number }) => Promise`
The JS property on the element matches the expected value.
`name` must be a non-empty string; `value` must be JSON-serializable (assertion parameters are transmitted through the Browser Mode channel).
```ts
await expect
.element(page.getByLabel('Agree'))
.toHaveJSProperty('checked', true);
```
## Related API
- [Locator](/api/runtime-api/browser-mode/locator.md)
- [Page (Locator entry point)](/api/runtime-api/browser-mode/locator.md#page)
- [Expect](/api/runtime-api/test-api/expect.md)
---
url: /api/runtime-api/rstest/index.md
---
# Rstest utility
Rstest provides utility functions to help you out through its `rstest` helper.
You can import it from `@rstest/core` directly, and you can also use its alias `rs`.
```ts
import { rstest } from '@rstest/core';
const fn = rstest.fn();
fn.mockResolvedValue('foo');
```
```ts
import { rs } from '@rstest/core';
const fn = rs.fn();
fn.mockResolvedValue('foo');
```
Or, you can access it globally like `jest` (when [globals](/config/test/globals.md) configuration is enabled).
---
url: /api/runtime-api/rstest/mock-modules.md
---
# Mock modules
Rstest supports mocking modules, which allows you to replace the implementation of modules in tests. Rstest provides utility functions in `rs` (`rstest`) for mocking modules. You can directly use the following methods to mock modules:
## rs.mock
- **Type**: `(moduleName: string | Promise, factoryOrOptions?: (() => Promise> | Partial) | { spy: true } | { mock: true }) => void`
Mocks and replaces the module specified in the first parameter.
:::tip Hoisting
`rs.mock` is hoisted to the top of the current module, so even if you execute `import fn from 'some_module'` before calling `rs.mock('some_module')`, `some_module` will be mocked from the beginning.
:::
### With factory function
If a factory function is provided as the second parameter, the module will be replaced with the return value of the factory function.
#### Basic example
```ts title="src/sum.test.ts"
import { sum } from './sum';
rs.mock('./sum', () => {
return {
sum: (a: number, b: number) => a + b + 100,
};
});
expect(sum(1, 2)).toBe(103); // PASS
```
```ts title="src/sum.ts"
export const sum = (a: number, b: number) => a + b;
```
#### With `rs.fn()` for call tracking
Use `rs.fn()` to create mock functions that can track calls and configure return values:
```ts title="src/api.test.ts"
import { expect, rs, test } from '@rstest/core';
import { fetchUser, fetchPosts } from './api';
rs.mock('./api', () => ({
fetchUser: rs.fn().mockResolvedValue({ id: 1, name: 'John' }),
fetchPosts: rs.fn().mockResolvedValue([{ id: 1, title: 'Hello' }]),
}));
test('fetch user data', async () => {
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'John' });
expect(fetchUser).toHaveBeenCalledWith(1);
});
```
#### With `rs.mockObject()` for auto-mocking
Use `rs.mockObject()` to automatically mock all properties of an object:
```ts title="src/service.test.ts"
import { expect, rs, test } from '@rstest/core';
import { userService } from './userService';
rs.mock('./userService', () => ({
// Auto-mock all methods, they will return undefined and track calls
userService: rs.mockObject({
getUser: () => {},
updateUser: () => {},
deleteUser: () => {},
}),
}));
test('service methods are mocked', () => {
userService.getUser(1);
expect(userService.getUser).toHaveBeenCalledWith(1);
});
```
#### Partial mock with `importActual`
Use [import attributes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with) `with { rstest: 'importActual' }` to load the original module, then combine with `rs.mock` to keep some original implementations:
```ts title="src/utils.test.ts"
import { expect, rs, test } from '@rstest/core';
import * as dateUtils from './dateUtils' with { rstest: 'importActual' };
import { formatDate, parseDate } from './dateUtils';
rs.mock('./dateUtils', () => ({
...dateUtils,
// Only mock formatDate, keep others
formatDate: rs.fn().mockReturnValue('2024-01-01'),
}));
test('formatDate is mocked, parseDate is real', () => {
expect(formatDate(new Date())).toBe('2024-01-01');
// parseDate uses original implementation
expect(parseDate('2024-01-01')).toBeInstanceOf(Date);
});
```
### With `__mocks__` directory
If `rs.mock` is called without providing a factory function, it will attempt to resolve a module with the same name in the `__mocks__` directory at the same level as the mocked module.
**Resolution rules:**
1. **Local modules**: If there is a `__mocks__` folder at the same level as the file being mocked containing a file with the same name, Rstest will use that file as the mock implementation.
2. **npm dependencies**: If there is a `__mocks__` folder in the root directory containing a file with the same name as the mocked module, Rstest will use that file as the mock implementation.
3. **Node.js built-in modules**: If there is a `__mocks__` folder in the root directory containing a file with the same name as the built-in module (e.g., `__mocks__/fs.mjs`, `__mocks__/path.ts`), Rstest will use that file. The `node:` prefix will be ignored.
**Example:**
```txt
├── __mocks__
│ └── lodash.js
├── src
│ ├── multiple.ts
│ └── __mocks__
│ └── multiple.ts
└── __test__
└── multiple.test.ts
```
```ts title="src/multiple.test.ts"
import { rs } from '@rstest/core';
// lodash is a default export from `__mocks__/lodash.js`
import lodash from 'lodash';
// multiple is a named export from `src/__mocks__/multiple.ts`
import { multiple } from '../src/multiple';
rs.mock('lodash');
rs.mock('../src/multiple');
lodash.random(multiple(1, 2), multiple(3, 4));
```
### With `{ spy: true }` option
If `{ spy: true }` is provided as the second parameter, the module will be auto-mocked but the original implementations will be preserved. All exports will be wrapped in spy functions that track calls while still executing the original code.
This is useful when you want to assert that a function was called correctly without replacing its implementation.
```ts title="src/calculator.test.ts"
import { expect, rs, test } from '@rstest/core';
import { calculate, add } from './calculator';
rs.mock('./calculator', { spy: true });
test('calculate calls add with correct arguments', () => {
// Original implementation still works
const result = calculate(1, 2);
expect(result).toBe(3);
// But we can also assert on the calls
expect(add).toHaveBeenCalledWith(1, 2);
expect(add).toHaveReturnedWith(3);
});
```
:::note ESM and CommonJS modules
- **ESM modules**: All named exports are wrapped in spy functions.
- **CommonJS modules**: In addition to wrapping exports, a `default` export is automatically added (pointing to the module itself) to preserve `import x from 'cjs-module'` behavior.
:::
### With `{ mock: true }` option
If `{ mock: true }` is provided as the second parameter, the module will be auto-mocked with all function exports replaced by mock functions. Unlike `{ spy: true }`, the original implementations are **not** preserved - mock functions return `undefined` by default.
This is useful when you want to completely replace a module's behavior and configure mock return values or implementations.
```ts title="src/math.test.ts"
import { expect, rs, test } from '@rstest/core';
import { add, multiply } from './math';
rs.mock('./math', { mock: true });
test('mock functions return undefined by default', () => {
// Original implementation is NOT preserved
expect(add(1, 2)).toBeUndefined();
expect(multiply(3, 4)).toBeUndefined();
// Functions are mock functions
expect(rs.isMockFunction(add)).toBe(true);
});
test('can configure mock implementations', () => {
// Configure return values
rs.mocked(add).mockReturnValue(100);
expect(add(1, 2)).toBe(100);
// Configure implementations
rs.mocked(multiply).mockImplementation((a, b) => a * b * 2);
expect(multiply(3, 4)).toBe(24);
});
```
### Type enhancement with `Promise`
`rs.mock` supports passing a `Promise` (via dynamic import) as the first parameter to get better type hints in IDEs. This only enhances type hints and has no impact on the module mocking capabilities.
```ts
// Compared to rs.mock('../src/b', ...) the type is enhanced.
rs.mock(import('../src/b'), () => {
return {
b: 222,
};
});
```
## rs.doMock
- **Type**: `(moduleName: string | Promise, factoryOrOptions?: (() => Promise> | Partial) | { spy: true } | { mock: true }) => void`
Similar to `rs.mock`, but it is **not hoisted** to the top of the module. It is called when it's executed, which means that if a module has already been imported before calling `rs.doMock`, that module will not be mocked, while modules imported after calling `rs.doMock` will be mocked.
Supports the same options as `rs.mock`: factory function, `__mocks__` directory, `{ spy: true }`, and `{ mock: true }`.
```ts title="src/sum.test.ts"
import { rs } from '@rstest/core';
import { sum } from './sum';
it('test', async () => {
// sum is imported before executing doMock, it's not mocked yet
expect(sum(1, 2)).toBe(3); // PASS
rs.doMock('./sum');
const { sum: mockedSum } = await import('./sum');
// sum is imported after executing doMock, it's mocked now
expect(mockedSum(1, 2)).toBe(3); // FAILED
});
```
## rs.mockRequire
- **Type**: `(moduleName: string, factoryOrOptions?: (() => T) | { spy: true } | { mock: true }) => void`
Mocks modules loaded through CommonJS `require()`. Like `rs.mock`, this API is hoisted to the top of the current module.
Use this API when the target module is consumed via `require()` instead of `import`.
:::tip
Difference from `rs.mock` in dual packages (one ESM entry and one CJS entry):
- `rs.mock()` mocks the **ESM entry** (used by `import`)
- `rs.mockRequire()` mocks the **CJS entry** (used by `require()`)
If your code path uses `require()`, prefer `rs.mockRequire()` to avoid mocking the wrong entry.
:::
```ts title="src/math.test.cjs"
const { sum } = require('./math.cjs');
rs.mockRequire('./math.cjs', () => ({
sum: (a, b) => a + b + 100,
}));
test('mock cjs module with require', () => {
expect(sum(1, 2)).toBe(103);
});
```
## rs.doMockRequire
- **Type**: `(moduleName: string, factoryOrOptions?: (() => T) | { spy: true } | { mock: true }) => void`
Similar to `rs.mockRequire`, but it is **not hoisted**. The mock is applied only after `rs.doMockRequire` is executed, and only affects subsequent `require()` calls.
```ts title="src/math.test.cjs"
test('doMockRequire only affects later require calls', () => {
const { sum } = require('./math.cjs');
expect(sum(1, 2)).toBe(3);
rs.doMockRequire('./math.cjs', () => ({
sum: (a, b) => a + b + 100,
}));
const { sum: mockedSum } = require('./math.cjs');
expect(mockedSum(1, 2)).toBe(103);
});
```
## rs.unmockRequire
- **Type**: `(path: string) => void`
Cancels the mock implementation for modules loaded through `require()`. Like `rs.mockRequire`, this call is hoisted to the top of the file.
Use this API when you want later `require()` calls to load the original CommonJS module again.
```ts title="src/math.test.cjs"
const { sum } = require('./math.cjs');
rs.mockRequire('./math.cjs', () => ({
sum: (a, b) => a + b + 100,
}));
rs.unmockRequire('./math.cjs');
test('unmockRequire restores the original CommonJS module', () => {
expect(sum(1, 2)).toBe(3);
});
```
## rs.doUnmockRequire
- **Type**: `(path: string) => void`
Same as `rs.unmockRequire`, but it is not hoisted. Only later `require()` calls will load the original module again.
```ts title="src/math.test.cjs"
test('doUnmockRequire only affects later require calls', () => {
rs.doMockRequire('./math.cjs', () => ({
sum: (a, b) => a + b + 100,
}));
const { sum: mockedSum } = require('./math.cjs');
expect(mockedSum(1, 2)).toBe(103);
rs.doUnmockRequire('./math.cjs');
const { sum } = require('./math.cjs');
expect(sum(1, 2)).toBe(3);
});
```
## rs.hoisted
- **Type**: `(fn: () => T) => T`
`rs.hoisted` is a helper function that allows you to create values that can be accessed in hoisted functions like `rs.mock` factory functions. Like `rs.mock`, `rs.hoisted` is also hoisted to the top of the module, and it provides access to the `rs` utilities within the hoisted scope.
This is useful when you need to create mock functions or values that should be shared between the mock factory function and your test code.
```ts title="src/sum.test.ts"
import { expect, it, rs } from '@rstest/core';
import { foo } from './sum';
// `rs` utilities can be accessed in hoisted function.
const mocks = rs.hoisted(() => {
return {
hoistedFn: rs.fn(),
};
});
rs.mock('./sum', () => {
return { foo: mocks.hoistedFn };
});
it('hoisted', () => {
mocks.hoistedFn(42);
expect(mocks.hoistedFn).toHaveBeenCalledOnce();
expect(mocks.hoistedFn).toHaveBeenCalledWith(42);
expect(foo).toBe(mocks.hoistedFn);
});
```
In this example, `rs.hoisted` allows you to create a mock function using `rs.fn()` that can be used both in the `rs.mock` factory function and in your test assertions. Without `rs.hoisted`, you would not be able to access `rs` utilities in the scope where `rs.mock` factory functions are evaluated.
## rs.importActual
- **Type**: `>(path: string) => Promise`
Loads the original implementation of a module even if it has already been mocked. Use `rs.importActual` when you want a partial mock so you can merge the real exports with your overrides.
```ts title="src/sum.test.ts"
rs.mock('./sum');
it('test', async () => {
const actualModule = await rs.importActual('./sum');
expect(actualModule.sum(1, 2)).toBe(3);
});
```
Rstest also exposes a synchronous `importActual` option for static imports. Add the [import attribute](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with) `with { rstest: 'importActual' }` to load the real module as the file evaluates:
```ts title="src/api.test.ts"
import * as apiActual from './api' with { rstest: 'importActual' };
// Partially mock the './api' module
rs.mock('./api', () => ({
...apiActual,
fetchUser: rs.fn().mockResolvedValue({ id: 'mocked' }),
}));
```
## rs.importMock
- **Type**: `>(path: string) => Promise`
Imports a module and all its properties as mock implementations.
```ts title="src/sum.test.ts"
it('test', async () => {
const mockedModule = await rs.importMock('./sum');
expect(mockedModule.sum2(1, 2)).toBe(103);
});
```
## rs.unmock
- **Type**: `(path: string) => void`
Cancels the mock implementation of the specified module. After this, all calls to `import` will return the original module, even if it was previously mocked. Like `rs.mock`, this call is hoisted to the top of the file, so it will only cancel module mocks executed in `setupFiles`.
```ts title="src/sum.test.ts"
import { rs } from '@rstest/core';
import { sum } from './src/sum';
rs.unmock('./src/sum');
expect(sum(1, 2)).toBe(3); // PASS
```
```ts title="rstest.setup.ts"
import { rs } from '@rstest/core'
;
rs.mock('./src/sum', () => {
return {
sum: (a: number, b: number) => a + b + 100,
};
});
```
## rs.doUnmock
- **Type**: `(path: string) => void`
Same as `rs.unmock`, but it is not hoisted to the top of the file. The next import of the module will import the original module instead of the mock. This will not cancel modules that were imported before the mock.
## rs.resetModules
- **Type**: `resetModules: () => RstestUtilities`
Clears the cache of all modules. This allows modules to be re-executed when re-imported. This is useful for isolating the state of modules shared between different tests.
:::warning
Does not reset mocked modules. To clear mocked modules, use [`rs.unmock`](#rsunmock), [`rs.doUnmock`](#rsdounmock), [`rs.unmockRequire`](#rsunmockrequire), or [`rs.doUnmockRequire`](#rsdounmockrequire).
:::
---
url: /api/runtime-api/rstest/mock-functions.md
---
# Mock functions
Rstest provides some utility functions to help you mock functions powered by [tinyspy](https://github.com/tinylibs/tinyspy).
## rstest.fn
- **Alias:** `rs.fn`
- **Type:**
```ts
export interface Mock extends MockInstance {
(...args: Parameters): ReturnType;
}
export type MockFn = (fn?: T) => Mock;
```
Creates a spy on a function.
```ts
const sayHi = rstest.fn((name: string) => `hi ${name}`);
const res = sayHi('bob');
expect(res).toBe('hi bob');
expect(sayHi).toHaveBeenCalledTimes(1);
```
## rstest.spyOn
- **Alias:** `rs.spyOn`
- **Type:**
```ts
export type SpyFn = (
obj: Record,
methodName: string,
accessType?: 'get' | 'set',
) => MockInstance;
```
Creates a spy on a method of an object.
```ts
const sayHi = () => 'hi';
const hi = {
sayHi,
};
const spy = rstest.spyOn(hi, 'sayHi');
expect(hi.sayHi()).toBe('hi');
expect(spy).toHaveBeenCalled();
```
If you call `rstest.spyOn` multiple times for the same method, Rstest reuses the existing spy instead of redefining it.
```ts
const hi = {
sayHi: () => 'hi',
};
rstest.spyOn(hi, 'sayHi').mockImplementation(() => 'hello');
expect(hi.sayHi()).toBe('hello');
// should get the same spy instance
expect(rstest.spyOn(hi, 'sayHi')).toBeCalled();
```
## rstest.isMockFunction
- **Alias:** `rs.isMockFunction`
- **Type:** `(fn: any) => fn is MockInstance`
Determines if the given function is a mocked function.
## rstest.mockObject
- **Alias:** `rs.mockObject`
- **Type:**
```ts
type MockObject = (
object: T,
options?: { spy?: boolean },
) => MaybeMockedDeep;
```
Creates a deep mock of an object. All methods are replaced with mock functions, while primitive values and plain objects are preserved.
### Basic usage
```ts
const original = {
method() {
return 42;
},
nested: {
getValue() {
return 'real';
},
},
prop: 'foo',
};
const mocked = rstest.mockObject(original);
// Methods return undefined by default
expect(mocked.method()).toBe(undefined);
expect(mocked.nested.getValue()).toBe(undefined);
// Primitive values are preserved
expect(mocked.prop).toBe('foo');
// Methods are mock functions
expect(rstest.isMockFunction(mocked.method)).toBe(true);
```
### Mocking return values
You can configure mocked methods to return specific values:
```ts
const mocked = rstest.mockObject({
fetchData: () => 'real data',
});
mocked.fetchData.mockReturnValue('mocked data');
expect(mocked.fetchData()).toBe('mocked data');
```
### Spy mode
When `{ spy: true }` is passed as the second argument, the original implementations are preserved while still tracking calls:
```ts
const original = {
add: (a: number, b: number) => a + b,
};
const spied = rstest.mockObject(original, { spy: true });
// Original implementation is preserved
expect(spied.add(1, 2)).toBe(3);
// Calls are tracked
expect(spied.add).toHaveBeenCalledWith(1, 2);
expect(spied.add.mock.results[0]).toEqual({ type: 'return', value: 3 });
```
### Arrays
By default, arrays are replaced with empty arrays. With `{ spy: true }`, arrays keep their original values:
```ts
const mocked = rstest.mockObject({ array: [1, 2, 3] });
expect(mocked.array).toEqual([]);
const spied = rstest.mockObject({ array: [1, 2, 3] }, { spy: true });
expect(spied.array).toEqual([1, 2, 3]);
```
### Mocking classes
You can also mock class constructors. Use `{ spy: true }` to preserve the original class behavior while tracking calls:
```ts
class UserService {
getUser() {
return { id: 1, name: 'Alice' };
}
}
// Use { spy: true } to keep original implementation
const MockedService = rstest.mockObject(UserService, { spy: true });
const instance = new MockedService();
// Original method works
expect(instance.getUser()).toEqual({ id: 1, name: 'Alice' });
// Override the implementation
rs.mocked(instance.getUser).mockImplementation(() => ({ id: 2, name: 'Bob' }));
expect(instance.getUser()).toEqual({ id: 2, name: 'Bob' });
```
## rstest.mocked
- **Alias:** `rs.mocked`
- **Type:**
```ts
type MockedFn = (
item: T,
deep?: boolean | { partial?: boolean; deep?: boolean },
) => Mocked;
```
A type helper for TypeScript that wraps an object with mock types without changing its runtime behavior. This is useful when you have mocked a module and want proper type hints for the mock methods.
```ts
import { myModule } from './myModule';
rs.mock('./myModule', { spy: true });
// TypeScript now knows myModule.method is a MockInstance
const mockedModule = rstest.mocked(myModule);
mockedModule.method.mockReturnValue('mocked');
```
The function simply returns the same object at runtime - it only affects TypeScript types.
## rstest.clearAllMocks
- **Alias:** `rs.clearAllMocks`
- **Type:** `() => Rstest`
Clears the `mock.calls`, `mock.instances`, `mock.contexts` and `mock.results` properties of all mocks.
## rstest.resetAllMocks
- **Alias:** `rs.resetAllMocks`
- **Type:** `() => Rstest`
Clears all mocks properties and reset each mock's implementation to its original.
## rstest.restoreAllMocks
- **Alias:** `rs.restoreAllMocks`
- **Type:** `() => Rstest`
Reset all mocks and restore original descriptors of spied-on objects.
## More
- [Mock Matchers](/api/runtime-api/test-api/expect.md#mock-matchers)
- [MockInstance API](/api/runtime-api/rstest/mock-instance.md)
---
url: /api/runtime-api/rstest/mock-instance.md
---
# Mock instance
`MockInstance` is the type of all mock/spy instances, providing a rich set of APIs for controlling and inspecting mocks.
## getMockName
- **Type:** `() => string`
Returns the mock name string set by `.mockName()`.
```ts
const fn = rstest.fn();
fn.mockName('myMock');
expect(fn.getMockName()).toBe('myMock');
```
## mockName
- **Type:** `(name: string) => MockInstance`
Sets the mock name for this mock instance, useful for debugging and output.
```ts
const fn = rstest.fn();
fn.mockName('logger');
```
## mockClear
- **Type:** `() => MockInstance`
Clears all information about every call (calls, instances, contexts, results, etc.).
```ts
const fn = rstest.fn();
fn(1);
fn.mockClear();
expect(fn.mock.calls.length).toBe(0);
```
## mockReset
- **Type:** `() => MockInstance`
Clears all call information and resets the implementation to the initial state.
```ts
const fn = rstest.fn().mockImplementation(() => 1);
fn.mockReset();
// Implementation is reset
```
## mockRestore
- **Type:** `() => MockInstance`
Restores the original method of a spied object (only effective for spies).
```ts
const obj = { foo: () => 1 };
const spy = rstest.spyOn(obj, 'foo');
spy.mockRestore();
```
## getMockImplementation
- **Type:** `() => Function | undefined`
Returns the current mock implementation function, if any.
```ts
const fn = rstest.fn(() => 123);
const impl = fn.getMockImplementation();
```
## mockImplementation
- **Type:** `(fn: Function) => MockInstance`
Sets the implementation function for the mock.
```ts
const fn = rstest.fn();
fn.mockImplementation((a, b) => a + b);
```
## mockImplementationOnce
- **Type:** `(fn: Function) => MockInstance`
Sets the implementation function for the next call only.
```ts
const fn = rstest.fn();
fn.mockImplementationOnce(() => 1);
fn(); // returns 1
fn(); // returns undefined
```
## withImplementation
- **Type:** `(fn: Function, callback: () => any) => void | Promise`
Temporarily replaces the mock implementation while the callback is executed, then restores the original implementation.
```ts
const fn = rstest.fn(() => 1);
fn.withImplementation(
() => 2,
() => {
expect(fn()).toBe(2);
},
);
expect(fn()).toBe(1);
```
## mockReturnThis
- **Type:** `() => this`
Makes the mock return `this` when called.
```ts
const fn = rstest.fn();
fn.mockReturnThis();
const obj = { fn };
expect(obj.fn()).toBe(obj);
```
## mockReturnValue
- **Type:** `(value: any) => MockInstance`
Makes the mock always return the specified value.
```ts
const fn = rstest.fn();
fn.mockReturnValue(42);
expect(fn()).toBe(42);
```
## mockReturnValueOnce
- **Type:** `(value: any) => MockInstance`
Makes the mock return the specified value for the next call only.
```ts
const fn = rstest.fn();
fn.mockReturnValueOnce(1);
expect(fn()).toBe(1);
expect(fn()).toBe(undefined);
```
## mockResolvedValue
- **Type:** `(value: any) => MockInstance`
Makes the mock return a Promise that resolves to the specified value.
```ts
const fn = rstest.fn();
fn.mockResolvedValue(123);
await expect(fn()).resolves.toBe(123);
```
## mockResolvedValueOnce
- **Type:** `(value: any) => MockInstance`
Makes the mock return a Promise that resolves to the specified value for the next call only.
```ts
const fn = rstest.fn();
fn.mockResolvedValueOnce(1);
await expect(fn()).resolves.toBe(1);
await expect(fn()).resolves.toBe(undefined);
```
## mockRejectedValue
- **Type:** `(error: any) => MockInstance`
Makes the mock return a Promise that rejects with the specified error.
```ts
const fn = rstest.fn();
fn.mockRejectedValue(new Error('fail'));
await expect(fn()).rejects.toThrow('fail');
```
## mockRejectedValueOnce
- **Type:** `(error: any) => MockInstance`
Makes the mock return a Promise that rejects with the specified error for the next call only.
```ts
const fn = rstest.fn();
fn.mockRejectedValueOnce(new Error('fail'));
await expect(fn()).rejects.toThrow('fail');
await expect(fn()).resolves.toBe(undefined);
```
## mock
The context of the mock, including call arguments, return values, instances, contexts, etc.
```ts
const fn = rstest.fn((a, b) => a + b);
fn(1, 2);
expect(fn.mock.calls[0]).toEqual([1, 2]);
```
### mock.calls
- **Type:** `Array>`
An array containing the arguments for each call to the mock function.
```ts
const fn = rstest.fn((a, b) => a + b);
fn(1, 2);
fn(3, 4);
console.log(fn.mock.calls); // [[1, 2], [3, 4]]
```
### mock.instances
- **Type:** `Array>`
An array containing the instances that have been instantiated from the mock (when used as a constructor).
```ts
const Fn = rstest.fn(function () {
this.x = 1;
});
const a = new Fn();
const b = new Fn();
console.log(Fn.mock.instances); // [a, b]
```
### mock.contexts
- **Type:** `Array>`
An array containing the `this` context for each call to the mock function.
```ts
const fn = vi.fn();
const context = {};
fn.apply(context);
fn.call(context);
fn.mock.contexts[0] === context;
fn.mock.contexts[1] === context;
```
### mock.invocationCallOrder
- **Type:** `Array`
An array of numbers representing the order in which the mock was called, shared across all mocks. The index starts from `1`.
```ts
const fn1 = rstest.fn();
const fn2 = rstest.fn();
fn1();
fn2();
fn1();
console.log(fn1.mock.invocationCallOrder); // [1, 3]
console.log(fn2.mock.invocationCallOrder); // [2]
```
### mock.lastCall
- **Type:** `Parameters | undefined`
The arguments of the last call to the mock function, or `undefined` if it has not been called.
```ts
const fn = rstest.fn();
fn(1, 2);
fn(3, 4);
console.log(fn.mock.lastCall); // [3, 4]
```
### mock.results
- **Type:** `Array>>`
An array containing the result of each call to the mock function, including returned values, thrown errors, or incomplete calls.
```ts
const fn = rstest.fn((a, b) => a + b);
fn(1, 2);
try {
fn();
throw new Error('fail');
} catch {}
console.log(fn.mock.results);
// [{ type: 'return', value: 3 }, { type: 'throw', value: Error }]
```
### mock.settledResults
- **Type:** `Array>>>`
An array containing the settled results (fulfilled or rejected) of all async calls to the mock function.
```ts
const fn = rstest.fn(async (x) => {
if (x > 0) return x;
throw new Error('fail');
});
await fn(1);
try {
await fn(0);
} catch {}
console.log(fn.mock.settledResults);
// [{ type: 'fulfilled', value: 1 }, { type: 'rejected', value: Error }]
```
---
url: /api/runtime-api/rstest/fake-timers.md
---
# Fake timers
The fake timers may be useful when a piece of code sets a long timeout that we don't want to wait for in a test.
Rstest provides some utility functions to help you fake timers powered by [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers).
## rstest.useFakeTimers
- **Alias:** `rs.useFakeTimers`
- **Type:** `(config?: FakeTimerInstallOpts) => Rstest`
To enable mocking timers, you need to call this method. It uses [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) under the hood.
```ts
rstest.useFakeTimers();
```
You can also pass a configuration object to customize the behavior of the fake timers.
## rstest.useRealTimers
- **Alias:** `rs.useRealTimers`
- **Type:** `() => Rstest`
Restores the original timer functions (such as `setTimeout`, `setInterval`, etc.), disabling the fake timers.
```ts
rstest.useRealTimers();
```
## rstest.isFakeTimers
- **Alias:** `rs.isFakeTimers`
- **Type:** `() => boolean`
Returns `true` if fake timers are currently enabled, otherwise `false`.
```ts
if (rstest.isFakeTimers()) {
// Fake timers are active
}
```
## rstest.setSystemTime
- **Alias:** `rs.setSystemTime`
- **Type:** `(now?: number | Date) => Rstest`
Sets the current system time used by fake timers. Useful for testing code that depends on the current date or time.
```ts
rstest.useFakeTimers();
rstest.setSystemTime(new Date('2020-01-01T00:00:00Z'));
```
## rstest.getRealSystemTime
- **Alias:** `rs.getRealSystemTime`
- **Type:** `() => number`
Returns the real system time (as a timestamp), even when fake timers are enabled.
```ts
const realTime = rstest.getRealSystemTime();
```
## rstest.runAllTicks
- **Alias:** `rs.runAllTicks`
- **Type:** `() => Rstest`
Runs all queued microtasks (e.g., `process.nextTick`).
## rstest.runAllTimers
- **Alias:** `rs.runAllTimers`
- **Type:** `() => Rstest`
Executes all pending timers (both timeouts and intervals).
## rstest.runAllTimersAsync
- **Alias:** `rs.runAllTimersAsync`
- **Type:** `() => Promise`
Asynchronously executes all pending timers.
## rstest.runOnlyPendingTimers
- **Alias:** `rs.runOnlyPendingTimers`
- **Type:** `() => Rstest`
Runs only the currently pending timers (does not schedule new ones).
## rstest.runOnlyPendingTimersAsync
- **Alias:** `rs.runOnlyPendingTimersAsync`
- **Type:** `() => Promise`
Asynchronously runs only the currently pending timers.
## rstest.advanceTimersByTime
- **Alias:** `rs.advanceTimersByTime`
- **Type:** `(ms: number) => Rstest`
Advances the fake timers by the specified milliseconds, executing any timers scheduled within that time.
## rstest.advanceTimersByTimeAsync
- **Alias:** `rs.advanceTimersByTimeAsync`
- **Type:** `(ms: number) => Promise`
Asynchronously advances the fake timers by the specified milliseconds.
## rstest.advanceTimersToNextTimer
- **Alias:** `rs.advanceTimersToNextTimer`
- **Type:** `(steps?: number) => Rstest`
Advances the timers to the next scheduled timer, optionally for a given number of steps.
## rstest.advanceTimersToNextTimerAsync
- **Alias:** `rs.advanceTimersToNextTimerAsync`
- **Type:** `(steps?: number) => Promise`
Asynchronously advances the timers to the next scheduled timer.
## rstest.advanceTimersToNextFrame
- **Alias:** `rs.advanceTimersToNextFrame`
- **Type:** `() => Rstest`
Advances the timers to the next animation frame.
## rstest.getTimerCount
- **Alias:** `rs.getTimerCount`
- **Type:** `() => number`
Returns the number of fake timers still left to run.
```ts
const count = rstest.getTimerCount();
```
## rstest.clearAllTimers
- **Alias:** `rs.clearAllTimers`
- **Type:** `() => Rstest`
Removes all timers that are scheduled to run.
```ts
rstest.clearAllTimers();
```
---
url: /api/runtime-api/rstest/utilities.md
---
# Utilities
A set of useful utility functions.
## rstest.stubEnv
- **Alias:** `rs.stubEnv`
- **Type:** `(name: string, value: string | undefined) => Rstest`
Temporarily sets an environment variable in `process.env` and `import.meta.env` to the specified value. Useful for testing code that depends on environment variables.
- If `value` is `undefined`, the variable will be removed from `process.env` and `import.meta.env`.
- You can call this multiple times to stub multiple variables.
- Use `rstest.unstubAllEnvs()` to restore all environment variables changed by this method.
**Example:**
```ts
rstest.stubEnv('NODE_ENV', 'test');
expect(process.env.NODE_ENV).toBe('test');
expect(import.meta.env.NODE_ENV).toBe('test');
rstest.stubEnv('MY_VAR', undefined);
expect(process.env.MY_VAR).toBeUndefined();
expect(import.meta.env.MY_VAR).toBeUndefined();
```
## rstest.unstubAllEnvs
- **Alias:** `rs.unstubAllEnvs`
- **Type:** `() => Rstest`
Restores all environment variables that were changed using `rstest.stubEnv` to their original values.
- Call this after your test to clean up any environment changes.
- Automatically called before each test if `unstubEnvs` config is enabled.
**Example:**
```ts
rstest.stubEnv('NODE_ENV', 'test');
// ... run some code
rstest.unstubAllEnvs();
expect(process.env.NODE_ENV).not.toBe('test');
```
## rstest.stubGlobal
- **Alias:** `rs.stubGlobal`
- **Type:** `(name: string | number | symbol, value: unknown) => Rstest`
Temporarily sets a global variable to the specified value. Useful for mocking global objects or functions.
- You can call this multiple times to stub multiple globals.
- Use `rstest.unstubAllGlobals()` to restore all globals changed by this method.
**Example:**
```ts
rstest.stubGlobal('myGlobal', 123);
expect(globalThis.myGlobal).toBe(123);
rstest.stubGlobal(Symbol.for('foo'), 'bar');
expect(globalThis[Symbol.for('foo')]).toBe('bar');
```
## rstest.unstubAllGlobals
- **Alias:** `rs.unstubAllGlobals`
- **Type:** `() => Rstest`
Restores all global variables that were changed using `rstest.stubGlobal` to their original values.
- Call this after your test to clean up any global changes.
- Automatically called before each test if `unstubGlobals` config is enabled.
**Example:**
```ts
rstest.stubGlobal('myGlobal', 123);
// ... run some code
rstest.unstubAllGlobals();
expect(globalThis.myGlobal).toBeUndefined();
```
## rstest.setConfig
- **Alias:** `rs.setConfig`
- **Type:**
```ts
type RuntimeConfig = {
testTimeout?: number;
hookTimeout?: number;
clearMocks?: boolean;
resetMocks?: boolean;
restoreMocks?: boolean;
maxConcurrency?: number;
retry?: number;
};
type SetConfig = (config: RuntimeConfig) => void;
```
Dynamically updates the runtime configuration for the current test file. Useful for temporarily overriding test settings such as timeouts, concurrency, or mock behavior.
**Example:**
```ts
rstest.setConfig({ testTimeout: 1000, retry: 2 });
// ... run some code with the new config
rstest.resetConfig(); // Restore to default config
```
## rstest.resetConfig
- **Alias:** `rs.resetConfig`
- **Type:** `() => void`
Resets the runtime configuration that was changed using `rstest.setConfig` back to the default values.
## rstest.getConfig
- **Alias:** `rs.getConfig`
- **Type:** `() => RuntimeConfig`
Retrieves the current runtime configuration for the test file. Useful for inspecting or logging the current settings.
**Example:**
```ts
const config = rstest.getConfig();
console.log(config);
```
## rstest.waitFor
- **Alias:** `rs.waitFor`
- **Type:**
```ts
type WaitForOptions = {
timeout?: number; // default: 1000
interval?: number; // default: 50
};
type WaitFor = (
callback: () => T | Promise,
options?: number | WaitForOptions,
) => Promise;
```
Retries `callback` until it succeeds (does not throw) or timeout is reached.
- If `options` is a number, it is treated as `timeout`.
- If timeout is reached, it throws the last error from the callback.
**Example:**
```ts
await rs.waitFor(
async () => {
const res = await fetch(url);
expect(res.ok).toBe(true);
},
{ timeout: 30_000, interval: 1_000 },
);
```
## rstest.waitUntil
- **Alias:** `rs.waitUntil`
- **Type:**
```ts
type WaitUntilOptions = {
timeout?: number; // default: 1000
interval?: number; // default: 50
};
type WaitUntil = (
callback: () => T | Promise,
options?: number | WaitUntilOptions,
) => Promise;
```
Calls `callback` repeatedly only while it returns `undefined` (no value) or any falsy value.
It resolves when the callback returns a truthy value.
- If `options` is a number, it is treated as `timeout`.
- If the callback throws, execution is interrupted immediately and the error is thrown.
- If timeout is reached, it throws a timeout error.
**Example:**
```ts
const serverReady = await rs.waitUntil(
async () => {
const status = await getServerStatus();
return status.ready ? status : null;
},
{ timeout: 10_000, interval: 200 },
);
```
---
url: /api/runtime-api/environment-variables.md
---
# Environment variables
Rstest sets a small set of environment variables you can read from tests, setup files, and source code.
## `NODE_ENV`
Set to `'test'` when the test runner starts (if not already defined). Existing values are preserved, so you can override it from your shell or `package.json` script.
Available as both `process.env.NODE_ENV` and `import.meta.env.NODE_ENV` across all test environments, including [browser mode](/guide/browser-testing/index.md). Prefer `import.meta.env.NODE_ENV` in ESM source code where `process` may not be available.
```ts
if (import.meta.env.NODE_ENV === 'test') {
// running under Rstest
}
```
## `RSTEST`
`process.env.RSTEST` is set to `'true'` whenever the test runner is active. Use it to gate test-only branches in source code.
```ts
if (process.env.RSTEST) {
// running under Rstest
}
```
For production builds, define `process.env.RSTEST` as `false` so the bundler can eliminate the dead branch — see [Detect Rstest environment](/guide/basic/configure-rstest.md#detect-rstest-environment).
For [in-source tests](/config/test/include-source.md), prefer `import.meta.rstest` — it exposes the Rstest test API directly and is `undefined` in production builds.
## `RSTEST_WORKER_ID`
A stringified integer that uniquely identifies the worker process running the current test file. The first worker is `'1'`. Equivalent to Jest's [`JEST_WORKER_ID`](https://jestjs.io/docs/environment-variables#jest_worker_id).
Use it to isolate shared external resources across parallel workers — typically database schemas, ports, temp directories, or any resource that would collide if two workers used the same name.
```ts title="db.test.ts"
import { afterAll, beforeAll, test } from '@rstest/core';
const dbName = `myapp_test_${process.env.RSTEST_WORKER_ID}`;
beforeAll(async () => {
await createDatabase(dbName);
});
afterAll(async () => {
await dropDatabase(dbName);
});
test('inserts a row', async () => {
// ...
});
```
Concurrent workers are always given different IDs, so the value is safe to use as part of a resource name during a worker's lifetime. The maximum number of concurrent workers is bounded by [`pool.maxWorkers`](/config/test/pool.md); IDs are assigned monotonically as workers spawn and are not recycled within a single Rstest run.
---
url: /api/javascript-api/reporter.md
---
# Reporter API
The Reporter API allows you to create custom test result processors and output formatters. This interface is experimental and may change in future versions.
> **Note**: For usage examples and configuration guidance, see the [Reporters guide](/guide/basic/reporters.md).
## Using custom reporter
Create a custom reporter by implementing the `Reporter` interface:
```ts
import type { Reporter, TestFileResult } from '@rstest/core';
const customReporter: Reporter = {
onTestFileStart(test) {
console.log(`Starting: ${test.testPath}`);
},
onTestFileResult(result) {
console.log(`Finished: ${result.testPath}`);
},
onTestRunEnd({ results, duration }) {
console.log(`All tests completed in ${duration.totalTime}ms`);
},
};
```
Use the custom reporter in your configuration:
```ts
import { defineConfig } from '@rstest/core';
import { customReporter } from './path/to/custom-reporter';
export default defineConfig({
reporters: [customReporter],
});
```
## Interface overview
The `Reporter` interface provides lifecycle hooks for test execution. Each hook is called at specific points during the test run.
> **Important**: For the most up-to-date interface definition and complete type signatures, please refer to the [source code](https://github.com/web-infra-dev/rstest/blob/main/packages/core/src/types/reporter.ts).
### Hook categories
- **File-level hooks**: `onTestFileStart`, `onTestFileReady`, `onTestFileResult`
- **Suite-level hooks**: `onTestSuiteStart`, `onTestSuiteResult`
- **Case-level hooks**: `onTestCaseStart`, `onTestCaseResult`
- **Run-level hooks**: `onTestRunStart`, `onTestRunEnd`, `onUserConsoleLog`, `onExit`
### Basic interface structure
```ts
interface Reporter {
onTestFileStart?(test: TestFileInfo): void;
onTestFileReady?(test: TestFileInfo): void;
onTestFileResult?(test: TestFileResult): void;
onTestSuiteStart?(test: TestSuiteInfo): void;
onTestSuiteResult?(result: TestResult): void;
onTestCaseStart?(test: TestCaseInfo): void;
onTestCaseResult?(result: TestResult): void;
onTestRunEnd?(context: TestRunEndContext): MaybePromise;
onUserConsoleLog?(log: UserConsoleLog): void;
onExit?(): void;
}
```
## Examples
### Simple custom reporter
```ts
import type { Reporter } from '@rstest/core';
const simpleReporter: Reporter = {
onTestFileStart(test) {
console.log(`📁 ${test.testPath}`);
},
onTestCaseResult(result) {
const status = result.status === 'pass' ? '✅' : '❌';
console.log(`${status} ${result.name}`);
},
onTestRunEnd({ results }) {
const passed = results.filter((r) => r.status === 'pass').length;
const failed = results.filter((r) => r.status === 'fail').length;
console.log(`\n📊 ${passed} passed, ${failed} failed`);
},
};
```
### File output reporter
```ts
import { writeFileSync } from 'node:fs';
import type { Reporter } from '@rstest/core';
const jsonReporter: Reporter = {
onTestRunEnd({ results }) {
const report = {
timestamp: new Date().toISOString(),
results: results.map((r) => ({
path: r.testPath,
status: r.status,
duration: r.duration,
errors: r.errors,
})),
};
writeFileSync('test-report.json', JSON.stringify(report, null, 2));
},
};
```
---
url: /api/javascript-api/rstest-core.md
---
# Rstest core
Rstest offers these core methods.
## defineConfig
This function helps you to autocomplete configuration types. It accepts a Rstest config object, or a function that returns a config.
- **Type:**
```ts
function defineConfig(config: RstestConfig): RstestConfig;
function defineConfig(config: RstestConfigSyncFn): RstestConfigSyncFn;
function defineConfig(config: RstestConfigAsyncFn): RstestConfigAsyncFn;
function defineConfig(config: RstestConfigExport): RstestConfigExport;
```
- **Example:**
```ts
import { defineConfig } from '@rstest/core';
export default defineConfig({
testTimeout: 10000,
coverage: {
enabled: true,
provider: 'istanbul',
},
});
```
## defineProject
This function helps you to autocomplete configuration types for [Rstest Project](/guide/basic/projects.md). It accepts a Rstest project config object, or a function that returns a config.
- **Type:**
```ts
type NestedProjectConfig = {
projects: (InlineProjectConfig | string)[];
};
function defineProject(config: ProjectConfig): ProjectConfig;
function defineProject(config: NestedProjectConfig): NestedProjectConfig;
function defineProject(config: () => ProjectConfig): () => ProjectConfig;
function defineProject(
config: () => NestedProjectConfig,
): () => NestedProjectConfig;
function defineProject(
config: () => Promise,
): () => Promise;
function defineProject(
config: () => Promise,
): () => Promise;
```
- **Example:**
```ts
import { defineProject } from '@rstest/core';
export default defineProject({
root: './apps/web',
testTimeout: 15000,
});
```
Use `defineProject` for the top-level export of a project config file. If that file returns a nested `projects` object, the object items inside `projects` are still inline projects.
Compared with `defineInlineProject`, `defineProject` does not require `name` on the top-level exported project. If `name` is omitted, Rstest will resolve it the same way as the [name](/config/test/name.md) option: first from the current project's `package.json`, then from the folder name.
## defineInlineProject
[Added in v0.9.6](https://github.com/web-infra-dev/rstest/releases/tag/v0.9.6)
This function helps you to autocomplete inline project configuration types inside `defineConfig({ projects: [...] })`. Inline projects must include a `name`.
Unlike `defineProject`, `defineInlineProject` is for object items inside a `projects` array. Inline projects must provide `name` explicitly because they do not have a standalone project root to infer it from.
- **Type:**
```ts
function defineInlineProject(config: InlineProjectConfig): InlineProjectConfig;
```
- **Example:**
```ts
import { defineConfig, defineInlineProject } from '@rstest/core';
export default defineConfig({
projects: [
defineInlineProject({
name: 'web',
root: './apps/web',
testTimeout: 15000,
}),
],
});
```
## mergeRstestConfig
Merge multiple Rstest config objects into a single config. It deeply merges each configuration object, automatically combining multiple function values into an array of sequentially executed functions, and returns a merged configuration object.
- **Type:**
```ts
function mergeRstestConfig(...configs: RstestConfig[]): RstestConfig;
```
- **Example:**
```ts
import { mergeRstestConfig } from '@rstest/core';
const config = mergeRstestConfig(
{
testTimeout: 5000,
},
{
coverage: {
enabled: true,
},
},
);
```
## mergeProjectConfig
Merge multiple Project config objects into a single config.
- **Type:**
```ts
function mergeProjectConfig(...configs: ProjectConfig[]): ProjectConfig;
```
- **Example:**
```ts
import { mergeProjectConfig } from '@rstest/core';
const config = mergeProjectConfig(
{
projects: ['packages/*'],
},
{
testTimeout: 10000,
},
);
```
## loadConfig
Load Rstest configuration.
- **Type:**
```ts
function loadConfig(params?: {
// Default is process.cwd()
cwd?: string;
// Specify the configuration file (relative or absolute path)
path?: string;
envMode?: string;
configLoader?: 'auto' | 'jiti' | 'native';
}): Promise<{
content: RstestConfig;
filePath: string | null;
}>;
```
- **Example:**
```ts
import { loadConfig } from '@rstest/core';
const { content, filePath } = await loadConfig({
cwd: './my-project',
path: './rstest.config.ts',
});
console.log('Config loaded from:', filePath);
console.log('Config:', content);
```
If the Rstest config file does not exist in the cwd directory, the return value of the loadConfig method is `{ content: {}, filePath: null }`.
---
url: /index.md
---
body:not(.notTopArrived) .rp-nav {background: transparent !important; border-bottom: 1px solid transparent !important;}.rp-nav {background: color-mix(in srgb,var(--rp-c-bg) 60%,transparent);backdrop-filter: blur(25px);-webkit-backdrop-filter: blur(25px);}# Rstest
Rspack-based Testing Framework
Provide first-class support for Rspack ecosystem
Quick Start[GitHubGitHub](https://github.com/web-infra-dev/rstest)♻️## Reuse your build setup
Use existing Rsbuild or Rspack config so aliases, transforms, and plugins stay consistent in tests.
🎯## Test through the build pipeline
Run tests through Rspack's bundling model to match production behavior more closely.
⚡## Fast by design
Rspack-powered bundling keeps startup and reruns fast, even in larger projects.
🧠## Modern by default
TypeScript, ESM, CJS, CSS Modules, and modern JavaScript work out of the box.
🧰## Testing essentials included
Assertions, snapshots, mocks, coverage, and lifecycle hooks are ready when you start.
🌐## Real browser testing
Run tests in Chromium, Firefox, or WebKit through Playwright when jsdom isn't enough.
# Rstack
A unified JavaScript toolchain centered on Rspack, with high performance and consistent architecture
[RspackA high performance JavaScript bundler written in Rust, with webpack-compatible API
rspack.rs](https://rspack.rs)[RsbuildAn Rspack-based build tool that provides out-of-the-box setup
rsbuild.rs](https://rsbuild.rs)[RslibA Rsbuild-based library development tool for creating libraries and UI components
rslib.rs](https://rslib.rs)[RspressAn Rsbuild-based static site generator for creating documentation sites
rspress.rs](https://rspress.rs)[RsdoctorA one-stop build analyzer that makes the build process transparent
rsdoctor.rs](https://rsdoctor.rs)[RstestA testing framework that provides first-class support for Rspack ecosystem
rstest.rs](https://rstest.rs/)[RslintA high-performance JavaScript and TypeScript linter based on typescript-go
rslint.rs](https://rslint.rs/)Rstest is free and open source software released under the MIT license.
© 2024-present ByteDance Inc.