···120120 - name: Test Examples
121121 run: pnpm run test:examples
122122123123- - name: Unit Test UI
124124- run: pnpm run -C packages/ui test:ui
125125-126123 - uses: actions/upload-artifact@v6
127124 if: ${{ !cancelled() }}
128125 with:
+21-1
docs/config/api.md
···5566# api
7788-- **Type:** `boolean | number`
88+- **Type:** `boolean | number | object`
99- **Default:** `false`
1010- **CLI:** `--api`, `--api.port`, `--api.host`, `--api.strictPort`
11111212Listen to port and serve API for [the UI](/guide/ui) or [browser server](/guide/browser/). When set to `true`, the default port is `51204`.
1313+1414+## api.allowWrite <Version>4.1.0</Version> {#api-allowwrite}
1515+1616+- **Type:** `boolean`
1717+- **Default:** `true` if not exposed to the network, `false` otherwise
1818+1919+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 arbitary code on your machine.
2020+2121+::: danger SECURITY ADVICE
2222+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`.
2323+2424+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.
2525+:::
2626+2727+## api.allowExec <Version>4.1.0</Version> {#api-allowexec}
2828+2929+- **Type:** `boolean`
3030+- **Default:** `true` if not exposed to the network, `false` otherwise
3131+3232+Allows running any test file via the API. See the security advice in [`api.allowWrite`](#api-allowwrite).
-626
docs/config/browser.md
···11----
22-title: Browser Config Reference | Config
33-outline: deep
44----
55-66-# Browser Config Reference
77-88-You can change the browser configuration by updating the `test.browser` field in your [config file](/config/). An example of a simple config file:
99-1010-```ts [vitest.config.ts]
1111-import { defineConfig } from 'vitest/config'
1212-import { playwright } from '@vitest/browser-playwright'
1313-1414-export default defineConfig({
1515- test: {
1616- browser: {
1717- enabled: true,
1818- provider: playwright(),
1919- instances: [
2020- {
2121- browser: 'chromium',
2222- setupFile: './chromium-setup.js',
2323- },
2424- ],
2525- },
2626- },
2727-})
2828-```
2929-3030-Please, refer to the ["Config Reference"](/config/) article for different config examples.
3131-3232-::: warning
3333-_All listed options_ on this page are located within a `test` property inside the configuration:
3434-3535-```ts [vitest.config.js]
3636-export default defineConfig({
3737- test: {
3838- browser: {},
3939- },
4040-})
4141-```
4242-:::
4343-4444-## browser.enabled
4545-4646-- **Type:** `boolean`
4747-- **Default:** `false`
4848-- **CLI:** `--browser`, `--browser.enabled=false`
4949-5050-Run all tests inside a browser by default. Note that `--browser` only works if you have at least one [`browser.instances`](#browser-instances) item.
5151-5252-## browser.instances
5353-5454-- **Type:** `BrowserConfig`
5555-- **Default:** `[]`
5656-5757-Defines multiple browser setups. Every config has to have at least a `browser` field.
5858-5959-You can specify most of the [project options](/config/) (not marked with a <CRoot /> icon) and some of the `browser` options like `browser.testerHtmlPath`.
6060-6161-::: warning
6262-Every browser config inherits options from the root config:
6363-6464-```ts{3,9} [vitest.config.ts]
6565-export default defineConfig({
6666- test: {
6767- setupFile: ['./root-setup-file.js'],
6868- browser: {
6969- enabled: true,
7070- testerHtmlPath: './custom-path.html',
7171- instances: [
7272- {
7373- // will have both setup files: "root" and "browser"
7474- setupFile: ['./browser-setup-file.js'],
7575- // implicitly has "testerHtmlPath" from the root config // [!code warning]
7676- // testerHtmlPath: './custom-path.html', // [!code warning]
7777- },
7878- ],
7979- },
8080- },
8181-})
8282-```
8383-8484-For more examples, refer to the ["Multiple Setups" guide](/guide/browser/multiple-setups).
8585-:::
8686-8787-List of available `browser` options:
8888-8989-- [`browser.headless`](#browser-headless)
9090-- [`browser.locators`](#browser-locators)
9191-- [`browser.viewport`](#browser-viewport)
9292-- [`browser.testerHtmlPath`](#browser-testerhtmlpath)
9393-- [`browser.screenshotDirectory`](#browser-screenshotdirectory)
9494-- [`browser.screenshotFailures`](#browser-screenshotfailures)
9595-- [`browser.provider`](#browser-provider)
9696-- [`browser.detailsPanelPosition`](#browser-detailspanelposition)
9797-9898-Under the hood, Vitest transforms these instances into separate [test projects](/api/advanced/test-project) sharing a single Vite server for better caching performance.
9999-100100-## browser.headless
101101-102102-- **Type:** `boolean`
103103-- **Default:** `process.env.CI`
104104-- **CLI:** `--browser.headless`, `--browser.headless=false`
105105-106106-Run the browser in a `headless` mode. If you are running Vitest in CI, it will be enabled by default.
107107-108108-## browser.isolate <Deprecated />
109109-110110-- **Type:** `boolean`
111111-- **Default:** the same as [`--isolate`](/config/#isolate)
112112-- **CLI:** `--browser.isolate`, `--browser.isolate=false`
113113-114114-Run every test in a separate iframe.
115115-116116-::: danger DEPRECATED
117117-This option is deprecated. Use [`isolate`](/config/#isolate) instead.
118118-:::
119119-120120-## browser.testerHtmlPath
121121-122122-- **Type:** `string`
123123-124124-A path to the HTML entry point. Can be relative to the root of the project. This file will be processed with [`transformIndexHtml`](https://vite.dev/guide/api-plugin#transformindexhtml) hook.
125125-126126-## browser.api
127127-128128-- **Type:** `number | { port?, strictPort?, host? }`
129129-- **Default:** `63315`
130130-- **CLI:** `--browser.api=63315`, `--browser.api.port=1234, --browser.api.host=example.com`
131131-132132-Configure options for Vite server that serves code in the browser. Does not affect [`test.api`](#api) option. By default, Vitest assigns port `63315` to avoid conflicts with the development server, allowing you to run both in parallel.
133133-134134-## browser.provider {#browser-provider}
135135-136136-- **Type:** `BrowserProviderOption`
137137-- **Default:** `'preview'`
138138-- **CLI:** `--browser.provider=playwright`
139139-140140-The return value of the provider factory. You can import the factory from `@vitest/browser-<provider-name>` or make your own provider:
141141-142142-```ts{8-10}
143143-import { playwright } from '@vitest/browser-playwright'
144144-import { webdriverio } from '@vitest/browser-webdriverio'
145145-import { preview } from '@vitest/browser-preview'
146146-147147-export default defineConfig({
148148- test: {
149149- browser: {
150150- provider: playwright(),
151151- provider: webdriverio(),
152152- provider: preview(), // default
153153- },
154154- },
155155-})
156156-```
157157-158158-To configure how provider initializes the browser, you can pass down options to the factory function:
159159-160160-```ts{7-13,20-26}
161161-import { playwright } from '@vitest/browser-playwright'
162162-163163-export default defineConfig({
164164- test: {
165165- browser: {
166166- // shared provider options between all instances
167167- provider: playwright({
168168- launchOptions: {
169169- slowMo: 50,
170170- channel: 'chrome-beta',
171171- },
172172- actionTimeout: 5_000,
173173- }),
174174- instances: [
175175- { browser: 'chromium' },
176176- {
177177- browser: 'firefox',
178178- // overriding options only for a single instance
179179- // this will NOT merge options with the parent one
180180- provider: playwright({
181181- launchOptions: {
182182- firefoxUserPrefs: {
183183- 'browser.startup.homepage': 'https://example.com',
184184- },
185185- },
186186- })
187187- }
188188- ],
189189- },
190190- },
191191-})
192192-```
193193-194194-### Custom Provider <Badge type="danger">advanced</Badge>
195195-196196-::: danger ADVANCED API
197197-The custom provider API is highly experimental and can change between patches. If you just need to run tests in a browser, use the [`browser.instances`](#browser-instances) option instead.
198198-:::
199199-200200-```ts
201201-export interface BrowserProvider {
202202- name: string
203203- mocker?: BrowserModuleMocker
204204- readonly initScripts?: string[]
205205- /**
206206- * @experimental opt-in into file parallelisation
207207- */
208208- supportsParallelism: boolean
209209- getCommandsContext: (sessionId: string) => Record<string, unknown>
210210- openPage: (sessionId: string, url: string) => Promise<void>
211211- getCDPSession?: (sessionId: string) => Promise<CDPSession>
212212- close: () => Awaitable<void>
213213-}
214214-```
215215-216216-## browser.ui
217217-218218-- **Type:** `boolean`
219219-- **Default:** `!isCI`
220220-- **CLI:** `--browser.ui=false`
221221-222222-Should Vitest UI be injected into the page. By default, injects UI iframe during development.
223223-224224-## browser.detailsPanelPosition
225225-226226-- **Type:** `'right' | 'bottom'`
227227-- **Default:** `'right'`
228228-- **CLI:** `--browser.detailsPanelPosition=bottom`, `--browser.detailsPanelPosition=right`
229229-230230-Controls the default position of the details panel in the Vitest UI when running browser tests. See [`browser.detailsPanelPosition`](/config/browser/detailspanelposition) for more details.
231231-232232-## browser.viewport
233233-234234-- **Type:** `{ width, height }`
235235-- **Default:** `414x896`
236236-237237-Default iframe's viewport.
238238-239239-## browser.locators
240240-241241-Options for built-in [browser locators](/api/browser/locators).
242242-243243-### browser.locators.testIdAttribute
244244-245245-- **Type:** `string`
246246-- **Default:** `data-testid`
247247-248248-Attribute used to find elements with `getByTestId` locator.
249249-250250-## browser.screenshotDirectory
251251-252252-- **Type:** `string`
253253-- **Default:** `__screenshots__` in the test file directory
254254-255255-Path to the screenshots directory relative to the `root`.
256256-257257-## browser.screenshotFailures
258258-259259-- **Type:** `boolean`
260260-- **Default:** `!browser.ui`
261261-262262-Should Vitest take screenshots if the test fails.
263263-264264-## browser.orchestratorScripts
265265-266266-- **Type:** `BrowserScript[]`
267267-- **Default:** `[]`
268268-269269-Custom scripts that should be injected into the orchestrator HTML before test iframes are initiated. This HTML document only sets up iframes and doesn't actually import your code.
270270-271271-The script `src` and `content` will be processed by Vite plugins. Script should be provided in the following shape:
272272-273273-```ts
274274-export interface BrowserScript {
275275- /**
276276- * If "content" is provided and type is "module", this will be its identifier.
277277- *
278278- * If you are using TypeScript, you can add `.ts` extension here for example.
279279- * @default `injected-${index}.js`
280280- */
281281- id?: string
282282- /**
283283- * JavaScript content to be injected. This string is processed by Vite plugins if type is "module".
284284- *
285285- * You can use `id` to give Vite a hint about the file extension.
286286- */
287287- content?: string
288288- /**
289289- * Path to the script. This value is resolved by Vite so it can be a node module or a file path.
290290- */
291291- src?: string
292292- /**
293293- * If the script should be loaded asynchronously.
294294- */
295295- async?: boolean
296296- /**
297297- * Script type.
298298- * @default 'module'
299299- */
300300- type?: string
301301-}
302302-```
303303-304304-## browser.commands
305305-306306-- **Type:** `Record<string, BrowserCommand>`
307307-- **Default:** `{ readFile, writeFile, ... }`
308308-309309-Custom [commands](/api/browser/commands) that can be imported during browser tests from `vitest/browser`.
310310-311311-## browser.connectTimeout
312312-313313-- **Type:** `number`
314314-- **Default:** `60_000`
315315-316316-The timeout in milliseconds. If connection to the browser takes longer, the test suite will fail.
317317-318318-::: info
319319-This is the time it should take for the browser to establish the WebSocket connection with the Vitest server. In normal circumstances, this timeout should never be reached.
320320-:::
321321-322322-## browser.trace
323323-324324-- **Type:** `'on' | 'off' | 'on-first-retry' | 'on-all-retries' | 'retain-on-failure' | object`
325325-- **CLI:** `--browser.trace=on`, `--browser.trace=retain-on-failure`
326326-- **Default:** `'off'`
327327-328328-Capture a trace of your browser test runs. You can preview traces with [Playwright Trace Viewer](https://trace.playwright.dev/).
329329-330330-This options supports the following values:
331331-332332-- `'on'` - capture trace for all tests. (not recommended as it's performance heavy)
333333-- `'off'` - do not capture traces.
334334-- `'on-first-retry'` - capture trace only when retrying the test for the first time.
335335-- `'on-all-retries'` - capture trace on every retry of the test.
336336-- `'retain-on-failure'` - capture trace only for tests that fail. This will automatically delete traces for tests that pass.
337337-- `object` - an object with the following shape:
338338-339339-```ts
340340-interface TraceOptions {
341341- mode: 'on' | 'off' | 'on-first-retry' | 'on-all-retries' | 'retain-on-failure'
342342- /**
343343- * The directory where all traces will be stored. By default, Vitest
344344- * stores all traces in `__traces__` folder close to the test file.
345345- */
346346- tracesDir?: string
347347- /**
348348- * Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
349349- * @default true
350350- */
351351- screenshots?: boolean
352352- /**
353353- * If this option is true tracing will
354354- * - capture DOM snapshot on every action
355355- * - record network activity
356356- * @default true
357357- */
358358- snapshots?: boolean
359359-}
360360-```
361361-362362-::: danger WARNING
363363-This option is supported only by the [**playwright**](/config/browser/playwright) provider.
364364-:::
365365-366366-## browser.trackUnhandledErrors
367367-368368-- **Type:** `boolean`
369369-- **Default:** `true`
370370-371371-Enables tracking uncaught errors and exceptions so they can be reported by Vitest.
372372-373373-If you need to hide certain errors, it is recommended to use [`onUnhandledError`](/config/#onunhandlederror) option instead.
374374-375375-Disabling this will completely remove all Vitest error handlers, which can help debugging with the "Pause on exceptions" checkbox turned on.
376376-377377-## browser.expect
378378-379379-- **Type:** `ExpectOptions`
380380-381381-### browser.expect.toMatchScreenshot
382382-383383-Default options for the
384384-[`toMatchScreenshot` assertion](/api/browser/assertions.html#tomatchscreenshot).
385385-These options will be applied to all screenshot assertions.
386386-387387-::: tip
388388-Setting global defaults for screenshot assertions helps maintain consistency
389389-across your test suite and reduces repetition in individual tests. You can still
390390-override these defaults at the assertion level when needed for specific test cases.
391391-:::
392392-393393-```ts
394394-import { defineConfig } from 'vitest/config'
395395-396396-export default defineConfig({
397397- test: {
398398- browser: {
399399- enabled: true,
400400- expect: {
401401- toMatchScreenshot: {
402402- comparatorName: 'pixelmatch',
403403- comparatorOptions: {
404404- threshold: 0.2,
405405- allowedMismatchedPixels: 100,
406406- },
407407- resolveScreenshotPath: ({ arg, browserName, ext, testFileName }) =>
408408- `custom-screenshots/${testFileName}/${arg}-${browserName}${ext}`,
409409- },
410410- },
411411- },
412412- },
413413-})
414414-```
415415-416416-[All options available in the `toMatchScreenshot` assertion](/api/browser/assertions#options)
417417-can be configured here. Additionally, two path resolution functions are
418418-available: `resolveScreenshotPath` and `resolveDiffPath`.
419419-420420-#### browser.expect.toMatchScreenshot.resolveScreenshotPath
421421-422422-- **Type:** `(data: PathResolveData) => string`
423423-- **Default output:** `` `${root}/${testFileDirectory}/${screenshotDirectory}/${testFileName}/${arg}-${browserName}-${platform}${ext}` ``
424424-425425-A function to customize where reference screenshots are stored. The function
426426-receives an object with the following properties:
427427-428428-- `arg: string`
429429-430430- Path **without** extension, sanitized and relative to the test file.
431431-432432- This comes from the arguments passed to `toMatchScreenshot`; if called
433433- without arguments this will be the auto-generated name.
434434-435435- ```ts
436436- test('calls `onClick`', () => {
437437- expect(locator).toMatchScreenshot()
438438- // arg = "calls-onclick-1"
439439- })
440440-441441- expect(locator).toMatchScreenshot('foo/bar/baz.png')
442442- // arg = "foo/bar/baz"
443443-444444- expect(locator).toMatchScreenshot('../foo/bar/baz.png')
445445- // arg = "foo/bar/baz"
446446- ```
447447-448448-- `ext: string`
449449-450450- Screenshot extension, with leading dot.
451451-452452- This can be set through the arguments passed to `toMatchScreenshot`, but
453453- the value will fall back to `'.png'` if an unsupported extension is used.
454454-455455-- `browserName: string`
456456-457457- The instance's browser name.
458458-459459-- `platform: NodeJS.Platform`
460460-461461- The value of
462462- [`process.platform`](https://nodejs.org/docs/v22.16.0/api/process.html#processplatform).
463463-464464-- `screenshotDirectory: string`
465465-466466- The value provided to
467467- [`browser.screenshotDirectory`](/config/browser/screenshotdirectory),
468468- if none is provided, its default value.
469469-470470-- `root: string`
471471-472472- Absolute path to the project's [`root`](/config/#root).
473473-474474-- `testFileDirectory: string`
475475-476476- Path to the test file, relative to the project's [`root`](/config/#root).
477477-478478-- `testFileName: string`
479479-480480- The test's filename.
481481-482482-- `testName: string`
483483-484484- The [`test`](/api/test)'s name, including parent
485485- [`describe`](/api/describe), sanitized.
486486-487487-- `attachmentsDir: string`
488488-489489- The value provided to [`attachmentsDir`](/config/#attachmentsdir), if none is
490490- provided, its default value.
491491-492492-For example, to group screenshots by browser:
493493-494494-```ts
495495-resolveScreenshotPath: ({ arg, browserName, ext, root, testFileName }) =>
496496- `${root}/screenshots/${browserName}/${testFileName}/${arg}${ext}`
497497-```
498498-499499-#### browser.expect.toMatchScreenshot.resolveDiffPath
500500-501501-- **Type:** `(data: PathResolveData) => string`
502502-- **Default output:** `` `${root}/${attachmentsDir}/${testFileDirectory}/${testFileName}/${arg}-${browserName}-${platform}${ext}` ``
503503-504504-A function to customize where diff images are stored when screenshot comparisons
505505-fail. Receives the same data object as
506506-[`resolveScreenshotPath`](#browser-expect-tomatchscreenshot-resolvescreenshotpath).
507507-508508-For example, to store diffs in a subdirectory of attachments:
509509-510510-```ts
511511-resolveDiffPath: ({ arg, attachmentsDir, browserName, ext, root, testFileName }) =>
512512- `${root}/${attachmentsDir}/screenshot-diffs/${testFileName}/${arg}-${browserName}${ext}`
513513-```
514514-515515-#### browser.expect.toMatchScreenshot.comparators
516516-517517-- **Type:** `Record<string, Comparator>`
518518-519519-Register custom screenshot comparison algorithms, like [SSIM](https://en.wikipedia.org/wiki/Structural_similarity_index_measure) or other perceptual similarity metrics.
520520-521521-To create a custom comparator, you need to register it in your config. If using TypeScript, declare its options in the `ScreenshotComparatorRegistry` interface.
522522-523523-```ts
524524-import { defineConfig } from 'vitest/config'
525525-526526-// 1. Declare the comparator's options type
527527-declare module 'vitest/browser' {
528528- interface ScreenshotComparatorRegistry {
529529- myCustomComparator: {
530530- sensitivity?: number
531531- ignoreColors?: boolean
532532- }
533533- }
534534-}
535535-536536-// 2. Implement the comparator
537537-export default defineConfig({
538538- test: {
539539- browser: {
540540- expect: {
541541- toMatchScreenshot: {
542542- comparators: {
543543- myCustomComparator: async (
544544- reference,
545545- actual,
546546- {
547547- createDiff, // always provided by Vitest
548548- sensitivity = 0.01,
549549- ignoreColors = false,
550550- }
551551- ) => {
552552- // ...algorithm implementation
553553- return { pass, diff, message }
554554- },
555555- },
556556- },
557557- },
558558- },
559559- },
560560-})
561561-```
562562-563563-Then use it in your tests:
564564-565565-```ts
566566-await expect(locator).toMatchScreenshot({
567567- comparatorName: 'myCustomComparator',
568568- comparatorOptions: {
569569- sensitivity: 0.08,
570570- ignoreColors: true,
571571- },
572572-})
573573-```
574574-575575-**Comparator Function Signature:**
576576-577577-```ts
578578-type Comparator<Options> = (
579579- reference: {
580580- metadata: { height: number; width: number }
581581- data: TypedArray
582582- },
583583- actual: {
584584- metadata: { height: number; width: number }
585585- data: TypedArray
586586- },
587587- options: {
588588- createDiff: boolean
589589- } & Options
590590-) => Promise<{
591591- pass: boolean
592592- diff: TypedArray | null
593593- message: string | null
594594-}> | {
595595- pass: boolean
596596- diff: TypedArray | null
597597- message: string | null
598598-}
599599-```
600600-601601-The `reference` and `actual` images are decoded using the appropriate codec (currently only PNG). The `data` property is a flat `TypedArray` (`Buffer`, `Uint8Array`, or `Uint8ClampedArray`) containing pixel data in RGBA format:
602602-603603-- **4 bytes per pixel**: red, green, blue, alpha (from `0` to `255` each)
604604-- **Row-major order**: pixels are stored left-to-right, top-to-bottom
605605-- **Total length**: `width × height × 4` bytes
606606-- **Alpha channel**: always present. Images without transparency have alpha values set to `255` (fully opaque)
607607-608608-::: tip Performance Considerations
609609-The `createDiff` option indicates whether a diff image is needed. During [stable screenshot detection](/guide/browser/visual-regression-testing#how-visual-tests-work), Vitest calls comparators with `createDiff: false` to avoid unnecessary work.
610610-611611-**Respect this flag to keep your tests fast**.
612612-:::
613613-614614-::: warning Handle Missing Options
615615-The `options` parameter in `toMatchScreenshot()` is optional, so users might not provide all your comparator options. Always make them optional with default values:
616616-617617-```ts
618618-myCustomComparator: (
619619- reference,
620620- actual,
621621- { createDiff, threshold = 0.1, maxDiff = 100 },
622622-) => {
623623- // ...comparison logic
624624-}
625625-```
626626-:::
+4
docs/config/ui.md
···1414::: warning
1515This features requires a [`@vitest/ui`](https://www.npmjs.com/package/@vitest/ui) package to be installed. If you do not have it already, Vitest will install it when you run the test command for the first time.
1616:::
1717+1818+::: danger SECURITY ADVICE
1919+Make sure that your UI server is not exposed to the network. Since Vitest 4.1 setting [`api.host`](/config/api) to anything other than `localhost` will disable the buttons to save the code or run any tests for security reasons, effectively making UI a readonly reporter.
2020+:::
+28
docs/guide/cli-generated.md
···70707171Set to true to exit if port is already in use, instead of automatically trying the next available port
72727373+### api.allowExec
7474+7575+- **CLI:** `--api.allowExec`
7676+- **Config:** [api.allowExec](/config/api#api-allowexec)
7777+7878+Allow API to execute code. (Be careful when enabling this option in untrusted environments)
7979+8080+### api.allowWrite
8181+8282+- **CLI:** `--api.allowWrite`
8383+- **Config:** [api.allowWrite](/config/api#api-allowwrite)
8484+8585+Allow API to edit files. (Be careful when enabling this option in untrusted environments)
8686+7387### silent
74887589- **CLI:** `--silent [value]`
···331345- **Config:** [browser.api.strictPort](/config/browser/api#api-strictport)
332346333347Set to true to exit if port is already in use, instead of automatically trying the next available port
348348+349349+### browser.api.allowExec
350350+351351+- **CLI:** `--browser.api.allowExec`
352352+- **Config:** [browser.api.allowExec](/config/browser/api#api-allowexec)
353353+354354+Allow API to execute code. (Be careful when enabling this option in untrusted environments)
355355+356356+### browser.api.allowWrite
357357+358358+- **CLI:** `--browser.api.allowWrite`
359359+- **Config:** [browser.api.allowWrite](/config/browser/api#api-allowwrite)
360360+361361+Allow API to edit files. (Be careful when enabling this option in untrusted environments)
334362335363### browser.isolate
336364
···17171818::: tip
1919This API follows [`server.fs`](https://vitejs.dev/config/server-options.html#server-fs-allow) limitations for security reasons.
2020+2121+If [`browser.api.allowWrite`](/config/browser/api) or [`api.allowWrite`](/config/api#api-allowwrite) are disabled, `writeFile` and `removeFile` functions won't do anything.
2022:::
21232224```ts
+1-1
docs/api/browser/locators.md
···7788A locator is a representation of an element or a number of elements. Every locator is defined by a string called a selector. Vitest abstracts this selector by providing convenient methods that generate them behind the scenes.
991010-The locator API uses a fork of [Playwright's locators](https://playwright.dev/docs/api/class-locator) called [Ivya](https://npmjs.com/ivya). However, Vitest provides this API to every [provider](/config/browser#browser-provider), not just playwright.
1010+The locator API uses a fork of [Playwright's locators](https://playwright.dev/docs/api/class-locator) called [Ivya](https://npmjs.com/ivya). However, Vitest provides this API to every [provider](/config/browser/provider), not just playwright.
11111212::: tip
1313This page covers API usage. To better understand locators and their usage, read [Playwright's "Locators" documentation](https://playwright.dev/docs/locators).
+17-1
docs/config/browser/api.md
···5566# browser.api
7788-- **Type:** `number | { port?, strictPort?, host? }`
88+- **Type:** `number | object`
99- **Default:** `63315`
1010- **CLI:** `--browser.api=63315`, `--browser.api.port=1234, --browser.api.host=example.com`
11111212Configure options for Vite server that serves code in the browser. Does not affect [`test.api`](#api) option. By default, Vitest assigns port `63315` to avoid conflicts with the development server, allowing you to run both in parallel.
1313+1414+## api.allowWrite <Version>4.1.0</Version> {#api-allowwrite}
1515+1616+- **Type:** `boolean`
1717+- **Default:** `true` if not exposed to the network, `false` otherwise
1818+1919+Vitest saves [annotation attachments](/guide/test-annotations), [artifacts](/api/advanced/artifacts) and [snapshots](/guide/snapshot) by receiving a WebSocket connection from the browser. This allows anyone who can connect to the API write any arbitary code on your machine within the root of your project (configured by [`fs.allow`](https://vite.dev/config/server-options#server-fs-allow)).
2020+2121+If browser server is not exposed to the internet (the host is `localhost`), this should not be a problem, so the default value in that case is `true`. If you override the host, Vitest will set `allowWrite` to `false` by default to prevent potentially harmful writes.
2222+2323+## api.allowExec <Version>4.1.0</Version> {#api-allowexec}
2424+2525+- **Type:** `boolean`
2626+- **Default:** `true` if not exposed to the network, `false` otherwise
2727+2828+Allows running any test file via the UI. This only applies to the interactive elements (and the server code behind them) in the [UI](/guide/ui) that can run the code. If UI is disabled, this has no effect. See [`api.allowExec`](/config/api#api-allowexec) for more information.
···11+import { expect, test } from 'vitest';
22+33+test('basic test', () => {
44+ expect(1 + 1).toBe(2)
55+})
+22
test/cli/test/config/browser-configs.test.ts
···10611061 expect(stdout).toContain('✓ |chromium| browser-custom.test.ts')
10621062 expect(exitCode).toBe(0)
10631063})
10641064+10651065+test('show a warning if host is exposed', async () => {
10661066+ const { stderr } = await runVitest({
10671067+ config: false,
10681068+ root: './fixtures/basic',
10691069+ reporters: [
10701070+ {
10711071+ onInit() {
10721072+ throw new Error('stop')
10731073+ },
10741074+ },
10751075+ ],
10761076+ browser: {
10771077+ api: {
10781078+ host: 'custom-host',
10791079+ },
10801080+ },
10811081+ })
10821082+ expect(stderr).toContain(
10831083+ 'API server is exposed to network, disabling write and exec operations by default for security reasons. This can cause some APIs to not work as expected. Set `browser.api.allowExec` manually to hide this warning. See https://vitest.dev/config/browser/api for more details.',
10841084+ )
10851085+})
···4646 description:
4747 'Set to true to exit if port is already in use, instead of automatically trying the next available port',
4848 },
4949+ allowExec: {
5050+ description: 'Allow API to execute code. (Be careful when enabling this option in untrusted environments)',
5151+ },
5252+ allowWrite: {
5353+ description: 'Allow API to edit files. (Be careful when enabling this option in untrusted environments)',
5454+ },
4955 middlewareMode: null,
5056})
5157···106112 argument: '[port]',
107113 description: `Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. If true will be set to ${defaultPort}`,
108114 subcommands: apiConfig(defaultPort),
115115+ transform(portOrOptions) {
116116+ if (typeof portOrOptions === 'number') {
117117+ return { port: portOrOptions }
118118+ }
119119+ return portOrOptions
120120+ },
109121 },
110122 silent: {
111123 description: 'Silent console output from tests. Use `\'passed-only\'` to see logs from failing tests only.',
+28
packages/vitest/src/node/config/resolveConfig.ts
···11import type { ResolvedConfig as ResolvedViteConfig } from 'vite'
22import type { Vitest } from '../core'
33+import type { Logger } from '../logger'
34import type { BenchmarkBuiltinReporters } from '../reporters'
45import type { ResolvedBrowserOptions } from '../types/browser'
56import type {
···5556 return { host, port: Number(port) || defaultInspectPort }
5657}
57585959+/**
6060+ * @deprecated Internal function
6161+ */
5862export function resolveApiServerConfig<Options extends ApiConfig & Omit<UserConfig, 'expect'>>(
5963 options: Options,
6064 defaultPort: number,
6565+ parentApi?: ApiConfig,
6666+ logger?: Logger,
6167): ApiConfig | undefined {
6268 let api: ApiConfig | undefined
6369···95101 }
96102 else {
97103 api = { middlewareMode: true }
104104+ }
105105+106106+ // if the API server is exposed to network, disable write operations by default
107107+ if (!api.middlewareMode && api.host && api.host !== 'localhost' && api.host !== '127.0.0.1') {
108108+ // assigned to browser
109109+ if (parentApi) {
110110+ if (api.allowWrite == null && api.allowExec == null) {
111111+ logger?.error(
112112+ c.yellow(
113113+ `${c.yellowBright(' WARNING ')} API server is exposed to network, disabling write and exec operations by default for security reasons. This can cause some APIs to not work as expected. Set \`browser.api.allowExec\` manually to hide this warning. See https://vitest.dev/config/browser/api for more details.`,
114114+ ),
115115+ )
116116+ }
117117+ }
118118+ api.allowWrite ??= parentApi?.allowWrite ?? false
119119+ api.allowExec ??= parentApi?.allowExec ?? false
120120+ }
121121+ else {
122122+ api.allowWrite ??= parentApi?.allowWrite ?? true
123123+ api.allowExec ??= parentApi?.allowExec ?? true
98124 }
99125100126 return api
···801827 resolved.browser.api = resolveApiServerConfig(
802828 resolved.browser,
803829 defaultBrowserPort,
830830+ resolved.api,
831831+ logger,
804832 ) || {
805833 port: defaultBrowserPort,
806834 }
···4444export type ApiConfig = Pick<
4545 ServerOptions,
4646 'port' | 'strictPort' | 'host' | 'middlewareMode'
4747->
4747+> & {
4848+ /**
4949+ * Allow any write operations from the API server.
5050+ *
5151+ * @default true if `api.host` is exposed to network, false otherwise
5252+ */
5353+ allowWrite?: boolean
5454+ /**
5555+ * Allow running test files via the API.
5656+ * If `api.host` is exposed to network and `allowWrite` is true,
5757+ * anyone connected to the API server can run arbitrary code on your machine.
5858+ *
5959+ * @default true if `api.host` is exposed to network, false otherwise
6060+ */
6161+ allowExec?: boolean
6262+}
48634964export interface EnvironmentOptions {
5065 /**