-
+
By
-
+
-
- Next Generation Testing Framework
-
+
Next Generation Testing Framework
A Vite-native testing framework. It's fast!
@@ -16,15 +16,26 @@
Get Started
-
+
View on GitHub
-
-

+
+
diff --git a/docs/.vitepress/theme/Home.vue b/docs/.vitepress/theme/Home.vue
index 2c59554f2..493e64ed3 100644
--- a/docs/.vitepress/theme/Home.vue
+++ b/docs/.vitepress/theme/Home.vue
@@ -19,12 +19,14 @@ import Intro from './Intro.vue'
diff --git a/docs/.vitepress/theme/Intro.vue b/docs/.vitepress/theme/Intro.vue
index 44e81054a..422166d69 100644
--- a/docs/.vitepress/theme/Intro.vue
+++ b/docs/.vitepress/theme/Intro.vue
@@ -1,30 +1,36 @@
-
+
-

-
Why Vitest
+

+
Why Vitest
-
- The Vite Native Test Runner
-
-
Learn more
+
The Vite Native Test Runner
+
Learn more
- Vitest was created to make testing just work for Vite apps. By building on top of Vite, Vitest
- natively
- understands your Vite config and is able to reuse the same resolve and
- transform pipelines.
+ Vitest was created to make testing just work for
+ Vite
+ apps. By building on top of Vite, Vitest natively understands your Vite config and is able
+ to reuse the same resolve and transform pipelines.
- You can also use Vitest even if you are not using Vite. It is Jest-compatible
- and works for backend code too.
+ You can also use Vitest even if you are not using Vite. It is Jest-compatible and works
+ for backend code too.
-
Learn more
+
Learn more
diff --git a/docs/.vitepress/theme/SupportedVersions.vue b/docs/.vitepress/theme/SupportedVersions.vue
index 5ab471ba7..c859bcb2c 100644
--- a/docs/.vitepress/theme/SupportedVersions.vue
+++ b/docs/.vitepress/theme/SupportedVersions.vue
@@ -38,20 +38,15 @@ const checkedResult = computed(() => {
const satisfies = (targetVersion: string) => {
const compared = parseVersion(targetVersion)!
- return (
- parsedVersion.major === compared.major
- && parsedVersion.minor >= compared.minor
- )
+ return parsedVersion.major === compared.major && parsedVersion.minor >= compared.minor
}
- const satisfiesOneSupportedVersion
- = parsedVersion.major >= parsedViteVersion.major // Treat future major versions as supported
- || supportInfo.regularPatches.some(satisfies)
- || supportInfo.importantFixes.some(satisfies)
- || supportInfo.securityPatches.some(satisfies)
-
- return satisfiesOneSupportedVersion
- ? supportedVersionMessage
- : notSupportedVersionMessage
+ const satisfiesOneSupportedVersion =
+ parsedVersion.major >= parsedViteVersion.major || // Treat future major versions as supported
+ supportInfo.regularPatches.some(satisfies) ||
+ supportInfo.importantFixes.some(satisfies) ||
+ supportInfo.securityPatches.some(satisfies)
+
+ return satisfiesOneSupportedVersion ? supportedVersionMessage : notSupportedVersionMessage
})
function parseVersion(version: string) {
@@ -68,13 +63,11 @@ function parseVersion(version: string) {
return { major, minor, patch }
}
-function computeSupportInfo(
- version: NonNullable
>,
-) {
+function computeSupportInfo(version: NonNullable>) {
const { major, minor } = version
const f = (versions: string[]) => {
return versions
- .map(v => previousMajorLatestMinors[v] ?? v)
+ .map((v) => previousMajorLatestMinors[v] ?? v)
.filter((version) => {
if (!isValidVitestVersion(version)) {
return false
@@ -96,16 +89,14 @@ function computeSupportInfo(
}
function versionsToText(versions: string[]) {
- versions = versions.map(v => `vitest@${v}`)
+ versions = versions.map((v) => `vitest@${v}`)
if (versions.length === 0) {
return ''
}
if (versions.length === 1) {
return versions[0]
}
- return (
- `${versions.slice(0, -1).join(', ')} and ${versions.at(-1)}`
- )
+ return `${versions.slice(0, -1).join(', ')} and ${versions.at(-1)}`
}
function isValidVitestVersion(version: string) {
@@ -132,21 +123,14 @@ function isValidVitestVersion(version: string) {
.
- All versions before these are no longer supported. Users should upgrade
- to receive updates.
+ All versions before these are no longer supported. Users should upgrade to receive updates.
If you're using Vitest
- , it is
- {{
- checkedResult.text
- }}.
+ , it
+ is {{ checkedResult.text }}.
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index ee6600970..84661c233 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -1,16 +1,16 @@
import type { Theme } from 'vitepress'
import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client'
-import { inBrowser } from 'vitepress'
import VitestTheme from '@voidzero-dev/vitepress-theme/src/vitest'
+import { inBrowser } from 'vitepress'
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client'
-import Version from '../components/Version.vue'
-import CRoot from '../components/CRoot.vue'
-import Deprecated from '../components/Deprecated.vue'
-import Experimental from '../components/Experimental.vue'
import Advanced from '../components/Advanced.vue'
import ChangelogButton from '../components/ChangelogButton.vue'
import CopyPrompt from '../components/CopyPrompt.vue'
import CourseLink from '../components/CourseLink.vue'
+import CRoot from '../components/CRoot.vue'
+import Deprecated from '../components/Deprecated.vue'
+import Experimental from '../components/Experimental.vue'
+import Version from '../components/Version.vue'
import './styles.css'
import '@shikijs/vitepress-twoslash/style.css'
import 'virtual:group-icons.css'
@@ -36,7 +36,11 @@ function getRedirectPath(url: URL) {
// /config/#reporters -> /config/reporters
// /config/#coverage-provider -> /config/coverage#coverage-provider
// /config/#browser.enabled -> /config/browser/enabled
- if (url.pathname === '/config' || url.pathname === '/config/' || url.pathname === '/config.html') {
+ if (
+ url.pathname === '/config' ||
+ url.pathname === '/config/' ||
+ url.pathname === '/config.html'
+ ) {
if (url.hash.startsWith('#browser.')) {
const [page, ...hash] = url.hash.slice('#browser.'.length).toLowerCase().split('-')
return `/config/browser/${page}${hash.length ? `#${[page, ...hash].join('-')}` : ''}`
@@ -45,7 +49,11 @@ function getRedirectPath(url: URL) {
return `/config/${page}${hash.length ? `#${[page, ...hash].join('-')}` : ''}`
}
// /guide/browser/config#browser.locators-testidattribute -> /config/browser/locators#browser-locators-testidattribute
- if (url.pathname === '/guide/browser/config' || url.pathname === '/guide/browser/config/' || url.pathname === '/guide/browser/config.html') {
+ if (
+ url.pathname === '/guide/browser/config' ||
+ url.pathname === '/guide/browser/config/' ||
+ url.pathname === '/guide/browser/config.html'
+ ) {
const [page, ...hash] = url.hash.slice('#browser.'.length).toLowerCase().split('-')
return `/config/browser/${page}${hash.length ? `#${[page, ...hash].join('-')}` : ''}`
}
diff --git a/docs/.vitepress/theme/styles.css b/docs/.vitepress/theme/styles.css
index 03130483b..776336c24 100644
--- a/docs/.vitepress/theme/styles.css
+++ b/docs/.vitepress/theme/styles.css
@@ -1,9 +1,9 @@
-@import "@voidzero-dev/vitepress-theme/src/styles/index.css";
+@import '@voidzero-dev/vitepress-theme/src/styles/index.css';
@source "./**/*.vue";
/* Vitest */
-:root[data-variant="vitest"] {
+:root[data-variant='vitest'] {
--color-brand: #008039;
/* TODO: home page wcag-aa color contrast (remove this once fixed at void0 theme):
* - why vitest section and texts
@@ -13,19 +13,18 @@
--color-grey: #867e8e;
}
-:root.dark:not([data-theme])[data-variant="vitest"],
-:root[data-theme="dark"][data-variant="vitest"] {
+:root.dark:not([data-theme])[data-variant='vitest'],
+:root[data-theme='dark'][data-variant='vitest'] {
--color-brand: var(--color-zest);
}
-:root[data-variant="vitest"]:not(.dark):not([data-theme="light"]),
-:root[data-theme="light"][data-variant="vitest"] {
+:root[data-variant='vitest']:not(.dark):not([data-theme='light']),
+:root[data-theme='light'][data-variant='vitest'] {
--color-brand: #008039;
/* TODO: code block (remove this once fixed at void0 theme) */
--vp-code-color: #007d38;
}
-
.highlighted-word {
background-color: var(--vp-code-line-highlight-color);
transition: background-color 0.5s;
diff --git a/docs/api/advanced/plugin.md b/docs/api/advanced/plugin.md
index a176f8303..884865ba0 100644
--- a/docs/api/advanced/plugin.md
+++ b/docs/api/advanced/plugin.md
@@ -14,6 +14,7 @@ This guide assumes you know how to work with [Vite plugins](https://vite.dev/gui
Vitest supports a `configureVitest` [plugin](https://vite.dev/guide/api-plugin.html) hook since version 3.1.
::: code-group
+
```ts [only vitest]
import type { Vite, VitestPluginContext } from 'vitest/node'
@@ -26,6 +27,7 @@ export function plugin(): Vite.Plugin {
}
}
```
+
```ts [vite and vitest]
///
@@ -43,6 +45,7 @@ export function plugin(): Plugin {
}
}
```
+
:::
::: tip TypeScript
@@ -51,6 +54,7 @@ Vitest re-exports all Vite type-only imports via a `Vite` namespace, which you c
```ts
///
```
+
:::
Unlike [`reporter.onInit`](/api/advanced/reporters#oninit), this hooks runs early in Vitest lifecycle allowing you to make changes to configuration like `coverage` and `reporters`. A more notable change is that you can manipulate the global config from a [test project](/guide/projects) if your plugin is defined in the project and not in the global config.
diff --git a/docs/api/advanced/reporters.md b/docs/api/advanced/reporters.md
index df2d54c9b..e9213488d 100644
--- a/docs/api/advanced/reporters.md
+++ b/docs/api/advanced/reporters.md
@@ -61,6 +61,7 @@ Internally this method is called inside [`vitest.start`](/api/advanced/vitest#st
Note that you can also get access to `vitest` instance from test cases, suites and test modules via a [`project`](/api/advanced/test-project) property, but it might also be useful to store a reference to `vitest` in this method.
::: details Example
+
```ts
import type { Reporter, TestSpecification, Vitest } from 'vitest/node'
@@ -82,6 +83,7 @@ class MyReporter implements Reporter {
export default new MyReporter()
```
+
:::
## onBrowserInit {#onbrowserinit}
@@ -105,6 +107,7 @@ This method is called when a new test run has started. It receives an array of [
If Vitest didn't find any test files to run, this event will be invoked with an empty array, and then [`onTestRunEnd`](#ontestrunend) will be called immediately after.
::: details Example
+
```ts
import type { Reporter, TestSpecification } from 'vitest/node'
@@ -116,6 +119,7 @@ class MyReporter implements Reporter {
export default new MyReporter()
```
+
:::
## onTestRunEnd
@@ -143,6 +147,7 @@ The third argument indicated why the test run was finished:
If Vitest didn't find any test files to run, this event will be invoked with empty arrays of modules and errors, and the state will depend on the value of [`config.passWithNoTests`](/config/passwithnotests).
::: details Example
+
```ts
import type {
Reporter,
@@ -179,6 +184,7 @@ class MyReporter implements Reporter {
export default new MyReporter()
```
+
:::
## onCoverage
diff --git a/docs/api/advanced/runner.md b/docs/api/advanced/runner.md
index 1c514acbd..bec83f9bb 100644
--- a/docs/api/advanced/runner.md
+++ b/docs/api/advanced/runner.md
@@ -135,6 +135,7 @@ export default class Runner {
}
}
```
+
:::
::: warning
diff --git a/docs/api/advanced/test-case.md b/docs/api/advanced/test-case.md
index 680ad1376..e6d6a824f 100644
--- a/docs/api/advanced/test-case.md
+++ b/docs/api/advanced/test-case.md
@@ -71,6 +71,7 @@ const hash = generateFileHash(
undefined, // the project name or `undefined` is not set
)
```
+
:::
::: danger
diff --git a/docs/api/advanced/test-collection.md b/docs/api/advanced/test-collection.md
index 50e55a61e..1ba56361b 100644
--- a/docs/api/advanced/test-collection.md
+++ b/docs/api/advanced/test-collection.md
@@ -12,6 +12,7 @@ for (const child of module.children) {
console.log(child.type, child.name)
}
```
+
:::
## size
diff --git a/docs/api/advanced/test-module.md b/docs/api/advanced/test-module.md
index 9468ab792..237fccb8a 100644
--- a/docs/api/advanced/test-module.md
+++ b/docs/api/advanced/test-module.md
@@ -39,7 +39,9 @@ Module id relative to the project. This is the same as `task.name` in the deprec
This is a Vite's [`DevEnvironment`](https://vite.dev/guide/api-environment) that transforms all files inside of the test module.
::: details History
+
- `v4.0.15`: added as experimental
+
:::
## state
diff --git a/docs/api/advanced/test-project.md b/docs/api/advanced/test-project.md
index 091d82e1a..db203d865 100644
--- a/docs/api/advanced/test-project.md
+++ b/docs/api/advanced/test-project.md
@@ -13,6 +13,7 @@ This guide describes the advanced Node.js API. If you just want to define projec
The name is a unique string assigned by the user or interpreted by Vitest. If user did not provide a name, Vitest tries to load a `package.json` in the root of the project and takes the `name` property from there. If there is no `package.json`, Vitest uses the name of the folder by default. Inline projects use numbers as the name (converted to string).
::: code-group
+
```ts [node.js]
import { createVitest } from 'vitest/node'
@@ -24,6 +25,7 @@ vitest.projects.map(p => p.name) === [
'custom'
]
```
+
```ts [vitest.config.js]
import { defineConfig } from 'vitest/config'
@@ -48,6 +50,7 @@ export default defineConfig({
},
})
```
+
:::
::: info
@@ -74,6 +77,7 @@ The `serializedConfig` property is a getter. Every time it's accessed Vitest ser
```ts
project.serializedConfig === project.serializedConfig // ❌
```
+
:::
## globalConfig
@@ -128,6 +132,7 @@ function provide
(
A way to provide custom values to tests in addition to [`config.provide`](/config/provide) field. All values are validated with [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) before they are stored, but the values on `providedContext` themselves are not cloned.
::: code-group
+
```ts [node.js]
import { createVitest } from 'vitest/node'
@@ -136,10 +141,12 @@ const project = vitest.projects.find(p => p.name === 'custom')
project.provide('key', 'value')
await vitest.start()
```
+
```ts [test.spec.js]
import { inject } from 'vitest'
const value = inject('key')
```
+
:::
The values can be provided dynamically. Provided value in tests will be updated on their next run.
@@ -152,6 +159,7 @@ export default function setup({ provide }) {
provide('wsPort', 3000)
}
```
+
:::
## getProvidedContext
@@ -248,6 +256,7 @@ This method looks at several config options:
- `test.include`, `test.exclude` to find regular test files
- `test.includeSource`, `test.exclude` to find in-source tests
- `test.typecheck.include`, `test.typecheck.exclude` to find typecheck tests
+
:::
## matchesTestGlob
@@ -294,6 +303,7 @@ const dynamicExample = await project.import('./example.js')
dynamicExample !== staticExample // ✅
```
+
:::
::: info
diff --git a/docs/api/advanced/test-specification.md b/docs/api/advanced/test-specification.md
index 79e96a628..adeeb398e 100644
--- a/docs/api/advanced/test-specification.md
+++ b/docs/api/advanced/test-specification.md
@@ -55,12 +55,14 @@ This is an array of lines in the source code where the test files are defined. T
Note that if there is no test on at least one of the lines, the whole suite will fail. An example of a correct `testLines` configuration:
::: code-group
+
```ts [script.js]
const specification = project.createSpecification(
resolve('./example.test.ts'),
[3, 8, 9],
)
```
+
```ts:line-numbers{3,8,9} [example.test.js]
import { test, describe } from 'vitest'
@@ -73,6 +75,7 @@ describe('a group of tests', () => { // [!code error]
test.skip('skipped test')
})
```
+
:::
## testNamePattern 4.1.0 {#testnamepattern}
diff --git a/docs/api/advanced/test-suite.md b/docs/api/advanced/test-suite.md
index 7cbd5f097..21290c54e 100644
--- a/docs/api/advanced/test-suite.md
+++ b/docs/api/advanced/test-suite.md
@@ -72,6 +72,7 @@ const hash = generateFileHash(
undefined, // the project name or `undefined` is not set
)
```
+
:::
::: danger
@@ -145,6 +146,7 @@ function visit(collection: TestCollection) {
}
}
```
+
:::
## ok
diff --git a/docs/api/advanced/vitest.md b/docs/api/advanced/vitest.md
index cf797f1b0..12b3b4c13 100644
--- a/docs/api/advanced/vitest.md
+++ b/docs/api/advanced/vitest.md
@@ -168,6 +168,7 @@ This method can be slow because it needs to filter `--changed` flags. Do not use
- If you need to get the list of specifications for known test files, use [`getModuleSpecifications`](#getmodulespecifications) instead.
- If you need to get the list of all possible test files, use [`globTestSpecifications`](#globtestspecifications).
+
:::
## mergeReports
@@ -410,6 +411,7 @@ const dynamicExample = await vitest.import('./example.js')
dynamicExample !== staticExample // ✅
```
+
:::
::: info
@@ -483,6 +485,7 @@ function onFilterWatchedSpecification(
fn: (specification: TestSpecification) => boolean
): void
```
+
Register a handler that will be called when a file is changed. This callback should return `true` or `false`, indicating whether the test file needs to be rerun.
With this method, you can hook into the default watcher logic to delay or discard tests that the user doesn't want to keep track of at the moment:
@@ -580,6 +583,7 @@ import { escapeTestName } from 'vitest/node'
// turns into /hello, .+?/
const escapedPattern = new RegExp(escapeTestName('hello, %s', true))
```
+
:::
::: warning
@@ -621,6 +625,7 @@ export function experimental_getSourceModuleDiagnostic(
```
::: details Types
+
```ts
export interface ModuleDefinitionLocation {
line: number
@@ -660,6 +665,7 @@ export interface SourceModuleDiagnostic {
untrackedModules: UntrackedModuleDefinitionDiagnostic[]
}
```
+
:::
Returns module's diagnostic. If [`testModule`](/api/advanced/test-module) is not provided, `selfTime` and `totalTime` will be aggregated across all tests that were running the last time. If the module was not transformed or executed, the diagnostic will be empty.
@@ -705,7 +711,6 @@ const report = vitest.createReport('my-json-reporter')
const root = report.root
```
-
### Report.clean
```ts
@@ -772,7 +777,6 @@ const filenames: string[] = await report.readdir()
### Report.delete
-
```ts
function delete(filename: string): Promise
```
@@ -785,4 +789,3 @@ const report = vitest.createReport('my-json-reporter')
// Deletes file from .vitest/my-json-reporter/test-report.json
await report.delete('test-report.json')
```
-
diff --git a/docs/api/assert.md b/docs/api/assert.md
index ee3a14c10..02dbf48eb 100644
--- a/docs/api/assert.md
+++ b/docs/api/assert.md
@@ -6,6 +6,7 @@ Vitest reexports the `assert` method from [`chai`](https://www.chaijs.com/api/as
When using [assertion functions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions) such as `assert` from `import.meta.vitest` in [in-source tests](/guide/in-source), TypeScript reports error `TS2775` because they must be called via an explicitly annotated name. Annotate the variable with `Chai.Assert` or call it directly:
::: code-group
+
```ts [Annotated variable]
if (import.meta.vitest) {
const { test, assert } = import.meta.vitest // [!code --]
@@ -17,6 +18,7 @@ if (import.meta.vitest) {
})
}
```
+
```ts [Direct call]
if (import.meta.vitest) {
const { test, assert } = import.meta.vitest // [!code --]
@@ -28,6 +30,7 @@ if (import.meta.vitest) {
})
}
```
+
:::
## assert
@@ -419,7 +422,7 @@ test('assert.isDefined', () => {
- **Type:** `(value: T, message?: string) => void`
- **Alias:** `isCallable`
-Asserts that `value` is a function.
+ Asserts that `value` is a function.
```ts
import { assert, test } from 'vitest'
diff --git a/docs/api/browser/assertions.md b/docs/api/browser/assertions.md
index 72f3535f6..51d969433 100644
--- a/docs/api/browser/assertions.md
+++ b/docs/api/browser/assertions.md
@@ -12,6 +12,7 @@ If you are using [TypeScript](/guide/browser/#typescript) or want to have correc
```ts
///
```
+
:::
Tests in the browser might fail inconsistently due to their asynchronous nature. Because of this, it is important to have a way to guarantee that assertions succeed even if the condition is delayed (by a timeout, network request, or animation, for example). For this purpose, Vitest provides retriable assertions out of the box via the [`expect.poll`](/api/expect#poll) and `expect.element` APIs:
@@ -62,6 +63,7 @@ Like [`expect.poll`](/api/expect#poll), `expect.element` retries DOM assertions
// will fail immediately if .textContent is not `'Error!'`
expect(banner).toMatchTextContent('Error!')
```
+
:::
## toBeDisabled
@@ -1026,6 +1028,7 @@ Also note that unlike `testing-library`, Vitest ignores all custom roles except
await expect.element(getByTestId('switch')).toHaveRole('switch') // ✅
await expect.element(getByTestId('switch')).toHaveRole('alert') // ❌
```
+
:::
## toHaveSelection
@@ -1213,6 +1216,7 @@ await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button', {
},
})
```
+
:::
- `screenshotOptions: object`
diff --git a/docs/api/browser/context.md b/docs/api/browser/context.md
index 1ba6984d8..ca16b759d 100644
--- a/docs/api/browser/context.md
+++ b/docs/api/browser/context.md
@@ -346,22 +346,26 @@ const html = utils.prettyDOM(element, undefined, {
**Common Patterns:**
Filter out scripts and styles:
+
```ts
utils.configurePrettyDOM({ filterNode: 'script, style' })
```
Hide specific elements with data attributes:
+
```ts
utils.configurePrettyDOM({ filterNode: '[data-test-hide]' })
```
Hide nested content within an element:
+
```ts
// Hides all children of elements with data-test-hide-content
utils.configurePrettyDOM({ filterNode: '[data-test-hide-content] *' })
```
Combine multiple selectors:
+
```ts
utils.configurePrettyDOM({
filterNode: 'script, style, [data-test-hide], svg'
diff --git a/docs/api/browser/interactivity.md b/docs/api/browser/interactivity.md
index 69ddff4f3..b403cdcd1 100644
--- a/docs/api/browser/interactivity.md
+++ b/docs/api/browser/interactivity.md
@@ -56,6 +56,7 @@ beforeEach(async () => {
await userEvent.unhover(document.body)
})
```
+
:::
## userEvent.click
@@ -101,6 +102,7 @@ await userEvent.keyboard('{/Shift}')
```
With Playwright:
+
```ts
await userEvent.click(element, { modifiers: ['Shift'] })
```
diff --git a/docs/api/browser/locators.md b/docs/api/browser/locators.md
index 61a810022..c26275ba6 100644
--- a/docs/api/browser/locators.md
+++ b/docs/api/browser/locators.md
@@ -34,6 +34,7 @@ const deleteButton = page
await deleteButton.click()
await expect.element(deleteButton).toBeEnabled()
```
+
:::
## getByRole
@@ -504,6 +505,7 @@ page.getByRole('button')
.or(page.getByRole('link'))
.click() // ❌ matches multiple elements
```
+
:::
## filter
@@ -555,6 +557,7 @@ page.getByRole('article')
.filter({ has: page.getByRole('button', { name: 'delete row' }) })
.filter({ has: page.getByText('Vitest') })
```
+
:::
### hasNot
@@ -871,7 +874,7 @@ function query(): Element | null
This method returns a single element matching the locator's selector or `null` if no element is found.
-If multiple elements match the selector, this method will throw an error. Use [`.elements()`](#elements) when you need all matching DOM Elements or [`.all()`](#all) if you need an array of locators matching the selector.
+If multiple elements match the selector, this method will throw an error. Use [`.elements()`](#elements) when you need all matching DOM Elements or [`.all()`](#all) if you need an array of locators matching the selector.
::: danger
This is an escape hatch for external APIs that do not support locators. Prefer using locator methods instead.
@@ -921,6 +924,7 @@ It is called automatically when locator is used with `expect.element` every time
```ts
await expect.element(page.getByRole('button')).toBeDisabled()
```
+
:::
Consider the following DOM structure:
@@ -1128,6 +1132,7 @@ test('works correctly', async () => {
await commands.test(page.getByText('Hello')) // ✅
})
```
+
:::
### length
diff --git a/docs/api/browser/react.md b/docs/api/browser/react.md
index 4e87cd717..de21aac97 100644
--- a/docs/api/browser/react.md
+++ b/docs/api/browser/react.md
@@ -49,6 +49,7 @@ import { render } from 'vitest-browser-react'
const screen = render() // [!code --]
const screen = await render() // [!code ++]
```
+
:::
### Options
@@ -258,6 +259,7 @@ await renderHook(() => {}, {
wrapper: createWrapper(Wrapper, { value: 'foo' }),
})
```
+
:::
`renderHook` returns a few useful methods and properties:
diff --git a/docs/api/browser/svelte.md b/docs/api/browser/svelte.md
index 9db5c7071..37eeb6ffc 100644
--- a/docs/api/browser/svelte.md
+++ b/docs/api/browser/svelte.md
@@ -206,6 +206,7 @@ await expect.element(
For simple snippets, you can use a wrapper component and "dummy" children to test them. Setting `data-testid` attributes can be helpful when testing slots in this manner.
::: code-group
+
```ts [basic.test.js]
import { render } from 'vitest-browser-svelte'
import { expect, test } from 'vitest'
@@ -221,6 +222,7 @@ test('basic snippet', async () => {
await expect.element(child).toBeInTheDocument()
})
```
+
```svelte [basic-snippet.svelte]
diff --git a/packages/browser/src/client/orchestrator.ts b/packages/browser/src/client/orchestrator.ts
index 72359d273..26ea4a271 100644
--- a/packages/browser/src/client/orchestrator.ts
+++ b/packages/browser/src/client/orchestrator.ts
@@ -1,5 +1,12 @@
import type { Context as OTELContext } from '@opentelemetry/api'
-import type { GlobalChannelIncomingEvent, IframeChannelEvent, IframeChannelOutgoingEvent, IframeReceivedEvent, IframeViewportDoneEvent, IframeViewportFailEvent } from '@vitest/browser/client'
+import type {
+ GlobalChannelIncomingEvent,
+ IframeChannelEvent,
+ IframeChannelOutgoingEvent,
+ IframeReceivedEvent,
+ IframeViewportDoneEvent,
+ IframeViewportFailEvent,
+} from '@vitest/browser/client'
import type { BrowserTesterOptions, SerializedConfig } from 'vitest'
import type { FileSpecification } from 'vitest/internal/browser'
import { channel, client, globalChannel } from '@vitest/browser/client'
@@ -40,17 +47,12 @@ export class IframeOrchestrator {
sdkPath: `/@fs/${otelConfig?.browserSdkPath}`,
})
- channel.addEventListener(
- 'message',
- e => this.onIframeEvent(e),
- )
- globalChannel.addEventListener(
- 'message',
- e => this.onGlobalChannelEvent(e),
- )
+ channel.addEventListener('message', (e) => this.onIframeEvent(e))
+ globalChannel.addEventListener('message', (e) => this.onGlobalChannelEvent(e))
// Notify the server once the websocket is ready without blocking orchestrator creation.
- void client.waitForConnection()
+ void client
+ .waitForConnection()
.then(() => client.rpc.onOrchestratorReady())
.catch((error) => {
debug('failed to notify orchestrator readiness', error)
@@ -59,15 +61,13 @@ export class IframeOrchestrator {
public async createTesters(options: BrowserTesterOptions): Promise {
await this.traces.waitInit()
- this.traces.recordInitSpan(
- this.traces.getContextFromCarrier(getBrowserState().otelCarrier),
- )
+ this.traces.recordInitSpan(this.traces.getContextFromCarrier(getBrowserState().otelCarrier))
const orchestratorSpan = this.traces.startContextSpan(
'vitest.browser.orchestrator.run',
this.traces.getContextFromCarrier(options.otelCarrier),
)
orchestratorSpan.span.setAttributes({
- 'vitest.browser.files': options.files.map(f => f.filepath),
+ 'vitest.browser.files': options.files.map((f) => f.filepath),
})
const endSpan = async () => {
orchestratorSpan.span.end()
@@ -99,7 +99,7 @@ export class IframeOrchestrator {
return
}
- this.iframes.forEach(iframe => iframe.remove())
+ this.iframes.forEach((iframe) => iframe.remove())
this.iframes.clear()
this.readyIframes.clear()
this.readyWaiters.clear()
@@ -205,12 +205,7 @@ export class IframeOrchestrator {
this.removeIframe(file)
}
- await this.prepareIframe(
- container,
- file,
- startTime,
- otelContext,
- )
+ await this.prepareIframe(container, file, startTime, otelContext)
await setIframeViewport(width, height)
// running tests after the "prepare" event
await this.sendEventToIframe({
@@ -249,42 +244,45 @@ export class IframeOrchestrator {
const href = this.getIframeHref(iframe)
debug('iframe loaded with href', href)
if (href !== iframe.src) {
- reject(this.dispatchIframeError(new Error(
- `Cannot connect to the iframe. `
- + `Did you change the location or submitted a form? `
- + 'If so, don\'t forget to call `event.preventDefault()` to avoid reloading the page.\n\n'
- + `Received URL: ${href || 'unknown due to CORS'}\nExpected: ${iframe.src}`,
- )))
- }
- else if (this.iframes.has(iframeId)) {
+ reject(
+ this.dispatchIframeError(
+ new Error(
+ `Cannot connect to the iframe. ` +
+ `Did you change the location or submitted a form? ` +
+ "If so, don't forget to call `event.preventDefault()` to avoid reloading the page.\n\n" +
+ `Received URL: ${href || 'unknown due to CORS'}\nExpected: ${iframe.src}`,
+ ),
+ ),
+ )
+ } else if (this.iframes.has(iframeId)) {
const events = this.iframeEvents.get(iframe)
if (events?.size) {
- this.dispatchIframeError(new Error(this.createWarningMessage(iframeId, 'during a test')))
- }
- else {
+ this.dispatchIframeError(
+ new Error(this.createWarningMessage(iframeId, 'during a test')),
+ )
+ } else {
this.warnReload(iframe, iframeId)
}
- }
- else {
+ } else {
this.iframes.set(iframeId, iframe)
this.waitForReady(iframeId)
- .then(() => this.sendEventToIframe({
- event: 'prepare',
- iframeId,
- startTime,
- otelCarrier: this.traces.getContextCarrier(otelContext),
- }))
- .then(resolve, error => reject(this.dispatchIframeError(error)))
+ .then(() =>
+ this.sendEventToIframe({
+ event: 'prepare',
+ iframeId,
+ startTime,
+ otelCarrier: this.traces.getContextCarrier(otelContext),
+ }),
+ )
+ .then(resolve, (error) => reject(this.dispatchIframeError(error)))
}
}
iframe.onerror = (e) => {
if (typeof e === 'string') {
reject(this.dispatchIframeError(new Error(e)))
- }
- else if (e instanceof ErrorEvent) {
+ } else if (e instanceof ErrorEvent) {
reject(this.dispatchIframeError(e.error))
- }
- else {
+ } else {
reject(this.dispatchIframeError(new Error(`Cannot load the iframe ${iframeId}.`)))
}
}
@@ -313,10 +311,12 @@ export class IframeOrchestrator {
// never does (e.g. it threw during bootstrap), don't wait forever
const timer = setTimeout(() => {
this.readyWaiters.delete(iframeId)
- reject(new Error(
- `The iframe "${iframeId}" did not become ready within ${timeout}ms. `
- + `The tester likely failed to initialize, check the browser console for errors.`,
- ))
+ reject(
+ new Error(
+ `The iframe "${iframeId}" did not become ready within ${timeout}ms. ` +
+ `The tester likely failed to initialize, check the browser console for errors.`,
+ ),
+ )
}, timeout)
this.readyWaiters.set(iframeId, {
@@ -348,11 +348,13 @@ export class IframeOrchestrator {
private loggedIframe = new WeakSet()
private createWarningMessage(iframeId: string, location: string) {
- return `The iframe${iframeId === ID_ALL ? '' : ` for "${iframeId}"`} was reloaded ${location}. `
- + `This can lead to unexpected behavior during tests, duplicated test results or tests hanging.\n\n`
- + `Make sure that your test code does not change window's location, submit forms without preventing default behavior, or imports unoptimized dependencies.\n`
- + `If you are using a framework that manipulates browser history (like React Router), consider using memory-based routing for tests. `
- + `If you think this is a false positive, open an issue with a reproduction: https://github.com/vitest-dev/vitest/issues/new`
+ return (
+ `The iframe${iframeId === ID_ALL ? '' : ` for "${iframeId}"`} was reloaded ${location}. ` +
+ `This can lead to unexpected behavior during tests, duplicated test results or tests hanging.\n\n` +
+ `Make sure that your test code does not change window's location, submit forms without preventing default behavior, or imports unoptimized dependencies.\n` +
+ `If you are using a framework that manipulates browser history (like React Router), consider using memory-based routing for tests. ` +
+ `If you think this is a false positive, open an issue with a reproduction: https://github.com/vitest-dev/vitest/issues/new`
+ )
}
private warnReload(iframe: HTMLIFrameElement, iframeId: string) {
@@ -362,13 +364,17 @@ export class IframeOrchestrator {
this.loggedIframe.add(iframe)
const message = `\x1B[41m WARNING \x1B[49m ${this.createWarningMessage(iframeId, 'multiple times')}`
- client.rpc.sendLog('run', {
- type: 'stderr',
- time: Date.now(),
- content: message,
- size: message.length,
- taskId: iframeId === ID_ALL ? undefined : generateFileId(iframeId),
- }).catch(() => { /* ignore */ })
+ client.rpc
+ .sendLog('run', {
+ type: 'stderr',
+ time: Date.now(),
+ content: message,
+ size: message.length,
+ taskId: iframeId === ID_ALL ? undefined : generateFileId(iframeId),
+ })
+ .catch(() => {
+ /* ignore */
+ })
}
private getIframeHref(iframe: HTMLIFrameElement) {
@@ -377,8 +383,7 @@ export class IframeOrchestrator {
// same origin trusted iframe (where tests can run)
// also allows accessing "location"
return iframe.contentWindow?.location.href
- }
- catch {
+ } catch {
// looks like this iframe is not a tester.html
return undefined
}
@@ -404,9 +409,11 @@ export class IframeOrchestrator {
// enable scaling only when using the UI, without UI the iframe fills the page
if (config.browser.ui) {
if (config.browser.name !== 'firefox') {
- iframe.style.setProperty('transform', 'scale(min(1, calc(100cqw / var(--viewport-width)), calc(100cqh / var(--viewport-height))))')
- }
- else {
+ iframe.style.setProperty(
+ 'transform',
+ 'scale(min(1, calc(100cqw / var(--viewport-width)), calc(100cqh / var(--viewport-height))))',
+ )
+ } else {
// Firefox cannot resolve relative units like `cqw` directly inside `atan2()`
// Storing it in a CSS variable first forces Firefox to resolve `100cqw` to an absolute pixel value
iframe.style.setProperty('--container-width', '100cqw')
@@ -415,7 +422,10 @@ export class IframeOrchestrator {
// `tan(atan2(a, b))` produces a unit-less `a / b` ratio:
// - `atan2()` accepts two lengths and returns an ``
// - `tan()` converts it back to a unit-less ``
- iframe.style.setProperty('transform', 'scale(min(1, tan(atan2(var(--container-width), var(--viewport-width))), tan(atan2(var(--container-height), var(--viewport-height)))))')
+ iframe.style.setProperty(
+ 'transform',
+ 'scale(min(1, tan(atan2(var(--container-width), var(--viewport-width))), tan(atan2(var(--container-height), var(--viewport-height)))))',
+ )
}
iframe.style.setProperty('transform-origin', 'top left')
@@ -461,15 +471,18 @@ export class IframeOrchestrator {
break
}
await setIframeViewport(width, height)
- channel.postMessage({ event: 'viewport:done', iframeId: id } satisfies IframeViewportDoneEvent)
+ channel.postMessage({
+ event: 'viewport:done',
+ iframeId: id,
+ } satisfies IframeViewportDoneEvent)
break
}
default: {
// ignore acknowledgements and responses to events we sent
const event = e.data.event
if (
- typeof event === 'string'
- && (event.startsWith('response:') || event.startsWith('ack:'))
+ typeof event === 'string' &&
+ (event.startsWith('response:') || event.startsWith('ack:'))
) {
break
}
@@ -520,10 +533,12 @@ export class IframeOrchestrator {
const timeout = getIframeTimeout()
ackTimer = setTimeout(() => {
cleanupEvents()
- reject(new Error(
- `The iframe "${event.iframeId}" did not acknowledge the "${event.event}" message within ${timeout}ms. `
- + `The tester might have crashed, been removed, or be blocked by a long synchronous task.`,
- ))
+ reject(
+ new Error(
+ `The iframe "${event.iframeId}" did not acknowledge the "${event.event}" message within ${timeout}ms. ` +
+ `The tester might have crashed, been removed, or be blocked by a long synchronous task.`,
+ ),
+ )
}, timeout)
function onReceived(e: MessageEvent) {
@@ -573,28 +588,20 @@ async function getContainer(config: SerializedConfig): Promise {
function generateFileId(file: string) {
const config = getConfig()
const path = relative(config.root, file)
- return generateFileHash(
- path,
- config.name,
- {
- typecheck: config.pool === 'typescript',
- __vitest_label__: config.mergeReportsLabel,
- },
- )
+ return generateFileHash(path, config.name, {
+ typecheck: config.pool === 'typescript',
+ __vitest_label__: config.mergeReportsLabel,
+ })
}
let currentViewport: { width: number; height: number } | undefined
-async function setIframeViewport(
- width: number,
- height: number,
-) {
+async function setIframeViewport(width: number, height: number) {
const ui = getUiAPI()
if (ui) {
await ui.setIframeViewport(width, height)
- }
- else {
+ } else {
document.body.style.setProperty('--viewport-width', `${width}px`)
document.body.style.setProperty('--viewport-height', `${height}px`)
@@ -603,20 +610,13 @@ async function setIframeViewport(
// the size actually changes; other providers resize the window, which
// outside code can move under us, so they always re-pin
const cacheable = getBrowserState().provider === 'playwright'
- if (
- cacheable
- && currentViewport?.width === width
- && currentViewport?.height === height
- ) {
+ if (cacheable && currentViewport?.width === width && currentViewport?.height === height) {
return
}
- await client.rpc.triggerCommand(
- getBrowserState().sessionId,
- '__vitest_viewport',
- undefined,
- [{ width, height }],
- )
+ await client.rpc.triggerCommand(getBrowserState().sessionId, '__vitest_viewport', undefined, [
+ { width, height },
+ ])
currentViewport = cacheable ? { width, height } : undefined
}
}
diff --git a/packages/browser/src/client/public/error-catcher.js b/packages/browser/src/client/public/error-catcher.js
index 15f3c5b0e..302b5d1a1 100644
--- a/packages/browser/src/client/public/error-catcher.js
+++ b/packages/browser/src/client/public/error-catcher.js
@@ -2,9 +2,8 @@ import { client } from '@vitest/browser/client'
function serializeError(unhandledError) {
const state = globalThis.__vitest_worker__
- const VITEST_TEST_NAME = state && state.current && state.current.type === 'test'
- ? state.current.name
- : undefined
+ const VITEST_TEST_NAME =
+ state && state.current && state.current.type === 'test' ? state.current.name : undefined
const VITEST_TEST_PATH = state && state.filepath ? state.filepath : undefined
if (typeof unhandledError !== 'object' || !unhandledError) {
@@ -29,8 +28,7 @@ function catchWindowErrors(errorEvent, prop, cb) {
function throwUnhandlerError(e) {
if (userErrorListenerCount === 0 && e[prop] != null) {
cb(e)
- }
- else {
+ } else {
// `ErrorEvent` doesn't necessary have `ErrorEvent.error` defined
// but some has `ErrorEvent.message` defined, e.g. ResizeObserver error.
// https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent/error
@@ -59,24 +57,26 @@ function catchWindowErrors(errorEvent, prop, cb) {
}
function registerUnexpectedErrors() {
- const offError = catchWindowErrors('error', 'error', event =>
- reportUnexpectedError('Error', event.error))
- const offRejection = catchWindowErrors('unhandledrejection', 'reason', event =>
- reportUnexpectedError('Unhandled Rejection', event.reason))
+ const offError = catchWindowErrors('error', 'error', (event) =>
+ reportUnexpectedError('Error', event.error),
+ )
+ const offRejection = catchWindowErrors('unhandledrejection', 'reason', (event) =>
+ reportUnexpectedError('Unhandled Rejection', event.reason),
+ )
return () => {
offError()
offRejection()
}
}
-async function reportUnexpectedError(
- type,
- error,
-) {
+async function reportUnexpectedError(type, error) {
const processedError = serializeError(error)
- await client.waitForConnection().then(() => {
- return client.rpc.onUnhandledError(processedError, type)
- }).catch(console.error)
+ await client
+ .waitForConnection()
+ .then(() => {
+ return client.rpc.onUnhandledError(processedError, type)
+ })
+ .catch(console.error)
}
globalThis.__vitest_browser_runner__.disposeExceptionTracker = registerUnexpectedErrors()
diff --git a/packages/browser/src/client/tester/action.ts b/packages/browser/src/client/tester/action.ts
index 5d73b406d..b9c6159c8 100644
--- a/packages/browser/src/client/tester/action.ts
+++ b/packages/browser/src/client/tester/action.ts
@@ -73,9 +73,8 @@ class Action implements Promise {
async #run(): Promise {
const timeout = resolveActionTimeout(this.#options)
const options = timeout == null ? this.#options : { ...this.#options, timeout }
- const args = typeof this.#args === 'function'
- ? await this.#args(options)
- : [...this.#args, options]
+ const args =
+ typeof this.#args === 'function' ? await this.#args(options) : [...this.#args, options]
const promise = getBrowserState().commands.triggerCommand(
this.#command,
args,
@@ -90,7 +89,7 @@ class Action implements Promise {
// the command starts only when awaited, so an unawaited action cannot reject unhandled
#start(): Promise {
this.#awaited = true
- return this.#promise ??= this.#run()
+ return (this.#promise ??= this.#run())
}
then(
@@ -128,7 +127,12 @@ export class UploadAction extends Action {
options?: ActionOptions,
errorSource?: Error,
) {
- super('__vitest_upload', async options => [target, await readFiles(files), options], options, errorSource)
+ super(
+ '__vitest_upload',
+ async (options) => [target, await readFiles(files), options],
+ options,
+ errorSource,
+ )
}
}
@@ -138,27 +142,35 @@ export class ScreenshotAction extends Action {
options: ActionOptions,
serialize: () => Promise>,
) {
- super('__vitest_screenshot', async options => [name, { ...options, ...await serialize() }], options)
+ super(
+ '__vitest_screenshot',
+ async (options) => [name, { ...options, ...(await serialize()) }],
+ options,
+ )
}
}
-function readFiles(files: string | string[] | File | File[]): Promise<(string | { name: string; mimeType: string; base64: string })[]> {
- return Promise.all((Array.isArray(files) ? files : [files]).map(async (file) => {
- if (typeof file === 'string') {
- return file
- }
- const bas64String = await new Promise((resolve, reject) => {
- const reader = new FileReader()
- reader.onload = () => resolve(reader.result as string)
- reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`))
- reader.readAsDataURL(file)
- })
-
- return {
- name: file.name,
- mimeType: file.type,
- // strip prefix `data:[][;base64],`
- base64: bas64String.slice(bas64String.indexOf(',') + 1),
- }
- }))
+function readFiles(
+ files: string | string[] | File | File[],
+): Promise<(string | { name: string; mimeType: string; base64: string })[]> {
+ return Promise.all(
+ (Array.isArray(files) ? files : [files]).map(async (file) => {
+ if (typeof file === 'string') {
+ return file
+ }
+ const bas64String = await new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.onload = () => resolve(reader.result as string)
+ reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`))
+ reader.readAsDataURL(file)
+ })
+
+ return {
+ name: file.name,
+ mimeType: file.type,
+ // strip prefix `data:[][;base64],`
+ base64: bas64String.slice(bas64String.indexOf(',') + 1),
+ }
+ }),
+ )
}
diff --git a/packages/browser/src/client/tester/aria.ts b/packages/browser/src/client/tester/aria.ts
index 53d942184..3cce4d6f7 100644
--- a/packages/browser/src/client/tester/aria.ts
+++ b/packages/browser/src/client/tester/aria.ts
@@ -1,25 +1,13 @@
-import type {
- AriaNode,
- AriaTemplateNode,
-} from 'ivya/aria'
-import type {
- DomainMatchResult,
- DomainSnapshotAdapter,
- MatchersObject,
-} from 'vitest'
+import type { AriaNode, AriaTemplateNode } from 'ivya/aria'
+import type { DomainMatchResult, DomainSnapshotAdapter, MatchersObject } from 'vitest'
import * as aria from 'ivya/aria'
import { Snapshots } from 'vitest'
import { getBrowserState } from '../utils'
getBrowserState().aria = aria
-const {
- generateAriaTree,
- matchAriaTree,
- parseAriaTemplate,
- renderAriaTemplate,
- renderAriaTree,
-} = aria
+const { generateAriaTree, matchAriaTree, parseAriaTemplate, renderAriaTemplate, renderAriaTree } =
+ aria
const ariaSnapshotAdapter: DomainSnapshotAdapter = {
name: 'aria',
@@ -41,8 +29,7 @@ const ariaSnapshotAdapter: DomainSnapshotAdapter = {
Error.stackTraceLimit = limit + 20
try {
return parseAriaTemplate(input.trim())
- }
- finally {
+ } finally {
Error.stackTraceLimit = limit
}
},
@@ -71,7 +58,12 @@ export const ariaMatchers: MatchersObject = {
return Snapshots.toMatchDomainSnapshot.call(this, ariaSnapshotAdapter, actual)
},
toMatchAriaInlineSnapshot(actual: Element, inlineSnapshot?: string) {
- return Snapshots.toMatchDomainInlineSnapshot.call(this, ariaSnapshotAdapter, actual, inlineSnapshot)
+ return Snapshots.toMatchDomainInlineSnapshot.call(
+ this,
+ ariaSnapshotAdapter,
+ actual,
+ inlineSnapshot,
+ )
},
}
diff --git a/packages/browser/src/client/tester/context.ts b/packages/browser/src/client/tester/context.ts
index d3f2f73ac..aefeb4ef6 100644
--- a/packages/browser/src/client/tester/context.ts
+++ b/packages/browser/src/client/tester/context.ts
@@ -35,7 +35,10 @@ function triggerCommand(command: string, args: any[], error?: Error) {
return getBrowserState().commands.triggerCommand(command, args, error)
}
-export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent, options?: TestingLibraryOptions): UserEvent {
+export function createUserEvent(
+ __tl_user_event_base__?: TestingLibraryUserEvent,
+ options?: TestingLibraryOptions,
+): UserEvent {
if (__tl_user_event_base__) {
return createPreviewUserEvent(__tl_user_event_base__, options ?? {})
}
@@ -46,11 +49,8 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
// https://playwright.dev/docs/api/class-keyboard
// https://webdriver.io/docs/api/browser/keys/
- const modifier = provider === 'playwright'
- ? 'ControlOrMeta'
- : provider === 'webdriverio'
- ? 'Ctrl'
- : 'Control'
+ const modifier =
+ provider === 'playwright' ? 'ControlOrMeta' : provider === 'webdriverio' ? 'Ctrl' : 'Control'
const userEvent: UserEvent = {
setup() {
@@ -110,18 +110,14 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
const serializedElement = await serializeElement(element, options)
const { unreleased } = await triggerCommand<{ unreleased: string[] }>(
'__vitest_type',
- [
- serializedElement,
- text,
- { ...options, unreleased: keyboard.unreleased },
- ],
+ [serializedElement, text, { ...options, unreleased: keyboard.unreleased }],
error,
)
keyboard.unreleased = unreleased
})
},
tab(options = {}) {
- return ensureAwaited(error => triggerCommand('__vitest_tab', [options], error))
+ return ensureAwaited((error) => triggerCommand('__vitest_tab', [options], error))
},
keyboard(text) {
return ensureAwaited(async (error) => {
@@ -146,9 +142,12 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
return userEvent
}
-function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options?: TestingLibraryOptions): UserEvent {
+function createPreviewUserEvent(
+ userEventBase: TestingLibraryUserEvent,
+ options?: TestingLibraryOptions,
+): UserEvent {
let userEvent = userEventBase.setup({
- advanceTimers: delay => vi.advanceTimersByTimeAsync(delay),
+ advanceTimers: (delay) => vi.advanceTimersByTimeAsync(delay),
...options,
})
let clipboardData: DataTransfer | undefined
@@ -163,7 +162,7 @@ function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options?
},
async cleanup() {
userEvent = userEventBase.setup({
- advanceTimers: delay => vi.advanceTimersByTimeAsync(delay),
+ advanceTimers: (delay) => vi.advanceTimersByTimeAsync(delay),
...options,
})
},
@@ -183,10 +182,7 @@ function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options?
}
return option
})
- await userEvent.selectOptions(
- toElement(element),
- options as string[] | HTMLElement[],
- )
+ await userEvent.selectOptions(toElement(element), options as string[] | HTMLElement[])
},
async clear(element) {
await userEvent.clear(toElement(element))
@@ -203,15 +199,19 @@ function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options?
return file
}
- const { content: base64, basename, mime } = await triggerCommand<{
+ const {
+ content: base64,
+ basename,
+ mime,
+ } = await triggerCommand<{
content: string
basename: string
mime: string
}>('__vitest_fileInfo', [file, 'base64'])
const fileInstance = fetch(`data:${mime};base64,${base64}`)
- .then(r => r.blob())
- .then(blob => new File([blob], basename, { type: mime }))
+ .then((r) => r.blob())
+ .then((blob) => new File([blob], basename, { type: mime }))
return fileInstance
})
const uploadFiles = await Promise.all(uploadPromise)
@@ -274,7 +274,7 @@ function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options?
for (const [name, fn] of Object.entries(vitestUserEvent)) {
if (name !== 'setup') {
- (vitestUserEvent as any)[name] = function (this: any, ...args: any[]) {
+ ;(vitestUserEvent as any)[name] = function (this: any, ...args: any[]) {
return ensureAwaited(() => fn.apply(this, args))
}
}
@@ -318,9 +318,9 @@ export const page: BrowserPage = {
if (currentTest.concurrent) {
throw new Error(
- 'Cannot take a screenshot in a concurrent test because '
- + 'concurrent tests run at the same time in the same iframe and affect each other\'s environment. '
- + 'Use a non-concurrent test to take a screenshot.',
+ 'Cannot take a screenshot in a concurrent test because ' +
+ "concurrent tests run at the same time in the same iframe and affect each other's environment. " +
+ 'Use a non-concurrent test to take a screenshot.',
)
}
@@ -331,14 +331,13 @@ export const page: BrowserPage = {
screenshotIds[repeatCount] ??= {}
screenshotIds[repeatCount][taskName] = number + 1
- const name
- = options.path || `${taskName.replace(/[^a-z0-9]/gi, '-')}-${number}.png`
+ const name = options.path || `${taskName.replace(/[^a-z0-9]/gi, '-')}-${number}.png`
return new ScreenshotAction(name, options, async () => {
const [element, ...mask] = await Promise.all([
options.element ? serializeElement(options.element, options) : undefined,
...('mask' in options
- ? (options.mask as Array).map(el => serializeElement(el, options))
+ ? (options.mask as Array).map((el) => serializeElement(el, options))
: []),
])
return 'mask' in options ? { element, mask } : { element }
@@ -351,7 +350,8 @@ export const page: BrowserPage = {
): any {
const currentTest = getWorkerState().current
const hasActiveTrace = !!currentTest && getBrowserState().activeTraceTaskIds.has(currentTest.id)
- const hasActiveTraceView = !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id)
+ const hasActiveTraceView =
+ !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id)
if (typeof bodyOrOptions === 'function') {
return ensureAwaited(async (error) => {
@@ -360,10 +360,12 @@ export const page: BrowserPage = {
if (hasActiveTrace) {
await triggerCommand(
'__vitest_groupTraceStart',
- [{
- name,
- stack: options?.stack ?? error?.stack,
- }],
+ [
+ {
+ name,
+ stack: options?.stack ?? error?.stack,
+ },
+ ],
error,
)
}
@@ -377,12 +379,10 @@ export const page: BrowserPage = {
}
try {
return await bodyOrOptions()
- }
- catch (err) {
+ } catch (err) {
status = 'fail'
throw err
- }
- finally {
+ } finally {
if (hasActiveTraceView) {
await recordBrowserTraceEntry(currentTest, {
name,
@@ -416,10 +416,12 @@ export const page: BrowserPage = {
}
return triggerCommand(
'__vitest_markTrace',
- [{
- name,
- stack: bodyOrOptions?.stack ?? error?.stack,
- }],
+ [
+ {
+ name,
+ stack: bodyOrOptions?.stack ?? error?.stack,
+ },
+ ],
error,
)
})
@@ -453,7 +455,7 @@ export const page: BrowserPage = {
},
extend(methods) {
for (const key in methods) {
- (page as any)[key] = (methods as any)[key].bind(page)
+ ;(page as any)[key] = (methods as any)[key].bind(page)
}
return page
},
@@ -501,9 +503,10 @@ function getElementLocatorSelectors(element: Element): LocatorSelectors {
return {
getByAltText: (altText, options) => locator.getByAltText(altText, options),
getByLabelText: (labelText, options) => locator.getByLabelText(labelText, options),
- getByPlaceholder: (placeholderText, options) => locator.getByPlaceholder(placeholderText, options),
+ getByPlaceholder: (placeholderText, options) =>
+ locator.getByPlaceholder(placeholderText, options),
getByRole: (role, options) => locator.getByRole(role, options),
- getByTestId: testId => locator.getByTestId(testId),
+ getByTestId: (testId) => locator.getByTestId(testId),
getByText: (text, options) => locator.getByText(text, options),
getByTitle: (title, options) => locator.getByTitle(title, options),
...Array.from(__INTERNAL._extendedMethods).reduce((methods, method) => {
@@ -523,18 +526,19 @@ function debug(
options?: PrettyDOMOptions,
): void {
if (Array.isArray(el)) {
- // eslint-disable-next-line no-console
- el.forEach(e => console.log(prettyDOM(e, maxLength, options)))
- }
- else {
- // eslint-disable-next-line no-console
+ // oxlint-disable-next-line no-console
+ el.forEach((e) => console.log(prettyDOM(e, maxLength, options)))
+ } else {
+ // oxlint-disable-next-line no-console
console.log(prettyDOM(el, maxLength, options))
}
}
function prettyDOM(
dom?: Element | Locator | undefined | null,
- maxLength: number = Number(defaultOptions?.maxLength ?? import.meta.env.DEBUG_PRINT_LIMIT ?? 7000),
+ maxLength: number = Number(
+ defaultOptions?.maxLength ?? import.meta.env.DEBUG_PRINT_LIMIT ?? 7000,
+ ),
prettyFormatOptions: PrettyDOMOptions = {},
): string {
if (maxLength === 0) {
@@ -561,13 +565,14 @@ function prettyDOM(
...defaultOptions,
...prettyFormatOptions,
})
- return dom.outerHTML.length > maxLength
- ? `${pretty.slice(0, maxLength)}...`
- : pretty
+ return dom.outerHTML.length > maxLength ? `${pretty.slice(0, maxLength)}...` : pretty
}
function getElementError(selector: string | Locator, container: Element): Error {
- const locator = typeof selector === 'string' ? __INTERNAL._asLocator('javascript', selector) : selector.asLocator()
+ const locator =
+ typeof selector === 'string'
+ ? __INTERNAL._asLocator('javascript', selector)
+ : selector.asLocator()
const formatted = formatDOM(container)
const error = new Error(`Cannot find element with locator: ${locator}\n\n${formatted}`)
error.name = 'VitestBrowserElementError'
diff --git a/packages/browser/src/client/tester/dialog.ts b/packages/browser/src/client/tester/dialog.ts
index a41dd80cf..553708062 100644
--- a/packages/browser/src/client/tester/dialog.ts
+++ b/packages/browser/src/client/tester/dialog.ts
@@ -1,6 +1,6 @@
function showPopupWarning(name: string, value: T, defaultValue?: T) {
return (...params: any[]) => {
- const formattedParams = params.map(p => JSON.stringify(p)).join(', ')
+ const formattedParams = params.map((p) => JSON.stringify(p)).join(', ')
console.warn(`Vitest encountered a \`${name}(${formattedParams})\` call that it cannot handle by default, so it returned \`${value}\`. Read more in https://vitest.dev/guide/browser/#thread-blocking-dialogs.
If needed, mock the \`${name}\` call manually like:
@@ -9,8 +9,8 @@ If needed, mock the \`${name}\` call manually like:
import { expect, vi } from "vitest"
vi.spyOn(window, "${name}")${
- defaultValue ? `.mockReturnValue(${JSON.stringify(defaultValue)})` : ''
-}
+ defaultValue ? `.mockReturnValue(${JSON.stringify(defaultValue)})` : ''
+ }
${name}(${formattedParams})
expect(${name}).toHaveBeenCalledWith(${formattedParams})
\`\`\``)
diff --git a/packages/browser/src/client/tester/expect-element.ts b/packages/browser/src/client/tester/expect-element.ts
index 6337b2f60..f40656884 100644
--- a/packages/browser/src/client/tester/expect-element.ts
+++ b/packages/browser/src/client/tester/expect-element.ts
@@ -11,15 +11,27 @@ import { createBrowserTraceRangeId, recordBrowserTraceEntry } from './trace'
const kLocator = Symbol.for('$$vitest:locator')
-function element(elementOrLocator: T, options?: ExpectPollOptions): Assertion, HTMLElement | SVGElement | null> {
- if (elementOrLocator != null && !(elementOrLocator instanceof HTMLElement) && !(elementOrLocator instanceof SVGElement) && !(kLocator in elementOrLocator)) {
- throw new Error(`Invalid element or locator: ${elementOrLocator}. Expected an instance of HTMLElement, SVGElement or Locator, received ${getType(elementOrLocator)}`)
+function element(
+ elementOrLocator: T,
+ options?: ExpectPollOptions,
+): Assertion, HTMLElement | SVGElement | null> {
+ if (
+ elementOrLocator != null &&
+ !(elementOrLocator instanceof HTMLElement) &&
+ !(elementOrLocator instanceof SVGElement) &&
+ !(kLocator in elementOrLocator)
+ ) {
+ throw new Error(
+ `Invalid element or locator: ${elementOrLocator}. Expected an instance of HTMLElement, SVGElement or Locator, received ${getType(elementOrLocator)}`,
+ )
}
const timeout = resolveActionTimeout(options)
const pollOptions = timeout == null ? options : { ...options, timeout }
const deadline = pollOptions?.timeout ? now() + pollOptions.timeout : undefined
- const expectElement = expect.poll(async function element(this: object): Promise {
+ const expectElement = expect.poll(async function element(
+ this: object,
+ ): Promise {
if (elementOrLocator instanceof Element || elementOrLocator == null) {
return elementOrLocator
}
@@ -53,13 +65,15 @@ function element(elementOrL
// ask `expect.poll` to invoke trace after the assertion
const currentTest = getWorkerState().current
const hasActiveTrace = !!currentTest && getBrowserState().activeTraceTaskIds.has(currentTest.id)
- const hasActiveTraceView = !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id)
+ const hasActiveTraceView =
+ !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id)
if (currentTest && (hasActiveTrace || hasActiveTraceView)) {
const sourceError = new Error('__vitest_mark_trace__')
const traceRangeId = hasActiveTraceView ? createBrowserTraceRangeId() : undefined
- const getSelector = () => !elementOrLocator || elementOrLocator instanceof Element
- ? undefined
- : elementOrLocator.serialize()
+ const getSelector = () =>
+ !elementOrLocator || elementOrLocator instanceof Element
+ ? undefined
+ : elementOrLocator.serialize()
const getTraceName = (assertion: Assertion, status?: BrowserTraceEntryStatus) => {
const isNot = chai.util.flag(assertion, 'negate')
const name = chai.util.flag(assertion, '_name') || ''
@@ -77,30 +91,36 @@ function element(elementOrL
})
}
})
- chai.util.flag(expectElement, '_poll.onSettled', async (meta: { assertion: Assertion; status: BrowserTraceEntryStatus }) => {
- const traceName = getTraceName(meta.assertion, meta.status)
- if (hasActiveTraceView) {
- await recordBrowserTraceEntry(currentTest, {
- name: traceName,
- kind: 'expect',
- range: { id: traceRangeId!, phase: 'end' },
- status: meta.status,
- element: getSelector(),
- stack: sourceError.stack,
- })
- }
- if (hasActiveTrace) {
- await getBrowserState().commands.triggerCommand(
- '__vitest_markTrace',
- [{
+ chai.util.flag(
+ expectElement,
+ '_poll.onSettled',
+ async (meta: { assertion: Assertion; status: BrowserTraceEntryStatus }) => {
+ const traceName = getTraceName(meta.assertion, meta.status)
+ if (hasActiveTraceView) {
+ await recordBrowserTraceEntry(currentTest, {
name: traceName,
+ kind: 'expect',
+ range: { id: traceRangeId!, phase: 'end' },
+ status: meta.status,
element: getSelector(),
stack: sourceError.stack,
- }],
- sourceError,
- )
- }
- })
+ })
+ }
+ if (hasActiveTrace) {
+ await getBrowserState().commands.triggerCommand(
+ '__vitest_markTrace',
+ [
+ {
+ name: traceName,
+ element: getSelector(),
+ stack: sourceError.stack,
+ },
+ ],
+ sourceError,
+ )
+ }
+ },
+ )
}
return expectElement
diff --git a/packages/browser/src/client/tester/expect/toBeChecked.ts b/packages/browser/src/client/tester/expect/toBeChecked.ts
index 5f20c4f7b..8ebe79add 100644
--- a/packages/browser/src/client/tester/expect/toBeChecked.ts
+++ b/packages/browser/src/client/tester/expect/toBeChecked.ts
@@ -20,23 +20,17 @@ import { getElementFromUserInput, isInputElement, toSentence } from './utils'
const supportedRoles = getAriaCheckedRoles()
-export default function toBeChecked(
- this: MatcherState,
- actual: Element | Locator,
-): MatcherResult {
+export default function toBeChecked(this: MatcherState, actual: Element | Locator): MatcherResult {
const htmlElement = getElementFromUserInput(actual, toBeChecked, this)
const isValidInput = () => {
- return (
- isInputElement(htmlElement)
- && ['checkbox', 'radio'].includes(htmlElement.type)
- )
+ return isInputElement(htmlElement) && ['checkbox', 'radio'].includes(htmlElement.type)
}
const isValidAriaElement = () => {
return (
- supportedRoles.includes(getAriaRole(htmlElement) || '')
- && ['true', 'false'].includes(htmlElement.getAttribute('aria-checked') || '')
+ supportedRoles.includes(getAriaRole(htmlElement) || '') &&
+ ['true', 'false'].includes(htmlElement.getAttribute('aria-checked') || '')
)
}
@@ -56,11 +50,7 @@ export default function toBeChecked(
message: () => {
const is = isChecked ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeChecked`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeChecked`, 'element', ''),
'',
`Received element ${is} checked:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
@@ -71,7 +61,7 @@ export default function toBeChecked(
function supportedRolesSentence() {
return toSentence(
- supportedRoles.map(role => `role="${role}"`),
+ supportedRoles.map((role) => `role="${role}"`),
{ lastWordConnector: ' or ' },
)
}
diff --git a/packages/browser/src/client/tester/expect/toBeEmptyDOMElement.ts b/packages/browser/src/client/tester/expect/toBeEmptyDOMElement.ts
index bb0bfc43a..6eaded581 100644
--- a/packages/browser/src/client/tester/expect/toBeEmptyDOMElement.ts
+++ b/packages/browser/src/client/tester/expect/toBeEmptyDOMElement.ts
@@ -27,11 +27,7 @@ export default function toBeEmptyDOMElement(
pass: isEmptyElement(htmlElement),
message: () => {
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeEmptyDOMElement`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeEmptyDOMElement`, 'element', ''),
'',
'Received:',
` ${this.utils.printReceived(htmlElement.innerHTML)}`,
@@ -44,6 +40,8 @@ export default function toBeEmptyDOMElement(
* Identifies if an element doesn't contain child nodes (excluding comments)
*/
function isEmptyElement(element: HTMLElement | SVGElement): boolean {
- const nonCommentChildNodes = [...element.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE)
+ const nonCommentChildNodes = [...element.childNodes].filter(
+ (node) => node.nodeType !== Node.COMMENT_NODE,
+ )
return nonCommentChildNodes.length === 0
}
diff --git a/packages/browser/src/client/tester/expect/toBeEnabled.ts b/packages/browser/src/client/tester/expect/toBeEnabled.ts
index b512c11dd..4979dde8c 100644
--- a/packages/browser/src/client/tester/expect/toBeEnabled.ts
+++ b/packages/browser/src/client/tester/expect/toBeEnabled.ts
@@ -18,10 +18,7 @@ import type { Locator } from '../locators'
import { getAriaDisabled } from 'ivya/utils'
import { getElementFromUserInput, getTag } from './utils'
-export function toBeDisabled(
- this: MatcherState,
- actual: Element | Locator,
-): MatcherResult {
+export function toBeDisabled(this: MatcherState, actual: Element | Locator): MatcherResult {
const htmlElement = getElementFromUserInput(actual, toBeDisabled, this)
const isDisabled = isElementDisabled(htmlElement)
return {
@@ -29,11 +26,7 @@ export function toBeDisabled(
message: () => {
const is = isDisabled ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeDisabled`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeDisabled`, 'element', ''),
'',
`Received element ${is} disabled:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
@@ -42,10 +35,7 @@ export function toBeDisabled(
}
}
-export function toBeEnabled(
- this: MatcherState,
- actual: Element | Locator,
-): MatcherResult {
+export function toBeEnabled(this: MatcherState, actual: Element | Locator): MatcherResult {
const htmlElement = getElementFromUserInput(actual, toBeEnabled, this)
const isDisabled = isElementDisabled(htmlElement)
return {
@@ -53,11 +43,7 @@ export function toBeEnabled(
message: () => {
const is = !isDisabled ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeEnabled`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeEnabled`, 'element', ''),
'',
`Received element ${is} enabled:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
diff --git a/packages/browser/src/client/tester/expect/toBeInTheDocument.ts b/packages/browser/src/client/tester/expect/toBeInTheDocument.ts
index ccf986309..b610c9fa2 100644
--- a/packages/browser/src/client/tester/expect/toBeInTheDocument.ts
+++ b/packages/browser/src/client/tester/expect/toBeInTheDocument.ts
@@ -27,8 +27,8 @@ export default function toBeInTheDocument(
htmlElement = queryElementFromUserInput(actual, toBeInTheDocument, this)
}
- const pass
- = htmlElement === null
+ const pass =
+ htmlElement === null
? false
: htmlElement.ownerDocument === htmlElement.getRootNode({ composed: true })
@@ -45,11 +45,7 @@ export default function toBeInTheDocument(
pass,
message: () => {
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeInTheDocument`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeInTheDocument`, 'element', ''),
'',
this.utils.RECEIVED_COLOR(this.isNot ? errorFound() : errorNotFound()),
diff --git a/packages/browser/src/client/tester/expect/toBeInViewport.ts b/packages/browser/src/client/tester/expect/toBeInViewport.ts
index ee83cc4e4..1930410dc 100644
--- a/packages/browser/src/client/tester/expect/toBeInViewport.ts
+++ b/packages/browser/src/client/tester/expect/toBeInViewport.ts
@@ -33,11 +33,7 @@ export default function toBeInViewport(
const ratioText = expectedRatio > 0 ? ` with ratio ${expectedRatio}` : ''
const actualRatioText = ratio !== undefined ? ` (actual ratio: ${ratio.toFixed(3)})` : ''
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeInViewport`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeInViewport`, 'element', ''),
'',
`Received element ${is} in viewport${ratioText}${actualRatioText}:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
@@ -51,7 +47,10 @@ export default function toBeInViewport(
* Get viewport intersection ratio using IntersectionObserver API
* This implementation follows Playwright's approach using IntersectionObserver as the primary mechanism
*/
-async function getViewportIntersection(element: HTMLElement | SVGElement, expectedRatio: number): Promise<{ pass: boolean; ratio?: number }> {
+async function getViewportIntersection(
+ element: HTMLElement | SVGElement,
+ expectedRatio: number,
+): Promise<{ pass: boolean; ratio?: number }> {
// Use IntersectionObserver API to get the intersection ratio
// Following Playwright's exact pattern from viewportRatio function
const intersectionRatio = await new Promise((resolve) => {
@@ -59,8 +58,7 @@ async function getViewportIntersection(element: HTMLElement | SVGElement, expect
const observer = new IntersectionObserver((entries) => {
if (entries.length > 0) {
resolve(entries[0].intersectionRatio)
- }
- else {
+ } else {
resolve(0)
}
observer.disconnect()
@@ -75,7 +73,7 @@ async function getViewportIntersection(element: HTMLElement | SVGElement, expect
// Apply the same logic as Playwright:
// ratio > 0 && ratio > (expectedRatio - 1e-9)
- const pass = intersectionRatio > 0 && intersectionRatio > (expectedRatio - 1e-9)
+ const pass = intersectionRatio > 0 && intersectionRatio > expectedRatio - 1e-9
return { pass, ratio: intersectionRatio }
}
diff --git a/packages/browser/src/client/tester/expect/toBeInvalid.ts b/packages/browser/src/client/tester/expect/toBeInvalid.ts
index 7db4e9ffd..431d086dd 100644
--- a/packages/browser/src/client/tester/expect/toBeInvalid.ts
+++ b/packages/browser/src/client/tester/expect/toBeInvalid.ts
@@ -20,13 +20,12 @@ import { getElementFromUserInput, getTag } from './utils'
const FORM_TAGS = ['FORM', 'INPUT', 'SELECT', 'TEXTAREA']
function isElementHavingAriaInvalid(element: HTMLElement | SVGElement) {
- return (
- element.hasAttribute('aria-invalid')
- && element.getAttribute('aria-invalid') !== 'false'
- )
+ return element.hasAttribute('aria-invalid') && element.getAttribute('aria-invalid') !== 'false'
}
-function isSupportsValidityMethod(element: HTMLElement | SVGElement): element is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFormElement {
+function isSupportsValidityMethod(
+ element: HTMLElement | SVGElement,
+): element is HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLFormElement {
return FORM_TAGS.includes(getTag(element))
}
@@ -34,8 +33,7 @@ function isElementInvalid(element: HTMLElement | SVGElement) {
const isHaveAriaInvalid = isElementHavingAriaInvalid(element)
if (isSupportsValidityMethod(element)) {
return isHaveAriaInvalid || !element.checkValidity()
- }
- else {
+ } else {
return isHaveAriaInvalid
}
}
@@ -53,11 +51,7 @@ export function toBeInvalid(
message: () => {
const is = isInvalid ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeInvalid`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeInvalid`, 'element', ''),
'',
`Received element ${is} currently invalid:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
@@ -79,11 +73,7 @@ export function toBeValid(
message: () => {
const is = isValid ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeValid`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeValid`, 'element', ''),
'',
`Received element ${is} currently valid:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
diff --git a/packages/browser/src/client/tester/expect/toBePartiallyChecked.ts b/packages/browser/src/client/tester/expect/toBePartiallyChecked.ts
index b9344a0f3..d06c6bb11 100644
--- a/packages/browser/src/client/tester/expect/toBePartiallyChecked.ts
+++ b/packages/browser/src/client/tester/expect/toBePartiallyChecked.ts
@@ -25,9 +25,7 @@ export default function toBePartiallyChecked(
const htmlElement = getElementFromUserInput(actual, toBePartiallyChecked, this)
const isValidInput = () => {
- return (
- isInputElement(htmlElement) && htmlElement.type === 'checkbox'
- )
+ return isInputElement(htmlElement) && htmlElement.type === 'checkbox'
}
const isValidAriaElement = () => {
@@ -49,11 +47,7 @@ export default function toBePartiallyChecked(
message: () => {
const is = isPartiallyChecked ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBePartiallyChecked`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBePartiallyChecked`, 'element', ''),
'',
`Received element ${is} partially checked:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
@@ -67,8 +61,8 @@ function isAriaMixed(element: HTMLElement | SVGElement): boolean {
if (!isMixed) {
// playwright only looks at aria-checked if element is not a checkbox/radio
if (
- isInputElement(element)
- && ['checkbox', 'radio'].includes((element as HTMLInputElement).type)
+ isInputElement(element) &&
+ ['checkbox', 'radio'].includes((element as HTMLInputElement).type)
) {
const ariaValue = element.getAttribute('aria-checked')
if (ariaValue === 'mixed') {
diff --git a/packages/browser/src/client/tester/expect/toBeRequired.ts b/packages/browser/src/client/tester/expect/toBeRequired.ts
index cabb73ceb..10a41322f 100644
--- a/packages/browser/src/client/tester/expect/toBeRequired.ts
+++ b/packages/browser/src/client/tester/expect/toBeRequired.ts
@@ -22,14 +22,7 @@ const FORM_TAGS = ['SELECT', 'TEXTAREA']
const ARIA_FORM_TAGS = ['INPUT', 'SELECT', 'TEXTAREA']
-const UNSUPPORTED_INPUT_TYPES = [
- 'color',
- 'hidden',
- 'range',
- 'submit',
- 'image',
- 'reset',
-]
+const UNSUPPORTED_INPUT_TYPES = ['color', 'hidden', 'range', 'submit', 'image', 'reset']
const SUPPORTED_ARIA_ROLES = [
'checkbox',
@@ -48,21 +41,21 @@ function isRequiredOnFormTagsExceptInput(element: HTMLElement | SVGElement) {
function isRequiredOnSupportedInput(element: HTMLElement | SVGElement) {
return (
- getTag(element) === 'INPUT'
- && element.hasAttribute('required')
- && ((element.hasAttribute('type')
- && !UNSUPPORTED_INPUT_TYPES.includes(element.getAttribute('type') || ''))
- || !element.hasAttribute('type'))
+ getTag(element) === 'INPUT' &&
+ element.hasAttribute('required') &&
+ ((element.hasAttribute('type') &&
+ !UNSUPPORTED_INPUT_TYPES.includes(element.getAttribute('type') || '')) ||
+ !element.hasAttribute('type'))
)
}
function isElementRequiredByARIA(element: HTMLElement | SVGElement) {
return (
- element.hasAttribute('aria-required')
- && element.getAttribute('aria-required') === 'true'
- && (ARIA_FORM_TAGS.includes(getTag(element))
- || (element.hasAttribute('role')
- && SUPPORTED_ARIA_ROLES.includes(element.getAttribute('role') || '')))
+ element.hasAttribute('aria-required') &&
+ element.getAttribute('aria-required') === 'true' &&
+ (ARIA_FORM_TAGS.includes(getTag(element)) ||
+ (element.hasAttribute('role') &&
+ SUPPORTED_ARIA_ROLES.includes(element.getAttribute('role') || '')))
)
}
@@ -72,21 +65,17 @@ export default function toBeRequired(
): MatcherResult {
const htmlElement = getElementFromUserInput(element, toBeRequired, this)
- const isRequired
- = isRequiredOnFormTagsExceptInput(htmlElement)
- || isRequiredOnSupportedInput(htmlElement)
- || isElementRequiredByARIA(htmlElement)
+ const isRequired =
+ isRequiredOnFormTagsExceptInput(htmlElement) ||
+ isRequiredOnSupportedInput(htmlElement) ||
+ isElementRequiredByARIA(htmlElement)
return {
pass: isRequired,
message: () => {
const is = isRequired ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeRequired`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeRequired`, 'element', ''),
'',
`Received element ${is} required:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
diff --git a/packages/browser/src/client/tester/expect/toBeVisible.ts b/packages/browser/src/client/tester/expect/toBeVisible.ts
index 3080a9878..bd7b87068 100644
--- a/packages/browser/src/client/tester/expect/toBeVisible.ts
+++ b/packages/browser/src/client/tester/expect/toBeVisible.ts
@@ -19,13 +19,9 @@ import { beginAriaCaches, endAriaCaches, isElementVisible as ivyaIsVisible } fro
import { server } from 'vitest/browser'
import { getElementFromUserInput } from './utils'
-export default function toBeVisible(
- this: MatcherState,
- actual: Element | Locator,
-): MatcherResult {
+export default function toBeVisible(this: MatcherState, actual: Element | Locator): MatcherResult {
const htmlElement = getElementFromUserInput(actual, toBeVisible, this)
- const isInDocument
- = htmlElement.ownerDocument === htmlElement.getRootNode({ composed: true })
+ const isInDocument = htmlElement.ownerDocument === htmlElement.getRootNode({ composed: true })
beginAriaCaches()
const isVisible = isInDocument && isElementVisible(htmlElement)
endAriaCaches()
@@ -34,15 +30,9 @@ export default function toBeVisible(
message: () => {
const is = isVisible ? 'is' : 'is not'
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toBeVisible`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeVisible`, 'element', ''),
'',
- `Received element ${is} visible${
- isInDocument ? '' : ' (element is not in the document)'
- }:`,
+ `Received element ${is} visible${isInDocument ? '' : ' (element is not in the document)'}:`,
` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
].join('\n')
},
diff --git a/packages/browser/src/client/tester/expect/toContainElement.ts b/packages/browser/src/client/tester/expect/toContainElement.ts
index 487b56847..9787c971d 100644
--- a/packages/browser/src/client/tester/expect/toContainElement.ts
+++ b/packages/browser/src/client/tester/expect/toContainElement.ts
@@ -23,9 +23,10 @@ export default function toContainElement(
expectedElement: Element | Locator | null,
): MatcherResult {
const containerElement = getElementFromUserInput(actual, toContainElement, this)
- const childElement = expectedElement !== null
- ? getElementFromUserInput(expectedElement, toContainElement, this)
- : null
+ const childElement =
+ expectedElement !== null
+ ? getElementFromUserInput(expectedElement, toContainElement, this)
+ : null
return {
pass: containerElement.contains(childElement),
@@ -38,9 +39,7 @@ export default function toContainElement(
),
'',
- this.utils.RECEIVED_COLOR(`${this.utils.stringify(
- containerElement.cloneNode(false),
- )} ${
+ this.utils.RECEIVED_COLOR(`${this.utils.stringify(containerElement.cloneNode(false))} ${
this.isNot ? 'contains:' : 'does not contain:'
} ${this.utils.stringify(childElement ? childElement.cloneNode(false) : null)}
`),
diff --git a/packages/browser/src/client/tester/expect/toContainHTML.ts b/packages/browser/src/client/tester/expect/toContainHTML.ts
index c05d0150c..6f4ad5a7a 100644
--- a/packages/browser/src/client/tester/expect/toContainHTML.ts
+++ b/packages/browser/src/client/tester/expect/toContainHTML.ts
@@ -38,11 +38,7 @@ export default function toContainHTML(
pass: htmlElement.outerHTML.includes(getNormalizedHtml(htmlElement, htmlText)),
message: () => {
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toContainHTML`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toContainHTML`, 'element', ''),
'Expected:',
` ${this.utils.EXPECTED_COLOR(htmlText)}`,
'Received:',
diff --git a/packages/browser/src/client/tester/expect/toHaveAccessibleDescription.ts b/packages/browser/src/client/tester/expect/toHaveAccessibleDescription.ts
index c6236e1a7..b30fa96b3 100644
--- a/packages/browser/src/client/tester/expect/toHaveAccessibleDescription.ts
+++ b/packages/browser/src/client/tester/expect/toHaveAccessibleDescription.ts
@@ -34,10 +34,9 @@ export default function toHaveAccessibleDescription(
// When called without an expected value we only want to validate that the element has an
// accessible description, whatever it may be.
pass = actualAccessibleDescription !== ''
- }
- else {
- pass
- = expectedAccessibleDescription instanceof defaultView.RegExp
+ } else {
+ pass =
+ expectedAccessibleDescription instanceof defaultView.RegExp
? expectedAccessibleDescription.test(actualAccessibleDescription)
: this.equals(
actualAccessibleDescription,
diff --git a/packages/browser/src/client/tester/expect/toHaveAccessibleErrorMessage.ts b/packages/browser/src/client/tester/expect/toHaveAccessibleErrorMessage.ts
index be196fb24..890531a29 100644
--- a/packages/browser/src/client/tester/expect/toHaveAccessibleErrorMessage.ts
+++ b/packages/browser/src/client/tester/expect/toHaveAccessibleErrorMessage.ts
@@ -34,10 +34,9 @@ export default function toHaveAccessibleErrorMessage(
// When called without an expected value we only want to validate that the element has an
// accessible description, whatever it may be.
pass = actualAccessibleErrorMessage !== ''
- }
- else {
- pass
- = expectedAccessibleErrorMessage instanceof defaultView.RegExp
+ } else {
+ pass =
+ expectedAccessibleErrorMessage instanceof defaultView.RegExp
? expectedAccessibleErrorMessage.test(actualAccessibleErrorMessage)
: this.equals(
actualAccessibleErrorMessage,
@@ -60,7 +59,9 @@ export default function toHaveAccessibleErrorMessage(
),
`Expected element ${to} have accessible error message, but got${!this.isNot ? ' nothing' : ''}`,
this.isNot ? this.utils.RECEIVED_COLOR(redent(actualAccessibleErrorMessage, 2)) : '',
- ].filter(Boolean).join('\n\n')
+ ]
+ .filter(Boolean)
+ .join('\n\n')
}
return getMessage(
this,
diff --git a/packages/browser/src/client/tester/expect/toHaveAccessibleName.ts b/packages/browser/src/client/tester/expect/toHaveAccessibleName.ts
index cb70bfe2a..dcf36a39f 100644
--- a/packages/browser/src/client/tester/expect/toHaveAccessibleName.ts
+++ b/packages/browser/src/client/tester/expect/toHaveAccessibleName.ts
@@ -33,10 +33,9 @@ export default function toHaveAccessibleName(
// When called without an expected value we only want to validate that the element has an
// accessible name, whatever it may be.
pass = actualAccessibleName !== ''
- }
- else {
- pass
- = expectedAccessibleName instanceof defaultView.RegExp
+ } else {
+ pass =
+ expectedAccessibleName instanceof defaultView.RegExp
? expectedAccessibleName.test(actualAccessibleName)
: this.equals(actualAccessibleName, expectedAccessibleName, this.customTesters)
}
diff --git a/packages/browser/src/client/tester/expect/toHaveAttribute.ts b/packages/browser/src/client/tester/expect/toHaveAttribute.ts
index bb1885d2b..87901b322 100644
--- a/packages/browser/src/client/tester/expect/toHaveAttribute.ts
+++ b/packages/browser/src/client/tester/expect/toHaveAttribute.ts
@@ -44,11 +44,7 @@ export default function toHaveAttribute(
secondArgument: isExpectedValuePresent
? this.utils.printExpected(expectedValue)
: undefined,
- comment: getAttributeComment(
- this.utils.stringify,
- attribute,
- expectedValue,
- ),
+ comment: getAttributeComment(this.utils.stringify, attribute, expectedValue),
},
)
return getMessage(
diff --git a/packages/browser/src/client/tester/expect/toHaveClass.ts b/packages/browser/src/client/tester/expect/toHaveClass.ts
index 79901f33c..5601f0ef1 100644
--- a/packages/browser/src/client/tester/expect/toHaveClass.ts
+++ b/packages/browser/src/client/tester/expect/toHaveClass.ts
@@ -29,15 +29,13 @@ export default function toHaveClass(
const expected = expectedClassNames.reduce(
(acc, className) => {
return acc.concat(
- typeof className === 'string' || !className
- ? splitClassNames(className)
- : className,
+ typeof className === 'string' || !className ? splitClassNames(className) : className,
)
},
[] as (string | RegExp)[],
)
- const hasRegExp = expected.some(className => className instanceof RegExp)
+ const hasRegExp = expected.some((className) => className instanceof RegExp)
if (options.exact && hasRegExp) {
throw new Error('Exact option does not support RegExp expected class names')
}
@@ -113,8 +111,7 @@ function getExpectedClassNamesAndOptions(
if (typeof lastParam === 'object' && !(lastParam instanceof RegExp)) {
expectedClassNames = params
options = lastParam
- }
- else {
+ } else {
expectedClassNames = params.concat(lastParam)
options = { exact: false }
}
@@ -125,13 +122,13 @@ function splitClassNames(str: string | undefined | null): string[] {
if (!str) {
return []
}
- return str.split(/\s+/).filter(s => s.length > 0)
+ return str.split(/\s+/).filter((s) => s.length > 0)
}
function isSubset(subset: (string | RegExp)[], superset: string[]) {
- return subset.every(strOrRegexp =>
+ return subset.every((strOrRegexp) =>
typeof strOrRegexp === 'string'
? superset.includes(strOrRegexp)
- : superset.some(className => strOrRegexp.test(className)),
+ : superset.some((className) => strOrRegexp.test(className)),
)
}
diff --git a/packages/browser/src/client/tester/expect/toHaveDisplayValue.ts b/packages/browser/src/client/tester/expect/toHaveDisplayValue.ts
index 8bf677648..10981840a 100644
--- a/packages/browser/src/client/tester/expect/toHaveDisplayValue.ts
+++ b/packages/browser/src/client/tester/expect/toHaveDisplayValue.ts
@@ -39,8 +39,8 @@ export default function toHaveDisplayValue(
const values = getValues(tagName, htmlElement)
const expectedValues = getExpectedValues(expectedValue)
- const numberOfMatchesWithValues = expectedValues.filter(expected =>
- values.some(value =>
+ const numberOfMatchesWithValues = expectedValues.filter((expected) =>
+ values.some((value) =>
expected instanceof RegExp
? expected.test(value)
: this.equals(value, String(expected), this.customTesters),
@@ -48,19 +48,14 @@ export default function toHaveDisplayValue(
).length
const matchedWithAllValues = numberOfMatchesWithValues === values.length
- const matchedWithAllExpectedValues
- = numberOfMatchesWithValues === expectedValues.length
+ const matchedWithAllExpectedValues = numberOfMatchesWithValues === expectedValues.length
return {
pass: matchedWithAllValues && matchedWithAllExpectedValues,
message: () =>
getMessage(
this,
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toHaveDisplayValue`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toHaveDisplayValue`, 'element', ''),
`Expected element ${this.isNot ? 'not ' : ''}to have display value`,
expectedValue,
'Received',
@@ -72,11 +67,13 @@ export default function toHaveDisplayValue(
function getValues(tagName: string, htmlElement: HTMLElement | SVGElement) {
return tagName === 'SELECT'
? Array.from(htmlElement as HTMLSelectElement)
- .filter(option => (option as HTMLOptionElement).selected)
- .map(option => option.textContent || '')
+ .filter((option) => (option as HTMLOptionElement).selected)
+ .map((option) => option.textContent || '')
: [(htmlElement as HTMLInputElement).value]
}
-function getExpectedValues(expectedValue: string | RegExp | Array): Array {
+function getExpectedValues(
+ expectedValue: string | RegExp | Array,
+): Array {
return Array.isArray(expectedValue) ? expectedValue : [expectedValue]
}
diff --git a/packages/browser/src/client/tester/expect/toHaveFocus.ts b/packages/browser/src/client/tester/expect/toHaveFocus.ts
index 0ef7e065d..6c29afb01 100644
--- a/packages/browser/src/client/tester/expect/toHaveFocus.ts
+++ b/packages/browser/src/client/tester/expect/toHaveFocus.ts
@@ -17,34 +17,22 @@ import type { MatcherResult, MatcherState } from 'vitest'
import type { Locator } from '../locators'
import { getElementFromUserInput } from './utils'
-export default function toHaveFocus(
- this: MatcherState,
- actual: Element | Locator,
-): MatcherResult {
+export default function toHaveFocus(this: MatcherState, actual: Element | Locator): MatcherResult {
const htmlElement = getElementFromUserInput(actual, toHaveFocus, this)
return {
pass: htmlElement.ownerDocument.activeElement === htmlElement,
message: () => {
return [
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toHaveFocus`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toHaveFocus`, 'element', ''),
'',
...(this.isNot
- ? [
- 'Received element is focused:',
- ` ${this.utils.printReceived(htmlElement)}`,
- ]
+ ? ['Received element is focused:', ` ${this.utils.printReceived(htmlElement)}`]
: [
'Expected element with focus:',
` ${this.utils.printExpected(htmlElement)}`,
'Received element with focus:',
- ` ${this.utils.printReceived(
- htmlElement.ownerDocument.activeElement,
- )}`,
+ ` ${this.utils.printReceived(htmlElement.ownerDocument.activeElement)}`,
]),
].join('\n')
},
diff --git a/packages/browser/src/client/tester/expect/toHaveFormValues.ts b/packages/browser/src/client/tester/expect/toHaveFormValues.ts
index 26a79dffc..27bea1daf 100644
--- a/packages/browser/src/client/tester/expect/toHaveFormValues.ts
+++ b/packages/browser/src/client/tester/expect/toHaveFormValues.ts
@@ -16,7 +16,12 @@
import type { MatcherResult, MatcherState } from 'vitest'
import type { Locator } from '../locators'
import { cssEscape } from 'ivya/utils'
-import { arrayAsSetComparison, getElementFromUserInput, getSingleElementValue, getTag } from './utils'
+import {
+ arrayAsSetComparison,
+ getElementFromUserInput,
+ getSingleElementValue,
+ getTag,
+} from './utils'
export default function toHaveFormValues(
this: MatcherState,
@@ -27,8 +32,13 @@ export default function toHaveFormValues(
const defaultView = formElement.ownerDocument.defaultView || window
- if (!(formElement instanceof defaultView.HTMLFieldSetElement) && !(formElement instanceof defaultView.HTMLFormElement)) {
- throw new TypeError(`toHaveFormValues must be called on a form or a fieldset, instead got ${getTag(formElement)}`)
+ if (
+ !(formElement instanceof defaultView.HTMLFieldSetElement) &&
+ !(formElement instanceof defaultView.HTMLFormElement)
+ ) {
+ throw new TypeError(
+ `toHaveFormValues must be called on a form or a fieldset, instead got ${getTag(formElement)}`,
+ )
}
if (!expectedValues || typeof expectedValues !== 'object') {
@@ -69,24 +79,20 @@ function getMultiElementValue(elements: HTMLInputElement[]) {
let type = ''
for (const element of elements) {
if (type && type !== element.type) {
- throw new Error(
- 'Multiple form elements with the same name must be of the same type',
- )
+ throw new Error('Multiple form elements with the same name must be of the same type')
}
type = element.type
}
switch (type) {
case 'radio': {
- const selected = elements.find(radio => radio.checked)
+ const selected = elements.find((radio) => radio.checked)
return selected ? selected.value : undefined
}
case 'checkbox':
- return elements
- .filter(checkbox => checkbox.checked)
- .map(checkbox => checkbox.value)
+ return elements.filter((checkbox) => checkbox.checked).map((checkbox) => checkbox.value)
default:
// NOTE: Not even sure this is a valid use case, but just in case...
- return elements.map(element => element.value)
+ return elements.map((element) => element.value)
}
}
@@ -106,7 +112,7 @@ function getFormValue(container: HTMLFormElement | HTMLFieldSetElement, name: st
// Strips the `[]` suffix off a form value name
function getPureName(name: string) {
- return /\[\]$/.test(name) ? name.slice(0, -2) : name
+ return name.endsWith('[]') ? name.slice(0, -2) : name
}
function getAllFormValues(container: HTMLFormElement | HTMLFieldSetElement) {
diff --git a/packages/browser/src/client/tester/expect/toHaveRole.ts b/packages/browser/src/client/tester/expect/toHaveRole.ts
index ce3412550..36819f613 100644
--- a/packages/browser/src/client/tester/expect/toHaveRole.ts
+++ b/packages/browser/src/client/tester/expect/toHaveRole.ts
@@ -33,11 +33,7 @@ export default function toHaveRole(
const to = this.isNot ? 'not to' : 'to'
return getMessage(
this,
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toHaveRole`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toHaveRole`, 'element', ''),
`Expected element ${to} have role`,
expectedRole,
'Received',
diff --git a/packages/browser/src/client/tester/expect/toHaveSelection.ts b/packages/browser/src/client/tester/expect/toHaveSelection.ts
index c77eea55e..d9a9c91dd 100644
--- a/packages/browser/src/client/tester/expect/toHaveSelection.ts
+++ b/packages/browser/src/client/tester/expect/toHaveSelection.ts
@@ -34,7 +34,10 @@ export default function toHaveSelection(
return {
pass: expectsSelection
- ? this.equals(receivedSelection, expectedSelection, [arrayAsSetComparison, ...this.customTesters])
+ ? this.equals(receivedSelection, expectedSelection, [
+ arrayAsSetComparison,
+ ...this.customTesters,
+ ])
: Boolean(receivedSelection),
message: () => {
const to = this.isNot ? 'not to' : 'to'
@@ -70,9 +73,7 @@ function getSelection(element: HTMLElement | SVGElement): string {
if (input.selectionStart == null || input.selectionEnd == null) {
return ''
}
- return input.value
- .toString()
- .substring(input.selectionStart, input.selectionEnd)
+ return input.value.toString().substring(input.selectionStart, input.selectionEnd)
}
if (selection.anchorNode === null || selection.focusNode === null) {
@@ -88,37 +89,24 @@ function getSelection(element: HTMLElement | SVGElement): string {
temporaryRange.selectNodeContents(element)
selection.removeAllRanges()
selection.addRange(temporaryRange)
- }
- else if (
- element.contains(selection.anchorNode)
- && element.contains(selection.focusNode)
- ) {
+ } else if (element.contains(selection.anchorNode) && element.contains(selection.focusNode)) {
// Element contains selection, nothing to do
- }
- else {
+ } else {
// Element is partially selected
- const selectionStartsWithinElement
- = element === originalRange.startContainer
- || element.contains(originalRange.startContainer)
- const selectionEndsWithinElement
- = element === originalRange.endContainer
- || element.contains(originalRange.endContainer)
+ const selectionStartsWithinElement =
+ element === originalRange.startContainer || element.contains(originalRange.startContainer)
+ const selectionEndsWithinElement =
+ element === originalRange.endContainer || element.contains(originalRange.endContainer)
selection.removeAllRanges()
if (selectionStartsWithinElement || selectionEndsWithinElement) {
temporaryRange.selectNodeContents(element)
if (selectionStartsWithinElement) {
- temporaryRange.setStart(
- originalRange.startContainer,
- originalRange.startOffset,
- )
+ temporaryRange.setStart(originalRange.startContainer, originalRange.startOffset)
}
if (selectionEndsWithinElement) {
- temporaryRange.setEnd(
- originalRange.endContainer,
- originalRange.endOffset,
- )
+ temporaryRange.setEnd(originalRange.endContainer, originalRange.endOffset)
}
selection.addRange(temporaryRange)
diff --git a/packages/browser/src/client/tester/expect/toHaveStyle.ts b/packages/browser/src/client/tester/expect/toHaveStyle.ts
index 7808d136a..dda8942c5 100644
--- a/packages/browser/src/client/tester/expect/toHaveStyle.ts
+++ b/packages/browser/src/client/tester/expect/toHaveStyle.ts
@@ -48,9 +48,8 @@ export default function toHaveStyle(
const htmlElement = getElementFromUserInput(actual, toHaveStyle, this)
const { getComputedStyle } = htmlElement.ownerDocument.defaultView!
- const expected = typeof css === 'object'
- ? getStyleFromObjectCSS(css)
- : computeCSSStyleDeclaration(css)
+ const expected =
+ typeof css === 'object' ? getStyleFromObjectCSS(css) : computeCSSStyleDeclaration(css)
const received = getComputedStyle(htmlElement)
const receivedCustomKeys = new Set(Array.from(htmlElement.style))
@@ -60,36 +59,33 @@ export default function toHaveStyle(
const matcher = `${this.isNot ? '.not' : ''}.toHaveStyle`
const expectedKeys = new Set(Object.keys(expected))
const receivedObject = Array.from(received)
- .filter(prop => expectedKeys.has(prop))
+ .filter((prop) => expectedKeys.has(prop))
.reduce(
(obj, prop) => {
- const styleSheet = receivedCustomKeys.has(prop) && usedValuesProps.has(prop)
- ? htmlElement.style
- : received
+ const styleSheet =
+ receivedCustomKeys.has(prop) && usedValuesProps.has(prop)
+ ? htmlElement.style
+ : received
obj[prop] = styleSheet[prop as 'color']
return obj
},
{} as Record,
)
const receivedString = printoutObjectStyles(receivedObject)
- const diff = receivedString === ''
- ? 'Expected styles could not be parsed by the browser. Did you make a typo?'
- : this.utils.diff(
- printoutObjectStyles(expected),
- receivedString,
- )
- return [
- this.utils.matcherHint(matcher, 'element', ''),
- diff,
- ].join('\n\n')
+ const diff =
+ receivedString === ''
+ ? 'Expected styles could not be parsed by the browser. Did you make a typo?'
+ : this.utils.diff(printoutObjectStyles(expected), receivedString)
+ return [this.utils.matcherHint(matcher, 'element', ''), diff].join('\n\n')
},
}
}
function getStyleFromObjectCSS(css: Record): Record {
- const doc = browser === 'chrome' || browser === 'chromium'
- ? document
- : document.implementation.createHTMLDocument('')
+ const doc =
+ browser === 'chrome' || browser === 'chromium'
+ ? document
+ : document.implementation.createHTMLDocument('')
const copy = doc.createElement('div')
doc.body.appendChild(copy)
@@ -118,9 +114,10 @@ function getStyleFromObjectCSS(css: Record): Record {
// on chromium for styles to be computed, they need to be inserted into the actual document
// webkit will also not compute _some_ style like transform if it's not in the document
- const doc = browser === 'chrome' || browser === 'chromium' || browser === 'webkit'
- ? document
- : document.implementation.createHTMLDocument('')
+ const doc =
+ browser === 'chrome' || browser === 'chromium' || browser === 'webkit'
+ ? document
+ : document.implementation.createHTMLDocument('')
const rootElement = doc.createElement('div')
rootElement.setAttribute('style', css.replace(/\n/g, ''))
@@ -128,12 +125,15 @@ function computeCSSStyleDeclaration(css: string): Record {
const computedStyle = window.getComputedStyle(rootElement)
- const styleDeclaration = Array.from(rootElement.style).reduce((acc, prop) => {
- acc[prop] = usedValuesProps.has(prop)
- ? rootElement.style.getPropertyValue(prop)
- : computedStyle.getPropertyValue(prop)
- return acc
- }, {} as Record)
+ const styleDeclaration = Array.from(rootElement.style).reduce(
+ (acc, prop) => {
+ acc[prop] = usedValuesProps.has(prop)
+ ? rootElement.style.getPropertyValue(prop)
+ : computedStyle.getPropertyValue(prop)
+ return acc
+ },
+ {} as Record,
+ )
rootElement.remove()
return styleDeclaration
}
@@ -141,7 +141,7 @@ function computeCSSStyleDeclaration(css: string): Record {
function printoutObjectStyles(styles: Record): string {
return Object.keys(styles)
.sort()
- .map(prop => `${prop}: ${styles[prop]};`)
+ .map((prop) => `${prop}: ${styles[prop]};`)
.join('\n')
}
@@ -163,16 +163,11 @@ function isSubset(
spellingVariants.push(prop.toLowerCase())
}
- const pass = spellingVariants.some(
- (name) => {
- const styleSheet = receivedCustomKeys.has(prop) && usedValuesProps.has(prop)
- ? element.style
- : computedStyle
- return styleSheet[name as 'color'] === value
- || styleSheet.getPropertyValue(name) === value
- },
- )
+ const pass = spellingVariants.some((name) => {
+ const styleSheet =
+ receivedCustomKeys.has(prop) && usedValuesProps.has(prop) ? element.style : computedStyle
+ return styleSheet[name as 'color'] === value || styleSheet.getPropertyValue(name) === value
+ })
return pass
- },
- )
+ })
}
diff --git a/packages/browser/src/client/tester/expect/toHaveTextContent.ts b/packages/browser/src/client/tester/expect/toHaveTextContent.ts
index ff932a445..321073977 100644
--- a/packages/browser/src/client/tester/expect/toHaveTextContent.ts
+++ b/packages/browser/src/client/tester/expect/toHaveTextContent.ts
@@ -37,11 +37,7 @@ export default function toHaveTextContent(
const to = this.isNot ? 'not to' : 'to'
return getMessage(
this,
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toHaveTextContent`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toHaveTextContent`, 'element', ''),
`Expected element ${to} have text content`,
expectedText,
'Received',
diff --git a/packages/browser/src/client/tester/expect/toHaveValue.ts b/packages/browser/src/client/tester/expect/toHaveValue.ts
index 153cdc382..e8642ccd4 100644
--- a/packages/browser/src/client/tester/expect/toHaveValue.ts
+++ b/packages/browser/src/client/tester/expect/toHaveValue.ts
@@ -15,7 +15,13 @@
import type { MatcherResult, MatcherState } from 'vitest'
import type { Locator } from '../locators'
-import { arrayAsSetComparison, getElementFromUserInput, getMessage, getSingleElementValue, isInputElement } from './utils'
+import {
+ arrayAsSetComparison,
+ getElementFromUserInput,
+ getMessage,
+ getSingleElementValue,
+ isInputElement,
+} from './utils'
export default function toHaveValue(
this: MatcherState,
@@ -24,10 +30,7 @@ export default function toHaveValue(
): MatcherResult {
const htmlElement = getElementFromUserInput(actual, toHaveValue, this)
- if (
- isInputElement(htmlElement)
- && ['checkbox', 'radio'].includes(htmlElement.type)
- ) {
+ if (isInputElement(htmlElement) && ['checkbox', 'radio'].includes(htmlElement.type)) {
throw new Error(
'input with type=checkbox or type=radio cannot be used with .toHaveValue(). Use .toBeChecked() for type=checkbox or .toHaveFormValues() instead',
)
@@ -38,7 +41,7 @@ export default function toHaveValue(
let expectedTypedValue = expectedValue
let receivedTypedValue = receivedValue
- // eslint-disable-next-line eqeqeq
+ // oxlint-disable-next-line eqeqeq
if (expectedValue == receivedValue && expectedValue !== receivedValue) {
expectedTypedValue = `${expectedValue} (${typeof expectedValue})`
receivedTypedValue = `${receivedValue} (${typeof receivedValue})`
diff --git a/packages/browser/src/client/tester/expect/toMatchScreenshot.ts b/packages/browser/src/client/tester/expect/toMatchScreenshot.ts
index c714b9e7b..eebdb46a3 100644
--- a/packages/browser/src/client/tester/expect/toMatchScreenshot.ts
+++ b/packages/browser/src/client/tester/expect/toMatchScreenshot.ts
@@ -1,6 +1,9 @@
import type { AsyncMatcherResult, MatcherState, VisualRegressionArtifact } from 'vitest'
import type { BrowserPage, ScreenshotMatcherOptions } from '../../../../context'
-import type { ScreenshotMatcherArguments, ScreenshotMatcherOutput } from '../../../shared/screenshotMatcher/types'
+import type {
+ ScreenshotMatcherArguments,
+ ScreenshotMatcherOutput,
+} from '../../../shared/screenshotMatcher/types'
import type { Locator } from '../locators'
import { recordArtifact } from 'vitest'
import { getBrowserState } from '../../utils'
@@ -12,16 +15,14 @@ export default async function toMatchScreenshot(
this: MatcherState,
actual: BrowserPage | Element | Locator,
nameOrOptions?: ScreenshotMatcherOptions | string,
- options: ScreenshotMatcherOptions = typeof nameOrOptions === 'object'
- ? nameOrOptions
- : {},
+ options: ScreenshotMatcherOptions = typeof nameOrOptions === 'object' ? nameOrOptions : {},
): AsyncMatcherResult {
if (this.isNot) {
throw new Error('\'toMatchScreenshot\' cannot be used with "not"')
}
if (this.task === undefined || this.currentTestName === undefined) {
- throw new Error('\'toMatchScreenshot\' cannot be used without test context')
+ throw new Error("'toMatchScreenshot' cannot be used without test context")
}
const counterName = `${this.task.result?.repeatCount ?? 0}${this.testPath}${this.currentTestName}`
@@ -35,21 +36,21 @@ export default async function toMatchScreenshot(
counter.current += 1
- const name = typeof nameOrOptions === 'string'
- ? nameOrOptions
- : `${this.currentTestName} ${counter.current}`
+ const name =
+ typeof nameOrOptions === 'string' ? nameOrOptions : `${this.currentTestName} ${counter.current}`
const isPageTarget = isBrowserPage(actual)
const [element, ...mask] = await Promise.all([
isPageTarget ? undefined : serializeElement(actual, options),
- ...options.screenshotOptions && 'mask' in options.screenshotOptions
- ? (options.screenshotOptions.mask as Array)
- .map(m => serializeElement(m, options))
- : [],
+ ...(options.screenshotOptions && 'mask' in options.screenshotOptions
+ ? (options.screenshotOptions.mask as Array).map((m) =>
+ serializeElement(m, options),
+ )
+ : []),
])
- const normalizedOptions: Omit = (
+ const normalizedOptions: Omit =
options.screenshotOptions && 'mask' in options.screenshotOptions
? {
...options,
@@ -58,9 +59,8 @@ export default async function toMatchScreenshot(
mask,
},
}
- // TS believes `mask` to still be defined as `ReadonlyArray`
- : options as any
- )
+ : // TS believes `mask` to still be defined as `ReadonlyArray`
+ (options as any)
const result = await getBrowserState().commands.triggerCommand(
'__vitest_screenshotMatcher',
@@ -115,12 +115,10 @@ export default async function toMatchScreenshot(
result.actual
? `\nActual screenshot:\n ${this.utils.RECEIVED_COLOR(result.actual.path)}`
: null,
- result.diff
- ? this.utils.DIM_COLOR(`\nDiff image:\n ${result.diff.path}`)
- : null,
+ result.diff ? this.utils.DIM_COLOR(`\nDiff image:\n ${result.diff.path}`) : null,
'',
]
- .filter(element => element !== null)
+ .filter((element) => element !== null)
.join('\n'),
meta: {
outcome: result.outcome,
@@ -129,6 +127,10 @@ export default async function toMatchScreenshot(
}
function isBrowserPage(value: unknown): value is BrowserPage {
- return !!value && typeof value === 'object' && 'viewport' in value
- && typeof value.viewport === 'function'
+ return (
+ !!value &&
+ typeof value === 'object' &&
+ 'viewport' in value &&
+ typeof value.viewport === 'function'
+ )
}
diff --git a/packages/browser/src/client/tester/expect/toMatchTextContent.ts b/packages/browser/src/client/tester/expect/toMatchTextContent.ts
index dec2c7e78..2335f2f79 100644
--- a/packages/browser/src/client/tester/expect/toMatchTextContent.ts
+++ b/packages/browser/src/client/tester/expect/toMatchTextContent.ts
@@ -37,11 +37,7 @@ export default function toMatchTextContent(
const to = this.isNot ? 'not to' : 'to'
return getMessage(
this,
- this.utils.matcherHint(
- `${this.isNot ? '.not' : ''}.toMatchTextContent`,
- 'element',
- '',
- ),
+ this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toMatchTextContent`, 'element', ''),
checkingWithEmptyString
? `Checking with empty string will always match, use .toBeEmptyDOMElement() instead`
: `Expected element ${to} match text content`,
diff --git a/packages/browser/src/client/tester/expect/utils.ts b/packages/browser/src/client/tester/expect/utils.ts
index 739e9d8f5..fc6522c51 100644
--- a/packages/browser/src/client/tester/expect/utils.ts
+++ b/packages/browser/src/client/tester/expect/utils.ts
@@ -46,17 +46,13 @@ export function getElementFromUserInput(
const defaultView = elementOrLocator?.ownerDocument?.defaultView || window
if (
- elementOrLocator instanceof defaultView.HTMLElement
- || elementOrLocator instanceof defaultView.SVGElement
+ elementOrLocator instanceof defaultView.HTMLElement ||
+ elementOrLocator instanceof defaultView.SVGElement
) {
return elementOrLocator
}
- throw new UserInputElementTypeError(
- elementOrLocator,
- matcherFn,
- context,
- )
+ throw new UserInputElementTypeError(elementOrLocator, matcherFn, context)
}
export function getNodeFromUserInput(
@@ -70,17 +66,11 @@ export function getNodeFromUserInput(
const defaultView = elementOrLocator.ownerDocument?.defaultView || window
- if (
- elementOrLocator instanceof defaultView.Node
- ) {
+ if (elementOrLocator instanceof defaultView.Node) {
return elementOrLocator
}
- throw new UserInputNodeTypeError(
- elementOrLocator,
- matcherFn,
- context,
- )
+ throw new UserInputNodeTypeError(elementOrLocator, matcherFn, context)
}
export function getMessage(
@@ -155,7 +145,12 @@ export function toSentence(
}
class GenericTypeError extends Error {
- constructor(expectedString: string, received: unknown, matcherFn: (...args: any) => any, context: MatcherState) {
+ constructor(
+ expectedString: string,
+ received: unknown,
+ matcherFn: (...args: any) => any,
+ context: MatcherState,
+ ) {
super()
if (Error.captureStackTrace) {
@@ -163,22 +158,13 @@ class GenericTypeError extends Error {
}
let withType = ''
try {
- withType = context.utils.printWithType(
- 'Received',
- received,
- context.utils.printReceived,
- )
- }
- catch {
+ withType = context.utils.printWithType('Received', received, context.utils.printReceived)
+ } catch {
// Can throw for Document:
// https://github.com/jsdom/jsdom/issues/2304
}
this.message = [
- context.utils.matcherHint(
- `${context.isNot ? '.not' : ''}.${matcherFn.name}`,
- 'received',
- '',
- ),
+ context.utils.matcherHint(`${context.isNot ? '.not' : ''}.${matcherFn.name}`, 'received', ''),
'',
`${context.utils.RECEIVED_COLOR(
@@ -190,21 +176,13 @@ class GenericTypeError extends Error {
}
class UserInputElementTypeError extends GenericTypeError {
- constructor(
- element: unknown,
- matcherFn: (...args: any) => any,
- context: MatcherState,
- ) {
+ constructor(element: unknown, matcherFn: (...args: any) => any, context: MatcherState) {
super('an HTMLElement or an SVGElement', element, matcherFn, context)
}
}
class UserInputNodeTypeError extends GenericTypeError {
- constructor(
- element: unknown,
- matcherFn: (...args: any) => any,
- context: MatcherState,
- ) {
+ constructor(element: unknown, matcherFn: (...args: any) => any, context: MatcherState) {
super('a Node', element, matcherFn, context)
}
}
@@ -244,10 +222,10 @@ export function getSingleElementValue(
}
function getSelectValue({ multiple, options }: HTMLSelectElement) {
- const selectedOptions = [...options].filter(option => option.selected)
+ const selectedOptions = [...options].filter((option) => option.selected)
if (multiple) {
- return Array.from(selectedOptions, opt => opt.value)
+ return Array.from(selectedOptions, (opt) => opt.value)
}
/* istanbul ignore if */
if (selectedOptions.length === 0) {
@@ -282,8 +260,7 @@ export function normalize(text: string): string {
export function matches(textToMatch: string, matcher: string | RegExp): boolean {
if (matcher instanceof RegExp) {
return matcher.test(textToMatch)
- }
- else {
+ } else {
return textToMatch.includes(String(matcher))
}
}
diff --git a/packages/browser/src/client/tester/locators.ts b/packages/browser/src/client/tester/locators.ts
index 1751a61c2..8d9c0cd44 100644
--- a/packages/browser/src/client/tester/locators.ts
+++ b/packages/browser/src/client/tester/locators.ts
@@ -30,7 +30,12 @@ import { page, server, utils } from 'vitest/browser'
import { __INTERNAL, getSafeTimers } from 'vitest/internal/browser'
import { ensureAwaited, getBrowserState, getWorkerState } from '../utils'
import { LocatorAction, resolveActionTimeout, UploadAction } from './action'
-import { convertElementToCssSelector, escapeForTextSelector, isLocator, resolveUserEventWheelOptions } from './tester-utils'
+import {
+ convertElementToCssSelector,
+ escapeForTextSelector,
+ isLocator,
+ resolveUserEventWheelOptions,
+} from './tester-utils'
import { recordBrowserTraceEntry } from './trace'
export { ensureAwaited } from '../utils'
@@ -53,7 +58,7 @@ const waitForIntervals = [0, 20, 50, 100, 100, 500]
function sleep(ms: number): Promise {
const { setTimeout } = getSafeTimers()
- return new Promise(resolve => setTimeout(resolve, ms))
+ return new Promise((resolve) => setTimeout(resolve, ms))
}
export const selectorEngine: Ivya = Ivya.create({
@@ -136,14 +141,20 @@ export abstract class Locator {
}
public unhover(options?: UserEventHoverOptions): Promise {
- return this.action('__vitest_hover', [], options, { selector: 'html > body', locator: 'locator(\'body\')' })
+ return this.action('__vitest_hover', [], options, {
+ selector: 'html > body',
+ locator: "locator('body')",
+ })
}
public fill(text: string, options?: UserEventFillOptions): Promise {
return this.action('__vitest_fill', [text], options)
}
- public upload(files: string | string[] | File | File[], options?: UserEventUploadOptions): Promise {
+ public upload(
+ files: string | string[] | File | File[],
+ options?: UserEventUploadOptions,
+ ): Promise {
return new UploadAction(this.serialize(), files, options, this._errorSource)
}
@@ -161,7 +172,10 @@ export abstract class Locator {
? v.serialize()
: {
selector: convertElementToCssSelector(v),
- locator: __INTERNAL._asLocator('javascript', selectorEngine.generateSelectorSimple(v)),
+ locator: __INTERNAL._asLocator(
+ 'javascript',
+ selectorEngine.generateSelectorSimple(v),
+ ),
}
return { element }
}
@@ -175,10 +189,13 @@ export abstract class Locator {
base64: string
}>
public screenshot(options?: LocatorScreenshotOptions): Promise
- public screenshot(options?: LocatorScreenshotOptions): Promise {
+ public screenshot(options?: LocatorScreenshotOptions): Promise<
+ | string
+ | {
+ path: string
+ base64: string
+ }
+ > {
return page.screenshot({
...options,
element: this,
@@ -188,7 +205,8 @@ export abstract class Locator {
public mark(name: string, options?: MarkOptions): Promise {
const currentTest = getWorkerState().current
const hasActiveTrace = !!currentTest && getBrowserState().activeTraceTaskIds.has(currentTest.id)
- const hasActiveTraceView = !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id)
+ const hasActiveTraceView =
+ !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id)
if (!currentTest || (!hasActiveTrace && !hasActiveTraceView)) {
return Promise.resolve()
}
@@ -206,11 +224,13 @@ export abstract class Locator {
}
return getBrowserState().commands.triggerCommand(
'__vitest_markTrace',
- [{
- name,
- element: this.serialize(),
- stack: options?.stack ?? error?.stack,
- }],
+ [
+ {
+ name,
+ element: this.serialize(),
+ stack: options?.stack ?? error?.stack,
+ },
+ ],
error,
)
})
@@ -284,8 +304,12 @@ export abstract class Locator {
}
public query(): HTMLElement | SVGElement | null {
- const parsedSelector = this._parsedSelector || (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector))
- return selectorEngine.querySelector(parsedSelector, document.documentElement, true) as HTMLElement | SVGElement
+ const parsedSelector =
+ this._parsedSelector ||
+ (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector))
+ return selectorEngine.querySelector(parsedSelector, document.documentElement, true) as
+ | HTMLElement
+ | SVGElement
}
public element(): HTMLElement | SVGElement {
@@ -297,8 +321,13 @@ export abstract class Locator {
}
public elements(): (HTMLElement | SVGElement)[] {
- const parsedSelector = this._parsedSelector || (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector))
- return selectorEngine.querySelectorAll(parsedSelector, document.documentElement) as (HTMLElement | SVGElement)[]
+ const parsedSelector =
+ this._parsedSelector ||
+ (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector))
+ return selectorEngine.querySelectorAll(parsedSelector, document.documentElement) as (
+ | HTMLElement
+ | SVGElement
+ )[]
}
public get length(): number {
@@ -306,7 +335,7 @@ export abstract class Locator {
}
public all(): Locator[] {
- return this.elements().map(element => this.elementLocator(element))
+ return this.elements().map((element) => this.elementLocator(element))
}
public nth(index: number): Locator {
@@ -334,7 +363,10 @@ export abstract class Locator {
}
public asLocator(): string {
- return this._pwLocator || (this._pwLocator = asLocator('javascript', this._pwSelector || this.selector))
+ return (
+ this._pwLocator ||
+ (this._pwLocator = asLocator('javascript', this._pwSelector || this.selector))
+ )
}
public toJSON(): SerializedLocator {
@@ -363,9 +395,7 @@ export abstract class Locator {
throw utils.getElementError(this, this._container || document.body)
}
const interval = waitForIntervals[Math.min(intervalIndex++, waitForIntervals.length - 1)]
- const nextInterval = timeout != null
- ? Math.min(interval, timeout - elapsed)
- : interval
+ const nextInterval = timeout != null ? Math.min(interval, timeout - elapsed) : interval
await sleep(nextInterval)
}
}
@@ -383,13 +413,11 @@ export abstract class Locator {
/**
* @deprecated
*/
-export function triggerCommandWithTrace(
- options: {
- name: string
- arguments: unknown[]
- errorSource?: Error | undefined
- },
-): Promise {
+export function triggerCommandWithTrace(options: {
+ name: string
+ arguments: unknown[]
+ errorSource?: Error | undefined
+}): Promise {
return getBrowserState().commands.triggerCommand(
options.name,
options.arguments,
@@ -406,17 +434,13 @@ export interface SerializedLocator {
_pwSelector?: string
}
-function createStrictModeViolationError(
- locator: Locator,
- matches: Element[],
-) {
- const infos = matches.slice(0, 10).map(m => ({
+function createStrictModeViolationError(locator: Locator, matches: Element[]) {
+ const infos = matches.slice(0, 10).map((m) => ({
preview: selectorEngine.previewNode(m),
selector: selectorEngine.generateSelectorSimple(m),
}))
const lines = infos.map(
- (info, i) =>
- `\n ${i + 1}) ${info.preview} aka ${asLocator('javascript', info.selector)}`,
+ (info, i) => `\n ${i + 1}) ${info.preview} aka ${asLocator('javascript', info.selector)}`,
)
if (infos.length < matches.length) {
lines.push('\n ...')
diff --git a/packages/browser/src/client/tester/logger.ts b/packages/browser/src/client/tester/logger.ts
index 67601c7e9..30961bea3 100644
--- a/packages/browser/src/client/tester/logger.ts
+++ b/packages/browser/src/client/tester/logger.ts
@@ -42,7 +42,8 @@ export function setupConsoleLogSpy(): void {
trace(...args)
const content = processLog(args)
const error = new Error('$$Trace')
- const processor = (globalThis as any).__vitest_worker__?.onFilterStackTrace || ((s: string) => s || '')
+ const processor =
+ (globalThis as any).__vitest_worker__?.onFilterStackTrace || ((s: string) => s || '')
const stack = processor(error.stack || '')
sendLog('stderr', `${content}\n${stack}`, true)
}
@@ -59,8 +60,7 @@ export function setupConsoleLogSpy(): void {
timeLog(label)
if (!(label in timeLabels)) {
sendLog('stderr', `Timer "${label}" does not exist`)
- }
- else {
+ } else {
sendLog('stdout', `${label}: ${timeLabels[label]} ms`)
}
}
@@ -71,8 +71,7 @@ export function setupConsoleLogSpy(): void {
const start = timeLabels[label]
if (!(label in timeLabels)) {
sendLog('stderr', `Timer "${label}" does not exist`)
- }
- else if (typeof start !== 'undefined') {
+ } else if (typeof start !== 'undefined') {
const duration = end - start
sendLog('stdout', `${label}: ${duration} ms`)
}
@@ -117,19 +116,15 @@ function processLog(args: unknown[]) {
return format(args, { multiline: true })
}
-function sendLog(
- type: 'stdout' | 'stderr',
- content: string,
- disableStack?: boolean,
-) {
+function sendLog(type: 'stdout' | 'stderr', content: string, disableStack?: boolean) {
if (content.startsWith('[vite]')) {
return
}
const unknownTestId = '__vitest__unknown_test__'
// @ts-expect-error untyped global
const taskId = globalThis.__vitest_worker__?.current?.id ?? unknownTestId
- const origin
- = getConfig().printConsoleTrace && !disableStack
+ const origin =
+ getConfig().printConsoleTrace && !disableStack
? new Error('STACK_TRACE').stack?.split('\n').slice(1).join('\n')
: undefined
const runner = getBrowserRunner()
diff --git a/packages/browser/src/client/tester/rpc.ts b/packages/browser/src/client/tester/rpc.ts
index 527079a36..0e82f56bd 100644
--- a/packages/browser/src/client/tester/rpc.ts
+++ b/packages/browser/src/client/tester/rpc.ts
@@ -15,8 +15,7 @@ function withSafeTimers(getTimers: typeof getSafeTimers, fn: () => void) {
const result = fn()
return result
- }
- finally {
+ } finally {
globalThis.setTimeout = currentSetTimeout
globalThis.clearTimeout = currentClearTimeout
}
@@ -24,9 +23,7 @@ function withSafeTimers(getTimers: typeof getSafeTimers, fn: () => void) {
const promises = new Set>()
-export function createSafeRpc(
- client: VitestBrowserClient,
-): VitestBrowserClient['rpc'] {
+export function createSafeRpc(client: VitestBrowserClient): VitestBrowserClient['rpc'] {
return new Proxy(client.rpc, {
get(target, p, handler) {
if (p === 'then') {
@@ -39,8 +36,7 @@ export function createSafeRpc(
promises.add(result)
try {
return await result
- }
- finally {
+ } finally {
promises.delete(result)
}
})
diff --git a/packages/browser/src/client/tester/runner.ts b/packages/browser/src/client/tester/runner.ts
index 223d50b63..e41be5a5b 100644
--- a/packages/browser/src/client/tester/runner.ts
+++ b/packages/browser/src/client/tester/runner.ts
@@ -82,9 +82,10 @@ function createBrowserRunner(
super.onBeforeTryTask?.(test, options)
const trace = this.config.browser.trace
const { retry, repeats } = options
- const shouldTrace = trace !== 'off'
- && !(trace === 'on-all-retries' && retry === 0)
- && !(trace === 'on-first-retry' && retry !== 1)
+ const shouldTrace =
+ trace !== 'off' &&
+ !(trace === 'on-all-retries' && retry === 0) &&
+ !(trace === 'on-first-retry' && retry !== 1)
const shouldTraceView = this.config.browser.traceView.enabled
if (!shouldTraceView && !shouldTrace) {
getBrowserState().activeTraceTaskIds.delete(test.id)
@@ -92,10 +93,10 @@ function createBrowserRunner(
return
}
if (shouldTraceView) {
- getBrowserState().browserTraceDomSnapshot ??= await getOrchestratorState().browserTraceDomSnapshotPromise
+ getBrowserState().browserTraceDomSnapshot ??=
+ await getOrchestratorState().browserTraceDomSnapshotPromise
getBrowserState().browserTraceAttempts.set(test.id, { retry, repeats, startTime: now() })
- }
- else {
+ } else {
getBrowserState().browserTraceAttempts.delete(test.id)
}
if (!shouldTrace) {
@@ -112,13 +113,13 @@ function createBrowserRunner(
}
const name = getTraceName(test, retry, repeats)
- await this.commands.triggerCommand(
- '__vitest_startChunkTrace',
- [{ name, title }],
- )
+ await this.commands.triggerCommand('__vitest_startChunkTrace', [{ name, title }])
}
- onAfterRetryTask = async (test: Test, { retry, repeats }: { retry: number; repeats: number }) => {
+ onAfterRetryTask = async (
+ test: Test,
+ { retry, repeats }: { retry: number; repeats: number },
+ ) => {
const hasActiveTraceView = getBrowserState().browserTraceAttempts.has(test.id)
if (hasActiveTraceView) {
const status = test.result?.state
@@ -136,19 +137,20 @@ function createBrowserRunner(
if (!hasActiveTrace) {
return
}
- await this.commands.triggerCommand('__vitest_markTrace', [{
- name: `onAfterRetryTask [${test.result?.state}]`,
- stack: test.result?.errors?.[0].stack,
- }])
+ await this.commands.triggerCommand('__vitest_markTrace', [
+ {
+ name: `onAfterRetryTask [${test.result?.state}]`,
+ stack: test.result?.errors?.[0].stack,
+ },
+ ])
const name = getTraceName(test, retry, repeats)
if (!this.traces.has(test.id)) {
this.traces.set(test.id, [])
}
const traces = this.traces.get(test.id)!
- const { tracePath } = await this.commands.triggerCommand(
- '__vitest_stopChunkTrace',
- [{ name }],
- ) as { tracePath: string }
+ const { tracePath } = (await this.commands.triggerCommand('__vitest_stopChunkTrace', [
+ { name },
+ ])) as { tracePath: string }
traces.push(tracePath)
}
@@ -158,16 +160,11 @@ function createBrowserRunner(
const traces = this.traces.get(task.id) || []
if (traces.length) {
if (trace === 'retain-on-failure' && task.result?.state === 'pass') {
- await this.commands.triggerCommand(
- '__vitest_deleteTracing',
- [{ traces }],
- )
- }
- else {
- await this.commands.triggerCommand(
- '__vitest_annotateTraces',
- [{ testId: task.id, traces }],
- )
+ await this.commands.triggerCommand('__vitest_deleteTracing', [{ traces }])
+ } else {
+ await this.commands.triggerCommand('__vitest_annotateTraces', [
+ { testId: task.id, traces },
+ ])
}
}
if (this.config.bail && task.result?.state === 'fail') {
@@ -185,26 +182,29 @@ function createBrowserRunner(
// check custom matcher metadata in JestExtendError
const lastErrorContext = task.result?.errors?.at(-1)?.__vitest_error_context__
if (
- this.config.browser.screenshotFailures
- && document.body.clientHeight > 0
- && task.result?.state === 'fail'
- && task.type === 'test'
- && !(
- lastErrorContext
- && Reflect.get(lastErrorContext, 'assertionName') === 'toMatchScreenshot'
- && Reflect.get(lastErrorContext, 'meta')?.outcome !== 'unstable-screenshot')
+ this.config.browser.screenshotFailures &&
+ document.body.clientHeight > 0 &&
+ task.result?.state === 'fail' &&
+ task.type === 'test' &&
+ !(
+ lastErrorContext &&
+ Reflect.get(lastErrorContext, 'assertionName') === 'toMatchScreenshot' &&
+ Reflect.get(lastErrorContext, 'meta')?.outcome !== 'unstable-screenshot'
+ )
) {
- const screenshot = await page.screenshot({
- timeout: this.config.browser.providerOptions?.actionTimeout ?? 5_000,
- path: resolve(
- this.config.attachmentsDir,
- 'failure-screenshots',
- basename(task.file.filepath),
- `${task.fullTestName.replace(/\W/g, '-')}.png`,
- ),
- }).catch((err) => {
- console.error('[vitest] Failed to take a screenshot', err)
- })
+ const screenshot = await page
+ .screenshot({
+ timeout: this.config.browser.providerOptions?.actionTimeout ?? 5_000,
+ path: resolve(
+ this.config.attachmentsDir,
+ 'failure-screenshots',
+ basename(task.file.filepath),
+ `${task.fullTestName.replace(/\W/g, '-')}.png`,
+ ),
+ })
+ .catch((err) => {
+ console.error('[vitest] Failed to take a screenshot', err)
+ })
if (screenshot) {
await recordArtifact(task, {
type: 'internal:failureScreenshot',
@@ -228,10 +228,8 @@ function createBrowserRunner(
}
// usually resolved already: the request is fired as soon as the
// file finishes importing, while collection is still running
- const map = await (
- this.sourceMapPrefetches.get(suite.filepath)
- ?? rpc().getBrowserFileSourceMap(suite.filepath)
- )
+ const map = await (this.sourceMapPrefetches.get(suite.filepath) ??
+ rpc().getBrowserFileSourceMap(suite.filepath))
this.sourceMapPrefetches.delete(suite.filepath)
this.sourceMapCache.set(suite.filepath, map)
const snapshotEnvironment = this.config.snapshotOptions.snapshotEnvironment
@@ -245,15 +243,12 @@ function createBrowserRunner(
onAfterRunFiles = async (files: File[]) => {
super.onAfterRunFiles(files)
- const [coverage] = await Promise.all([
- coverageModule.takeCoverage(),
- mocker.invalidate(),
- ])
+ const [coverage] = await Promise.all([coverageModule.takeCoverage(), mocker.invalidate()])
if (coverage) {
await rpc().onAfterSuiteRun({
coverage,
- testFiles: files.map(file => file.name),
+ testFiles: files.map((file) => file.name),
environment: 'client',
projectName: this.config.name,
})
@@ -276,19 +271,25 @@ function createBrowserRunner(
if (this.config.includeTaskLocation) {
try {
await updateTestFilesLocations(files, this.sourceMapCache)
- }
- catch {}
+ } catch {}
}
return rpc().onCollected(this.method, files)
}
onTestAnnotate = (test: Test, annotation: TestAnnotation): Promise => {
- const artifact: TestArtifact = { type: 'internal:annotation', annotation, location: annotation.location }
+ const artifact: TestArtifact = {
+ type: 'internal:annotation',
+ annotation,
+ location: annotation.location,
+ }
return this.onTestArtifactRecord(test, artifact).then(({ annotation }) => annotation)
}
- onTestArtifactRecord = (test: Test, artifact: Artifact): Promise => {
+ onTestArtifactRecord = (
+ test: Test,
+ artifact: Artifact,
+ ): Promise => {
if (artifact.location) {
// the file should be the test file
// tests from other files are not supported
@@ -344,8 +345,7 @@ function createBrowserRunner(
}
try {
await import(/* @vite-ignore */ importpath)
- }
- catch (err) {
+ } catch (err) {
throw new Error(`Failed to import test file ${filepath}`, { cause: err })
}
@@ -354,14 +354,17 @@ function createBrowserRunner(
// request it early so onBeforeRunSuite doesn't have to wait
this.sourceMapPrefetches.set(
filepath,
- rpc().getBrowserFileSourceMap(filepath).catch(() => undefined),
+ rpc()
+ .getBrowserFileSourceMap(filepath)
+ .catch(() => undefined),
)
}
}
trace = (name: string, attributes: Record | (() => T), cb?: () => T): T => {
- const options: import('@opentelemetry/api').SpanOptions = typeof attributes === 'object' ? { attributes } : {}
- return this._otel.$(`vitest.test.runner.${name}`, options, cb || attributes as () => T)
+ const options: import('@opentelemetry/api').SpanOptions =
+ typeof attributes === 'object' ? { attributes } : {}
+ return this._otel.$(`vitest.test.runner.${name}`, options, cb || (attributes as () => T))
}
}
}
@@ -381,8 +384,7 @@ export async function initiateRunner(
return cachedRunner
}
const BrowserRunner = createBrowserRunner(mocker, state, {
- takeCoverage: () =>
- takeCoverageInsideWorker(config.coverage, moduleRunner),
+ takeCoverage: () => takeCoverageInsideWorker(config.coverage, moduleRunner),
})
if (!config.snapshotOptions.snapshotEnvironment) {
config.snapshotOptions.snapshotEnvironment = new VitestBrowserSnapshotEnvironment()
@@ -413,10 +415,14 @@ export async function initiateRunner(
}
async function getTraceMap(file: string, sourceMaps: Map) {
- const result = sourceMaps.get(file) || await rpc().getBrowserFileSourceMap(file).then((map) => {
- sourceMaps.set(file, map)
- return map
- })
+ const result =
+ sourceMaps.get(file) ||
+ (await rpc()
+ .getBrowserFileSourceMap(file)
+ .then((map) => {
+ sourceMaps.set(file, map)
+ return map
+ }))
if (!result) {
return null
}
diff --git a/packages/browser/src/client/tester/state.ts b/packages/browser/src/client/tester/state.ts
index d54d1627e..9d695d3d3 100644
--- a/packages/browser/src/client/tester/state.ts
+++ b/packages/browser/src/client/tester/state.ts
@@ -33,7 +33,7 @@ const state: WorkerGlobalState = {
throw new Error('Not called in the browser')
},
},
- onCleanup: fn => getBrowserState().cleanups.push(fn),
+ onCleanup: (fn) => getBrowserState().cleanups.push(fn),
evaluatedModules: new EvaluatedModules(),
resolvingModules: new Set(),
moduleExecutionInfo: new Map(),
@@ -94,7 +94,7 @@ function createCdp() {
off(event: string, listener: (payload: any) => void) {
const listenerId = getId(listener)
if (listeners[event]) {
- listeners[event] = listeners[event].filter(l => l !== listener)
+ listeners[event] = listeners[event].filter((l) => l !== listener)
}
rpc().trackCdpEvent(sessionId, 'off', event, listenerId).catch(error)
return cdp
@@ -104,8 +104,7 @@ function createCdp() {
listeners[event].forEach((l) => {
try {
l(payload)
- }
- catch (err) {
+ } catch (err) {
error(err)
}
})
diff --git a/packages/browser/src/client/tester/tester-utils.ts b/packages/browser/src/client/tester/tester-utils.ts
index 375636c75..61ec06afd 100644
--- a/packages/browser/src/client/tester/tester-utils.ts
+++ b/packages/browser/src/client/tester/tester-utils.ts
@@ -1,4 +1,10 @@
-import type { Locator, SelectorOptions, SerializedLocator, UserEventWheelDeltaOptions, UserEventWheelOptions } from 'vitest/browser'
+import type {
+ Locator,
+ SelectorOptions,
+ SerializedLocator,
+ UserEventWheelDeltaOptions,
+ UserEventWheelOptions,
+} from 'vitest/browser'
import type { BrowserRPC } from '../client'
import type { BrowserTraceEntryStatus } from './trace'
import { __INTERNAL } from 'vitest/internal/browser'
@@ -8,9 +14,7 @@ import { createBrowserTraceRangeId, recordBrowserTraceEntry } from './trace'
/* @__NO_SIDE_EFFECTS__ */
export function convertElementToCssSelector(element: Element): string {
if (!element || !(element instanceof Element)) {
- throw new Error(
- `Expected DOM element to be an instance of Element, received ${typeof element}`,
- )
+ throw new Error(`Expected DOM element to be an instance of Element, received ${typeof element}`)
}
return getUniqueCssSelector(element)
@@ -22,23 +26,30 @@ function escapeIdForCSSSelector(id: string) {
.map((char) => {
const code = char.charCodeAt(0)
- if (char === ' ' || char === '#' || char === '.' || char === ':' || char === '[' || char === ']' || char === '>' || char === '+' || char === '~' || char === '\\') {
+ if (
+ char === ' ' ||
+ char === '#' ||
+ char === '.' ||
+ char === ':' ||
+ char === '[' ||
+ char === ']' ||
+ char === '>' ||
+ char === '+' ||
+ char === '~' ||
+ char === '\\'
+ ) {
// Escape common special characters with backslashes
return `\\${char}`
- }
- else if (code >= 0x10000) {
+ } else if (code >= 0x10000) {
// Unicode escape for characters outside the BMP
return `\\${code.toString(16).toUpperCase().padStart(6, '0')} `
- }
- else if (code < 0x20 || code === 0x7F) {
+ } else if (code < 0x20 || code === 0x7f) {
// Non-printable ASCII characters (0x00-0x1F and 0x7F) are escaped
return `\\${code.toString(16).toUpperCase().padStart(2, '0')} `
- }
- else if (code >= 0x80) {
+ } else if (code >= 0x80) {
// Non-ASCII characters (0x80 and above) are escaped
return `\\${code.toString(16).toUpperCase().padStart(2, '0')} `
- }
- else {
+ } else {
// Allowable characters are used directly
return char
}
@@ -50,8 +61,8 @@ function getUniqueCssSelector(el: Element) {
const path = []
let parent: null | ParentNode
let hasShadowRoot = false
- // eslint-disable-next-line no-cond-assign
- while (parent = getParent(el)) {
+ // oxlint-disable-next-line no-cond-assign
+ while ((parent = getParent(el))) {
if ((parent as Element).shadowRoot) {
hasShadowRoot = true
}
@@ -59,11 +70,9 @@ function getUniqueCssSelector(el: Element) {
const tag = el.tagName
if (el.id) {
path.push(`#${escapeIdForCSSSelector(el.id)}`)
- }
- else if (!el.nextElementSibling && !el.previousElementSibling) {
+ } else if (!el.nextElementSibling && !el.previousElementSibling) {
path.push(tag.toLowerCase())
- }
- else {
+ } else {
let index = 0
let sameTagSiblings = 0
let elementIndex = 0
@@ -80,13 +89,12 @@ function getUniqueCssSelector(el: Element) {
if (sameTagSiblings > 1) {
path.push(`${tag.toLowerCase()}:nth-child(${elementIndex})`)
- }
- else {
+ } else {
path.push(tag.toLowerCase())
}
}
el = parent as Element
- };
+ }
return `${getBrowserState().provider === 'webdriverio' && hasShadowRoot ? '>>>' : ''}${path.reverse().join(' > ')}`
}
@@ -133,21 +141,23 @@ export class CommandsManager {
const rpc = state.rpc as any as BrowserRPC
const { sessionId, traces } = getBrowserState()
const filepath = state.filepath || state.current?.file?.filepath
- args = args.filter(arg => arg !== undefined) // remove optional fields
+ args = args.filter((arg) => arg !== undefined) // remove optional fields
const actionTraceGroupName = ACTION_TRACE_COMMANDS.has(command)
? `vitest:${command.slice('__vitest_'.length)}`
: undefined
const currentTest = getWorkerState().current
- const hasActiveTrace = !!actionTraceGroupName
- && !!currentTest
- && getBrowserState().activeTraceTaskIds.has(currentTest.id)
- const hasActiveTraceView = !!actionTraceGroupName
- && !!currentTest
- && getBrowserState().browserTraceAttempts.has(currentTest.id)
+ const hasActiveTrace =
+ !!actionTraceGroupName &&
+ !!currentTest &&
+ getBrowserState().activeTraceTaskIds.has(currentTest.id)
+ const hasActiveTraceView =
+ !!actionTraceGroupName &&
+ !!currentTest &&
+ getBrowserState().browserTraceAttempts.has(currentTest.id)
if (this._listeners.length) {
- await Promise.all(this._listeners.map(listener => listener(command, args)))
+ await Promise.all(this._listeners.map((listener) => listener(command, args)))
}
return traces.$(
'vitest.browser.tester.command',
@@ -159,19 +169,19 @@ export class CommandsManager {
},
async () => {
if (hasActiveTrace) {
- await rpc.triggerCommand(
- sessionId,
- '__vitest_groupTraceStart',
- filepath,
- [{
+ await rpc.triggerCommand(sessionId, '__vitest_groupTraceStart', filepath, [
+ {
name: actionTraceGroupName,
stack: clientError.stack,
- }],
- )
+ },
+ ])
}
let status: BrowserTraceEntryStatus = 'pass'
const traceRangeId = hasActiveTraceView ? createBrowserTraceRangeId() : undefined
- const element = typeof args[0] === 'object' && 'selector' in args[0] && 'locator' in args[0] ? args[0] : undefined
+ const element =
+ typeof args[0] === 'object' && 'selector' in args[0] && 'locator' in args[0]
+ ? args[0]
+ : undefined
if (hasActiveTraceView) {
// Covers provider-backed actionability/waiting after command dispatch.
// Local pre-command resolution, such as serializeElement/findElement paths
@@ -186,16 +196,14 @@ export class CommandsManager {
}
try {
return await rpc.triggerCommand(sessionId, command, filepath, args)
- }
- catch (err: any) {
+ } catch (err: any) {
status = 'fail'
// rethrow an error to keep the stack trace in browser
clientError.message = err.message
clientError.name = err.name
clientError.stack = clientError.stack?.replace(clientError.message, err.message)
throw clientError
- }
- finally {
+ } finally {
if (hasActiveTraceView) {
await recordBrowserTraceEntry(currentTest, {
name: actionTraceGroupName,
@@ -207,12 +215,7 @@ export class CommandsManager {
})
}
if (hasActiveTrace) {
- await rpc.triggerCommand(
- sessionId,
- '__vitest_groupTraceEnd',
- filepath,
- [],
- )
+ await rpc.triggerCommand(sessionId, '__vitest_groupTraceEnd', filepath, [])
}
}
},
@@ -224,7 +227,9 @@ export function getIframeScale(): number {
const iframe = window.frameElement
if (!iframe) {
- throw new Error(`Cannot find iframe element. This is a bug in Vitest. Please, open a new issue with reproduction.`)
+ throw new Error(
+ `Cannot find iframe element. This is a bug in Vitest. Please, open a new issue with reproduction.`,
+ )
}
// DOMMatrix parses the computed 2D transform matrix [a, b, c, d, e, f]
@@ -243,7 +248,9 @@ function escapeRegexForSelector(re: RegExp): string {
return String(re)
}
// Even number of backslashes followed by the quote -> insert a backslash.
- return String(re).replace(/(^|[^\\])(\\\\)*(["'`])/g, '$1$2\\$3').replace(/>>/g, '\\>\\>')
+ return String(re)
+ .replace(/(^|[^\\])(\\\\)*(["'`])/g, '$1$2\\$3')
+ .replace(/>>/g, '\\>\\>')
}
export function escapeForTextSelector(text: string | RegExp, exact: boolean): string {
@@ -256,7 +263,10 @@ export function escapeForTextSelector(text: string | RegExp, exact: boolean): st
const provider = getBrowserState().provider
const kElementLocator = Symbol.for('$$vitest:locator-resolved')
-export async function serializeElement(elementOrLocator: Element | Locator, options?: SelectorOptions): Promise {
+export async function serializeElement(
+ elementOrLocator: Element | Locator,
+ options?: SelectorOptions,
+): Promise {
if (!elementOrLocator) {
throw new Error('Expected element or locator to be defined.')
}
@@ -279,18 +289,19 @@ export async function serializeElement(elementOrLocator: Element | Locator, opti
const kLocator = Symbol.for('$$vitest:locator')
export function isLocator(element: unknown): element is Locator {
- return (!!element && typeof element === 'object' && kLocator in element)
+ return !!element && typeof element === 'object' && kLocator in element
}
const DEFAULT_WHEEL_DELTA = 100
-export function resolveUserEventWheelOptions(options: UserEventWheelOptions): UserEventWheelDeltaOptions {
+export function resolveUserEventWheelOptions(
+ options: UserEventWheelOptions,
+): UserEventWheelDeltaOptions {
let delta: UserEventWheelDeltaOptions['delta']
if (options.delta) {
delta = options.delta
- }
- else {
+ } else {
switch (options.direction) {
case 'up': {
delta = { y: -DEFAULT_WHEEL_DELTA }
diff --git a/packages/browser/src/client/tester/tester.html b/packages/browser/src/client/tester/tester.html
index 5bd4822cf..4b5f6dd8e 100644
--- a/packages/browser/src/client/tester/tester.html
+++ b/packages/browser/src/client/tester/tester.html
@@ -1,8 +1,8 @@
-
+
-
+
Vitest Browser Tester
diff --git a/packages/browser/src/client/tester/tester.ts b/packages/browser/src/client/tester/tester.ts
index c2790dda3..f24ac7da0 100644
--- a/packages/browser/src/client/tester/tester.ts
+++ b/packages/browser/src/client/tester/tester.ts
@@ -23,9 +23,10 @@ import { browserHashMap, initiateRunner } from './runner'
import { CommandsManager } from './tester-utils'
const debugVar = getConfig().env.VITEST_BROWSER_DEBUG
-const debug = debugVar && debugVar !== 'false'
- ? (...args: unknown[]) => client.rpc.debug?.(...args.map(String))
- : undefined
+const debug =
+ debugVar && debugVar !== 'false'
+ ? (...args: unknown[]) => client.rpc.debug?.(...args.map(String))
+ : undefined
const otelConfig = getConfig().experimental.openTelemetry
const traces = new Traces({
@@ -85,15 +86,14 @@ channel.addEventListener('message', async (e) => {
state.metaEnv.VITEST_WORKER_ID = String(workerId)
if (method === 'collect') {
- await executeTests('collect', files).catch(err => unhandledError(err, 'Collect Error'))
- }
- else {
- await executeTests('run', files).catch(err => unhandledError(err, 'Run Error'))
+ await executeTests('collect', files).catch((err) => unhandledError(err, 'Collect Error'))
+ } else {
+ await executeTests('run', files).catch((err) => unhandledError(err, 'Run Error'))
}
break
}
case 'cleanup': {
- await cleanup().catch(err => unhandledError(err, 'Cleanup Error'))
+ await cleanup().catch((err) => unhandledError(err, 'Cleanup Error'))
rootTesterSpan?.span.end()
await traces.finish()
break
@@ -102,12 +102,9 @@ channel.addEventListener('message', async (e) => {
await traces.waitInit()
const tracesContext = traces.getContextFromCarrier(data.otelCarrier)
traces.recordInitSpan(tracesContext)
- rootTesterSpan = traces.startContextSpan(
- `vitest.browser.tester.run`,
- tracesContext,
- )
+ rootTesterSpan = traces.startContextSpan(`vitest.browser.tester.run`, tracesContext)
traces.bind(rootTesterSpan.context)
- await prepare(data).catch(err => unhandledError(err, 'Prepare Error'))
+ await prepare(data).catch((err) => unhandledError(err, 'Prepare Error'))
break
}
case 'viewport:done':
@@ -120,13 +117,11 @@ channel.addEventListener('message', async (e) => {
unhandledError(error, 'Unknown Event')
}
}
- }
- catch (error: any) {
+ } catch (error: any) {
// errors not handled by the cases above (e.g. tracing setup/teardown) must
// not stop us from responding, otherwise the orchestrator would wait forever
await unhandledError(error, 'Tester Error')
- }
- finally {
+ } finally {
// always let the orchestrator know the event was handled so its
// `sendEventToIframe` promise resolves, even if the work above threw
channel.postMessage({
@@ -172,14 +167,9 @@ async function prepareTestEnvironment(options: PrepareOptions) {
state.rpc = rpc as any
const interceptor = createModuleMockerInterceptor()
- const mocker = new VitestBrowserClientMocker(
- interceptor,
- rpc,
- SpyModule.createMockInstance,
- {
- root: getBrowserState().viteConfig.root,
- },
- )
+ const mocker = new VitestBrowserClientMocker(interceptor, rpc, SpyModule.createMockInstance, {
+ root: getBrowserState().viteConfig.root,
+ })
// @ts-expect-error mocking vitest apis
globalThis.__vitest_mocker__ = mocker
@@ -220,13 +210,13 @@ async function prepareTestEnvironment(options: PrepareOptions) {
}
}
-let preparedData:
- | Awaited>
- | undefined
+let preparedData: Awaited> | undefined
async function executeTests(method: 'run' | 'collect', specifications: FileSpecification[]) {
if (!preparedData) {
- throw new Error(`Data was not properly initialized. This is a bug in Vitest. Please, open a new issue with reproduction.`)
+ throw new Error(
+ `Data was not properly initialized. This is a bug in Vitest. Please, open a new issue with reproduction.`,
+ )
}
debug?.('runner resolved successfully')
@@ -250,13 +240,11 @@ async function executeTests(method: 'run' | 'collect', specifications: FileSpeci
await traces.$(
`vitest.test.runner.${method}.module`,
- { attributes: { 'code.file.path': file.filepath },
- },
+ { attributes: { 'code.file.path': file.filepath } },
async () => {
if (method === 'run') {
await startTests([file], runner)
- }
- else {
+ } else {
await collectTests([file], runner)
}
},
@@ -309,37 +297,41 @@ async function cleanup() {
if (cleanupSymbol in page) {
try {
await (page[cleanupSymbol] as any)()
- }
- catch (error: any) {
+ } catch (error: any) {
await unhandledError(error, 'Cleanup Error')
}
}
// need to cleanup for each tester
// since playwright keyboard API is stateful on page instance level
- await userEvent.cleanup()
- .catch(error => unhandledError(error, 'Cleanup Error'))
+ await userEvent.cleanup().catch((error) => unhandledError(error, 'Cleanup Error'))
- await Promise.all(
- getBrowserState().cleanups.map(fn => fn()),
- ).catch(error => unhandledError(error, 'Cleanup Error'))
+ await Promise.all(getBrowserState().cleanups.map((fn) => fn())).catch((error) =>
+ unhandledError(error, 'Cleanup Error'),
+ )
// if isolation is disabled, Vitest reuses the same iframe and we
// don't need to switch the context back at all
if (contextSwitched) {
- await rpc.wdioSwitchContext('parent')
- .catch(error => unhandledError(error, 'Cleanup Error'))
+ await rpc.wdioSwitchContext('parent').catch((error) => unhandledError(error, 'Cleanup Error'))
}
- await stopCoverageInsideWorker(config.coverage, moduleRunner, { isolate: config.isolate }).catch((error) => {
- return unhandledError(error, 'Coverage Error')
- })
+ await stopCoverageInsideWorker(config.coverage, moduleRunner, { isolate: config.isolate }).catch(
+ (error) => {
+ return unhandledError(error, 'Coverage Error')
+ },
+ )
}
function unhandledError(e: Error, type: string) {
- return client.rpc.onUnhandledError({
- name: e.name,
- message: e.message,
- stack: e.stack,
- }, type).catch(() => {})
+ return client.rpc
+ .onUnhandledError(
+ {
+ name: e.name,
+ message: e.message,
+ stack: e.stack,
+ },
+ type,
+ )
+ .catch(() => {})
}
function isEvent(data: unknown): data is IframeReceivedEvent {
return typeof data === 'object' && !!data && 'event' in data
diff --git a/packages/browser/src/client/tester/trace.ts b/packages/browser/src/client/tester/trace.ts
index e5df35302..89b20908f 100644
--- a/packages/browser/src/client/tester/trace.ts
+++ b/packages/browser/src/client/tester/trace.ts
@@ -149,7 +149,7 @@ function takeSnapshot(serializedLocator?: SerializedLocator): TraceSnapshot {
}
for (const className of PSEUDO_CLASS_NAMES) {
const elements = document.querySelectorAll(className)
- const ids = Array.from(elements, el => mirror.getId(el)).filter(id => id !== -1)
+ const ids = Array.from(elements, (el) => mirror.getId(el)).filter((id) => id !== -1)
result.pseudoClassIds[className] = ids
}
if (serializedLocator) {
@@ -161,19 +161,16 @@ function takeSnapshot(serializedLocator?: SerializedLocator): TraceSnapshot {
)
if (!el) {
result.selectorResolution = 'missing'
- }
- else {
+ } else {
const id = mirror.getId(el)
if (id !== -1) {
result.selectorId = id
result.selectorResolution = 'matched'
- }
- else {
+ } else {
result.selectorResolution = 'missing'
}
}
- }
- catch (error) {
+ } catch (error) {
result.selectorResolution = 'error'
result.selectorError = error instanceof Error ? error.message : String(error)
}
diff --git a/packages/browser/src/client/tsconfig.json b/packages/browser/src/client/tsconfig.json
index 106691087..b1376cdaa 100644
--- a/packages/browser/src/client/tsconfig.json
+++ b/packages/browser/src/client/tsconfig.json
@@ -1,20 +1,10 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
- "lib": [
- "dom",
- "esnext",
- "DOM.Iterable"
- ],
+ "lib": ["dom", "esnext", "DOM.Iterable"],
"types": ["vite/client"],
"noEmit": true
},
- "include": [
- "./**/*.ts",
- "../types.ts",
- "../../matchers.d.ts"
- ],
- "exclude": [
- "./vite.config.ts"
- ]
+ "include": ["./**/*.ts", "../types.ts", "../../matchers.d.ts"],
+ "exclude": ["./vite.config.ts"]
}
diff --git a/packages/browser/src/client/utils.ts b/packages/browser/src/client/utils.ts
index c61647b26..42c5ebb6e 100644
--- a/packages/browser/src/client/utils.ts
+++ b/packages/browser/src/client/utils.ts
@@ -130,7 +130,9 @@ export function getWorkerState(): WorkerGlobalState {
// @ts-expect-error not typed global
const state = window.__vitest_worker__
if (!state) {
- throw new Error('Worker state is not found. This is an issue with Vitest. Please, open an issue.')
+ throw new Error(
+ 'Worker state is not found. This is an issue with Vitest. Please, open an issue.',
+ )
}
return state
}
diff --git a/packages/browser/src/client/vite.config.ts b/packages/browser/src/client/vite.config.ts
index 263e809b3..35d8605b6 100644
--- a/packages/browser/src/client/vite.config.ts
+++ b/packages/browser/src/client/vite.config.ts
@@ -19,22 +19,17 @@ export default vite.defineConfig({
assetsDir: '__vitest_browser__',
manifest: true,
rollupOptions: {
- output: 'rolldownVersion' in vite
- ? {
- minify: false,
- } as any
- : {},
+ output:
+ 'rolldownVersion' in vite
+ ? ({
+ minify: false,
+ } as any)
+ : {},
input: {
orchestrator: resolve(import.meta.dirname, './orchestrator.html'),
tester: resolve(import.meta.dirname, './tester/tester.html'),
},
- external: [
- /^vitest\//,
- 'vitest',
- /^msw/,
- 'vitest/browser',
- '@vitest/browser/client',
- ],
+ external: [/^vitest\//, 'vitest', /^msw/, 'vitest/browser', '@vitest/browser/client'],
},
},
})
diff --git a/packages/browser/src/node/cdp.ts b/packages/browser/src/node/cdp.ts
index 1654b1a44..6ccb95c7b 100644
--- a/packages/browser/src/node/cdp.ts
+++ b/packages/browser/src/node/cdp.ts
@@ -23,10 +23,7 @@ export class BrowserServerCDPHandler {
if (!this.listeners[event]) {
this.listeners[event] = (payload) => {
- this.tester.cdpEvent(
- event,
- payload,
- )
+ this.tester.cdpEvent(event, payload)
if (once) {
this.off(event, id)
}
@@ -40,7 +37,7 @@ export class BrowserServerCDPHandler {
if (!this.listenerIds[event]) {
this.listenerIds[event] = []
}
- this.listenerIds[event] = this.listenerIds[event].filter(l => l !== id)
+ this.listenerIds[event] = this.listenerIds[event].filter((l) => l !== id)
if (!this.listenerIds[event].length) {
this.session.off(event as any, this.listeners[event])
diff --git a/packages/browser/src/node/commands/fs.ts b/packages/browser/src/node/commands/fs.ts
index 4707d22cf..3b6632694 100644
--- a/packages/browser/src/node/commands/fs.ts
+++ b/packages/browser/src/node/commands/fs.ts
@@ -5,9 +5,11 @@ import { basename, dirname, resolve } from 'node:path'
import mime from 'mime/lite'
import { assertBrowserApiWrite, assertBrowserFileAccess } from '../utils'
-export const readFile: BrowserCommand<
- Parameters
-> = async ({ project }, path, options = {}) => {
+export const readFile: BrowserCommand> = async (
+ { project },
+ path,
+ options = {},
+) => {
const filepath = resolve(project.config.root, path)
assertBrowserFileAccess(project, filepath)
// never return a Buffer
@@ -17,9 +19,12 @@ export const readFile: BrowserCommand<
return fsp.readFile(filepath, options)
}
-export const writeFile: BrowserCommand<
- Parameters
-> = async ({ project }, path, data, options) => {
+export const writeFile: BrowserCommand> = async (
+ { project },
+ path,
+ data,
+ options,
+) => {
assertBrowserApiWrite(project, path)
const filepath = resolve(project.config.root, path)
assertBrowserFileAccess(project, filepath)
@@ -30,16 +35,21 @@ export const writeFile: BrowserCommand<
await fsp.writeFile(filepath, data, options)
}
-export const removeFile: BrowserCommand<
- Parameters
-> = async ({ project }, path) => {
+export const removeFile: BrowserCommand> = async (
+ { project },
+ path,
+) => {
assertBrowserApiWrite(project, path)
const filepath = resolve(project.config.root, path)
assertBrowserFileAccess(project, filepath)
await fsp.rm(filepath)
}
-export const _fileInfo: BrowserCommand<[path: string, encoding: BufferEncoding]> = async ({ project }, path, encoding) => {
+export const _fileInfo: BrowserCommand<[path: string, encoding: BufferEncoding]> = async (
+ { project },
+ path,
+ encoding,
+) => {
const filepath = resolve(project.config.root, path)
assertBrowserFileAccess(project, filepath)
const content = await fsp.readFile(filepath, encoding || 'base64')
diff --git a/packages/browser/src/node/commands/index.ts b/packages/browser/src/node/commands/index.ts
index 21bbe8a36..bdc6e74c0 100644
--- a/packages/browser/src/node/commands/index.ts
+++ b/packages/browser/src/node/commands/index.ts
@@ -1,9 +1,4 @@
-import {
- _fileInfo,
- readFile,
- removeFile,
- writeFile,
-} from './fs'
+import { _fileInfo, readFile, removeFile, writeFile } from './fs'
import { screenshot } from './screenshot'
import { screenshotMatcher } from './screenshotMatcher'
import { _groupTraceEnd, _groupTraceStart, _markTrace, _recordBrowserTrace } from './trace'
diff --git a/packages/browser/src/node/commands/screenshot.ts b/packages/browser/src/node/commands/screenshot.ts
index 2cbb11e8d..dd64ab0a0 100644
--- a/packages/browser/src/node/commands/screenshot.ts
+++ b/packages/browser/src/node/commands/screenshot.ts
@@ -12,7 +12,10 @@ declare module 'vitest/browser' {
/**
* @internal
*/
- __vitest_takeScreenshot: (name: string, options: ScreenshotCommandOptions) => Promise<{
+ __vitest_takeScreenshot: (
+ name: string,
+ options: ScreenshotCommandOptions,
+ ) => Promise<{
buffer: Buffer
path: string
}>
@@ -35,11 +38,7 @@ export const screenshot: BrowserCommand<[string, ScreenshotCommandOptions]> = as
return returnResult(options, path, buffer)
}
-function returnResult(
- options: ScreenshotCommandOptions,
- path: string,
- buffer: Buffer,
-) {
+function returnResult(options: ScreenshotCommandOptions, path: string, buffer: Buffer) {
if (!options.save) {
return buffer.toString('base64')
}
diff --git a/packages/browser/src/node/commands/screenshotMatcher/codecs/png.ts b/packages/browser/src/node/commands/screenshotMatcher/codecs/png.ts
index eaed3adde..813fb44d7 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/codecs/png.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/codecs/png.ts
@@ -4,21 +4,8 @@ import { PNG } from 'pngjs'
const codec: Codec = {
decode: (buffer, options) => {
- const {
- data,
- alpha,
- bpp,
- color,
- colorType,
- depth,
- height,
- interlace,
- palette,
- width,
- } = PNG.sync.read(
- Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer),
- options,
- )
+ const { data, alpha, bpp, color, colorType, depth, height, interlace, palette, width } =
+ PNG.sync.read(Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer), options)
return {
metadata: {
diff --git a/packages/browser/src/node/commands/screenshotMatcher/comparators/index.ts b/packages/browser/src/node/commands/screenshotMatcher/comparators/index.ts
index 8ef9307a2..6a0d9dfb4 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/comparators/index.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/comparators/index.ts
@@ -19,13 +19,7 @@ export function getComparator
+export type AnyComparator = Comparator<
+ ScreenshotComparatorRegistry[keyof ScreenshotComparatorRegistry]
+>
diff --git a/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts b/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts
index 5c8b29c4d..3d1cbf5e7 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/comparators/pixelmatch.ts
@@ -19,22 +19,21 @@ export const pixelmatch: Comparator
actual,
{ createDiff, ...options },
) => {
- if (reference.metadata.height !== actual.metadata.height || reference.metadata.width !== actual.metadata.width) {
+ if (
+ reference.metadata.height !== actual.metadata.height ||
+ reference.metadata.width !== actual.metadata.width
+ ) {
return {
pass: false,
diff: null,
message: `Expected image dimensions to be ${reference.metadata.width}×${
reference.metadata.height
- }px, but received ${actual.metadata.width}×${
- actual.metadata.height
- }px.`,
+ }px, but received ${actual.metadata.width}×${actual.metadata.height}px.`,
}
}
const optionsWithDefaults = { ...defaultOptions, ...options }
- const diffBuffer = createDiff
- ? new Uint8Array(reference.data.length)
- : undefined
+ const diffBuffer = createDiff ? new Uint8Array(reference.data.length) : undefined
const mismatchedPixels = diff(
reference.data,
@@ -49,9 +48,7 @@ export const pixelmatch: Comparator
let allowedMismatchedPixels = Math.min(
optionsWithDefaults.allowedMismatchedPixels ?? Number.POSITIVE_INFINITY,
- (optionsWithDefaults.allowedMismatchedPixelRatio
- ?? Number.POSITIVE_INFINITY)
- * imageArea,
+ (optionsWithDefaults.allowedMismatchedPixelRatio ?? Number.POSITIVE_INFINITY) * imageArea,
)
if (allowedMismatchedPixels === Number.POSITIVE_INFINITY) {
@@ -65,10 +62,10 @@ export const pixelmatch: Comparator
diff: diffBuffer ?? null,
message: pass
? null
- : `${mismatchedPixels} pixels (ratio ${(
- // as we compare using `<=`, use `Math.ceil` to ensure the reported ratio
- // doesn't appear equal to the allowed limit when it's a bit over
- Math.ceil((mismatchedPixels / imageArea) * 100) / 100
- ).toFixed(2)}) differ.`,
+ : `${mismatchedPixels} pixels (ratio ${
+ // as we compare using `<=`, use `Math.ceil` to ensure the reported ratio
+ // doesn't appear equal to the allowed limit when it's a bit over
+ (Math.ceil((mismatchedPixels / imageArea) * 100) / 100).toFixed(2)
+ }) differ.`,
}
}
diff --git a/packages/browser/src/node/commands/screenshotMatcher/index.ts b/packages/browser/src/node/commands/screenshotMatcher/index.ts
index d83c8ed7f..7ec0a3773 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/index.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/index.ts
@@ -2,7 +2,10 @@ import type { SerializedLocator } from '@vitest/browser'
import type { SnapshotUpdateState } from 'vitest'
import type { ScreenshotMatcherOptions } from 'vitest/browser'
import type { BrowserCommand, BrowserCommandContext, TestProject } from 'vitest/node'
-import type { ScreenshotMatcherArguments, ScreenshotMatcherOutput } from '../../../shared/screenshotMatcher/types'
+import type {
+ ScreenshotMatcherArguments,
+ ScreenshotMatcherOutput,
+} from '../../../shared/screenshotMatcher/types'
import type { AnyCodec } from './codecs'
import type { AnyComparator } from './comparators'
import type { TypedArray } from './types'
@@ -39,29 +42,29 @@ interface CapturedScreenshot {
* - `matched-after-comparison`: screenshot matched after another comparison
* - `mismatch`: screenshot differs from reference
*/
-type MatchOutcome
- = | {
- type: 'unstable-screenshot'
- reference: ScreenshotData | null
- }
+type MatchOutcome =
| {
- type: 'missing-reference'
- location: 'reference' | 'diffs'
- reference: ScreenshotData
- }
+ type: 'unstable-screenshot'
+ reference: ScreenshotData | null
+ }
| {
- type: 'update-reference'
- reference: ScreenshotData
- }
+ type: 'missing-reference'
+ location: 'reference' | 'diffs'
+ reference: ScreenshotData
+ }
+ | {
+ type: 'update-reference'
+ reference: ScreenshotData
+ }
| { type: 'matched-immediately' }
| { type: 'matched-after-comparison' }
| {
- type: 'mismatch'
- reference: ScreenshotData
- actual: ScreenshotData
- diff: ScreenshotData | null
- message: string | null
- }
+ type: 'mismatch'
+ reference: ScreenshotData
+ actual: ScreenshotData
+ diff: ScreenshotData | null
+ message: string | null
+ }
/**
* Browser command that compares a screenshot against a stored reference.
@@ -111,11 +114,14 @@ export const screenshotMatcher: BrowserCommand = asy
// Keep custom comparator semantics intact: only the built-in pixelmatch
// comparator is known to pass byte-identical PNGs without side effects.
- if (comparatorName === 'pixelmatch' && Buffer.compare(referenceFile, initialScreenshotBuffer) === 0) {
+ if (
+ comparatorName === 'pixelmatch' &&
+ Buffer.compare(referenceFile, initialScreenshotBuffer) === 0
+ ) {
return buildOutput({ type: 'matched-immediately' }, timeout)
}
- [reference, initialScreenshot] = await Promise.all([
+ ;[reference, initialScreenshot] = await Promise.all([
codec.decode(referenceFile, {}),
takeScreenshotData({
...screenshotCaptureOptions,
@@ -125,18 +131,21 @@ export const screenshotMatcher: BrowserCommand = asy
])
}
- const screenshotResult = await waitForStableScreenshot({
- codec,
- comparator,
- comparatorOptions,
- context,
- element,
- initialScreenshot,
- name: screenshotName,
- reference,
- screenshotOptions,
- target,
- }, timeout)
+ const screenshotResult = await waitForStableScreenshot(
+ {
+ codec,
+ comparator,
+ comparatorOptions,
+ context,
+ element,
+ initialScreenshot,
+ name: screenshotName,
+ reference,
+ screenshotOptions,
+ target,
+ },
+ timeout,
+ )
const outcome = await determineOutcome({
reference,
@@ -161,25 +170,23 @@ export const screenshotMatcher: BrowserCommand = asy
*
* The outcome carries all data needed by {@linkcode performSideEffects} and {@linkcode buildOutput}.
*/
-async function determineOutcome(
- {
- comparator,
- comparatorOptions,
- paths,
- reference,
- retries,
- screenshot,
- screenshotBuffer,
- updateSnapshot,
- }: Pick & {
- comparatorOptions: ResolvedOptions['resolvedOptions']['comparatorOptions']
- reference: DecodedImage | null
- retries: number
- screenshot: DecodedImage | null
- screenshotBuffer?: Buffer
- updateSnapshot: SnapshotUpdateState
- },
-): Promise {
+async function determineOutcome({
+ comparator,
+ comparatorOptions,
+ paths,
+ reference,
+ retries,
+ screenshot,
+ screenshotBuffer,
+ updateSnapshot,
+}: Pick & {
+ comparatorOptions: ResolvedOptions['resolvedOptions']['comparatorOptions']
+ reference: DecodedImage | null
+ retries: number
+ screenshot: DecodedImage | null
+ screenshotBuffer?: Buffer
+ updateSnapshot: SnapshotUpdateState
+}): Promise {
if (screenshot === null) {
return {
type: 'unstable-screenshot',
@@ -203,18 +210,14 @@ async function determineOutcome(
}
}
- const location = updateSnapshot === 'none'
- ? 'diffs'
- : 'reference'
+ const location = updateSnapshot === 'none' ? 'diffs' : 'reference'
return {
type: 'missing-reference',
location,
reference: {
image: screenshot,
- path: location === 'reference'
- ? paths.reference
- : paths.diffs.reference,
+ path: location === 'reference' ? paths.reference : paths.diffs.reference,
buffer: screenshotBuffer,
},
}
@@ -225,11 +228,10 @@ async function determineOutcome(
return { type: 'matched-immediately' }
}
- const comparisonResult = await comparator(
- reference,
- screenshot,
- { createDiff: true, ...comparatorOptions },
- )
+ const comparisonResult = await comparator(reference, screenshot, {
+ createDiff: true,
+ ...comparatorOptions,
+ })
if (comparisonResult.pass) {
return { type: 'matched-after-comparison' }
@@ -320,10 +322,7 @@ function encodeScreenshot(screenshot: ScreenshotData, codec: AnyCodec) {
*
* Maps each outcome to a pass/fail result with metadata and error messages.
*/
-function buildOutput(
- outcome: MatchOutcome,
- timeout: number,
-): Awaited {
+function buildOutput(outcome: MatchOutcome, timeout: number): Awaited {
switch (outcome.type) {
case 'unstable-screenshot':
return {
@@ -350,9 +349,10 @@ function buildOutput(
},
actual: null,
diff: null,
- message: outcome.location === 'reference'
- ? 'No existing reference screenshot found; a new one was created. Review it before running tests again.'
- : 'No existing reference screenshot found.',
+ message:
+ outcome.location === 'reference'
+ ? 'No existing reference screenshot found; a new one was created. Review it before running tests again.'
+ : 'No existing reference screenshot found.',
}
}
@@ -420,23 +420,20 @@ interface StableScreenshotOptions {
*
* Wraps {@linkcode getStableScreenshot} with an abort controller that triggers when the timeout expires. Returns `null` if the page never stabilizes.
*/
-async function waitForStableScreenshot(options: StableScreenshotOptions, timeout: number,
+async function waitForStableScreenshot(
+ options: StableScreenshotOptions,
+ timeout: number,
): Promise<{ actual: DecodedImage; buffer: Buffer; retries: number } | null> {
const abortController = new AbortController()
- const stableScreenshot = getStableScreenshot(
- options,
- abortController.signal,
- )
+ const stableScreenshot = getStableScreenshot(options, abortController.signal)
- const result = await (
- timeout === 0
- ? stableScreenshot
- : Promise.race([
- stableScreenshot,
- asyncTimeout(timeout).finally(() => abortController.abort()),
- ])
- )
+ const result = await (timeout === 0
+ ? stableScreenshot
+ : Promise.race([
+ stableScreenshot,
+ asyncTimeout(timeout).finally(() => abortController.abort()),
+ ]))
return result
}
@@ -456,18 +453,21 @@ async function waitForStableScreenshot(options: StableScreenshotOptions, timeout
*
* @returns `Promise` resolving to an object containing the retry count and final screenshot
*/
-async function getStableScreenshot({
- codec,
- context,
- comparator,
- comparatorOptions,
- element,
- initialScreenshot,
- name,
- reference,
- screenshotOptions,
- target,
-}: StableScreenshotOptions, signal: AbortSignal): Promise<{
+async function getStableScreenshot(
+ {
+ codec,
+ context,
+ comparator,
+ comparatorOptions,
+ element,
+ initialScreenshot,
+ name,
+ reference,
+ screenshotOptions,
+ target,
+ }: StableScreenshotOptions,
+ signal: AbortSignal,
+): Promise<{
retries: number
actual: DecodedImage
buffer: Buffer
@@ -499,11 +499,8 @@ async function getStableScreenshot({
const { image: image2 } = capturedScreenshot
lastCapturedScreenshot = capturedScreenshot
- const isStable = (await comparator(
- image1,
- image2,
- { ...comparatorOptions, createDiff: false },
- )).pass
+ const isStable = (await comparator(image1, image2, { ...comparatorOptions, createDiff: false }))
+ .pass
decodedBaseline = image2
nextScreenshot = null
@@ -545,13 +542,15 @@ async function takeScreenshotData({
screenshotOptions: ScreenshotMatcherArguments[2]['screenshotOptions']
target?: ScreenshotMatcherArguments[2]['target']
}): Promise {
- const screenshot = buffer ?? await takeScreenshotBuffer({
- context,
- element,
- name,
- screenshotOptions,
- target,
- })
+ const screenshot =
+ buffer ??
+ (await takeScreenshotBuffer({
+ context,
+ element,
+ name,
+ screenshotOptions,
+ target,
+ }))
return {
buffer: screenshot,
@@ -566,8 +565,7 @@ async function writeScreenshot(path: string, image: TypedArray, project: TestPro
assertBrowserFileAccess(project, path)
await mkdir(dirname(path), { recursive: true })
await writeFile(path, image)
- }
- catch (cause) {
- throw new Error('Couldn\'t write file to fs', { cause })
+ } catch (cause) {
+ throw new Error("Couldn't write file to fs", { cause })
}
}
diff --git a/packages/browser/src/node/commands/screenshotMatcher/types.ts b/packages/browser/src/node/commands/screenshotMatcher/types.ts
index d011ccf82..9da5b2b26 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/types.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/types.ts
@@ -4,11 +4,14 @@ import type {
ScreenshotMatcherOptions,
} from '@vitest/browser/context'
-interface BaseMetadata { height: number; width: number }
-export type TypedArray
- = | Buffer
- | Uint8Array
- | Uint8ClampedArray
+interface BaseMetadata {
+ height: number
+ width: number
+}
+export type TypedArray =
+ | Buffer
+ | Uint8Array
+ | Uint8ClampedArray
type Promisable = T | Promise
export interface Codec<
@@ -52,18 +55,15 @@ type CustomComparatorsToRegister = {
[Key in keyof NonStandardScreenshotComparators]: Comparator
}
-export type CustomComparatorsRegistry
- = keyof CustomComparatorsToRegister extends never
- ? { comparators?: Record>> }
- : { comparators: CustomComparatorsToRegister }
+export type CustomComparatorsRegistry = keyof CustomComparatorsToRegister extends never
+ ? { comparators?: Record>> }
+ : { comparators: CustomComparatorsToRegister }
declare module 'vitest/node' {
export interface ToMatchScreenshotOptions
- extends Omit<
- ScreenshotMatcherOptions,
- 'comparatorName' | 'comparatorOptions'
- >, CustomComparatorsRegistry {}
+ extends
+ Omit,
+ CustomComparatorsRegistry {}
- export interface ToMatchScreenshotComparators
- extends ScreenshotComparatorRegistry {}
+ export interface ToMatchScreenshotComparators extends ScreenshotComparatorRegistry {}
}
diff --git a/packages/browser/src/node/commands/screenshotMatcher/utils.ts b/packages/browser/src/node/commands/screenshotMatcher/utils.ts
index 2b60575c0..6134d5019 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/utils.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/utils.ts
@@ -1,5 +1,4 @@
import type { SerializedLocator } from '@vitest/browser'
-
// Note: this augments `screenshotOptions` types
import type {} from '@vitest/browser-playwright'
import type { BrowserCommandContext, BrowserConfigOptions } from 'vitest/node'
@@ -12,13 +11,15 @@ import { basename, dirname, extname, join, relative, resolve } from 'pathe'
import { getCodec } from './codecs'
import { getComparator } from './comparators'
-type GlobalOptions = Required['toMatchScreenshot']
- & NonNullable>
- >,
- 'comparators' | 'screenshotDirectory'
->>
+type GlobalOptions = Required<
+ Omit<
+ NonNullable<
+ NonNullable['toMatchScreenshot'] &
+ NonNullable>
+ >,
+ 'comparators' | 'screenshotDirectory'
+ >
+>
const defaultOptions = {
comparatorName: 'pixelmatch',
@@ -43,13 +44,14 @@ const defaultOptions = {
platform,
testFileDirectory,
testFileName,
- }) => resolve(
- root,
- attachmentsDir,
- testFileDirectory,
- testFileName,
- `${arg}-${browserName}-${platform}${ext}`,
- ),
+ }) =>
+ resolve(
+ root,
+ attachmentsDir,
+ testFileDirectory,
+ testFileName,
+ `${arg}-${browserName}-${platform}${ext}`,
+ ),
resolveScreenshotPath: ({
arg,
ext,
@@ -58,13 +60,14 @@ const defaultOptions = {
testFileDirectory,
testFileName,
browserName,
- }) => resolve(
- root,
- testFileDirectory,
- screenshotDirectory,
- testFileName,
- `${arg}-${browserName}-${platform}${ext}`,
- ),
+ }) =>
+ resolve(
+ root,
+ testFileDirectory,
+ screenshotDirectory,
+ testFileName,
+ `${arg}-${browserName}-${platform}${ext}`,
+ ),
} satisfies GlobalOptions
type SupportedCodecs = Parameters[0]
@@ -85,19 +88,17 @@ export interface ResolvedOptions {
}
}
-export function resolveOptions(
- {
- context,
- name,
- options,
- testName,
- }: {
- context: BrowserCommandContext
- name: string
- testName: string
- options: ScreenshotMatcherOptions
- },
-): ResolvedOptions {
+export function resolveOptions({
+ context,
+ name,
+ options,
+ testName,
+}: {
+ context: BrowserCommandContext
+ name: string
+ testName: string
+ options: ScreenshotMatcherOptions
+}): ResolvedOptions {
if (context.testPath === undefined) {
throw new Error('`resolveOptions` has to be used in a test file')
}
@@ -128,14 +129,14 @@ export function resolveOptions(
const resolvePathData = {
arg: sanitizeArg(
// remove the extension only if it ends up being used
- extensionFromName.endsWith(extension)
- ? basename(name, extensionFromName)
- : name,
+ extensionFromName.endsWith(extension) ? basename(name, extensionFromName) : name,
),
ext: `.${extension}`,
platform: platform(),
root,
- screenshotDirectory: context.project.config.browser.expect?.toMatchScreenshot?.screenshotDirectory ?? '__screenshots__',
+ screenshotDirectory:
+ context.project.config.browser.expect?.toMatchScreenshot?.screenshotDirectory ??
+ '__screenshots__',
attachmentsDir: relative(root, context.project.config.attachmentsDir),
testFileDirectory: relative(root, dirname(context.testPath)),
testFileName: basename(context.testPath),
@@ -202,7 +203,10 @@ function sanitize(input: string, keepPaths: boolean): string {
.replace(/-{2,}/g, '-')
}
- return input.split('/').map(path => sanitize(path, false)).join('/')
+ return input
+ .split('/')
+ .map((path) => sanitize(path, false))
+ .join('/')
}
/**
@@ -243,13 +247,14 @@ export function takeScreenshotBuffer({
screenshotOptions: ScreenshotMatcherArguments[2]['screenshotOptions']
target?: ScreenshotMatcherArguments[2]['target']
}): Promise> {
- return context.triggerCommand(
- '__vitest_takeScreenshot',
- name,
- { ...screenshotOptions, save: false, element, target },
- ).then(
- ({ buffer }) => buffer,
- )
+ return context
+ .triggerCommand('__vitest_takeScreenshot', name, {
+ ...screenshotOptions,
+ save: false,
+ element,
+ target,
+ })
+ .then(({ buffer }) => buffer)
}
export function takeDecodedScreenshot({
@@ -263,9 +268,7 @@ export function takeDecodedScreenshot({
screenshotOptions: ScreenshotMatcherArguments[2]['screenshotOptions']
target?: ScreenshotMatcherArguments[2]['target']
}): ReturnType {
- return takeScreenshotBuffer(options).then(
- buffer => codec.decode(buffer, {}),
- )
+ return takeScreenshotBuffer(options).then((buffer) => codec.decode(buffer, {}))
}
/**
@@ -279,8 +282,7 @@ export function asyncTimeout(timeout: number): Promise {
return new Promise((resolve) => {
if (timeout === 0) {
resolve(null)
- }
- else {
+ } else {
setTimeout(resolve, timeout, null)
}
})
diff --git a/packages/browser/src/node/commands/trace.ts b/packages/browser/src/node/commands/trace.ts
index bf7b5fca5..63589a69b 100644
--- a/packages/browser/src/node/commands/trace.ts
+++ b/packages/browser/src/node/commands/trace.ts
@@ -30,14 +30,14 @@ declare module 'vitest/browser' {
/**
* @internal
*/
- __vitest_recordBrowserTrace: (payload: { testId: string; data: BrowserTraceData }) => Promise
+ __vitest_recordBrowserTrace: (payload: {
+ testId: string
+ data: BrowserTraceData
+ }) => Promise
}
}
-export const _markTrace: BrowserCommand<[payload: MarkTracePayload]> = async (
- context,
- payload,
-) => {
+export const _markTrace: BrowserCommand<[payload: MarkTracePayload]> = async (context, payload) => {
if (context.provider.name === 'playwright') {
await context.triggerCommand('__vitest_markTrace', payload)
}
@@ -52,18 +52,15 @@ export const _groupTraceStart: BrowserCommand<[payload: GroupTracePayload]> = as
}
}
-export const _groupTraceEnd: BrowserCommand<[]> = async (
- context,
-) => {
+export const _groupTraceEnd: BrowserCommand<[]> = async (context) => {
if (context.provider.name === 'playwright') {
await context.triggerCommand('__vitest_groupTraceEnd')
}
}
-export const _recordBrowserTrace: BrowserCommand<[payload: { testId: string; data: BrowserTraceData }]> = async (
- { project },
- { testId, data },
-) => {
+export const _recordBrowserTrace: BrowserCommand<
+ [payload: { testId: string; data: BrowserTraceData }]
+> = async ({ project }, { testId, data }) => {
// resolve stack strings → source locations server-side (requires source maps)
const entries: BrowserTraceEntry[] = data.entries.map((entry) => {
if (!entry.stack || entry.location) {
diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts
index a709d1969..692e7f194 100644
--- a/packages/browser/src/node/index.ts
+++ b/packages/browser/src/node/index.ts
@@ -1,6 +1,13 @@
import type { ServerResponse } from 'node:http'
import type { HtmlTagDescriptor, UserConfig, UserConfig as ViteUserConfig } from 'vite'
-import type { BrowserCommand, BrowserProviderOption, BrowserServerContribution, BrowserServerFactory, PluginHarness, ResolvedConfig } from 'vitest/node'
+import type {
+ BrowserCommand,
+ BrowserProviderOption,
+ BrowserServerContribution,
+ BrowserServerFactory,
+ PluginHarness,
+ ResolvedConfig,
+} from 'vitest/node'
import { createReadStream, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { MockerRegistry } from '@vitest/mocker'
@@ -10,7 +17,13 @@ import { cleanUrl, toArray } from '@vitest/utils/helpers'
import { join, resolve } from 'pathe'
import sirv from 'sirv'
import c from 'tinyrainbow'
-import { isCSSRequest, isFileServingAllowed, isValidApiRequest, rolldownVersion, distDir as vitestDist } from 'vitest/node'
+import {
+ isCSSRequest,
+ isFileServingAllowed,
+ isValidApiRequest,
+ rolldownVersion,
+ distDir as vitestDist,
+} from 'vitest/node'
import { version } from '../../package.json'
import { distRoot } from './constants'
import { createOrchestratorMiddleware } from './middlewares/orchestratorMiddleware'
@@ -33,7 +46,12 @@ export function defineBrowserCommand(
}
// export type { ProjectBrowser } from './project'
-export { assertBrowserApiWrite, assertBrowserFileAccess, parseKeyDef, resolveScreenshotPath } from './utils'
+export {
+ assertBrowserApiWrite,
+ assertBrowserFileAccess,
+ parseKeyDef,
+ resolveScreenshotPath,
+} from './utils'
const versionRegexp = /(?:\?|&)v=\w{8}/
@@ -68,17 +86,18 @@ export const createBrowserServer: BrowserServerFactory = async () => {
return
}
- const stateJs = typeof parentServer.stateJs === 'string'
- ? parentServer.stateJs
- : await parentServer.stateJs
+ const stateJs =
+ typeof parentServer.stateJs === 'string' ? parentServer.stateJs : await parentServer.stateJs
const testerTags: HtmlTagDescriptor[] = []
- const isDefaultTemplate = resolve(distRoot, 'client/tester/tester.html') === projectBrowser.testerFilepath
+ const isDefaultTemplate =
+ resolve(distRoot, 'client/tester/tester.html') === projectBrowser.testerFilepath
if (!isDefaultTemplate) {
- const manifestContent = parentServer.manifest instanceof Promise
- ? await parentServer.manifest
- : parentServer.manifest
+ const manifestContent =
+ parentServer.manifest instanceof Promise
+ ? await parentServer.manifest
+ : parentServer.manifest
const testerEntry = manifestContent['tester/tester.html']
testerTags.push({
@@ -94,21 +113,18 @@ export const createBrowserServer: BrowserServerFactory = async () => {
for (const importName of testerEntry.imports || []) {
const entryManifest = manifestContent[importName]
if (entryManifest) {
- testerTags.push(
- {
- tag: 'link',
- attrs: {
- href: `${parentServer.base}${entryManifest.file}`,
- rel: 'modulepreload',
- crossorigin: '',
- },
- injectTo: 'head',
+ testerTags.push({
+ tag: 'link',
+ attrs: {
+ href: `${parentServer.base}${entryManifest.file}`,
+ rel: 'modulepreload',
+ crossorigin: '',
},
- )
+ injectTo: 'head',
+ })
}
}
- }
- else {
+ } else {
// inject the reset style only in the default template,
// allowing users to customize the style in their own template
testerTags.push({
@@ -154,22 +170,25 @@ body {
},
injectTo: 'head' as const,
},
- ...parentServer.initScripts.map(script => ({
- tag: 'script',
- attrs: {
- type: 'module',
- src: join('/@fs/', script),
- },
- injectTo: 'head',
- } as const)),
+ ...parentServer.initScripts.map(
+ (script) =>
+ ({
+ tag: 'script',
+ attrs: {
+ type: 'module',
+ src: join('/@fs/', script),
+ },
+ injectTo: 'head',
+ }) as const,
+ ),
...testerTags,
- ].filter(s => s != null)
+ ].filter((s) => s != null)
},
configureServer(server) {
const parentServer = contribution.parent as ParentBrowserProject
parentServer.setServer(server)
- // eslint-disable-next-line prefer-arrow-callback
+ // oxlint-disable-next-line prefer-arrow-callback
server.middlewares.use(function vitestHeaders(_req, res, next) {
const headers = server.config.server.headers
if (headers) {
@@ -183,8 +202,8 @@ body {
// the plugin pipeline sees the original import id (e.g. virtual modules' load hook).
server.middlewares.use((req, _res, next) => {
if (
- req.url?.includes('_vitest_original')
- && parentServer.config.browser.provider?.name === 'playwright'
+ req.url?.includes('_vitest_original') &&
+ parentServer.config.browser.provider?.name === 'playwright'
) {
req.url = req.url
.replace(/[?&]_vitest_original(?=[]|$)/, '')
@@ -196,14 +215,11 @@ body {
server.middlewares.use(createOrchestratorMiddleware(parentServer))
server.middlewares.use(createTesterMiddleware(parentServer))
- server.middlewares.use(
- `/favicon.svg`,
- (_, res) => {
- const content = readFileSync(resolve(distRoot, 'client/favicon.svg'))
- res.write(content, 'utf-8')
- res.end()
- },
- )
+ server.middlewares.use(`/favicon.svg`, (_, res) => {
+ const content = readFileSync(resolve(distRoot, 'client/favicon.svg'))
+ res.write(content, 'utf-8')
+ res.end()
+ })
// Serve coverage HTML at ./coverage if configured
const coverageHtmlDir = parentServer.vitest.config.coverage?.htmlDir
@@ -223,10 +239,7 @@ body {
csp.replace(/frame-ancestors [^;]+/, 'frame-ancestors *'),
)
}
- res.setHeader(
- 'Cache-Control',
- 'public,max-age=0,must-revalidate',
- )
+ res.setHeader('Cache-Control', 'public,max-age=0,must-revalidate')
},
}),
)
@@ -236,15 +249,18 @@ body {
// browser contexts don't outlive the process, so there is no reason
// to revalidate them in every tester iframe. Skipped for persistent
// contexts and preview providers — their disk cache would survive a vitest upgrade
- const persistentContext = (parentServer.config.browser.provider?.options as { persistentContext?: unknown } | undefined)?.persistentContext
- const immutablePrefixes = persistentContext || parentServer.config.browser.provider?.name === 'preview'
- ? []
- : ['/__vitest_browser__/', `/@fs${vitestDist}`, `/@fs${distRoot}`]
+ const persistentContext = (
+ parentServer.config.browser.provider?.options as { persistentContext?: unknown } | undefined
+ )?.persistentContext
+ const immutablePrefixes =
+ persistentContext || parentServer.config.browser.provider?.name === 'preview'
+ ? []
+ : ['/__vitest_browser__/', `/@fs${vitestDist}`, `/@fs${distRoot}`]
server.middlewares.use((req, res, next) => {
const url = req.url
if (url) {
- if (immutablePrefixes.some(prefix => url.startsWith(prefix))) {
+ if (immutablePrefixes.some((prefix) => url.startsWith(prefix))) {
pinCacheControl(res, 'public,max-age=31536000,immutable')
}
// 9000 mega head move
@@ -284,16 +300,12 @@ body {
}
try {
- res.setHeader(
- 'content-type',
- contentType,
- )
+ res.setHeader('content-type', contentType)
return createReadStream(fsPath)
.pipe(res)
.on('close', () => res.end())
- }
- catch (err) {
+ } catch (err) {
return next(err)
}
})
@@ -302,10 +314,7 @@ body {
// `/__vitest__` (including the token-injected index.html and its assets),
// so registering sirv here would shadow it and serve the page without a token.
if (!parentServer.vitest.config.ui) {
- server.middlewares.use(
- '/__vitest__',
- sirv(uiClientRoot),
- )
+ server.middlewares.use('/__vitest__', sirv(uiClientRoot))
}
},
// Resolution-time config: only what is derivable from the (partial) user
@@ -351,9 +360,9 @@ body {
if (vitest.version !== version) {
vitest.logger.warn(
c.yellow(
- `Loaded ${c.inverse(c.yellow(` vitest@${vitest.version} `))} and ${c.inverse(c.yellow(` @vitest/browser@${version} `))}.`
- + '\nRunning mixed versions is not supported and may lead into bugs'
- + '\nUpdate your dependencies and make sure the versions match.',
+ `Loaded ${c.inverse(c.yellow(` vitest@${vitest.version} `))} and ${c.inverse(c.yellow(` @vitest/browser@${version} `))}.` +
+ '\nRunning mixed versions is not supported and may lead into bugs' +
+ '\nUpdate your dependencies and make sure the versions match.',
),
)
}
@@ -380,9 +389,9 @@ body {
// Sourcemaps of user files and (by default) their dependencies are
// kept — they point error stacks and devtools at original sources.
if (
- !parentServer
- || !isHeadlessServer(parentServer)
- || parentServer.vitest.config.inspector.enabled
+ !parentServer ||
+ !isHeadlessServer(parentServer) ||
+ parentServer.vitest.config.inspector.enabled
) {
return null
}
@@ -399,9 +408,9 @@ body {
// `preserveSymlinks` where workspace code keeps its node_modules
// path and would be wrongly treated as a dependency
if (
- parentServer.config.browser.dependencySourcemaps === false
- && path.includes('/node_modules/')
- && (parentServer.vite.config.server.sourcemapIgnoreList(path, path) ?? true)
+ parentServer.config.browser.dependencySourcemaps === false &&
+ path.includes('/node_modules/') &&
+ (parentServer.vite.config.server.sourcemapIgnoreList(path, path) ?? true)
) {
return { code, map: { mappings: '' } as any }
}
@@ -422,7 +431,7 @@ function isHeadlessServer(parentServer: ParentBrowserProject): boolean {
// override `headless` — check the static instance options instead of the
// lazily populated `children` to stay deterministic across runs
const instances = parentServer.config.browser.instances ?? []
- return instances.every(instance => instance.headless !== false)
+ return instances.every((instance) => instance.headless !== false)
}
function resolveBrowserOptimizeDeps(
@@ -437,9 +446,7 @@ function resolveBrowserOptimizeDeps(
const testConfig = projectConfigs[0]
const root = testConfig.root || process.cwd()
- const setupFiles = new Set(
- projectConfigs.flatMap(config => toArray(config.setupFiles || [])),
- )
+ const setupFiles = new Set(projectConfigs.flatMap((config) => toArray(config.setupFiles || [])))
const entries: string[] = [
...testFiles,
@@ -483,15 +490,13 @@ function resolveBrowserOptimizeDeps(
entries.push(path)
exclude.push('@vitest/coverage-v8/browser')
}
- }
- else if (provider === 'istanbul') {
+ } else if (provider === 'istanbul') {
const path = tryResolve('@vitest/coverage-istanbul', [root])
if (path) {
entries.push(path)
exclude.push('@vitest/coverage-istanbul')
}
- }
- else if (provider === 'custom' && testConfig.coverage.customProviderModule) {
+ } else if (provider === 'custom' && testConfig.coverage.customProviderModule) {
entries.push(testConfig.coverage.customProviderModule)
}
}
@@ -545,8 +550,7 @@ function resolveBrowserOptimizeDeps(
if (otelConfig?.enabled && otelConfig.browserSdkPath) {
entries.push(otelConfig.browserSdkPath)
include.push('@opentelemetry/api')
- }
- else {
+ } else {
exclude.push('@opentelemetry/api')
}
@@ -584,8 +588,7 @@ function resolveBrowserOptimizeDeps(
function tryResolve(path: string, paths: string[]) {
try {
return getRequire().resolve(path, { paths })
- }
- catch {
+ } catch {
return undefined
}
}
@@ -598,10 +601,9 @@ function getRequire(): ReturnType {
return _require
}
-export function defineBrowserProvider(options: Omit<
- BrowserProviderOption,
- 'serverFactory' | 'options'
-> & { options?: T }): BrowserProviderOption {
+export function defineBrowserProvider(
+ options: Omit, 'serverFactory' | 'options'> & { options?: T },
+): BrowserProviderOption {
return {
...options,
options: options.options || {},
diff --git a/packages/browser/src/node/middlewares/orchestratorMiddleware.ts b/packages/browser/src/node/middlewares/orchestratorMiddleware.ts
index e98c5403a..66ddf621e 100644
--- a/packages/browser/src/node/middlewares/orchestratorMiddleware.ts
+++ b/packages/browser/src/node/middlewares/orchestratorMiddleware.ts
@@ -3,7 +3,9 @@ import type { ParentBrowserProject } from '../projectParent'
import { resolveOrchestrator } from '../serverOrchestrator'
import { allowIframes, disableCache } from './utils'
-export function createOrchestratorMiddleware(parentServer: ParentBrowserProject): Connect.NextHandleFunction {
+export function createOrchestratorMiddleware(
+ parentServer: ParentBrowserProject,
+): Connect.NextHandleFunction {
return async function vitestOrchestratorMiddleware(req, res, next) {
if (!req.url) {
return next()
diff --git a/packages/browser/src/node/middlewares/testerMiddleware.ts b/packages/browser/src/node/middlewares/testerMiddleware.ts
index 386585bf3..3d35a66f1 100644
--- a/packages/browser/src/node/middlewares/testerMiddleware.ts
+++ b/packages/browser/src/node/middlewares/testerMiddleware.ts
@@ -3,7 +3,9 @@ import type { ParentBrowserProject } from '../projectParent'
import { resolveTester } from '../serverTester'
import { allowIframes, disableCache } from './utils'
-export function createTesterMiddleware(browserServer: ParentBrowserProject): Connect.NextHandleFunction {
+export function createTesterMiddleware(
+ browserServer: ParentBrowserProject,
+): Connect.NextHandleFunction {
return async function vitestTesterMiddleware(req, res, next) {
if (!req.url) {
return next()
diff --git a/packages/browser/src/node/middlewares/utils.ts b/packages/browser/src/node/middlewares/utils.ts
index a9f6477d9..b25991d55 100644
--- a/packages/browser/src/node/middlewares/utils.ts
+++ b/packages/browser/src/node/middlewares/utils.ts
@@ -1,10 +1,7 @@
import type { ServerResponse } from 'node:http'
export function disableCache(res: ServerResponse): void {
- res.setHeader(
- 'Cache-Control',
- 'no-cache, max-age=0, must-revalidate',
- )
+ res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate')
res.setHeader('Content-Type', 'text/html; charset=utf-8')
}
diff --git a/packages/browser/src/node/plugins/pluginContext.ts b/packages/browser/src/node/plugins/pluginContext.ts
index 5f4ef2634..5e2203def 100644
--- a/packages/browser/src/node/plugins/pluginContext.ts
+++ b/packages/browser/src/node/plugins/pluginContext.ts
@@ -32,10 +32,7 @@ export default function BrowserContext(contribution: BrowserServerContribution):
}
}
-async function generateContextFile(
- this: Rollup.PluginContext,
- globalServer: ParentBrowserProject,
-) {
+async function generateContextFile(this: Rollup.PluginContext, globalServer: ParentBrowserProject) {
const commands = Object.keys(globalServer.commands)
// The provider instance is initialized lazily when a page opens, so reading
// `child.provider` here races and can be `undefined` before the first page is
@@ -53,16 +50,13 @@ async function generateContextFile(
}
const commandsCode = commands
- .filter(command => !command.startsWith('__vitest'))
+ .filter((command) => !command.startsWith('__vitest'))
.map((command) => {
return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`
})
.join('\n')
- const userEventNonProviderImport = await getUserEventImport(
- provider,
- this.resolve.bind(this),
- )
+ const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this))
const distContextPath = slash(`/@fs/${resolve(__dirname, 'context.js')}`)
return `
@@ -85,7 +79,10 @@ export { page, cdp, locators, utils }
`
}
-async function getUserEventImport(provider: BrowserProvider | undefined, resolve: (id: string, importer: string) => Promise) {
+async function getUserEventImport(
+ provider: BrowserProvider | undefined,
+ resolve: (id: string, importer: string) => Promise,
+) {
if (!provider || provider.name !== 'preview') {
return 'const _userEventSetup = undefined'
}
diff --git a/packages/browser/src/node/project.ts b/packages/browser/src/node/project.ts
index 9bd58196d..2b71edada 100644
--- a/packages/browser/src/node/project.ts
+++ b/packages/browser/src/node/project.ts
@@ -52,20 +52,14 @@ export class ProjectBrowser implements IProjectBrowser {
throw new Error(`Tester HTML file "${testerHtmlPath}" doesn't exist.`)
}
this.testerFilepath = testerHtmlPath
- this.testerHtml = readFile(
- this.testerFilepath,
- 'utf8',
- ).then(html => (this.testerHtml = html))
+ this.testerHtml = readFile(this.testerFilepath, 'utf8').then((html) => (this.testerHtml = html))
}
private commands = {} as Record