--- url: /guide/advanced.md --- # Getting Started advanced {#getting-started} ::: warning This guide lists advanced APIs to run tests via a Node.js script. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. ::: You can import any method from the `vitest/node` entry-point. ## startVitest ```ts function startVitest( mode: VitestRunMode, cliFilters: string[] = [], options: CliOptions = {}, viteOverrides?: ViteUserConfig, vitestOptions?: VitestOptions, ): Promise ``` You can start running Vitest tests using its Node API: ```js import { startVitest } from 'vitest/node' const vitest = await startVitest('test') await vitest.close() ``` `startVitest` function returns [`Vitest`](/api/advanced/vitest) instance if tests can be started. If watch mode is not enabled, Vitest will call `close` method automatically. If watch mode is enabled and the terminal supports TTY, Vitest will register console shortcuts. You can pass down a list of filters as a second argument. Vitest will run only tests that contain at least one of the passed-down strings in their file path. Additionally, you can use the third argument to pass in CLI arguments, which will override any test config options. Alternatively, you can pass in the complete Vite config as the fourth argument, which will take precedence over any other user-defined options. After running the tests, you can get the results from the [`state.getTestModules`](/api/advanced/test-module) API: ```ts import type { TestModule } from 'vitest/node' const vitest = await startVitest('test') console.log(vitest.state.getTestModules()) // [TestModule] ``` ::: tip The ["Running Tests"](/guide/advanced/tests#startvitest) guide has a usage example. ::: ## createVitest ```ts function createVitest( mode: VitestRunMode, options: CliOptions, viteOverrides: ViteUserConfig = {}, vitestOptions: VitestOptions = {}, ): Promise ``` You can create Vitest instance by using `createVitest` function. It returns the same [`Vitest`](/api/advanced/vitest) instance as `startVitest`, but it doesn't start tests and doesn't validate installed packages. ```js import { createVitest } from 'vitest/node' const vitest = await createVitest('test', { watch: false, }) ``` ::: tip The ["Running Tests"](/guide/advanced/tests#createvitest) guide has a usage example. ::: ## resolveConfig ```ts function resolveConfig( options: UserConfig = {}, viteOverrides: ViteUserConfig = {}, ): Promise<{ vitestConfig: ResolvedConfig viteConfig: ResolvedViteConfig }> ``` This method resolves the config with custom parameters. If no parameters are given, the `root` will be `process.cwd()`. ```ts import { resolveConfig } from 'vitest/node' // vitestConfig only has resolved "test" properties const { vitestConfig, viteConfig } = await resolveConfig({ mode: 'custom', configFile: false, resolve: { conditions: ['custom'] }, test: { setupFiles: ['/my-setup-file.js'], pool: 'threads', }, }) ``` ::: info Due to how Vite's `createServer` works, Vitest has to resolve the config during the plugin's `configResolve` hook. Therefore, this method is not actually used internally and is exposed exclusively as a public API. If you pass down the config to the `startVitest` or `createVitest` APIs, Vitest will still resolve the config again. ::: ::: warning The `resolveConfig` doesn't resolve `projects`. To resolve projects configs, Vitest needs an established Vite server. Also note that `viteConfig.test` will not be fully resolved. If you need Vitest config, use `vitestConfig` instead. ::: ## parseCLI ```ts function parseCLI(argv: string | string[], config: CliParseOptions = {}): { filter: string[] options: CliOptions } ``` You can use this method to parse CLI arguments. It accepts a string (where arguments are split by a single space) or a strings array of CLI arguments in the same format that Vitest CLI uses. It returns a filter and `options` that you can later pass down to `createVitest` or `startVitest` methods. ```ts import { parseCLI } from 'vitest/node' const result = parseCLI('vitest ./files.ts --coverage --browser=chrome') result.options // { // coverage: { enabled: true }, // browser: { name: 'chrome', enabled: true } // } result.filter // ['./files.ts'] ``` --- --- url: /config/alias.md --- # alias * **Type:** `Record | Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>` Define custom aliases when running inside tests. They will be merged with aliases from `resolve.alias`. ::: warning Vitest uses Vite SSR primitives to run tests which has [certain pitfalls](https://vitejs.dev/guide/ssr.html#ssr-externals). 1. Aliases affect only modules imported directly with an `import` keyword by an [inlined](/config/server#server-deps-inline) module (all source code is inlined by default). 2. Vitest does not support aliasing `require` calls. 3. If you are aliasing an external dependency (e.g., `react` -> `preact`), you may want to alias the actual `node_modules` packages instead to make it work for externalized dependencies. Both [Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) and [pnpm](https://pnpm.io/aliases/) support aliasing via the `npm:` prefix. ::: --- --- url: /config/allowonly.md --- # allowOnly * **Type**: `boolean` * **Default**: `!process.env.CI` * **CLI:** `--allowOnly`, `--allowOnly=false` By default, Vitest does not permit tests marked with the [`only`](/api/test#test-only) flag in Continuous Integration (CI) environments. Conversely, in local development environments, Vitest allows these tests to run. ::: info Vitest uses [`std-env`](https://npmx.dev/package/std-env) package to detect the environment. ::: You can customize this behavior by explicitly setting the `allowOnly` option to either `true` or `false`. ::: code-group ```js [vitest.config.js] import { defineConfig } from 'vitest/config' export default defineConfig({ test: { allowOnly: true, }, }) ``` ```bash [CLI] vitest --allowOnly ``` ::: When enabled, Vitest will not fail the test suite if tests marked with [`only`](/api/test#test-only) are detected, including in CI environments. When disabled, Vitest will fail the test suite if tests marked with [`only`](/api/test#test-only) are detected, including in local development environments. --- --- url: /config/api.md --- # api * **Type:** `boolean | number | object` * **Default:** `false` * **CLI:** `--api`, `--api.port`, `--api.host`, `--api.strictPort` Listen to port and serve API for [the UI](/guide/ui) or [browser server](/guide/browser/). When set to `true`, the default port is `51204`. ## api.allowWrite 4.1.0 {#api-allowwrite} * **Type:** `boolean` * **Default:** `true` if not exposed to the network, `false` otherwise Vitest server can save test files or snapshot files via the API. This allows anyone who can connect to the API the ability to run any arbitrary code on your machine. ::: danger SECURITY ADVICE Vitest does not expose the API to the internet by default and only listens on `localhost`. However if `host` is manually exposed to the network, anyone who connects to it can run arbitrary code on your machine, unless `api.allowWrite` and `api.allowExec` are set to `false`. If the host is set to anything other than `localhost` or `127.0.0.1`, Vitest will set `api.allowWrite` and `api.allowExec` to `false` by default. This means that any write operations (like changing the code in the UI) will not work. However, if you understand the security implications, you can override them. ::: ## api.allowExec 4.1.0 {#api-allowexec} * **Type:** `boolean` * **Default:** `true` if not exposed to the network, `false` otherwise Allows running any test file via the API. See the security advice in [`api.allowWrite`](#api-allowwrite). --- --- url: /guide/browser/aria-snapshots.md --- # ARIA Snapshots experimental 4.1.4 ARIA snapshots let you test the accessibility structure of your pages. Instead of asserting against raw HTML or visual output, you assert against the accessibility tree — the same structure that screen readers and other assistive technologies use. Given this HTML: ```html ``` You can assert its accessibility tree: ```ts await expect.element(page.getByRole('navigation')).toMatchAriaInlineSnapshot(` - navigation "Main": - link "Home": - /url: / - link "About": - /url: /about `) ``` This catches accessibility regressions: missing labels, broken roles, incorrect heading levels, and more — things that DOM snapshots would miss. Even if the underlying HTML structure changes, the assertion would not fail as long as content matches semantically. ## Snapshot Workflow ARIA snapshots use the same Vitest snapshot workflow as other snapshot assertions. File snapshots, inline snapshots, `--update` / `-u`, watch mode updates, and CI snapshot behavior all work the same way. See the main [Snapshot guide](/guide/snapshot) for the general snapshot workflow, update behavior, and review guidelines. ## Basic Usage Given a page with this HTML: ```html
``` ### File Snapshots Use `toMatchAriaSnapshot()` to store the snapshot in a `.snap` file alongside your test: ```ts [basic.test.ts] import { expect, test } from 'vitest' test('login form', async () => { await expect.element(page.getByRole('form')).toMatchAriaSnapshot() }) ``` On first run, Vitest generates a snapshot file entry: ```js [__snapshots__/basic.test.ts.snap] // Vitest Snapshot ... exports[`login form 1`] = ` - form "Log In": - textbox "Email" - textbox "Password" - button "Submit" ` ``` ### Inline Snapshots Use `toMatchAriaInlineSnapshot()` to store the snapshot directly in the test file: ```ts import { expect, test } from 'vitest' test('login form', async () => { await expect.element(page.getByRole('form')).toMatchAriaInlineSnapshot(` - form "Log In": - textbox "Email" - textbox "Password" - button "Submit" `) }) ``` ## Browser Mode Retry Behavior In [Browser Mode](/guide/browser/), `expect.element()` polls the DOM and waits for the accessibility tree to **stabilize** before evaluating the result. On each poll, the matcher re-queries the element and re-captures the accessibility tree. The snapshot is considered stable when two consecutive polls produce the same output. ```ts await expect.element(page.getByRole('form')).toMatchAriaInlineSnapshot(` - form "Log In": - textbox "Email" - textbox "Password" - button "Submit" `) ``` On first run or with `--update`, the stable result is written as the new snapshot. When an existing snapshot is present, the matcher also checks whether the stable result matches. If it does not, polling resets and continues — giving the DOM time to reach the expected state. This handles cases like animations, async rendering, or delayed state updates where the tree may briefly stabilize in an intermediate state before settling into its final form. ## Preserving Hand-Edited Patterns When you hand-edit a snapshot to use regex patterns, those patterns survive `--update`. Only the literal parts that changed are overwritten. This lets you write flexible assertions that don't break when content changes. ### Example **Step 1.** Your shopping cart page renders this HTML: ```html

Your Cart

  • Wireless Headphones — $79.99
``` You run your test for the first time with `--update`. Vitest generates the snapshot: ```yaml - heading "Your Cart" [level=1] - list "Cart Items": - listitem: Wireless Headphones — $79.99 - button "Checkout" ``` **Step 2.** The item names and prices are seeded test data that may change. You hand-edit those lines to regex patterns, but keep the stable structure as literals: ```yaml - heading "Your Cart" [level=1] - list "Cart Items": - listitem: /.+ — \$\d+\.\d+/ - button "Checkout" ``` **Step 3.** Later, a developer renames the button from "Checkout" to "Place Order". Running `--update` updates that literal but preserves your regex patterns: ```yaml - heading "Your Cart" [level=1] - list "Cart Items": - listitem: /.+ — \$\d+\.\d+/ - button "Place Order" 👈 New snapshot updated with new string ``` The regex patterns you wrote in step 2 are preserved because they still match the actual content. Only the mismatched literal "Checkout" was updated to "Place Order". ## Snapshot Format ARIA snapshots use a YAML-like syntax. Each line represents a node in the accessibility tree. ::: info ARIA snapshot templates use a **subset of YAML** syntax. Only the features needed for accessibility trees are supported: scalar values, nested mappings via indentation, and sequences (`- item`). Advanced YAML features like anchors, tags, flow collections, and multi-line scalars are not supported. Captured text is also whitespace-normalized before it is rendered into the snapshot. Newlines, `
` line breaks, tabs, and repeated whitespace collapse to single spaces, so multi-line DOM text is emitted as a single-line snapshot value. ::: Each accessible element in the tree is represented as a YAML node: ```yaml - role "name" [attribute=value] ``` * `role`: The ARIA role of the element, such as `heading`, `list`, `listitem`, or `button` * `"name"`: The [accessible name](https://w3c.github.io/accname/), when present. Quoted strings match exact values, and `/patterns/` match regular expressions * `[attribute=value]`: Accessibility states and properties such as `checked`, `disabled`, `expanded`, `level`, `pressed`, or `selected` These values come from ARIA attributes and the browser's accessibility tree, including semantics inferred from native HTML elements. Because ARIA snapshots reflect the browser's accessibility tree, content excluded from that tree, such as `aria-hidden="true"` or `display: none`, does not appear in the snapshot. ### Roles and Accessible Names For example: ```html

Welcome

Home ``` ```yaml - button "Submit" - heading "Welcome" [level=1] - link "Home" - textbox "Email" ``` The role usually comes from the element's native semantics, though it can also be defined with ARIA. The accessible name is computed from text content, associated labels, `aria-label`, `aria-labelledby`, and related naming rules. For a closer look at how names are computed, see [Accessible Name and Description Computation](https://w3c.github.io/accname/). Some content appears in the snapshot as a text node instead of a role-based element: ```html Hello world ``` ```yaml - text: Hello world ``` Text values are always serialized on a single line after whitespace normalization. For example: ```html

Line 1 Line 2
Line 3 Line 4

``` ```yaml - paragraph: Line 1 Line 2 Line 3 Line 4 ``` ### Children Child elements appear nested under their parent: ```html
  • First
  • Second
  • Third
``` ```yaml - list: - listitem: First - listitem: Second - listitem: Third ``` If the parent has an accessible name, the snapshot includes it before the nested children: ```html ``` ```yaml - navigation "Main": - link "Home" - link "About" ``` If an element only contains a single text child and has no other properties, the text is rendered inline: ```html

Hello world

``` ```yaml - paragraph: Hello world ``` ### Attributes ARIA states and properties appear in brackets: | HTML | Snapshot | | ---------------------------------------------------------------------- | ----------------------------------------- | | `` | `- checkbox "Agree" [checked]` | | `` | `- checkbox "Select all" [checked=mixed]` | | `` | `- button "Submit" [disabled]` | | `` | `- button "Menu" [expanded]` | | `

Title

` | `- heading "Title" [level=2]` | | `` | `- button "Bold" [pressed]` | | `` | `- button "Bold" [pressed=mixed]` | | `` | `- option "English" [selected]` | Attributes only appear when they are active. A button that is not disabled simply has no `[disabled]` attribute — there is no `[disabled=false]`. ### Pseudo-Attributes Some DOM properties that aren't part of ARIA but are useful for testing are exposed with a `/` prefix: #### `/url:` Links include their URL: ```html Home ``` ```yaml - link "Home": - /url: / ``` #### `/placeholder:` Textboxes can include their placeholder text: ```html ``` ```yaml - textbox "Email": - /placeholder: user@example.com ``` ::: tip When does `/placeholder:` appear? The `/placeholder:` pseudo-attribute only appears when the placeholder text is **different from the accessible name**. When an input has a placeholder but no `aria-label` or associated `