Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:

- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
with:
files: |
docs/**
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/issue-close-require.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
issues: write # for actions-cool/issues-helper to update issues
steps:
- name: needs reproduction
uses: actions-cool/issues-helper@e2ff99831a4f13625d35064e2b3dfe65c07a0396 # v3.7.5
uses: actions-cool/issues-helper@71b62d7da76e59ff7b193904feb6e77d4dbb2777 # v3.7.6
with:
actions: close-issues
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/issue-labeled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- name: needs reproduction
if: github.event.label.name == 'needs reproduction'
uses: actions-cool/issues-helper@e2ff99831a4f13625d35064e2b3dfe65c07a0396 # v3.7.5
uses: actions-cool/issues-helper@71b62d7da76e59ff7b193904feb6e77d4dbb2777 # v3.7.6
with:
actions: create-comment
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
57 changes: 57 additions & 0 deletions docs/api/browser/locators.md
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,63 @@ page.getByText('Hello').elements() // ✅ [HTMLElement, HTMLElement]
page.getByText('Hello USA').elements() // ✅ []
```

### findElement <Version>4.1.0</Version> {#findelement}

```ts
function findElement(
options?: SelectorOptions
): Promise<HTMLElement | SVGElement>
```

::: danger WARNING
This is an escape hatch for cases where you need the raw DOM element — for example, to pass it to a third-party library like FormKit that doesn't accept Vitest locators. If you are interacting with the element yourself, use other [builtin methods](#methods) instead.
:::

This method returns an element matching the locator. Unlike [`.element()`](#element), this method will wait and retry until a matching element appears in the DOM, using increasing intervals (0, 20, 50, 100, 100, 500ms).

If _no element_ is found before the timeout, an error is thrown. By default, the timeout matches the test timeout.

If _multiple elements_ match the selector and `strict` is `true` (the default), an error is thrown immediately without retrying. Set `strict` to `false` to return the first matching element instead.

It accepts options:

- `timeout: number` - How long to wait in milliseconds until at least one element is found. By default, this shares timeout with the test.
- `strict: boolean` - When `true` (default), throws an error if multiple elements match the locator. When `false`, returns the first matching element.

Consider the following DOM structure:

```html
<div>Hello <span>World</span></div>
<div>Hello Germany</div>
<div>Hello</div>
```

These locators will resolve successfully:

```ts
await page.getByText('Hello World').findElement() // ✅ HTMLDivElement
await page.getByText('World').findElement() // ✅ HTMLSpanElement
await page.getByText('Hello Germany').findElement() // ✅ HTMLDivElement
```

These locators will throw an error:

```ts
// multiple elements match, strict mode rejects
await page.getByText('Hello').findElement() // ❌
await page.getByText(/^Hello/).findElement() // ❌

// no matching element before timeout
await page.getByText('Hello USA').findElement() // ❌
```

Using `strict: false` to allow multiple matches:

```ts
// returns the first matching element instead of throwing
await page.getByText('Hello').findElement({ strict: false }) // ✅ HTMLDivElement
```

### all

```ts
Expand Down
4 changes: 3 additions & 1 deletion docs/config/browser/playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ These options are directly passed down to `playwright[browser].connect` command.
Use `connectOptions.wsEndpoint` to connect to an existing Playwright server instead of launching browsers locally. This is useful for running browsers in Docker, in CI, or on a remote machine.

::: warning
Since this command connects to an existing Playwright server, any `launch` options will be ignored.

Vitest forwards `launchOptions` to Playwright server via the `x-playwright-launch-options` header. This works only if the remote Playwright server supports this header, for example when using the `playwright run-server` CLI.

:::

::: details Example: Running a Playwright Server in Docker
Expand Down
4 changes: 4 additions & 0 deletions docs/config/clearmocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ export default defineConfig({
},
})
```

::: warning
Be aware that this option may cause problems with async [concurrent tests](/api/test#test-concurrent). If enabled, the completion of one test will clear the mock history for all mocks, including those currently being used by other tests in progress.
:::
4 changes: 4 additions & 0 deletions docs/config/mockreset.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ export default defineConfig({
},
})
```

::: warning
Be aware that this option may cause problems with async [concurrent tests](/api/test#test-concurrent). If enabled, the completion of one test will clear the mock history and implementation for all mocks, including those currently being used by other tests in progress.
:::
4 changes: 4 additions & 0 deletions docs/config/restoremocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ export default defineConfig({
},
})
```

::: warning
Be aware that this option may cause problems with async [concurrent tests](/api/test#test-concurrent). If enabled, the completion of one test will restore the implementation for all spies, including those currently being used by other tests in progress.
:::
4 changes: 4 additions & 0 deletions docs/config/unstubenvs.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ export default defineConfig({
},
})
```

::: warning
Be aware that this option may cause problems with async [concurrent tests](/api/test#test-concurrent). If enabled, the completion of one test will restore all the values changed with [`vi.stubEnv`](/api/vi#vi-stubenv), including those currently being used by other tests in progress.
:::
4 changes: 4 additions & 0 deletions docs/config/unstubglobals.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ export default defineConfig({
},
})
```

::: warning
Be aware that this option may cause problems with async [concurrent tests](/api/test#test-concurrent). If enabled, the completion of one test will restore all global values that were changed with [`vi.stubGlobal`](/api/vi#vi-stubglobal), including those currently being used by other tests in progress.
:::
14 changes: 10 additions & 4 deletions docs/config/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ outline: deep

# update <CRoot /> {#update}

- **Type:** `boolean | 'new' | 'all'`
- **Type:** `boolean | 'new' | 'all' | 'none'`
- **Default:** `false`
- **CLI:** `-u`, `--update`, `--update=false`, `--update=new`
- **CLI:** `-u`, `--update`, `--update=false`, `--update=new`, `--update=none`

Update snapshot files. The behaviour depends on the value:
Define snapshot update behavior.

- `true` or `'all'`: updates all changed snapshots and delete obsolete ones
- `true` or `'all'`: updates all changed snapshots and deletes obsolete ones
- `new`: generates new snapshots without changing or deleting obsolete ones
- `none`: does not write snapshots and fails on snapshot mismatches, missing snapshots, and obsolete snapshots

When `update` is `false` (the default), Vitest resolves snapshot update mode by environment:

- Local runs (non-CI): works same as `new`
- CI runs (`process.env.CI` is truthy): works same as `none`
2 changes: 1 addition & 1 deletion docs/guide/cli-generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Path to config file
- **CLI:** `-u, --update [type]`
- **Config:** [update](/config/update)

Update snapshot (accepts boolean, "new" or "all")
Update snapshot (accepts boolean, "new", "all" or "none")

### watch

Expand Down
6 changes: 6 additions & 0 deletions docs/guide/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ Or you can use the `--update` or `-u` flag in the CLI to make Vitest update snap
vitest -u
```

### CI behavior

By default, Vitest does not write snapshots in CI (`process.env.CI` is truthy) and any snapshot mismatches, missing snapshots, and obsolete snapshots fail the run. See [`update`](/config/update) for the details.

An **obsolete snapshot** is a snapshot entry (or snapshot file) that no longer matches any collected test. This usually happens after removing or renaming tests.

## File Snapshots

When calling `toMatchSnapshot()`, we store all snapshots in a formatted snap file. That means we need to escape some characters (namely the double-quote `"` and backtick `` ` ``) in the snapshot string. Meanwhile, you might lose the syntax highlighting for the snapshot content (if they are in some language).
Expand Down
4 changes: 2 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
"@vite-pwa/assets-generator": "^1.0.2",
"@vite-pwa/vitepress": "^1.1.0",
"@vitejs/plugin-vue": "catalog:",
"@voidzero-dev/vitepress-theme": "^4.4.2",
"@voidzero-dev/vitepress-theme": "^4.5.0",
"https-localhost": "^4.7.1",
"tinyglobby": "catalog:",
"unocss": "catalog:",
"vite": "^6.3.5",
"vite-plugin-pwa": "^1.2.0",
"vitepress": "2.0.0-alpha.16",
"vitepress-plugin-group-icons": "^1.7.1",
"vitepress-plugin-llms": "^1.10.0",
"vitepress-plugin-llms": "^1.11.0",
"vitepress-plugin-tabs": "^0.7.3",
"workbox-window": "^7.4.0"
}
Expand Down
2 changes: 1 addition & 1 deletion examples/lit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"devDependencies": {
"@vitest/browser-playwright": "latest",
"jsdom": "latest",
"playwright": "^1.58.1",
"playwright": "^1.58.2",
"vite": "latest",
"vitest": "latest"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/opentelemetry/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ services:
# for testing open-telemetry integration locally
# https://www.jaegertracing.io/docs/2.12/getting-started/
jaeger:
image: cr.jaegertracing.io/jaegertracing/jaeger:2.14.1
image: cr.jaegertracing.io/jaegertracing/jaeger:2.15.1
# Assign ports for Jaeger UI and OTLP receiver
ports:
# UI http://localhost:16686
Expand Down
10 changes: 5 additions & 5 deletions examples/opentelemetry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-zone": "^2.5.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.211.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-node": "^0.211.0",
"@opentelemetry/sdk-trace-web": "^2.5.0",
"@opentelemetry/context-zone": "^2.5.1",
"@opentelemetry/exporter-trace-otlp-proto": "^0.212.0",
"@opentelemetry/resources": "^2.5.1",
"@opentelemetry/sdk-node": "^0.212.0",
"@opentelemetry/sdk-trace-web": "^2.5.1",
"@vitest/browser-playwright": "latest",
"vite": "latest",
"vitest": "latest"
Expand Down
4 changes: 2 additions & 2 deletions examples/projects/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.2.10",
"@vitejs/plugin-react": "^5.1.3",
"@types/react": "^19.2.14",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/ui": "latest",
"fastify": "^5.7.2",
"jsdom": "^27.4.0",
Expand Down
2 changes: 1 addition & 1 deletion examples/typecheck/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"test:run": "vitest run"
},
"devDependencies": {
"@types/node": "^24.10.9",
"@types/node": "^24.10.13",
"@vitest/ui": "latest",
"typescript": "^5.9.3",
"vite": "latest",
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "module",
"version": "4.1.0-beta.4",
"private": true,
"packageManager": "pnpm@10.28.2",
"packageManager": "pnpm@10.30.1",
"description": "Next generation testing framework powered by Vite",
"engines": {
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
Expand Down Expand Up @@ -41,26 +41,26 @@
"devDependencies": {
"@antfu/eslint-config": "^7.2.0",
"@antfu/ni": "^28.2.0",
"@playwright/test": "^1.58.1",
"@playwright/test": "^1.58.2",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@types/node": "24.10.9",
"@types/node": "24.10.13",
"@types/ws": "catalog:",
"@vitest/browser": "workspace:*",
"@vitest/coverage-istanbul": "workspace:*",
"@vitest/coverage-v8": "workspace:*",
"@vitest/ui": "workspace:*",
"bumpp": "^10.4.0",
"bumpp": "^10.4.1",
"changelogithub": "^14.0.0",
"esbuild": "^0.27.2",
"esbuild": "^0.27.3",
"eslint": "^9.39.2",
"magic-string": "^0.30.21",
"pathe": "^2.0.3",
"premove": "^4.0.0",
"rollup": "^4.57.1",
"rollup-plugin-dts": "^6.3.0",
"rollup-plugin-license": "^3.6.0",
"rollup-plugin-license": "^3.7.0",
"tinyglobby": "catalog:",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"tinyrainbow": "catalog:"
},
"devDependencies": {
"playwright": "^1.58.1",
"playwright": "^1.58.2",
"vitest": "workspace:*"
}
}
35 changes: 21 additions & 14 deletions packages/browser-playwright/src/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,6 @@ export class PlaywrightBrowserProvider implements BrowserProvider {

const playwright = await import('playwright')

if (this.options.connectOptions) {
if (this.options.launchOptions) {
this.project.vitest.logger.warn(
c.yellow(`Found both ${c.bold(c.italic(c.yellow('connect')))} and ${c.bold(c.italic(c.yellow('launch')))} options in browser instance configuration.
Ignoring ${c.bold(c.italic(c.yellow('launch')))} options and using ${c.bold(c.italic(c.yellow('connect')))} mode.
You probably want to remove one of the two options and keep only the one you want to use.`),
)
}
const browser = await playwright[this.browserName].connect(this.options.connectOptions.wsEndpoint, this.options.connectOptions)
this.browser = browser
this.browserPromise = null
return this.browser
}

const launchOptions: LaunchOptions = {
...this.options.launchOptions,
headless: options.headless,
Expand Down Expand Up @@ -216,6 +202,27 @@ export class PlaywrightBrowserProvider implements BrowserProvider {
}

debug?.('[%s] initializing the browser with launch options: %O', this.browserName, launchOptions)

if (this.options.connectOptions) {
let { wsEndpoint, headers = {}, ...connectOptions } = this.options.connectOptions
if ('x-playwright-launch-options' in headers) {
this.project.vitest.logger.warn(
c.yellow(
'Detected "x-playwright-launch-options" in connectOptions.headers. Provider config launchOptions is ignored.',
),
)
}
else {
headers = { ...headers, 'x-playwright-launch-options': JSON.stringify(launchOptions) }
}
this.browser = await playwright[this.browserName].connect(wsEndpoint, {
...connectOptions,
headers,
})
this.browserPromise = null
return this.browser
}

let persistentContextOption = this.options.persistentContext
if (persistentContextOption && openBrowserOptions.parallel) {
persistentContextOption = false
Expand Down
Loading
Loading