* Hidden Element
@@ -49,13 +49,13 @@ export interface TestingLibraryMatchers
{
*
* // Check if any part of element is in viewport
* await expect.element(page.getByTestId('visible-element')).toBeInViewport()
- *
+ *
* // Check if element is outside viewport
* await expect.element(page.getByTestId('hidden-element')).not.toBeInViewport()
- *
+ *
* // Check if at least 50% of element is visible
* await expect.element(page.getByTestId('large-element')).toBeInViewport({ ratio: 0.5 })
- *
+ *
* // Check if element is completely visible
* await expect.element(page.getByTestId('visible-element')).toBeInViewport({ ratio: 1 })
* @see https://vitest.dev/guide/browser/assertion-api#tobeinviewport
diff --git a/packages/browser/matchers.d.ts b/packages/browser/matchers.d.ts
index 0d05313b9..75d2d512e 100644
--- a/packages/browser/matchers.d.ts
+++ b/packages/browser/matchers.d.ts
@@ -1,4 +1,4 @@
-import type { Locator } from '@vitest/browser/context'
+import type { Locator } from './context.js'
import type { TestingLibraryMatchers } from './jest-dom.js'
import type { Assertion, ExpectPollOptions } from 'vitest'
diff --git a/packages/browser/package.json b/packages/browser/package.json
index 57c03ddcf..3fe10c6a5 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -20,10 +20,6 @@
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
- "./providers/*": {
- "types": "./dist/providers/*.d.ts",
- "default": "./dist/providers/*.js"
- },
"./context": {
"types": "./context.d.ts",
"default": "./context.js"
@@ -35,15 +31,15 @@
"types": "./matchers.d.ts",
"default": "./dummy.js"
},
- "./locator": {
- "types": "./dist/locators/index.d.ts",
- "default": "./dist/locators/index.js"
+ "./locators": {
+ "types": "./dist/locators.d.ts",
+ "default": "./dist/locators.js"
},
"./utils": {
"types": "./utils.d.ts",
"default": "./dist/utils.js"
},
- "./*": "./*"
+ "./package.json": "./package.json"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -66,24 +62,9 @@
"dev": "premove dist && pnpm run --stream '/^dev:/'"
},
"peerDependencies": {
- "playwright": "*",
- "vitest": "workspace:*",
- "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "playwright": {
- "optional": true
- },
- "safaridriver": {
- "optional": true
- },
- "webdriverio": {
- "optional": true
- }
+ "vitest": "workspace:*"
},
"dependencies": {
- "@testing-library/dom": "^10.4.1",
- "@testing-library/user-event": "^14.6.1",
"@vitest/mocker": "workspace:*",
"@vitest/utils": "workspace:*",
"magic-string": "catalog:",
@@ -94,18 +75,15 @@
"ws": "catalog:"
},
"devDependencies": {
+ "@testing-library/user-event": "^14.6.1",
"@types/pngjs": "^6.0.5",
"@types/ws": "catalog:",
"@vitest/runner": "workspace:*",
- "@wdio/types": "^9.19.2",
"birpc": "catalog:",
"flatted": "catalog:",
"ivya": "^1.7.0",
"mime": "^4.1.0",
"pathe": "catalog:",
- "playwright": "^1.55.0",
- "playwright-core": "^1.55.0",
- "vitest": "workspace:*",
- "webdriverio": "^9.19.2"
+ "vitest": "workspace:*"
}
}
diff --git a/packages/browser/providers.d.ts b/packages/browser/providers.d.ts
deleted file mode 100644
index 1106f2d6d..000000000
--- a/packages/browser/providers.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { BrowserProviderModule } from 'vitest/node'
-
-declare const webdriverio: BrowserProviderModule
-declare const playwright: BrowserProviderModule
-declare const preview: BrowserProviderModule
-
-export { webdriverio, playwright, preview }
diff --git a/packages/browser/rollup.config.js b/packages/browser/rollup.config.js
index 6b56b7034..ca40ff131 100644
--- a/packages/browser/rollup.config.js
+++ b/packages/browser/rollup.config.js
@@ -14,10 +14,13 @@ const external = [
...Object.keys(pkg.peerDependencies || {}),
/^@?vitest(\/|$)/,
'@vitest/browser/utils',
+ '@vitest/browser/context',
+ '@vitest/browser/client',
+ 'vitest/browser',
'worker_threads',
'node:worker_threads',
'vite',
- 'playwright-core/types/protocol',
+ 'vitest/internal/browser',
]
const dtsUtils = createDtsUtils()
@@ -39,10 +42,7 @@ const plugins = [
]
const input = {
- 'index': './src/node/index.ts',
- 'providers/playwright': './src/node/providers/playwright.ts',
- 'providers/webdriverio': './src/node/providers/webdriverio.ts',
- 'providers/preview': './src/node/providers/preview.ts',
+ index: './src/node/index.ts',
}
export default () =>
@@ -75,12 +75,8 @@ export default () =>
},
{
input: {
- 'locators/playwright': './src/client/tester/locators/playwright.ts',
- 'locators/webdriverio': './src/client/tester/locators/webdriverio.ts',
- 'locators/preview': './src/client/tester/locators/preview.ts',
- 'locators/index': './src/client/tester/locators/index.ts',
+ 'locators': './src/client/tester/locators/index.ts',
'expect-element': './src/client/tester/expect-element.ts',
- 'utils': './src/client/tester/public-utils.ts',
},
output: {
dir: 'dist',
@@ -102,7 +98,7 @@ export default () =>
file: 'dist/context.js',
format: 'esm',
},
- external: ['@vitest/browser/utils'],
+ external: ['vitest/internal/browser'],
plugins: [
oxc({
transform: { target: 'node18' },
@@ -150,7 +146,7 @@ export default () =>
},
{
input: dtsUtilsClient.dtsInput({
- 'locators/index': './src/client/tester/locators/index.ts',
+ locators: './src/client/tester/locators/index.ts',
}),
output: {
dir: 'dist',
@@ -161,17 +157,4 @@ export default () =>
external,
plugins: dtsUtilsClient.dts(),
},
- // {
- // input: './src/client/tester/jest-dom.ts',
- // output: {
- // file: './jest-dom.d.ts',
- // format: 'esm',
- // },
- // external: [],
- // plugins: [
- // dts({
- // respectExternal: true,
- // }),
- // ],
- // },
])
diff --git a/packages/browser/src/client/client.ts b/packages/browser/src/client/client.ts
index 1606fad5b..9e5b72905 100644
--- a/packages/browser/src/client/client.ts
+++ b/packages/browser/src/client/client.ts
@@ -143,7 +143,7 @@ function createClient() {
`Cannot connect to the server in ${connectTimeout / 1000} seconds`,
),
)
- }, connectTimeout)?.unref?.()
+ }, connectTimeout)
if (ctx.ws.OPEN === ctx.ws.readyState) {
resolve()
}
diff --git a/packages/browser/src/client/orchestrator.ts b/packages/browser/src/client/orchestrator.ts
index 213871de2..06e73d2d6 100644
--- a/packages/browser/src/client/orchestrator.ts
+++ b/packages/browser/src/client/orchestrator.ts
@@ -29,6 +29,8 @@ export class IframeOrchestrator {
}
public async createTesters(options: BrowserTesterOptions): Promise {
+ const startTime = performance.now()
+
this.cancelled = false
const config = getConfig()
@@ -46,7 +48,7 @@ export class IframeOrchestrator {
}
if (config.browser.isolate === false) {
- await this.runNonIsolatedTests(container, options)
+ await this.runNonIsolatedTests(container, options, startTime)
return
}
@@ -65,6 +67,7 @@ export class IframeOrchestrator {
container,
file,
options,
+ startTime,
)
}
}
@@ -95,7 +98,7 @@ export class IframeOrchestrator {
this.recreateNonIsolatedIframe = true
}
- private async runNonIsolatedTests(container: HTMLDivElement, options: BrowserTesterOptions) {
+ private async runNonIsolatedTests(container: HTMLDivElement, options: BrowserTesterOptions, startTime: number) {
if (this.recreateNonIsolatedIframe) {
// recreate a new non-isolated iframe during watcher reruns
// because we called "cleanup" in the previous run
@@ -108,7 +111,7 @@ export class IframeOrchestrator {
if (!this.iframes.has(ID_ALL)) {
debug('preparing non-isolated iframe')
- await this.prepareIframe(container, ID_ALL, options.startTime)
+ await this.prepareIframe(container, ID_ALL, startTime)
}
const config = getConfig()
@@ -133,6 +136,7 @@ export class IframeOrchestrator {
container: HTMLDivElement,
file: string,
options: BrowserTesterOptions,
+ startTime: number,
) {
const config = getConfig()
const { width, height } = config.browser.viewport
@@ -142,7 +146,7 @@ export class IframeOrchestrator {
this.iframes.delete(file)
}
- const iframe = await this.prepareIframe(container, file, options.startTime)
+ const iframe = await this.prepareIframe(container, file, startTime)
await setIframeViewport(iframe, width, height)
// running tests after the "prepare" event
await sendEventToIframe({
diff --git a/packages/browser/src/client/tester/context.ts b/packages/browser/src/client/tester/context.ts
index c640ca61a..d682fc1d5 100644
--- a/packages/browser/src/client/tester/context.ts
+++ b/packages/browser/src/client/tester/context.ts
@@ -2,19 +2,21 @@ import type {
Options as TestingLibraryOptions,
UserEvent as TestingLibraryUserEvent,
} from '@testing-library/user-event'
+import type { RunnerTask } from 'vitest'
import type {
BrowserLocators,
BrowserPage,
Locator,
+ LocatorSelectors,
UserEvent,
-} from '@vitest/browser/context'
-import type { RunnerTask } from 'vitest'
+} from 'vitest/browser'
+import type { StringifyOptions } from 'vitest/internal/browser'
import type { IframeViewportEvent } from '../client'
import type { BrowserRunnerState } from '../utils'
import type { Locator as LocatorAPI } from './locators/index'
-import { getElementLocatorSelectors } from '@vitest/browser/utils'
+import { __INTERNAL, stringify } from 'vitest/internal/browser'
import { ensureAwaited, getBrowserState, getWorkerState } from '../utils'
-import { convertToSelector, processTimeoutOptions } from './utils'
+import { convertToSelector, processTimeoutOptions } from './tester-utils'
// this file should not import anything directly, only types and utils
@@ -38,7 +40,7 @@ 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`
+ const modifier = provider === 'playwright'
? 'ControlOrMeta'
: provider === 'webdriverio'
? 'Ctrl'
@@ -340,9 +342,6 @@ export const page: BrowserPage = {
frameLocator() {
throw new Error(`Method "frameLocator" is not supported by the "${provider}" provider.`)
},
- _createLocator() {
- throw new Error(`Method "_createLocator" is not supported by the "${provider}" provider.`)
- },
extend(methods) {
for (const key in methods) {
(page as any)[key] = (methods as any)[key].bind(page)
@@ -365,9 +364,9 @@ function getTaskFullName(task: RunnerTask): string {
export const locators: BrowserLocators = {
createElementLocators: getElementLocatorSelectors,
extend(methods) {
- const Locator = page._createLocator('css=body').constructor as typeof LocatorAPI
+ const Locator = __INTERNAL._createLocator('css=body').constructor as typeof LocatorAPI
for (const method in methods) {
- locators._extendedMethods.add(method)
+ __INTERNAL._extendedMethods.add(method)
const cb = (methods as any)[method] as (...args: any[]) => string | Locator
// @ts-expect-error types are hard to make work
Locator.prototype[method] = function (...args: any[]) {
@@ -380,23 +379,90 @@ export const locators: BrowserLocators = {
page[method as 'getByRole'] = function (...args: any[]) {
const selectorOrLocator = cb.call(this, ...args)
if (typeof selectorOrLocator === 'string') {
- return page._createLocator(selectorOrLocator)
+ return __INTERNAL._createLocator(selectorOrLocator)
}
return selectorOrLocator
}
}
},
- _extendedMethods: new Set(),
}
-declare module '@vitest/browser/context' {
- interface BrowserPage {
- /** @internal */
- _createLocator: (selector: string) => Locator
- }
-
- interface BrowserLocators {
- /** @internal */
- _extendedMethods: Set
+function getElementLocatorSelectors(element: Element): LocatorSelectors {
+ const locator = page.elementLocator(element)
+ return {
+ getByAltText: (altText, options) => locator.getByAltText(altText, options),
+ getByLabelText: (labelText, options) => locator.getByLabelText(labelText, options),
+ getByPlaceholder: (placeholderText, options) => locator.getByPlaceholder(placeholderText, options),
+ getByRole: (role, options) => locator.getByRole(role, options),
+ 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) => {
+ methods[method] = (...args: any[]) => (locator as any)[method](...args)
+ return methods
+ }, {} as any),
}
}
+
+type PrettyDOMOptions = Omit
+
+function debug(
+ el?: Element | Locator | null | (Element | Locator)[],
+ maxLength?: number,
+ 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
+ console.log(prettyDOM(el, maxLength, options))
+ }
+}
+
+function prettyDOM(
+ dom?: Element | Locator | undefined | null,
+ maxLength: number = Number(import.meta.env.DEBUG_PRINT_LIMIT ?? 7000),
+ prettyFormatOptions: PrettyDOMOptions = {},
+): string {
+ if (maxLength === 0) {
+ return ''
+ }
+
+ if (!dom) {
+ dom = document.body
+ }
+
+ if ('element' in dom && 'all' in dom) {
+ dom = dom.element()
+ }
+
+ const type = typeof dom
+ if (type !== 'object' || !dom.outerHTML) {
+ const typeName = type === 'object' ? dom.constructor.name : type
+ throw new TypeError(`Expecting a valid DOM element, but got ${typeName}.`)
+ }
+
+ const pretty = stringify(dom, Number.POSITIVE_INFINITY, {
+ maxLength,
+ highlight: true,
+ ...prettyFormatOptions,
+ })
+ return dom.outerHTML.length > maxLength
+ ? `${pretty.slice(0, maxLength)}...`
+ : pretty
+}
+
+function getElementError(selector: string, container: Element): Error {
+ const error = new Error(`Cannot find element with locator: ${__INTERNAL._asLocator('javascript', selector)}\n\n${prettyDOM(container)}`)
+ error.name = 'VitestBrowserElementError'
+ return error
+}
+
+export const utils = {
+ getElementError,
+ prettyDOM,
+ debug,
+ getElementLocatorSelectors,
+}
diff --git a/packages/browser/src/client/tester/expect-element.ts b/packages/browser/src/client/tester/expect-element.ts
index 903e87273..c7854cafc 100644
--- a/packages/browser/src/client/tester/expect-element.ts
+++ b/packages/browser/src/client/tester/expect-element.ts
@@ -1,9 +1,9 @@
-import type { Locator } from '@vitest/browser/context'
import type { ExpectPollOptions, PromisifyDomAssertion } from 'vitest'
+import type { Locator } from 'vitest/browser'
import { chai, expect } from 'vitest'
import { getType } from 'vitest/internal/browser'
import { matchers } from './expect'
-import { processTimeoutOptions } from './utils'
+import { processTimeoutOptions } from './tester-utils'
const kLocator = Symbol.for('$$vitest:locator')
diff --git a/packages/browser/src/client/tester/expect/toBeVisible.ts b/packages/browser/src/client/tester/expect/toBeVisible.ts
index afa599b62..808951ad4 100644
--- a/packages/browser/src/client/tester/expect/toBeVisible.ts
+++ b/packages/browser/src/client/tester/expect/toBeVisible.ts
@@ -15,8 +15,8 @@
import type { ExpectationResult, MatcherState } from '@vitest/expect'
import type { Locator } from '../locators'
-import { server } from '@vitest/browser/context'
import { beginAriaCaches, endAriaCaches, isElementVisible as ivyaIsVisible } from 'ivya/utils'
+import { server } from 'vitest/browser'
import { getElementFromUserInput } from './utils'
export default function toBeVisible(
diff --git a/packages/browser/src/client/tester/expect/toHaveStyle.ts b/packages/browser/src/client/tester/expect/toHaveStyle.ts
index df7e28ade..03b7f8d2e 100644
--- a/packages/browser/src/client/tester/expect/toHaveStyle.ts
+++ b/packages/browser/src/client/tester/expect/toHaveStyle.ts
@@ -1,6 +1,6 @@
import type { ExpectationResult, MatcherState } from '@vitest/expect'
import type { Locator } from '../locators'
-import { server } from '@vitest/browser/context'
+import { server } from 'vitest/browser'
import { getElementFromUserInput } from './utils'
const browser = server.config.browser.name
diff --git a/packages/browser/src/client/tester/expect/toMatchScreenshot.ts b/packages/browser/src/client/tester/expect/toMatchScreenshot.ts
index 5e439499d..e30898f36 100644
--- a/packages/browser/src/client/tester/expect/toMatchScreenshot.ts
+++ b/packages/browser/src/client/tester/expect/toMatchScreenshot.ts
@@ -3,7 +3,7 @@ import type { ScreenshotMatcherOptions } from '../../../../context'
import type { ScreenshotMatcherArguments, ScreenshotMatcherOutput } from '../../../shared/screenshotMatcher/types'
import type { Locator } from '../locators'
import { getBrowserState, getWorkerState } from '../../utils'
-import { convertToSelector } from '../utils'
+import { convertToSelector } from '../tester-utils'
const counters = new Map([])
diff --git a/packages/browser/src/client/tester/locators/index.ts b/packages/browser/src/client/tester/locators/index.ts
index fd7ecf0a7..1e1706fab 100644
--- a/packages/browser/src/client/tester/locators/index.ts
+++ b/packages/browser/src/client/tester/locators/index.ts
@@ -1,3 +1,4 @@
+import type { ParsedSelector } from 'ivya'
import type {
LocatorByRoleOptions,
LocatorOptions,
@@ -9,10 +10,9 @@ import type {
UserEventHoverOptions,
UserEventSelectOptions,
UserEventUploadOptions,
-} from '@vitest/browser/context'
-import type { ParsedSelector } from 'ivya'
-import { page, server } from '@vitest/browser/context'
+} from 'vitest/browser'
import {
+ asLocator,
getByAltTextSelector,
getByLabelSelector,
getByPlaceholderSelector,
@@ -21,11 +21,24 @@ import {
getByTextSelector,
getByTitleSelector,
Ivya,
-
} from 'ivya'
+import { page, server, utils } from 'vitest/browser'
+import { __INTERNAL } from 'vitest/internal/browser'
import { ensureAwaited, getBrowserState } from '../../utils'
-import { getElementError } from '../public-utils'
-import { escapeForTextSelector } from '../utils'
+import { escapeForTextSelector, isLocator } from '../tester-utils'
+
+export { convertElementToCssSelector, getIframeScale, processTimeoutOptions } from '../tester-utils'
+export {
+ getByAltTextSelector,
+ getByLabelSelector,
+ getByPlaceholderSelector,
+ getByRoleSelector,
+ getByTestIdSelector,
+ getByTextSelector,
+ getByTitleSelector,
+} from 'ivya'
+
+__INTERNAL._asLocator = asLocator
// we prefer using playwright locators because they are more powerful and support Shadow DOM
export const selectorEngine: Ivya = Ivya.create({
@@ -125,7 +138,7 @@ export abstract class Locator {
): Promise {
const values = (Array.isArray(value) ? value : [value]).map((v) => {
if (typeof v !== 'string') {
- const selector = 'element' in v ? v.selector : selectorEngine.generateSelectorSimple(v)
+ const selector = isLocator(v) ? v.selector : selectorEngine.generateSelectorSimple(v)
return { element: selector }
}
return v
@@ -223,7 +236,7 @@ export abstract class Locator {
public element(): HTMLElement | SVGElement {
const element = this.query()
if (!element) {
- throw getElementError(this._pwSelector || this.selector, this._container || document.body)
+ throw utils.getElementError(this._pwSelector || this.selector, this._container || document.body)
}
return element
}
diff --git a/packages/browser/src/client/tester/public-utils.ts b/packages/browser/src/client/tester/public-utils.ts
deleted file mode 100644
index ea005e319..000000000
--- a/packages/browser/src/client/tester/public-utils.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { Locator, LocatorSelectors } from '@vitest/browser/context'
-import type { StringifyOptions } from 'vitest/internal/browser'
-import { locators, page } from '@vitest/browser/context'
-import { asLocator } from 'ivya'
-import { stringify } from 'vitest/internal/browser'
-
-export function getElementLocatorSelectors(element: Element): LocatorSelectors {
- const locator = page.elementLocator(element)
- return {
- getByAltText: (altText, options) => locator.getByAltText(altText, options),
- getByLabelText: (labelText, options) => locator.getByLabelText(labelText, options),
- getByPlaceholder: (placeholderText, options) => locator.getByPlaceholder(placeholderText, options),
- getByRole: (role, options) => locator.getByRole(role, options),
- getByTestId: testId => locator.getByTestId(testId),
- getByText: (text, options) => locator.getByText(text, options),
- getByTitle: (title, options) => locator.getByTitle(title, options),
- ...Array.from(locators._extendedMethods).reduce((methods, method) => {
- methods[method] = (...args: any[]) => (locator as any)[method](...args)
- return methods
- }, {} as any),
- }
-}
-
-type PrettyDOMOptions = Omit
-
-export function debug(
- el?: Element | Locator | null | (Element | Locator)[],
- maxLength?: number,
- 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
- console.log(prettyDOM(el, maxLength, options))
- }
-}
-
-export function prettyDOM(
- dom?: Element | Locator | undefined | null,
- maxLength: number = Number(import.meta.env.DEBUG_PRINT_LIMIT ?? 7000),
- prettyFormatOptions: PrettyDOMOptions = {},
-): string {
- if (maxLength === 0) {
- return ''
- }
-
- if (!dom) {
- dom = document.body
- }
-
- if ('element' in dom && 'all' in dom) {
- dom = dom.element()
- }
-
- const type = typeof dom
- if (type !== 'object' || !dom.outerHTML) {
- const typeName = type === 'object' ? dom.constructor.name : type
- throw new TypeError(`Expecting a valid DOM element, but got ${typeName}.`)
- }
-
- const pretty = stringify(dom, Number.POSITIVE_INFINITY, {
- maxLength,
- highlight: true,
- ...prettyFormatOptions,
- })
- return dom.outerHTML.length > maxLength
- ? `${pretty.slice(0, maxLength)}...`
- : pretty
-}
-
-export function getElementError(selector: string, container: Element): Error {
- const error = new Error(`Cannot find element with locator: ${asLocator('javascript', selector)}\n\n${prettyDOM(container)}`)
- error.name = 'VitestBrowserElementError'
- return error
-}
diff --git a/packages/browser/src/client/tester/runner.ts b/packages/browser/src/client/tester/runner.ts
index b3dfd2c61..d02eea215 100644
--- a/packages/browser/src/client/tester/runner.ts
+++ b/packages/browser/src/client/tester/runner.ts
@@ -11,10 +11,10 @@ import type {
} from '@vitest/runner'
import type { SerializedConfig, TestExecutionMethod, WorkerGlobalState } from 'vitest'
import type { VitestBrowserClientMocker } from './mocker'
-import type { CommandsManager } from './utils'
+import type { CommandsManager } from './tester-utils'
import { globalChannel, onCancel } from '@vitest/browser/client'
-import { page, userEvent } from '@vitest/browser/context'
import { getTestName } from '@vitest/runner/utils'
+import { page, userEvent } from 'vitest/browser'
import {
DecodedMap,
getOriginalPosition,
diff --git a/packages/browser/src/client/tester/utils.ts b/packages/browser/src/client/tester/tester-utils.ts
similarity index 95%
rename from packages/browser/src/client/tester/utils.ts
rename to packages/browser/src/client/tester/tester-utils.ts
index e3702d4b7..c22fef6c4 100644
--- a/packages/browser/src/client/tester/utils.ts
+++ b/packages/browser/src/client/tester/tester-utils.ts
@@ -1,4 +1,4 @@
-import type { Locator } from '@vitest/browser/context'
+import type { Locator } from 'vitest/browser'
import type { BrowserRPC } from '../client'
import { getBrowserState, getWorkerState } from '../utils'
@@ -206,8 +206,14 @@ export function convertToSelector(elementOrLocator: Element | Locator): string {
if (elementOrLocator instanceof Element) {
return convertElementToCssSelector(elementOrLocator)
}
- if ('selector' in elementOrLocator) {
- return (elementOrLocator as any).selector
+ if (isLocator(elementOrLocator)) {
+ return elementOrLocator.selector
}
throw new Error('Expected element or locator to be an instance of Element or Locator.')
}
+
+const kLocator = Symbol.for('$$vitest:locator')
+
+export function isLocator(element: unknown): element is Locator {
+ return (!!element && typeof element === 'object' && kLocator in element)
+}
diff --git a/packages/browser/src/client/tester/tester.ts b/packages/browser/src/client/tester/tester.ts
index e542e8033..34cdf4e8b 100644
--- a/packages/browser/src/client/tester/tester.ts
+++ b/packages/browser/src/client/tester/tester.ts
@@ -1,7 +1,7 @@
import type { BrowserRPC, IframeChannelEvent } from '@vitest/browser/client'
import { channel, client, onCancel } from '@vitest/browser/client'
-import { page, server, userEvent } from '@vitest/browser/context'
import { parse } from 'flatted'
+import { page, server, userEvent } from 'vitest/browser'
import {
collectTests,
setupCommonEnv,
@@ -17,7 +17,7 @@ import { VitestBrowserClientMocker } from './mocker'
import { createModuleMockerInterceptor } from './mocker-interceptor'
import { createSafeRpc } from './rpc'
import { browserHashMap, initiateRunner } from './runner'
-import { CommandsManager } from './utils'
+import { CommandsManager } from './tester-utils'
const debugVar = getConfig().env.VITEST_BROWSER_DEBUG
const debug = debugVar && debugVar !== 'false'
diff --git a/packages/browser/src/client/utils.ts b/packages/browser/src/client/utils.ts
index fc965e1c8..ee72e4606 100644
--- a/packages/browser/src/client/utils.ts
+++ b/packages/browser/src/client/utils.ts
@@ -1,7 +1,7 @@
import type { VitestRunner } from '@vitest/runner'
import type { EvaluatedModules, SerializedConfig, WorkerGlobalState } from 'vitest'
import type { IframeOrchestrator } from './orchestrator'
-import type { CommandsManager } from './tester/utils'
+import type { CommandsManager } from './tester/tester-utils'
export async function importId(id: string): Promise {
const name = `/@id/${id}`.replace(/\\/g, '/')
diff --git a/packages/browser/src/client/vite.config.ts b/packages/browser/src/client/vite.config.ts
index a4814a6a4..95c64a690 100644
--- a/packages/browser/src/client/vite.config.ts
+++ b/packages/browser/src/client/vite.config.ts
@@ -35,7 +35,7 @@ export default vite.defineConfig({
/^vitest\//,
'vitest',
/^msw/,
- '@vitest/browser/context',
+ 'vitest/browser',
'@vitest/browser/client',
],
},
diff --git a/packages/browser/src/node/commands/clear.ts b/packages/browser/src/node/commands/clear.ts
deleted file mode 100644
index 79f26644b..000000000
--- a/packages/browser/src/node/commands/clear.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const clear: UserEventCommand = async (
- context,
- selector,
-) => {
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const { iframe } = context
- const element = iframe.locator(selector)
- await element.clear()
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- const browser = context.browser
- const element = await browser.$(selector)
- await element.clearValue()
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`)
- }
-}
diff --git a/packages/browser/src/node/commands/click.ts b/packages/browser/src/node/commands/click.ts
deleted file mode 100644
index 31047a013..000000000
--- a/packages/browser/src/node/commands/click.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const click: UserEventCommand = async (
- context,
- selector,
- options = {},
-) => {
- const provider = context.provider
- if (provider instanceof PlaywrightBrowserProvider) {
- const tester = context.iframe
- await tester.locator(selector).click(options)
- }
- else if (provider instanceof WebdriverBrowserProvider) {
- const browser = context.browser
- await browser.$(selector).click(options as any)
- }
- else {
- throw new TypeError(`Provider "${provider.name}" doesn't support click command`)
- }
-}
-
-export const dblClick: UserEventCommand = async (
- context,
- selector,
- options = {},
-) => {
- const provider = context.provider
- if (provider instanceof PlaywrightBrowserProvider) {
- const tester = context.iframe
- await tester.locator(selector).dblclick(options)
- }
- else if (provider instanceof WebdriverBrowserProvider) {
- const browser = context.browser
- await browser.$(selector).doubleClick()
- }
- else {
- throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`)
- }
-}
-
-export const tripleClick: UserEventCommand = async (
- context,
- selector,
- options = {},
-) => {
- const provider = context.provider
- if (provider instanceof PlaywrightBrowserProvider) {
- const tester = context.iframe
- await tester.locator(selector).click({
- ...options,
- clickCount: 3,
- })
- }
- else if (provider instanceof WebdriverBrowserProvider) {
- const browser = context.browser
- await browser
- .action('pointer', { parameters: { pointerType: 'mouse' } })
- // move the pointer over the button
- .move({ origin: browser.$(selector) })
- // simulate 3 clicks
- .down()
- .up()
- .pause(50)
- .down()
- .up()
- .pause(50)
- .down()
- .up()
- .pause(50)
- // run the sequence
- .perform()
- }
- else {
- throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`)
- }
-}
diff --git a/packages/browser/src/node/commands/dragAndDrop.ts b/packages/browser/src/node/commands/dragAndDrop.ts
deleted file mode 100644
index c665fd9ea..000000000
--- a/packages/browser/src/node/commands/dragAndDrop.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const dragAndDrop: UserEventCommand = async (
- context,
- source,
- target,
- options_,
-) => {
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const frame = await context.frame()
- await frame.dragAndDrop(
- source,
- target,
- options_,
- )
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- const $source = context.browser.$(source)
- const $target = context.browser.$(target)
- const options = (options_ || {}) as any
- const duration = options.duration ?? 10
-
- // https://github.com/webdriverio/webdriverio/issues/8022#issuecomment-1700919670
- await context.browser
- .action('pointer')
- .move({ duration: 0, origin: $source, x: options.sourceX ?? 0, y: options.sourceY ?? 0 })
- .down({ button: 0 })
- .move({ duration: 0, origin: 'pointer', x: 0, y: 0 })
- .pause(duration)
- .move({ duration: 0, origin: $target, x: options.targetX ?? 0, y: options.targetY ?? 0 })
- .move({ duration: 0, origin: 'pointer', x: 1, y: 0 })
- .move({ duration: 0, origin: 'pointer', x: -1, y: 0 })
- .up({ button: 0 })
- .perform()
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`)
- }
-}
diff --git a/packages/browser/src/node/commands/fill.ts b/packages/browser/src/node/commands/fill.ts
deleted file mode 100644
index c3256cccb..000000000
--- a/packages/browser/src/node/commands/fill.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const fill: UserEventCommand = async (
- context,
- selector,
- text,
- options = {},
-) => {
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const { iframe } = context
- const element = iframe.locator(selector)
- await element.fill(text, options)
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- const browser = context.browser
- await browser.$(selector).setValue(text)
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support filling inputs`)
- }
-}
diff --git a/packages/browser/src/node/commands/hover.ts b/packages/browser/src/node/commands/hover.ts
deleted file mode 100644
index 7672eb839..000000000
--- a/packages/browser/src/node/commands/hover.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const hover: UserEventCommand = async (
- context,
- selector,
- options = {},
-) => {
- if (context.provider instanceof PlaywrightBrowserProvider) {
- await context.iframe.locator(selector).hover(options)
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- const browser = context.browser
- await browser.$(selector).moveTo(options as any)
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support hover`)
- }
-}
diff --git a/packages/browser/src/node/commands/index.ts b/packages/browser/src/node/commands/index.ts
index 4b4e02ba2..64e115b73 100644
--- a/packages/browser/src/node/commands/index.ts
+++ b/packages/browser/src/node/commands/index.ts
@@ -1,56 +1,18 @@
-import { clear } from './clear'
-import { click, dblClick, tripleClick } from './click'
-import { dragAndDrop } from './dragAndDrop'
-import { fill } from './fill'
import {
_fileInfo,
readFile,
removeFile,
writeFile,
} from './fs'
-import { hover } from './hover'
-import { keyboard, keyboardCleanup } from './keyboard'
import { screenshot } from './screenshot'
import { screenshotMatcher } from './screenshotMatcher'
-import { selectOptions } from './select'
-import { tab } from './tab'
-import {
- annotateTraces,
- deleteTracing,
- startChunkTrace,
- startTracing,
- stopChunkTrace,
-} from './trace'
-import { type } from './type'
-import { upload } from './upload'
-import { viewport } from './viewport'
export default {
readFile: readFile as typeof readFile,
removeFile: removeFile as typeof removeFile,
writeFile: writeFile as typeof writeFile,
-
// private commands
__vitest_fileInfo: _fileInfo as typeof _fileInfo,
- __vitest_upload: upload as typeof upload,
- __vitest_click: click as typeof click,
- __vitest_dblClick: dblClick as typeof dblClick,
- __vitest_tripleClick: tripleClick as typeof tripleClick,
__vitest_screenshot: screenshot as typeof screenshot,
- __vitest_type: type as typeof type,
- __vitest_clear: clear as typeof clear,
- __vitest_fill: fill as typeof fill,
- __vitest_tab: tab as typeof tab,
- __vitest_keyboard: keyboard as typeof keyboard,
- __vitest_selectOptions: selectOptions as typeof selectOptions,
- __vitest_dragAndDrop: dragAndDrop as typeof dragAndDrop,
- __vitest_hover: hover as typeof hover,
- __vitest_cleanup: keyboardCleanup as typeof keyboardCleanup,
- __vitest_viewport: viewport as typeof viewport,
__vitest_screenshotMatcher: screenshotMatcher as typeof screenshotMatcher,
- __vitest_deleteTracing: deleteTracing as typeof deleteTracing,
- __vitest_startChunkTrace: startChunkTrace as typeof startChunkTrace,
- __vitest_startTracing: startTracing as typeof startTracing,
- __vitest_stopChunkTrace: stopChunkTrace as typeof stopChunkTrace,
- __vitest_annotateTraces: annotateTraces as typeof annotateTraces,
}
diff --git a/packages/browser/src/node/commands/keyboard.ts b/packages/browser/src/node/commands/keyboard.ts
deleted file mode 100644
index 6024e55ab..000000000
--- a/packages/browser/src/node/commands/keyboard.ts
+++ /dev/null
@@ -1,208 +0,0 @@
-import type { BrowserProvider } from 'vitest/node'
-import type { UserEventCommand } from './utils'
-import { defaultKeyMap } from '@testing-library/user-event/dist/esm/keyboard/keyMap.js'
-import { parseKeyDef } from '@testing-library/user-event/dist/esm/keyboard/parseKeyDef.js'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export interface KeyboardState {
- unreleased: string[]
-}
-
-export const keyboard: UserEventCommand<(text: string, state: KeyboardState) => Promise<{ unreleased: string[] }>> = async (
- context,
- text,
- state,
-) => {
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const frame = await context.frame()
- await frame.evaluate(focusIframe)
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- await context.browser.execute(focusIframe)
- }
-
- const pressed = new Set(state.unreleased)
-
- await keyboardImplementation(
- pressed,
- context.provider,
- context.sessionId,
- text,
- async () => {
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const frame = await context.frame()
- await frame.evaluate(selectAll)
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- await context.browser.execute(selectAll)
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`)
- }
- },
- true,
- )
-
- return {
- unreleased: Array.from(pressed),
- }
-}
-
-export const keyboardCleanup: UserEventCommand<(state: KeyboardState) => Promise> = async (
- context,
- state,
-) => {
- const { provider, sessionId } = context
- if (!state.unreleased) {
- return
- }
- if (provider instanceof PlaywrightBrowserProvider) {
- const page = provider.getPage(sessionId)
- for (const key of state.unreleased) {
- await page.keyboard.up(key)
- }
- }
- else if (provider instanceof WebdriverBrowserProvider) {
- const keyboard = provider.browser!.action('key')
- for (const key of state.unreleased) {
- keyboard.up(key)
- }
- await keyboard.perform()
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support keyboard api`)
- }
-}
-
-// fallback to insertText for non US key
-// https://github.com/microsoft/playwright/blob/50775698ae13642742f2a1e8983d1d686d7f192d/packages/playwright-core/src/server/input.ts#L95
-const VALID_KEYS = new Set(['Escape', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'Backquote', '`', '~', 'Digit1', '1', '!', 'Digit2', '2', '@', 'Digit3', '3', '#', 'Digit4', '4', '$', 'Digit5', '5', '%', 'Digit6', '6', '^', 'Digit7', '7', '&', 'Digit8', '8', '*', 'Digit9', '9', '(', 'Digit0', '0', ')', 'Minus', '-', '_', 'Equal', '=', '+', 'Backslash', '\\', '|', 'Backspace', 'Tab', 'KeyQ', 'q', 'Q', 'KeyW', 'w', 'W', 'KeyE', 'e', 'E', 'KeyR', 'r', 'R', 'KeyT', 't', 'T', 'KeyY', 'y', 'Y', 'KeyU', 'u', 'U', 'KeyI', 'i', 'I', 'KeyO', 'o', 'O', 'KeyP', 'p', 'P', 'BracketLeft', '[', '{', 'BracketRight', ']', '}', 'CapsLock', 'KeyA', 'a', 'A', 'KeyS', 's', 'S', 'KeyD', 'd', 'D', 'KeyF', 'f', 'F', 'KeyG', 'g', 'G', 'KeyH', 'h', 'H', 'KeyJ', 'j', 'J', 'KeyK', 'k', 'K', 'KeyL', 'l', 'L', 'Semicolon', ';', ':', 'Quote', '\'', '"', 'Enter', '\n', '\r', 'ShiftLeft', 'Shift', 'KeyZ', 'z', 'Z', 'KeyX', 'x', 'X', 'KeyC', 'c', 'C', 'KeyV', 'v', 'V', 'KeyB', 'b', 'B', 'KeyN', 'n', 'N', 'KeyM', 'm', 'M', 'Comma', ',', '<', 'Period', '.', '>', 'Slash', '/', '?', 'ShiftRight', 'ControlLeft', 'Control', 'MetaLeft', 'Meta', 'AltLeft', 'Alt', 'Space', ' ', 'AltRight', 'AltGraph', 'MetaRight', 'ContextMenu', 'ControlRight', 'PrintScreen', 'ScrollLock', 'Pause', 'PageUp', 'PageDown', 'Insert', 'Delete', 'Home', 'End', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'NumLock', 'NumpadDivide', 'NumpadMultiply', 'NumpadSubtract', 'Numpad7', 'Numpad8', 'Numpad9', 'Numpad4', 'Numpad5', 'Numpad6', 'NumpadAdd', 'Numpad1', 'Numpad2', 'Numpad3', 'Numpad0', 'NumpadDecimal', 'NumpadEnter', 'ControlOrMeta'])
-
-export async function keyboardImplementation(
- pressed: Set,
- provider: BrowserProvider,
- sessionId: string,
- text: string,
- selectAll: () => Promise,
- skipRelease: boolean,
-): Promise<{ pressed: Set }> {
- if (provider instanceof PlaywrightBrowserProvider) {
- const page = provider.getPage(sessionId)
- const actions = parseKeyDef(defaultKeyMap, text)
-
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
- const key = keyDef.key!
-
- // TODO: instead of calling down/up for each key, join non special
- // together, and call `type` once for all non special keys,
- // and then `press` for special keys
- if (pressed.has(key)) {
- if (VALID_KEYS.has(key)) {
- await page.keyboard.up(key)
- }
- pressed.delete(key)
- }
-
- if (!releasePrevious) {
- if (key === 'selectall') {
- await selectAll()
- continue
- }
-
- for (let i = 1; i <= repeat; i++) {
- if (VALID_KEYS.has(key)) {
- await page.keyboard.down(key)
- }
- else {
- await page.keyboard.insertText(key)
- }
- }
-
- if (releaseSelf) {
- if (VALID_KEYS.has(key)) {
- await page.keyboard.up(key)
- }
- }
- else {
- pressed.add(key)
- }
- }
- }
-
- if (!skipRelease && pressed.size) {
- for (const key of pressed) {
- if (VALID_KEYS.has(key)) {
- await page.keyboard.up(key)
- }
- }
- }
- }
- else if (provider instanceof WebdriverBrowserProvider) {
- const { Key } = await import('webdriverio')
- const browser = provider.browser!
- const actions = parseKeyDef(defaultKeyMap, text)
-
- let keyboard = browser.action('key')
-
- for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
- let key = keyDef.key!
- const special = Key[key as 'Shift']
-
- if (special) {
- key = special
- }
-
- if (pressed.has(key)) {
- keyboard.up(key)
- pressed.delete(key)
- }
-
- if (!releasePrevious) {
- if (key === 'selectall') {
- await keyboard.perform()
- keyboard = browser.action('key')
- await selectAll()
- continue
- }
-
- for (let i = 1; i <= repeat; i++) {
- keyboard.down(key)
- }
-
- if (releaseSelf) {
- keyboard.up(key)
- }
- else {
- pressed.add(key)
- }
- }
- }
-
- // seems like webdriverio doesn't release keys automatically if skipRelease is true and all events are keyUp
- const allRelease = keyboard.toJSON().actions.every(action => action.type === 'keyUp')
-
- await keyboard.perform(allRelease ? false : skipRelease)
- }
-
- return {
- pressed,
- }
-}
-
-function focusIframe() {
- if (
- !document.activeElement
- || document.activeElement.ownerDocument !== document
- || document.activeElement === document.body
- ) {
- window.focus()
- }
-}
-
-function selectAll() {
- const element = document.activeElement as HTMLInputElement
- if (element && typeof element.select === 'function') {
- element.select()
- }
-}
diff --git a/packages/browser/src/node/commands/screenshot.ts b/packages/browser/src/node/commands/screenshot.ts
index 14c986914..7e5dce337 100644
--- a/packages/browser/src/node/commands/screenshot.ts
+++ b/packages/browser/src/node/commands/screenshot.ts
@@ -1,17 +1,23 @@
-import type { BrowserCommand, BrowserCommandContext, ResolvedConfig } from 'vitest/node'
+import type { BrowserCommand } from 'vitest/node'
import type { ScreenshotOptions } from '../../../context'
-import { mkdir, rm } from 'node:fs/promises'
-import { normalize as platformNormalize } from 'node:path'
-import { nanoid } from '@vitest/utils/helpers'
-import { basename, dirname, normalize, relative, resolve } from 'pathe'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
interface ScreenshotCommandOptions extends Omit {
element?: string
mask?: readonly string[]
}
+declare module 'vitest/browser' {
+ interface BrowserCommands {
+ /**
+ * @internal
+ */
+ __vitest_takeScreenshot: (name: string, options: ScreenshotCommandOptions) => Promise<{
+ buffer: Buffer
+ path: string
+ }>
+ }
+}
+
export const screenshot: BrowserCommand<[string, ScreenshotCommandOptions]> = async (
context,
name: string,
@@ -23,119 +29,11 @@ export const screenshot: BrowserCommand<[string, ScreenshotCommandOptions]> = as
options.base64 = true
}
- const { buffer, path } = await takeScreenshot(context, name, options)
+ const { buffer, path } = await context.triggerCommand('__vitest_takeScreenshot', name, options)
return returnResult(options, path, buffer)
}
-/**
- * Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path.
- *
- * **Note**: the returned `path` indicates where the screenshot *might* be found.
- * It is not guaranteed to exist, especially if `options.save` is `false`.
- *
- * @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots.
- */
-export async function takeScreenshot(
- context: BrowserCommandContext,
- name: string,
- options: Omit,
-): Promise<{ buffer: Buffer; path: string }> {
- if (!context.testPath) {
- throw new Error(`Cannot take a screenshot without a test path`)
- }
-
- const path = resolveScreenshotPath(
- context.testPath,
- name,
- context.project.config,
- options.path,
- )
-
- // playwright does not need a screenshot path if we don't intend to save it
- let savePath: string | undefined
-
- if (options.save) {
- savePath = normalize(path)
-
- await mkdir(dirname(savePath), { recursive: true })
- }
-
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const mask = options.mask?.map(selector => context.iframe.locator(selector))
-
- if (options.element) {
- const { element: selector, ...config } = options
- const element = context.iframe.locator(selector)
- const buffer = await element.screenshot({
- ...config,
- mask,
- path: savePath,
- })
- return { buffer, path }
- }
-
- const buffer = await context.iframe.locator('body').screenshot({
- ...options,
- mask,
- path: savePath,
- })
- return { buffer, path }
- }
-
- if (context.provider instanceof WebdriverBrowserProvider) {
- // webdriverio needs a path, so if one is not already set we create a temporary one
- if (savePath === undefined) {
- savePath = resolve(context.project.tmpDir, nanoid())
-
- await mkdir(context.project.tmpDir, { recursive: true })
- }
-
- const page = context.provider.browser!
- const element = !options.element
- ? await page.$('body')
- : await page.$(`${options.element}`)
-
- // webdriverio expects the path to contain the extension and only works with PNG files
- const savePathWithExtension = savePath.endsWith('.png') ? savePath : `${savePath}.png`
-
- // there seems to be a bug in webdriverio, `X:/` gets appended to cwd, so we convert to `X:\`
- const buffer = await element.saveScreenshot(
- platformNormalize(savePathWithExtension),
- )
- if (!options.save) {
- await rm(savePathWithExtension, { force: true })
- }
- return { buffer, path }
- }
-
- throw new Error(
- `Provider "${context.provider.name}" does not support screenshots`,
- )
-}
-
-function resolveScreenshotPath(
- testPath: string,
- name: string,
- config: ResolvedConfig,
- customPath: string | undefined,
-): string {
- if (customPath) {
- return resolve(dirname(testPath), customPath)
- }
- const dir = dirname(testPath)
- const base = basename(testPath)
- if (config.browser.screenshotDirectory) {
- return resolve(
- config.browser.screenshotDirectory,
- relative(config.root, dir),
- base,
- name,
- )
- }
- return resolve(dir, '__screenshots__', base, name)
-}
-
function returnResult(
options: ScreenshotCommandOptions,
path: string,
diff --git a/packages/browser/src/node/commands/screenshotMatcher/types.ts b/packages/browser/src/node/commands/screenshotMatcher/types.ts
index 2f20b4c19..b1c0cd127 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/types.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/types.ts
@@ -1,3 +1,8 @@
+import type {
+ ScreenshotComparatorRegistry,
+ ScreenshotMatcherOptions,
+} from '@vitest/browser/context'
+
interface BaseMetadata { height: number; width: number }
export type TypedArray
= | Buffer
@@ -41,3 +46,14 @@ export type Comparator> = (
createDiff: boolean
} & Options
) => Promisable<{ pass: boolean; diff: TypedArray | null; message: string | null }>
+
+declare module 'vitest/node' {
+ export interface ToMatchScreenshotOptions
+ extends Omit<
+ ScreenshotMatcherOptions,
+ 'comparatorName' | 'comparatorOptions'
+ > {}
+
+ 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 ae2bf7489..96b95e3e3 100644
--- a/packages/browser/src/node/commands/screenshotMatcher/utils.ts
+++ b/packages/browser/src/node/commands/screenshotMatcher/utils.ts
@@ -5,7 +5,6 @@ import type { AnyCodec } from './codecs'
import { platform } from 'node:os'
import { deepMerge } from '@vitest/utils/helpers'
import { basename, dirname, extname, join, relative, resolve } from 'pathe'
-import { takeScreenshot } from '../screenshot'
import { getCodec } from './codecs'
import { getComparator } from './comparators'
@@ -238,8 +237,8 @@ export function takeDecodedScreenshot({
name: string
screenshotOptions: ScreenshotMatcherArguments[2]['screenshotOptions']
}): ReturnType {
- return takeScreenshot(
- context,
+ return context.triggerCommand(
+ '__vitest_takeScreenshot',
name,
{ ...screenshotOptions, save: false, element },
).then(
diff --git a/packages/browser/src/node/commands/select.ts b/packages/browser/src/node/commands/select.ts
deleted file mode 100644
index b2910a317..000000000
--- a/packages/browser/src/node/commands/select.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { ElementHandle } from 'playwright'
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const selectOptions: UserEventCommand = async (
- context,
- selector,
- userValues,
- options = {},
-) => {
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const value = userValues as any as (string | { element: string })[]
- const { iframe } = context
- const selectElement = iframe.locator(selector)
-
- const values = await Promise.all(value.map(async (v) => {
- if (typeof v === 'string') {
- return v
- }
- const elementHandler = await iframe.locator(v.element).elementHandle()
- if (!elementHandler) {
- throw new Error(`Element not found: ${v.element}`)
- }
- return elementHandler
- })) as (readonly string[]) | (readonly ElementHandle[])
-
- await selectElement.selectOption(values, options)
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- const values = userValues as any as [({ index: number })]
-
- if (!values.length) {
- return
- }
-
- const browser = context.browser
-
- if (values.length === 1 && 'index' in values[0]) {
- const selectElement = browser.$(selector)
- await selectElement.selectByIndex(values[0].index)
- }
- else {
- throw new Error('Provider "webdriverio" doesn\'t support selecting multiple values at once')
- }
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`)
- }
-}
diff --git a/packages/browser/src/node/commands/tab.ts b/packages/browser/src/node/commands/tab.ts
deleted file mode 100644
index 3739841b9..000000000
--- a/packages/browser/src/node/commands/tab.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const tab: UserEventCommand = async (
- context,
- options = {},
-) => {
- const provider = context.provider
- if (provider instanceof PlaywrightBrowserProvider) {
- const page = context.page
- await page.keyboard.press(options.shift === true ? 'Shift+Tab' : 'Tab')
- return
- }
- if (provider instanceof WebdriverBrowserProvider) {
- const { Key } = await import('webdriverio')
- const browser = context.browser
- await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab])
- return
- }
- throw new Error(`Provider "${provider.name}" doesn't support tab command`)
-}
diff --git a/packages/browser/src/node/commands/type.ts b/packages/browser/src/node/commands/type.ts
deleted file mode 100644
index 189e7343b..000000000
--- a/packages/browser/src/node/commands/type.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import type { UserEvent } from '../../../context'
-import type { UserEventCommand } from './utils'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-import { keyboardImplementation } from './keyboard'
-
-export const type: UserEventCommand = async (
- context,
- selector,
- text,
- options = {},
-) => {
- const { skipClick = false, skipAutoClose = false } = options
- const unreleased = new Set(Reflect.get(options, 'unreleased') as string[] ?? [])
-
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const { iframe } = context
- const element = iframe.locator(selector)
-
- if (!skipClick) {
- await element.focus()
- }
-
- await keyboardImplementation(
- unreleased,
- context.provider,
- context.sessionId,
- text,
- () => element.selectText(),
- skipAutoClose,
- )
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- const browser = context.browser
- const element = browser.$(selector)
-
- if (!skipClick && !await element.isFocused()) {
- await element.click()
- }
-
- await keyboardImplementation(
- unreleased,
- context.provider,
- context.sessionId,
- text,
- () => browser.execute(() => {
- const element = document.activeElement as HTMLInputElement
- if (element && typeof element.select === 'function') {
- element.select()
- }
- }),
- skipAutoClose,
- )
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support typing`)
- }
-
- return {
- unreleased: Array.from(unreleased),
- }
-}
diff --git a/packages/browser/src/node/commands/upload.ts b/packages/browser/src/node/commands/upload.ts
deleted file mode 100644
index 0893fa37a..000000000
--- a/packages/browser/src/node/commands/upload.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { UserEventUploadOptions } from '@vitest/browser/context'
-import type { UserEventCommand } from './utils'
-import { resolve } from 'pathe'
-import { PlaywrightBrowserProvider } from '../providers/playwright'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const upload: UserEventCommand<(element: string, files: Array, options: UserEventUploadOptions) => void> = async (
- context,
- selector,
- files,
- options,
-) => {
- const testPath = context.testPath
- if (!testPath) {
- throw new Error(`Cannot upload files outside of a test`)
- }
- const root = context.project.config.root
-
- if (context.provider instanceof PlaywrightBrowserProvider) {
- const { iframe } = context
- const playwrightFiles = files.map((file) => {
- if (typeof file === 'string') {
- return resolve(root, file)
- }
- return {
- name: file.name,
- mimeType: file.mimeType,
- buffer: Buffer.from(file.base64, 'base64'),
- }
- })
- await iframe.locator(selector).setInputFiles(playwrightFiles as string[], options)
- }
- else if (context.provider instanceof WebdriverBrowserProvider) {
- for (const file of files) {
- if (typeof file !== 'string') {
- throw new TypeError(`The "${context.provider.name}" provider doesn't support uploading files objects. Provide a file path instead.`)
- }
- }
-
- const element = context.browser.$(selector)
-
- for (const file of files) {
- const filepath = resolve(root, file as string)
- const remoteFilePath = await context.browser.uploadFile(filepath)
- await element.addValue(remoteFilePath)
- }
- }
- else {
- throw new TypeError(`Provider "${context.provider.name}" does not support uploading files via userEvent.upload`)
- }
-}
diff --git a/packages/browser/src/node/commands/viewport.ts b/packages/browser/src/node/commands/viewport.ts
deleted file mode 100644
index a117275d3..000000000
--- a/packages/browser/src/node/commands/viewport.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { UserEventCommand } from './utils'
-import { WebdriverBrowserProvider } from '../providers/webdriverio'
-
-export const viewport: UserEventCommand<(options: {
- width: number
- height: number
-}) => void> = async (context, options) => {
- if (context.provider instanceof WebdriverBrowserProvider) {
- await context.provider.setViewport(options)
- }
- else {
- throw new TypeError(`Provider ${context.provider.name} doesn't support "viewport" command`)
- }
-}
diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts
index 1c6312a37..d2c7f16bc 100644
--- a/packages/browser/src/node/index.ts
+++ b/packages/browser/src/node/index.ts
@@ -1,25 +1,27 @@
-import type { Plugin } from 'vitest/config'
-import type { TestProject } from 'vitest/node'
+import type { BrowserCommand, BrowserProviderOption, BrowserServerFactory } from 'vitest/node'
import { MockerRegistry } from '@vitest/mocker'
import { interceptorPlugin } from '@vitest/mocker/node'
import c from 'tinyrainbow'
import { createViteLogger, createViteServer } from 'vitest/node'
import { version } from '../../package.json'
+import { distRoot } from './constants'
import BrowserPlugin from './plugin'
import { ParentBrowserProject } from './projectParent'
import { setupBrowserRpc } from './rpc'
-export { distRoot } from './constants'
-export { createBrowserPool } from './pool'
+export function defineBrowserCommand(
+ fn: BrowserCommand,
+): BrowserCommand {
+ return fn
+}
-export type { ProjectBrowser } from './project'
+// export type { ProjectBrowser } from './project'
+export { parseKeyDef, resolveScreenshotPath } from './utils'
+
+export const createBrowserServer: BrowserServerFactory = async (options) => {
+ const project = options.project
+ const configFile = project.vite.config.configFile
-export async function createBrowserServer(
- project: TestProject,
- configFile: string | undefined,
- prePlugins: Plugin[] = [],
- postPlugins: Plugin[] = [],
-): Promise {
if (project.vitest.version !== version) {
project.vitest.logger.warn(
c.yellow(
@@ -42,6 +44,7 @@ export async function createBrowserServer(
const mockerRegistry = new MockerRegistry()
+ let cacheDir: string
const vite = await createViteServer({
...project.options, // spread project config inlined in root workspace config
base: '/',
@@ -71,11 +74,25 @@ export async function createBrowserServer(
},
cacheDir: project.vite.config.cacheDir,
plugins: [
- ...prePlugins,
+ {
+ name: 'vitest-internal:browser-cacheDir',
+ configResolved(config) {
+ cacheDir = config.cacheDir
+ },
+ },
+ ...options.mocksPlugins({
+ filter(id) {
+ if (id.includes(distRoot) || id.includes(cacheDir)) {
+ return false
+ }
+ return true
+ },
+ }),
+ options.metaEnvReplacer(),
...(project.options?.plugins || []),
BrowserPlugin(server),
interceptorPlugin({ registry: mockerRegistry }),
- ...postPlugins,
+ options.coveragePlugin(),
],
})
@@ -85,3 +102,14 @@ export async function createBrowserServer(
return server
}
+
+export function defineBrowserProvider(options: Omit<
+ BrowserProviderOption,
+ 'serverFactory' | 'options'
+> & { options?: T }): BrowserProviderOption {
+ return {
+ ...options,
+ options: options.options || {},
+ serverFactory: createBrowserServer,
+ }
+}
diff --git a/packages/browser/src/node/plugin.ts b/packages/browser/src/node/plugin.ts
index 30091fa08..faacb214d 100644
--- a/packages/browser/src/node/plugin.ts
+++ b/packages/browser/src/node/plugin.ts
@@ -8,7 +8,7 @@ import { createRequire } from 'node:module'
import { dynamicImportPlugin } from '@vitest/mocker/node'
import { toArray } from '@vitest/utils/helpers'
import MagicString from 'magic-string'
-import { basename, dirname, extname, resolve } from 'pathe'
+import { basename, dirname, extname, join, resolve } from 'pathe'
import sirv from 'sirv'
import { coverageConfigDefaults } from 'vitest/config'
import {
@@ -24,7 +24,6 @@ import { createOrchestratorMiddleware } from './middlewares/orchestratorMiddlewa
import { createTesterMiddleware } from './middlewares/testerMiddleware'
import BrowserContext from './plugins/pluginContext'
-export { defineBrowserCommand } from './commands/utils'
export type { BrowserCommand } from 'vitest/node'
const versionRegexp = /(?:\?|&)v=\w{8}/
@@ -232,9 +231,9 @@ export default (parentServer: ParentBrowserProject, base = '/'): Plugin[] => {
const exclude = [
'vitest',
+ 'vitest/browser',
'vitest/internal/browser',
'vitest/runners',
- '@vitest/browser',
'@vitest/browser/client',
'@vitest/utils',
'@vitest/utils/source-map',
@@ -282,10 +281,16 @@ export default (parentServer: ParentBrowserProject, base = '/'): Plugin[] => {
'vitest > expect-type',
'vitest > @vitest/snapshot > magic-string',
'vitest > @vitest/expect > chai',
- '@vitest/browser > @testing-library/user-event',
- '@vitest/browser > @testing-library/dom',
]
+ const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider
+ if (provider?.name === 'preview') {
+ include.push(
+ '@vitest/browser-preview > @testing-library/user-event',
+ '@vitest/browser-preview > @testing-library/dom',
+ )
+ }
+
const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root
const svelte = isPackageExists('vitest-browser-svelte', fileRoot)
@@ -549,16 +554,14 @@ body {
},
injectTo: 'head' as const,
},
- parentServer.locatorsUrl
- ? {
- tag: 'script',
- attrs: {
- type: 'module',
- src: parentServer.locatorsUrl,
- },
- injectTo: 'head',
- } as const
- : null,
+ ...parentServer.initScripts.map(script => ({
+ tag: 'script',
+ attrs: {
+ type: 'module',
+ src: join('/@fs/', script),
+ },
+ injectTo: 'head',
+ } as const)),
...testerTags,
].filter(s => s != null)
},
diff --git a/packages/browser/src/node/plugins/pluginContext.ts b/packages/browser/src/node/plugins/pluginContext.ts
index cb266f701..fd93f8fee 100644
--- a/packages/browser/src/node/plugins/pluginContext.ts
+++ b/packages/browser/src/node/plugins/pluginContext.ts
@@ -1,12 +1,18 @@
import type { Rollup } from 'vite'
import type { Plugin } from 'vitest/config'
+import type { BrowserProvider } from 'vitest/node'
import type { ParentBrowserProject } from '../projectParent'
import { fileURLToPath } from 'node:url'
import { slash } from '@vitest/utils/helpers'
import { dirname, resolve } from 'pathe'
-const VIRTUAL_ID_CONTEXT = '\0@vitest/browser/context'
-const ID_CONTEXT = '@vitest/browser/context'
+const VIRTUAL_ID_CONTEXT = '\0vitest/browser'
+const ID_CONTEXT = 'vitest/browser'
+// for libraries that use an older import but are not type checked
+const DEPRECATED_ID_CONTEXT = '@vitest/browser/context'
+
+const DEPRECATED_VIRTUAL_ID_UTILS = '\0@vitest/browser/utils'
+const DEPRECATED_ID_UTILS = '@vitest/browser/utils'
const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -14,15 +20,37 @@ export default function BrowserContext(globalServer: ParentBrowserProject): Plug
return {
name: 'vitest:browser:virtual-module:context',
enforce: 'pre',
- resolveId(id) {
+ resolveId(id, importer) {
if (id === ID_CONTEXT) {
return VIRTUAL_ID_CONTEXT
}
+ if (id === DEPRECATED_ID_CONTEXT) {
+ if (importer) {
+ globalServer.vitest.logger.deprecate(
+ `${importer} tries to load a deprecated "${id}" module. `
+ + `This import will stop working in the next major version. `
+ + `Please, use "vitest/browser" instead.`,
+ )
+ }
+ return VIRTUAL_ID_CONTEXT
+ }
+ if (id === DEPRECATED_ID_UTILS) {
+ return DEPRECATED_VIRTUAL_ID_UTILS
+ }
},
load(id) {
if (id === VIRTUAL_ID_CONTEXT) {
return generateContextFile.call(this, globalServer)
}
+ if (id === DEPRECATED_VIRTUAL_ID_UTILS) {
+ return `
+import { utils } from 'vitest/browser'
+export const getElementLocatorSelectors = utils.getElementLocatorSelectors
+export const debug = utils.debug
+export const prettyDOM = utils.prettyDOM
+export const getElementError = utils.getElementError
+ `
+ }
},
}
}
@@ -32,8 +60,8 @@ async function generateContextFile(
globalServer: ParentBrowserProject,
) {
const commands = Object.keys(globalServer.commands)
- const provider = [...globalServer.children][0].provider || { name: 'preview' }
- const providerName = provider.name
+ const provider = [...globalServer.children][0].provider
+ const providerName = provider?.name || 'preview'
const commandsCode = commands
.filter(command => !command.startsWith('__vitest'))
@@ -43,13 +71,13 @@ async function generateContextFile(
.join('\n')
const userEventNonProviderImport = await getUserEventImport(
- providerName,
+ provider,
this.resolve.bind(this),
)
const distContextPath = slash(`/@fs/${resolve(__dirname, 'context.js')}`)
return `
-import { page, createUserEvent, cdp, locators } from '${distContextPath}'
+import { page, createUserEvent, cdp, locators, utils } from '${distContextPath}'
${userEventNonProviderImport}
export const server = {
@@ -64,17 +92,18 @@ export const server = {
}
export const commands = server.commands
export const userEvent = createUserEvent(_userEventSetup)
-export { page, cdp, locators }
+export { page, cdp, locators, utils }
`
}
-async function getUserEventImport(provider: string, resolve: (id: string, importer: string) => Promise) {
- if (provider !== 'preview') {
+async function getUserEventImport(provider: BrowserProvider | undefined, resolve: (id: string, importer: string) => Promise) {
+ if (!provider || provider.name !== 'preview') {
return 'const _userEventSetup = undefined'
}
- const resolved = await resolve('@testing-library/user-event', __dirname)
+ const previewDistRoot = (provider as any).distRoot
+ const resolved = await resolve('@testing-library/user-event', previewDistRoot)
if (!resolved) {
- throw new Error(`Failed to resolve user-event package from ${__dirname}`)
+ throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`)
}
return `\
import { userEvent as __vitest_user_event__ } from '${slash(`/@fs/${resolved.id}`)}'
diff --git a/packages/browser/src/node/project.ts b/packages/browser/src/node/project.ts
index d8a1f12bc..21dbf8292 100644
--- a/packages/browser/src/node/project.ts
+++ b/packages/browser/src/node/project.ts
@@ -2,12 +2,15 @@ import type { StackTraceParserOptions } from '@vitest/utils/source-map'
import type { ViteDevServer } from 'vite'
import type { ParsedStack, SerializedConfig, TestError } from 'vitest'
import type {
+ BrowserCommand,
+ BrowserCommandContext,
BrowserProvider,
ProjectBrowser as IProjectBrowser,
ResolvedConfig,
TestProject,
Vitest,
} from 'vitest/node'
+import type { BrowserCommands } from '../../context'
import type { ParentBrowserProject } from './projectParent'
import { existsSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
@@ -57,6 +60,37 @@ export class ProjectBrowser implements IProjectBrowser {
return this.parent.vite
}
+ private commands = {} as Record>
+
+ public registerCommand(
+ name: K,
+ cb: BrowserCommand<
+ Parameters,
+ ReturnType
+ >,
+ ): void {
+ if (!/^[a-z_$][\w$]*$/i.test(name)) {
+ throw new Error(
+ `Invalid command name "${name}". Only alphanumeric characters, $ and _ are allowed.`,
+ )
+ }
+ this.commands[name] = cb
+ }
+
+ public triggerCommand = ((
+ name: K,
+ context: BrowserCommandContext,
+ ...args: Parameters
+ ): ReturnType => {
+ if (name in this.commands) {
+ return this.commands[name](context, ...args)
+ }
+ if (name in this.parent.commands) {
+ return this.parent.commands[name](context, ...args)
+ }
+ throw new Error(`Provider ${this.provider.name} does not support command "${name}".`)
+ }) as any
+
wrapSerializedConfig(): SerializedConfig {
const config = wrapConfig(this.project.serializedConfig)
config.env ??= {}
@@ -69,6 +103,16 @@ export class ProjectBrowser implements IProjectBrowser {
return
}
this.provider = await getBrowserProvider(project.config.browser, project)
+ if (this.provider.initScripts) {
+ this.parent.initScripts = this.provider.initScripts
+ // make sure the script can be imported
+ this.provider.initScripts.forEach((script) => {
+ const allow = this.parent.vite.config.server.fs.allow
+ if (!allow.includes(script)) {
+ allow.push(script)
+ }
+ })
+ }
}
public parseErrorStacktrace(
diff --git a/packages/browser/src/node/projectParent.ts b/packages/browser/src/node/projectParent.ts
index 699bce4ec..9c46f75a2 100644
--- a/packages/browser/src/node/projectParent.ts
+++ b/packages/browser/src/node/projectParent.ts
@@ -38,6 +38,8 @@ export class ParentBrowserProject {
public matchersUrl: string
public stateJs: Promise | string
+ public initScripts: string[] = []
+
public commands: Record> = {}
public children: Set = new Set()
public vitest: Vitest
@@ -129,11 +131,6 @@ export class ParentBrowserProject {
).then(js => (this.injectorJs = js))
this.errorCatcherUrl = join('/@fs/', resolve(distRoot, 'client/error-catcher.js'))
- const builtinProviders = ['playwright', 'webdriverio', 'preview']
- const providerName = project.config.browser.provider?.name || 'preview'
- if (builtinProviders.includes(providerName)) {
- this.locatorsUrl = join('/@fs/', distRoot, 'locators', `${providerName}.js`)
- }
this.matchersUrl = join('/@fs/', distRoot, 'expect-element.js')
this.stateJs = readFile(
resolve(distRoot, 'state.js'),
diff --git a/packages/browser/src/node/providers/index.ts b/packages/browser/src/node/providers/index.ts
deleted file mode 100644
index a75c6cb36..000000000
--- a/packages/browser/src/node/providers/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { playwright } from './playwright'
-export { preview } from './preview'
-export { webdriverio } from './webdriverio'
diff --git a/packages/browser/src/node/rpc.ts b/packages/browser/src/node/rpc.ts
index 16b84aea2..b6d24fb65 100644
--- a/packages/browser/src/node/rpc.ts
+++ b/packages/browser/src/node/rpc.ts
@@ -5,7 +5,6 @@ import type { BrowserCommandContext, ResolveSnapshotPathHandlerContext, TestProj
import type { WebSocket } from 'ws'
import type { WebSocketBrowserEvents, WebSocketBrowserHandlers } from '../types'
import type { ParentBrowserProject } from './projectParent'
-import type { WebdriverBrowserProvider } from './providers/webdriverio'
import type { BrowserServerState } from './state'
import { existsSync, promises as fs } from 'node:fs'
import { AutomockedModule, AutospiedModule, ManualMockedModule, RedirectedModule } from '@vitest/mocker'
@@ -220,7 +219,7 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke
return vitest.state.getCountOfFailedTests()
},
async wdioSwitchContext(direction) {
- const provider = project.browser!.provider as WebdriverBrowserProvider
+ const provider = project.browser!.provider
if (!provider) {
throw new Error('Commands are only available for browser tests.')
}
@@ -228,10 +227,10 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke
throw new Error('Switch context is only available for WebDriverIO provider.')
}
if (direction === 'iframe') {
- await provider.switchToTestFrame()
+ await (provider as any).switchToTestFrame()
}
else {
- await provider.switchToMainFrame()
+ await (provider as any).switchToMainFrame()
}
},
async triggerCommand(sessionId, command, testPath, payload) {
@@ -240,10 +239,6 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke
if (!provider) {
throw new Error('Commands are only available for browser tests.')
}
- const commands = globalServer.commands
- if (!commands || !commands[command]) {
- throw new Error(`Unknown command "${command}".`)
- }
const context = Object.assign(
{
testPath,
@@ -251,10 +246,21 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke
provider,
contextId: sessionId,
sessionId,
+ triggerCommand: (name: string, ...args: any[]) => {
+ return project.browser!.triggerCommand(
+ name as any,
+ context,
+ ...args,
+ )
+ },
},
provider.getCommandsContext(sessionId),
) as any as BrowserCommandContext
- return await commands[command](context, ...payload)
+ return await project.browser!.triggerCommand(
+ command as any,
+ context,
+ ...payload,
+ )
},
resolveMock(rawId, importer, options) {
return mockResolver.resolveMock(rawId, importer, options)
diff --git a/packages/browser/src/node/utils.ts b/packages/browser/src/node/utils.ts
index 306d82920..131c640f1 100644
--- a/packages/browser/src/node/utils.ts
+++ b/packages/browser/src/node/utils.ts
@@ -1,9 +1,69 @@
-import type { BrowserProvider, BrowserProviderOption, ResolvedBrowserOptions, TestProject } from 'vitest/node'
+import type {
+ BrowserProvider,
+ ResolvedBrowserOptions,
+ ResolvedConfig,
+ TestProject,
+} from 'vitest/node'
+
+import { defaultKeyMap } from '@testing-library/user-event/dist/esm/keyboard/keyMap.js'
+import { parseKeyDef as tlParse } from '@testing-library/user-event/dist/esm/keyboard/parseKeyDef.js'
+import { basename, dirname, relative, resolve } from 'pathe'
+
+declare enum DOM_KEY_LOCATION {
+ STANDARD = 0,
+ LEFT = 1,
+ RIGHT = 2,
+ NUMPAD = 3,
+}
+
+interface keyboardKey {
+ /** Physical location on a keyboard */
+ code?: string
+ /** Character or functional key descriptor */
+ key?: string
+ /** Location on the keyboard for keys with multiple representation */
+ location?: DOM_KEY_LOCATION
+ /** Does the character in `key` require/imply AltRight to be pressed? */
+ altGr?: boolean
+ /** Does the character in `key` require/imply a shiftKey to be pressed? */
+ shift?: boolean
+}
+
+export function parseKeyDef(text: string): {
+ keyDef: keyboardKey
+ releasePrevious: boolean
+ releaseSelf: boolean
+ repeat: number
+}[] {
+ return tlParse(defaultKeyMap, text)
+}
export function replacer(code: string, values: Record): string {
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _)
}
+export function resolveScreenshotPath(
+ testPath: string,
+ name: string,
+ config: ResolvedConfig,
+ customPath: string | undefined,
+): string {
+ if (customPath) {
+ return resolve(dirname(testPath), customPath)
+ }
+ const dir = dirname(testPath)
+ const base = basename(testPath)
+ if (config.browser.screenshotDirectory) {
+ return resolve(
+ config.browser.screenshotDirectory,
+ relative(config.root, dir),
+ base,
+ name,
+ )
+ }
+ return resolve(dir, '__screenshots__', base, name)
+}
+
export async function getBrowserProvider(
options: ResolvedBrowserOptions,
project: TestProject,
@@ -15,19 +75,8 @@ export async function getBrowserProvider(
`${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`,
)
}
- if (
- // nothing is provided by default
- options.provider == null
- // the provider is provided via `--browser.provider=playwright`
- // or the config was serialized, but we can infer the factory by the name
- || ('_cli' in options.provider && typeof options.provider.factory !== 'function')
- ) {
- const providers = await import('./providers/index')
- const name = (options.provider?.name || 'preview') as 'preview' | 'webdriverio' | 'playwright'
- if (!(name in providers)) {
- throw new Error(`Unknown browser provider "${name}". Available providers: ${Object.keys(providers).join(', ')}.`)
- }
- return (providers[name] as (options?: object) => BrowserProviderOption)(options.provider?.options).factory(project)
+ if (options.provider == null) {
+ throw new Error(`Browser Mode requires the "provider" to always be specified.`)
}
const supportedBrowsers = options.provider.supportedBrowser || []
if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
@@ -37,10 +86,10 @@ export async function getBrowserProvider(
}". Supported browsers: ${supportedBrowsers.join(', ')}.`,
)
}
- if (typeof options.provider.factory !== 'function') {
- throw new TypeError(`The "${name}" browser provider does not provide a "factory" function. Received ${typeof options.provider.factory}.`)
+ if (typeof options.provider.providerFactory !== 'function') {
+ throw new TypeError(`The "${name}" browser provider does not provide a "providerFactory" function. Received ${typeof options.provider.providerFactory}.`)
}
- return options.provider.factory(project)
+ return options.provider.providerFactory(project)
}
export function slash(path: string): string {
diff --git a/packages/browser/utils.d.ts b/packages/browser/utils.d.ts
index 72d08cce6..652144c38 100644
--- a/packages/browser/utils.d.ts
+++ b/packages/browser/utils.d.ts
@@ -1,21 +1,21 @@
-// should be in sync with tester/public-utils.ts
-// we cannot bundle it because vitest depend on the @vitest/browser and vice versa
-// fortunately, the file is quite small
-
-import { LocatorSelectors, Locator } from '@vitest/browser/context'
+import { LocatorSelectors, Locator } from './context'
import { StringifyOptions } from 'vitest/internal/browser'
export type PrettyDOMOptions = Omit
+/** @deprecated use `import('vitest/browser').utils.getElementLocatorSelectors` instead */
export declare function getElementLocatorSelectors(element: Element): LocatorSelectors
+/** @deprecated use `import('vitest/browser').utils.debug` instead */
export declare function debug(
el?: Element | Locator | null | (Element | Locator)[],
maxLength?: number,
options?: PrettyDOMOptions,
): void
+/** @deprecated use `import('vitest/browser').utils.prettyDOM` instead */
export declare function prettyDOM(
dom?: Element | Locator | undefined | null,
maxLength?: number,
prettyFormatOptions?: PrettyDOMOptions,
): string
+/** @deprecated use `import('vitest/browser').utils.getElementError` instead */
export declare function getElementError(selector: string, container?: Element): Error
diff --git a/packages/coverage-v8/src/browser.ts b/packages/coverage-v8/src/browser.ts
index e1a668df7..7b93a345f 100644
--- a/packages/coverage-v8/src/browser.ts
+++ b/packages/coverage-v8/src/browser.ts
@@ -1,6 +1,6 @@
import type { CoverageProviderModule } from 'vitest/node'
import type { V8CoverageProvider } from './provider'
-import { cdp } from '@vitest/browser/context'
+import { cdp } from 'vitest/browser'
import { loadProvider } from './load-provider'
const session = cdp()
diff --git a/packages/coverage-v8/tsconfig.json b/packages/coverage-v8/tsconfig.json
index 8ff7219b1..93e30d6aa 100644
--- a/packages/coverage-v8/tsconfig.json
+++ b/packages/coverage-v8/tsconfig.json
@@ -2,7 +2,6 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"moduleResolution": "Bundler",
- "types": ["@vitest/browser/providers/playwright"],
"isolatedDeclarations": true
},
"include": ["./src/**/*.ts"],
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 2b72cc701..f8c551b06 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -66,6 +66,7 @@
"@types/ws": "catalog:",
"@unocss/reset": "catalog:",
"@vitejs/plugin-vue": "catalog:",
+ "@vitest/browser-playwright": "workspace:*",
"@vitest/runner": "workspace:*",
"@vitest/ws-client": "workspace:*",
"@vue/test-utils": "^2.4.6",
diff --git a/packages/ui/vitest.config.ts b/packages/ui/vitest.config.ts
index 1c3e3e320..075a3a10d 100644
--- a/packages/ui/vitest.config.ts
+++ b/packages/ui/vitest.config.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { mergeConfig } from 'vite'
import { defineConfig } from 'vitest/config'
import viteConfig from './vite.config'
diff --git a/packages/utils/src/source-map.ts b/packages/utils/src/source-map.ts
index c8ca1a2ac..69e9484c1 100644
--- a/packages/utils/src/source-map.ts
+++ b/packages/utils/src/source-map.ts
@@ -32,6 +32,9 @@ const stackIgnorePatterns: (string | RegExp)[] = [
'/deps/@vitest',
'/deps/loupe',
'/deps/chai',
+ '/browser-playwright/dist/locators.js',
+ '/browser-webdriverio/dist/locators.js',
+ '/browser-preview/dist/locators.js',
/node:\w+/,
/__vitest_test__/,
/__vitest_browser__/,
diff --git a/packages/vitest/browser/context.d.ts b/packages/vitest/browser/context.d.ts
new file mode 100644
index 000000000..6819fb7f4
--- /dev/null
+++ b/packages/vitest/browser/context.d.ts
@@ -0,0 +1,6 @@
+// @ts-ignore -- @vitest/browser-playwright might not be installed
+export * from '@vitest/browser-playwright/context'
+// @ts-ignore -- @vitest/browser-webdriverio might not be installed
+export * from '@vitest/browser-webdriverio/context'
+// @ts-ignore -- @vitest/browser-preview might not be installed
+export * from '@vitest/browser-preview/context'
diff --git a/packages/vitest/browser/context.js b/packages/vitest/browser/context.js
new file mode 100644
index 000000000..17119bb01
--- /dev/null
+++ b/packages/vitest/browser/context.js
@@ -0,0 +1,20 @@
+// Vitest resolves "vitest/browser" as a virtual module instead
+
+// fake exports for static analysis
+export const page = null
+export const server = null
+export const userEvent = null
+export const cdp = null
+export const commands = null
+export const locators = null
+export const utils = null
+
+const pool = globalThis.__vitest_worker__?.ctx?.pool
+
+throw new Error(
+ // eslint-disable-next-line prefer-template
+ 'vitest/browser can be imported only inside the Browser Mode. '
+ + (pool
+ ? `Your test is running in ${pool} pool. Make sure your regular tests are excluded from the "test.include" glob pattern.`
+ : 'Instead, it was imported outside of Vitest.'),
+)
diff --git a/packages/vitest/package.json b/packages/vitest/package.json
index 36d6c3d14..f88e98a8e 100644
--- a/packages/vitest/package.json
+++ b/packages/vitest/package.json
@@ -39,6 +39,10 @@
"default": "./index.cjs"
}
},
+ "./browser": {
+ "types": "./browser/context.d.ts",
+ "default": "./browser/context.js"
+ },
"./package.json": "./package.json",
"./optional-types.js": {
"types": "./optional-types.d.ts"
@@ -114,6 +118,7 @@
"*.d.ts",
"*.mjs",
"bin",
+ "browser",
"dist"
],
"engines": {
@@ -127,7 +132,9 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "workspace:*",
+ "@vitest/browser-playwright": "workspace:*",
+ "@vitest/browser-preview": "workspace:*",
+ "@vitest/browser-webdriverio": "workspace:*",
"@vitest/ui": "workspace:*",
"happy-dom": "*",
"jsdom": "*"
@@ -142,7 +149,13 @@
"@types/node": {
"optional": true
},
- "@vitest/browser": {
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
"optional": true
},
"@vitest/ui": {
@@ -192,6 +205,7 @@
"@types/picomatch": "^4.0.2",
"@types/prompts": "^2.4.9",
"@types/sinonjs__fake-timers": "^8.1.5",
+ "@vitest/browser-playwright": "workspace:*",
"acorn-walk": "catalog:",
"birpc": "catalog:",
"cac": "catalog:",
diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js
index f4d3a7921..d5288bee7 100644
--- a/packages/vitest/rollup.config.js
+++ b/packages/vitest/rollup.config.js
@@ -70,6 +70,7 @@ const external = [
'node:console',
'inspector',
'vitest/optional-types.js',
+ 'vitest/browser',
'vite/module-runner',
'@vitest/mocker',
'@vitest/mocker/node',
diff --git a/packages/vitest/src/create/browser/creator.ts b/packages/vitest/src/create/browser/creator.ts
index 6563c2868..16ee157d9 100644
--- a/packages/vitest/src/create/browser/creator.ts
+++ b/packages/vitest/src/create/browser/creator.ts
@@ -41,27 +41,6 @@ function getBrowserNames(provider: BrowserBuiltinProvider) {
}
}
-function getProviderPackageNames(provider: BrowserBuiltinProvider) {
- switch (provider) {
- case 'webdriverio':
- return {
- types: '@vitest/browser/providers/webdriverio',
- pkg: 'webdriverio',
- }
- case 'playwright':
- return {
- types: '@vitest/browser/providers/playwright',
- pkg: 'playwright',
- }
- case 'preview':
- return {
- types: '@vitest/browser/matchers',
- pkg: null,
- }
- }
- throw new Error(`Unsupported provider: ${provider}`)
-}
-
function getFramework(): prompt.Choice[] {
return [
{
@@ -156,15 +135,6 @@ function getFrameworkPluginPackage(framework: string) {
return null
}
-async function updateTsConfig(type: string | undefined | null) {
- if (type == null) {
- return
- }
- const msg = `Add "${c.bold(type)}" to your tsconfig.json "${c.bold('compilerOptions.types')}" field to have better intellisense support.`
- log()
- log(c.yellow('◼'), c.yellow(msg))
-}
-
function getLanguageOptions(): prompt.Choice[] {
return [
{
@@ -295,7 +265,7 @@ async function generateFrameworkConfigFile(options: {
const configContent = [
`import { defineConfig } from 'vitest/config'`,
- `import { ${options.provider} } from '@vitest/browser/providers/${options.provider}'`,
+ `import { ${options.provider} } from '@vitest/browser-${options.provider}'`,
options.frameworkPlugin ? frameworkImport : null,
``,
'export default defineConfig({',
@@ -433,7 +403,7 @@ export async function create(): Promise {
}
const dependenciesToInstall = [
- '@vitest/browser',
+ `@vitest/browser-${provider}`,
]
const frameworkPackage = getFrameworkTestPackage(framework)
@@ -441,10 +411,6 @@ export async function create(): Promise {
dependenciesToInstall.push(frameworkPackage)
}
- const providerPkg = getProviderPackageNames(provider)
- if (providerPkg.pkg) {
- dependenciesToInstall.push(providerPkg.pkg)
- }
const frameworkPlugin = getFrameworkPluginPackage(framework)
if (frameworkPlugin) {
dependenciesToInstall.push(frameworkPlugin)
@@ -514,11 +480,6 @@ export async function create(): Promise {
})
}
- // TODO: can we do this ourselves?
- if (lang === 'ts') {
- await updateTsConfig(providerPkg?.types)
- }
-
log()
const exampleTestFile = await generateExampleFiles(framework, lang)
log(c.green('✔'), 'Created example test file in', c.bold(relative(process.cwd(), exampleTestFile)))
diff --git a/packages/vitest/src/node/config/resolveConfig.ts b/packages/vitest/src/node/config/resolveConfig.ts
index b8e67fc63..d64d5c4b4 100644
--- a/packages/vitest/src/node/config/resolveConfig.ts
+++ b/packages/vitest/src/node/config/resolveConfig.ts
@@ -253,21 +253,30 @@ export function resolveConfig(
const browser = resolved.browser
if (browser.enabled) {
- if (!browser.name && !browser.instances) {
- throw new Error(`Vitest Browser Mode requires "browser.name" (deprecated) or "browser.instances" options, none were set.`)
+ const instances = browser.instances
+ if (!browser.instances) {
+ browser.instances = []
}
- const instances = browser.instances
- if (browser.name && browser.instances) {
+ // use `chromium` by default when the preview provider is specified
+ // for a smoother experience. if chromium is not available, it will
+ // open the default browser anyway
+ if (!browser.instances.length && browser.provider?.name === 'preview') {
+ browser.instances = [{ browser: 'chromium' }]
+ }
+
+ if (browser.name && instances?.length) {
// --browser=chromium filters configs to a single one
browser.instances = browser.instances.filter(instance => instance.browser === browser.name)
- }
- if (browser.instances && !browser.instances.length) {
- throw new Error([
- `"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`,
- browser.name && instances?.length ? ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map(c => c.browser).join(', ')}). Did you mean to use another name?` : '',
- ].join(''))
+ // if `instances` were defined, but now they are empty,
+ // let's throw an error because the filter is invalid
+ if (!browser.instances.length) {
+ throw new Error([
+ `"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`,
+ ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map(c => c.browser).join(', ')}). Did you mean to use another name?`,
+ ].join(''))
+ }
}
}
@@ -750,12 +759,8 @@ export function resolveConfig(
resolved.browser.locators ??= {} as any
resolved.browser.locators.testIdAttribute ??= 'data-testid'
- if (resolved.browser.enabled && stdProvider === 'stackblitz') {
- resolved.browser.provider = undefined // reset to "preview"
- }
-
if (typeof resolved.browser.provider === 'string') {
- const source = `@vitest/browser/providers/${resolved.browser.provider}`
+ const source = `@vitest/browser-${resolved.browser.provider}`
throw new TypeError(
'The `browser.provider` configuration was changed to accept a factory instead of a string. '
+ `Add an import of "${resolved.browser.provider}" from "${source}" instead. See: https://vitest.dev/guide/browser/config#provider`,
@@ -763,6 +768,10 @@ export function resolveConfig(
}
const isPreview = resolved.browser.provider?.name === 'preview'
+
+ if (!isPreview && resolved.browser.enabled && stdProvider === 'stackblitz') {
+ throw new Error(`stackblitz environment does not support the ${resolved.browser.provider?.name} provider. Please, use "@vitest/browser-preview" instead.`)
+ }
if (isPreview && resolved.browser.screenshotFailures === true) {
console.warn(c.yellow(
[
diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts
index 47dfcbb13..b2b5b3334 100644
--- a/packages/vitest/src/node/core.ts
+++ b/packages/vitest/src/node/core.ts
@@ -273,7 +273,11 @@ export class Vitest {
throw new Error(`No projects matched the filter "${filter}".`)
}
else {
- throw new Error(`Vitest wasn't able to resolve any project.`)
+ let error = `Vitest wasn't able to resolve any project.`
+ if (this.config.browser.enabled && !this.config.browser.instances?.length) {
+ error += ` Please, check that you specified the "browser.instances" option.`
+ }
+ throw new Error(error)
}
}
diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts
index c28039586..4c9f2d343 100644
--- a/packages/vitest/src/node/logger.ts
+++ b/packages/vitest/src/node/logger.ts
@@ -247,7 +247,7 @@ export class Logger {
const output = project.isRootProject()
? ''
: formatProjectName(project)
- const provider = project.browser.provider.name
+ const provider = project.browser.provider?.name
const providerString = provider === 'preview' ? '' : ` by ${c.reset(c.bold(provider))}`
this.log(
c.dim(
diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts
index c7040b98b..ce71d3bdd 100644
--- a/packages/vitest/src/node/pool.ts
+++ b/packages/vitest/src/node/pool.ts
@@ -8,6 +8,7 @@ import { resolve } from 'pathe'
import { version as viteVersion } from 'vite'
import { rootDir } from '../paths'
import { isWindows } from '../utils/env'
+import { createBrowserPool } from './pools/browser'
import { createForksPool } from './pools/forks'
import { createThreadsPool } from './pools/threads'
import { createTypecheckPool } from './pools/typecheck'
@@ -175,13 +176,6 @@ export function createPool(ctx: Vitest): ProcessPool {
return getConcurrentPool(pool, () => resolveCustomPool(pool))
}
- function getBrowserPool() {
- return getConcurrentPool('browser', async () => {
- const { createBrowserPool } = await import('@vitest/browser')
- return createBrowserPool(ctx)
- })
- }
-
const groupedSpecifications: Record = {}
const groups = new Set()
@@ -191,6 +185,7 @@ export function createPool(ctx: Vitest): ProcessPool {
threads: specs => createThreadsPool(ctx, options, specs),
forks: specs => createForksPool(ctx, options, specs),
typescript: () => createTypecheckPool(ctx),
+ browser: () => createBrowserPool(ctx),
}
for (const spec of files) {
@@ -255,11 +250,6 @@ export function createPool(ctx: Vitest): ProcessPool {
return pools[pool]
}
- if (pool === 'browser') {
- pools.browser ??= await getBrowserPool()
- return pools.browser[method](specs, invalidate)
- }
-
const poolHandler = await getCustomPool(pool)
pools[poolHandler.name] ??= poolHandler
return poolHandler[method](specs, invalidate)
diff --git a/packages/browser/src/node/pool.ts b/packages/vitest/src/node/pools/browser.ts
similarity index 97%
rename from packages/browser/src/node/pool.ts
rename to packages/vitest/src/node/pools/browser.ts
index 994b88b3c..a6b277c56 100644
--- a/packages/browser/src/node/pool.ts
+++ b/packages/vitest/src/node/pools/browser.ts
@@ -1,17 +1,14 @@
import type { DeferPromise } from '@vitest/utils/helpers'
-import type {
- BrowserProvider,
- ProcessPool,
- TestProject,
- TestSpecification,
- Vitest,
-} from 'vitest/node'
+import type { Vitest } from '../core'
+import type { ProcessPool } from '../pool'
+import type { TestProject } from '../project'
+import type { TestSpecification } from '../spec'
+import type { BrowserProvider } from '../types/browser'
import crypto from 'node:crypto'
import * as nodeos from 'node:os'
-import { performance } from 'node:perf_hooks'
import { createDefer } from '@vitest/utils/helpers'
import { stringify } from 'flatted'
-import { createDebugger } from 'vitest/node'
+import { createDebugger } from '../../utils/debugger'
const debug = createDebugger('vitest:browser:pool')
@@ -310,7 +307,6 @@ class BrowserPool {
if (!this._promise) {
throw new Error(`Unexpected empty queue`)
}
- const startTime = performance.now()
const orchestrator = this.getOrchestrator(sessionId)
debug?.('[%s] run test %s', sessionId, file)
@@ -324,7 +320,6 @@ class BrowserPool {
// this will be parsed by the test iframe, not the orchestrator
// so we need to stringify it first to avoid double serialization
providedContext: this._providedContext || '[{}]',
- startTime,
},
)
.then(() => {
diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts
index b98c26ed1..53ff73a8b 100644
--- a/packages/vitest/src/node/project.ts
+++ b/packages/vitest/src/node/project.ts
@@ -449,39 +449,23 @@ export class TestProject {
/** @internal */
public _parent?: TestProject
/** @internal */
- _initParentBrowser = deduped(async () => {
+ _initParentBrowser = deduped(async (childProject: TestProject) => {
if (!this.isBrowserEnabled() || this._parentBrowser) {
return
}
- await this.vitest.packageInstaller.ensureInstalled(
- '@vitest/browser',
- this.config.root,
- this.vitest.version,
- )
- const { createBrowserServer, distRoot } = await import('@vitest/browser')
- let cacheDir: string
- const browser = await createBrowserServer(
- this,
- this.vite.config.configFile,
- [
- {
- name: 'vitest:browser-cacheDir',
- configResolved(config) {
- cacheDir = config.cacheDir
- },
- },
- ...MocksPlugins({
- filter(id) {
- if (id.includes(distRoot) || id.includes(cacheDir)) {
- return false
- }
- return true
- },
- }),
- MetaEnvReplacerPlugin(),
- ],
- [CoverageTransform(this.vitest)],
- )
+ const provider = this.config.browser.provider || childProject.config.browser.provider
+ if (provider == null) {
+ throw new Error(`Proider was not specified in the "browser.provider" setting. Please, pass down playwright(), webdriverio() or preview() from "@vitest/browser-playwright", "@vitest/browser-webdriverio" or "@vitest/browser-preview" package respectively.`)
+ }
+ if (typeof provider.serverFactory !== 'function') {
+ throw new TypeError(`The browser provider options do not return a "serverFactory" function. Are you using the latest "@vitest/browser-${provider.name}" package?`)
+ }
+ const browser = await provider.serverFactory({
+ project: this,
+ mocksPlugins: options => MocksPlugins(options),
+ metaEnvReplacer: () => MetaEnvReplacerPlugin(),
+ coveragePlugin: () => CoverageTransform(this.vitest),
+ })
this._parentBrowser = browser
if (this.config.browser.ui) {
setup(this.vitest, browser.vite)
@@ -490,7 +474,7 @@ export class TestProject {
/** @internal */
_initBrowserServer = deduped(async () => {
- await this._parent?._initParentBrowser()
+ await this._parent?._initParentBrowser(this)
if (!this.browser && this._parent?._parentBrowser) {
this.browser = this._parent._parentBrowser.spawn(this)
diff --git a/packages/vitest/src/node/projects/resolveProjects.ts b/packages/vitest/src/node/projects/resolveProjects.ts
index 1f08dd168..f03418455 100644
--- a/packages/vitest/src/node/projects/resolveProjects.ts
+++ b/packages/vitest/src/node/projects/resolveProjects.ts
@@ -15,10 +15,8 @@ import { basename, dirname, relative, resolve } from 'pathe'
import { glob, isDynamicPattern } from 'tinyglobby'
import { mergeConfig } from 'vite'
import { configFiles as defaultConfigFiles } from '../../constants'
-import { isTTY } from '../../utils/env'
import { VitestFilteredOutProjectError } from '../errors'
import { initializeProject, TestProject } from '../project'
-import { withLabel } from '../reporters/renderers/utils'
// vitest.config.*
// vite.config.*
@@ -190,25 +188,9 @@ export async function resolveBrowserProjects(
return
}
const instances = project.config.browser.instances || []
- const browser = project.config.browser.name
- if (instances.length === 0 && browser) {
- instances.push({
- browser,
- name: project.name ? `${project.name} (${browser})` : browser,
- })
- vitest.logger.warn(
- withLabel(
- 'yellow',
- 'Vitest',
- [
- `No browser "instances" were defined`,
- project.name ? ` for the "${project.name}" project. ` : '. ',
- `Running tests in "${project.config.browser.name}" browser. `,
- 'The "browser.name" field is deprecated since Vitest 3. ',
- 'Read more: https://vitest.dev/guide/browser/config#browser-instances',
- ].filter(Boolean).join(''),
- ),
- )
+ if (instances.length === 0) {
+ removeProjects.add(project)
+ return
}
const originalName = project.config.name
// if original name is in the --project=name filter, keep all instances
@@ -237,6 +219,9 @@ export async function resolveBrowserProjects(
if (name == null) {
throw new Error(`The browser configuration must have a "name" property. This is a bug in Vitest. Please, open a new issue with reproduction`)
}
+ if (config.provider?.name != null && project.config.browser.provider?.name != null && config.provider?.name !== project.config.browser.provider?.name) {
+ throw new Error(`The instance cannot have a different provider from its parent. The "${name}" instance specifies "${config.provider?.name}" provider, but its parent has a "${project.config.browser.provider?.name}" provider.`)
+ }
if (names.has(name)) {
throw new Error(
@@ -257,36 +242,7 @@ export async function resolveBrowserProjects(
removeProjects.add(project)
})
- resolvedProjects = resolvedProjects.filter(project => !removeProjects.has(project))
-
- const headedBrowserProjects = resolvedProjects.filter((project) => {
- return project.config.browser.enabled && !project.config.browser.headless
- })
- if (headedBrowserProjects.length > 1) {
- const message = [
- `Found multiple projects that run browser tests in headed mode: "${headedBrowserProjects.map(p => p.name).join('", "')}".`,
- ` Vitest cannot run multiple headed browsers at the same time.`,
- ].join('')
- if (!isTTY) {
- throw new Error(`${message} Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option.`)
- }
- const prompts = await import('prompts')
- const { projectName } = await prompts.default({
- type: 'select',
- name: 'projectName',
- choices: headedBrowserProjects.map(project => ({
- title: project.name,
- value: project.name,
- })),
- message: `${message} Select a single project to run or cancel and run tests with "headless: true" option. Note that you can also start tests with --browser=name or --project=name flag.`,
- })
- if (!projectName) {
- throw new Error('The test run was aborted.')
- }
- return resolvedProjects.filter(project => project.name === projectName)
- }
-
- return resolvedProjects
+ return resolvedProjects.filter(project => !removeProjects.has(project))
}
function cloneConfig(project: TestProject, { browser, ...config }: BrowserInstanceOption) {
diff --git a/packages/vitest/src/node/types/browser.ts b/packages/vitest/src/node/types/browser.ts
index 7c9a34dee..dcca5b43f 100644
--- a/packages/vitest/src/node/types/browser.ts
+++ b/packages/vitest/src/node/types/browser.ts
@@ -2,7 +2,8 @@ import type { MockedModule } from '@vitest/mocker'
import type { CancelReason } from '@vitest/runner'
import type { Awaitable, ParsedStack, TestError } from '@vitest/utils'
import type { StackTraceParserOptions } from '@vitest/utils/source-map'
-import type { ViteDevServer } from 'vite'
+import type { Plugin, ViteDevServer } from 'vite'
+import type { BrowserCommands } from 'vitest/browser'
import type { BrowserTraceViewMode } from '../../runtime/config'
import type { BrowserTesterOptions } from '../../types/browser'
import type { TestProject } from '../project'
@@ -25,12 +26,25 @@ export interface BrowserProviderOption {
name: string
supportedBrowser?: ReadonlyArray
options: Options
- factory: (project: TestProject) => BrowserProvider
+ providerFactory: (project: TestProject) => BrowserProvider
+ serverFactory: BrowserServerFactory
+}
+
+export interface BrowserServerOptions {
+ project: TestProject
+ coveragePlugin: () => Plugin
+ mocksPlugins: (options: { filter: (id: string) => boolean }) => Plugin[]
+ metaEnvReplacer: () => Plugin
+}
+
+export interface BrowserServerFactory {
+ (otpions: BrowserServerOptions): Promise
}
export interface BrowserProvider {
name: string
mocker?: BrowserModuleMocker
+ readonly initScripts?: string[]
/**
* @experimental opt-in into file parallelisation
*/
@@ -42,6 +56,7 @@ export interface BrowserProvider {
}
export type BrowserBuiltinProvider = 'webdriverio' | 'playwright' | 'preview'
+export interface _BrowserNames {}
type UnsupportedProperties
= | 'browser'
@@ -72,14 +87,16 @@ export interface BrowserInstanceOption extends
| 'testerHtmlPath'
| 'screenshotDirectory'
| 'screenshotFailures'
- | 'provider'
> {
/**
* Name of the browser
*/
- browser: string
+ browser: keyof _BrowserNames extends never
+ ? string
+ : _BrowserNames[keyof _BrowserNames]
name?: string
+ provider?: BrowserProviderOption
}
export interface BrowserConfigOptions {
@@ -100,10 +117,21 @@ export interface BrowserConfigOptions {
/**
* Configurations for different browser setups
*/
- instances: BrowserInstanceOption[]
+ instances?: BrowserInstanceOption[]
/**
* Browser provider
+ * @example
+ * ```ts
+ * import { playwright } from '@vitest/browser-playwright'
+ * export default defineConfig({
+ * test: {
+ * browser: {
+ * provider: playwright(),
+ * },
+ * },
+ * })
+ * ```
*/
provider?: BrowserProviderOption
@@ -222,7 +250,7 @@ export interface BrowserConfigOptions {
/**
* Commands that will be executed on the server
- * via the browser `import("@vitest/browser/context").commands` API.
+ * via the browser `import("vitest/browser").commands` API.
* @see {@link https://vitest.dev/guide/browser/commands}
*/
commands?: Record>
@@ -264,6 +292,10 @@ export interface BrowserCommandContext {
provider: BrowserProvider
project: TestProject
sessionId: string
+ triggerCommand: (
+ name: K,
+ ...args: Parameters
+ ) => ReturnType
}
export interface BrowserServerStateSession {
@@ -285,6 +317,7 @@ export interface BrowserServerState {
export interface ParentProjectBrowser {
spawn: (project: TestProject) => ProjectBrowser
+ vite: ViteDevServer
}
export interface ProjectBrowser {
@@ -295,10 +328,22 @@ export interface ProjectBrowser {
initBrowserProvider: (project: TestProject) => Promise
parseStacktrace: (stack: string) => ParsedStack[]
parseErrorStacktrace: (error: TestError, options?: StackTraceParserOptions) => ParsedStack[]
+ registerCommand: (
+ name: K,
+ cb: BrowserCommand<
+ Parameters,
+ ReturnType
+ >
+ ) => void
+ triggerCommand: (
+ name: K,
+ context: BrowserCommandContext,
+ ...args: Parameters
+ ) => ReturnType
}
-export interface BrowserCommand {
- (context: BrowserCommandContext, ...payload: Payload): Awaitable
+export interface BrowserCommand {
+ (context: BrowserCommandContext, ...payload: Payload): Awaitable
}
export interface BrowserScript {
diff --git a/packages/vitest/src/public/browser.ts b/packages/vitest/src/public/browser.ts
index 02ec2a0ce..0a01e19a2 100644
--- a/packages/vitest/src/public/browser.ts
+++ b/packages/vitest/src/public/browser.ts
@@ -3,7 +3,6 @@ export {
stopCoverageInsideWorker,
takeCoverageInsideWorker,
} from '../integrations/coverage'
-
export {
loadDiffConfig,
loadSnapshotSerializers,
@@ -24,3 +23,13 @@ export {
getOriginalPosition,
} from '@vitest/utils/source-map'
export { getSafeTimers, setSafeTimers } from '@vitest/utils/timers'
+/**
+ * @internal
+ */
+export const __INTERNAL: {
+ _asLocator: (lang: 'javascript', selector: string) => string
+ _createLocator: (selector: string) => any
+ _extendedMethods: Set
+} = {
+ _extendedMethods: new Set(),
+} as any
diff --git a/packages/vitest/src/public/node.ts b/packages/vitest/src/public/node.ts
index 0f2cfc903..2f671b987 100644
--- a/packages/vitest/src/public/node.ts
+++ b/packages/vitest/src/public/node.ts
@@ -59,6 +59,7 @@ export { registerConsoleShortcuts } from '../node/stdin'
export type { BenchmarkUserOptions } from '../node/types/benchmark'
export type {
+ _BrowserNames,
BrowserBuiltinProvider,
BrowserCommand,
BrowserCommandContext,
@@ -69,6 +70,8 @@ export type {
BrowserProvider,
BrowserProviderOption,
BrowserScript,
+ BrowserServerFactory,
+ BrowserServerOptions,
BrowserServerState,
BrowserServerStateSession,
CDPSession,
diff --git a/packages/vitest/src/types/browser.ts b/packages/vitest/src/types/browser.ts
index 4e124e023..11d7c2122 100644
--- a/packages/vitest/src/types/browser.ts
+++ b/packages/vitest/src/types/browser.ts
@@ -4,5 +4,4 @@ export interface BrowserTesterOptions {
method: TestExecutionMethod
files: string[]
providedContext: string
- startTime: number
}
diff --git a/packages/vitest/src/utils/graph.ts b/packages/vitest/src/utils/graph.ts
index 875307dc6..d3afaa759 100644
--- a/packages/vitest/src/utils/graph.ts
+++ b/packages/vitest/src/utils/graph.ts
@@ -18,7 +18,7 @@ export async function getModuleGraph(
if (!mod || !mod.id) {
return
}
- if (mod.id === '\0@vitest/browser/context') {
+ if (mod.id === '\0vitest/browser') {
return
}
if (seen.has(mod)) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e72e38aa9..680c99f3a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -120,6 +120,9 @@ catalogs:
overrides:
'@vitest/browser': workspace:*
+ '@vitest/browser-playwright': workspace:*
+ '@vitest/browser-preview': workspace:*
+ '@vitest/browser-webdriverio': workspace:*
'@vitest/ui': workspace:*
acorn: 8.11.3
mlly: ^1.8.0
@@ -351,9 +354,9 @@ importers:
specifier: ^3.3.1
version: 3.3.1
devDependencies:
- '@vitest/browser':
+ '@vitest/browser-playwright':
specifier: workspace:*
- version: link:../../packages/browser
+ version: link:../../packages/browser-playwright
jsdom:
specifier: latest
version: 27.0.0(postcss@8.5.6)
@@ -438,12 +441,6 @@ importers:
packages/browser:
dependencies:
- '@testing-library/dom':
- specifier: ^10.4.1
- version: 10.4.1
- '@testing-library/user-event':
- specifier: ^14.6.1
- version: 14.6.1(@testing-library/dom@10.4.1)
'@vitest/mocker':
specifier: workspace:*
version: link:../mocker
@@ -469,6 +466,9 @@ importers:
specifier: 'catalog:'
version: 8.18.3
devDependencies:
+ '@testing-library/user-event':
+ specifier: ^14.6.1
+ version: 14.6.1(@testing-library/dom@10.4.1)
'@types/pngjs':
specifier: ^6.0.5
version: 6.0.5
@@ -478,9 +478,6 @@ importers:
'@vitest/runner':
specifier: workspace:*
version: link:../runner
- '@wdio/types':
- specifier: ^9.19.2
- version: 9.19.2
birpc:
specifier: 'catalog:'
version: 2.5.0
@@ -496,6 +493,22 @@ importers:
pathe:
specifier: 'catalog:'
version: 2.0.3
+ vitest:
+ specifier: workspace:*
+ version: link:../vitest
+
+ packages/browser-playwright:
+ dependencies:
+ '@vitest/browser':
+ specifier: workspace:*
+ version: link:../browser
+ '@vitest/mocker':
+ specifier: workspace:*
+ version: link:../mocker
+ tinyrainbow:
+ specifier: 'catalog:'
+ version: 3.0.3
+ devDependencies:
playwright:
specifier: ^1.55.0
version: 1.55.0
@@ -505,6 +518,35 @@ importers:
vitest:
specifier: workspace:*
version: link:../vitest
+
+ packages/browser-preview:
+ dependencies:
+ '@testing-library/dom':
+ specifier: ^10.4.1
+ version: 10.4.1
+ '@testing-library/user-event':
+ specifier: ^14.6.1
+ version: 14.6.1(@testing-library/dom@10.4.1)
+ '@vitest/browser':
+ specifier: workspace:*
+ version: link:../browser
+ devDependencies:
+ vitest:
+ specifier: workspace:*
+ version: link:../vitest
+
+ packages/browser-webdriverio:
+ dependencies:
+ '@vitest/browser':
+ specifier: workspace:*
+ version: link:../browser
+ devDependencies:
+ '@wdio/types':
+ specifier: ^9.19.2
+ version: 9.19.2
+ vitest:
+ specifier: workspace:*
+ version: link:../vitest
webdriverio:
specifier: ^9.19.2
version: 9.19.2
@@ -784,6 +826,9 @@ importers:
'@vitejs/plugin-vue':
specifier: 'catalog:'
version: 6.0.1(vite@7.1.5(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.30.1)(sass-embedded@1.93.0)(sass@1.93.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
+ '@vitest/browser-playwright':
+ specifier: workspace:*
+ version: link:../browser-playwright
'@vitest/runner':
specifier: workspace:*
version: link:../runner
@@ -903,9 +948,12 @@ importers:
packages/vitest:
dependencies:
- '@vitest/browser':
+ '@vitest/browser-preview':
specifier: workspace:*
- version: link:../browser
+ version: link:../browser-preview
+ '@vitest/browser-webdriverio':
+ specifier: workspace:*
+ version: link:../browser-webdriverio
'@vitest/expect':
specifier: workspace:*
version: link:../expect
@@ -1012,6 +1060,9 @@ importers:
'@types/sinonjs__fake-timers':
specifier: ^8.1.5
version: 8.1.5(patch_hash=0218b33f433e26861380c2b90c757bde6fea871cb988083c0bd4a9a1f6c00252)
+ '@vitest/browser-playwright':
+ specifier: workspace:*
+ version: link:../browser-playwright
acorn-walk:
specifier: 'catalog:'
version: 8.3.4
@@ -1104,6 +1155,15 @@ importers:
'@vitest/browser':
specifier: workspace:*
version: link:../../packages/browser
+ '@vitest/browser-playwright':
+ specifier: workspace:*
+ version: link:../../packages/browser-playwright
+ '@vitest/browser-preview':
+ specifier: workspace:*
+ version: link:../../packages/browser-preview
+ '@vitest/browser-webdriverio':
+ specifier: workspace:*
+ version: link:../../packages/browser-webdriverio
'@vitest/bundled-lib':
specifier: link:./bundled-lib
version: link:bundled-lib
@@ -1149,6 +1209,12 @@ importers:
'@vitejs/plugin-basic-ssl':
specifier: ^2.1.0
version: 2.1.0(vite@7.1.5(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.30.1)(sass-embedded@1.93.0)(sass@1.93.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1))
+ '@vitest/browser-playwright':
+ specifier: workspace:*
+ version: link:../../packages/browser-playwright
+ '@vitest/browser-preview':
+ specifier: workspace:*
+ version: link:../../packages/browser-preview
'@vitest/runner':
specifier: workspace:^
version: link:../../packages/runner
@@ -1173,6 +1239,15 @@ importers:
test/config:
devDependencies:
+ '@vitest/browser-playwright':
+ specifier: workspace:*
+ version: link:../../packages/browser-playwright
+ '@vitest/browser-preview':
+ specifier: workspace:*
+ version: link:../../packages/browser-preview
+ '@vitest/browser-webdriverio':
+ specifier: workspace:*
+ version: link:../../packages/browser-webdriverio
'@vitest/test-dep-conditions':
specifier: file:./deps/test-dep-conditions
version: file:test/config/deps/test-dep-conditions
@@ -1296,9 +1371,9 @@ importers:
'@vitejs/plugin-vue':
specifier: latest
version: 6.0.1(vite@7.1.5(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.30.1)(sass-embedded@1.93.0)(sass@1.93.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
- '@vitest/browser':
+ '@vitest/browser-playwright':
specifier: workspace:*
- version: link:../../packages/browser
+ version: link:../../packages/browser-playwright
'@vitest/coverage-istanbul':
specifier: workspace:*
version: link:../../packages/coverage-istanbul
@@ -1356,9 +1431,9 @@ importers:
test/dts-playwright:
devDependencies:
- '@vitest/browser':
+ '@vitest/browser-playwright':
specifier: workspace:*
- version: link:../../packages/browser
+ version: link:../../packages/browser-playwright
vitest:
specifier: workspace:*
version: link:../../packages/vitest
@@ -1481,9 +1556,12 @@ importers:
test/watch:
devDependencies:
- '@vitest/browser':
+ '@vitest/browser-playwright':
specifier: workspace:*
- version: link:../../packages/browser
+ version: link:../../packages/browser-playwright
+ '@vitest/browser-webdriverio':
+ specifier: workspace:*
+ version: link:../../packages/browser-webdriverio
vite:
specifier: ^7.1.5
version: 7.1.5(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.30.1)(sass-embedded@1.93.0)(sass@1.93.0)(terser@5.44.0)(tsx@4.20.5)(yaml@2.8.1)
@@ -1511,9 +1589,9 @@ importers:
test/workspaces-browser:
devDependencies:
- '@vitest/browser':
+ '@vitest/browser-playwright':
specifier: workspace:*
- version: link:../../packages/browser
+ version: link:../../packages/browser-playwright
vitest:
specifier: workspace:*
version: link:../../packages/vitest
diff --git a/test/browser/fixtures/broken-iframe/submit-form.test.ts b/test/browser/fixtures/broken-iframe/submit-form.test.ts
index 2b8f245d3..a3d76a9e2 100644
--- a/test/browser/fixtures/broken-iframe/submit-form.test.ts
+++ b/test/browser/fixtures/broken-iframe/submit-form.test.ts
@@ -1,4 +1,4 @@
-import { userEvent } from '@vitest/browser/context';
+import { userEvent } from 'vitest/browser';
import { test } from 'vitest';
test('submitting a form reloads the iframe with "?" query', async () => {
diff --git a/test/browser/fixtures/browser-crash/browser-crash.test.ts b/test/browser/fixtures/browser-crash/browser-crash.test.ts
index eccc9f375..7bff8d59a 100644
--- a/test/browser/fixtures/browser-crash/browser-crash.test.ts
+++ b/test/browser/fixtures/browser-crash/browser-crash.test.ts
@@ -1,7 +1,7 @@
-import { commands } from '@vitest/browser/context'
+import { commands } from 'vitest/browser'
import { it } from 'vitest'
-declare module '@vitest/browser/context' {
+declare module 'vitest/browser' {
interface BrowserCommands {
forceCrash: () => Promise
}
diff --git a/test/browser/fixtures/expect-dom/toHaveLength.test.ts b/test/browser/fixtures/expect-dom/toHaveLength.test.ts
index 17455f5ab..8734af726 100644
--- a/test/browser/fixtures/expect-dom/toHaveLength.test.ts
+++ b/test/browser/fixtures/expect-dom/toHaveLength.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest';
import { render } from './utils';
-import { page } from '@vitest/browser/context';
+import { page } from 'vitest/browser';
describe('.toHaveLength', () => {
test('accepts locator', async () => {
diff --git a/test/browser/fixtures/expect-dom/toMatchScreenshot.test.ts b/test/browser/fixtures/expect-dom/toMatchScreenshot.test.ts
index 8c1c92832..7ac7ced6a 100644
--- a/test/browser/fixtures/expect-dom/toMatchScreenshot.test.ts
+++ b/test/browser/fixtures/expect-dom/toMatchScreenshot.test.ts
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, test } from 'vitest'
import { extractToMatchScreenshotPaths, render } from './utils'
-import { page, server } from '@vitest/browser/context'
+import { page, server } from 'vitest/browser'
import { join } from 'pathe'
const blockSize = 19
diff --git a/test/browser/fixtures/failing/failing.test.ts b/test/browser/fixtures/failing/failing.test.ts
index 7dc7689a8..9f3e18d75 100644
--- a/test/browser/fixtures/failing/failing.test.ts
+++ b/test/browser/fixtures/failing/failing.test.ts
@@ -1,4 +1,4 @@
-import { page } from '@vitest/browser/context'
+import { page } from 'vitest/browser'
import { index } from '@vitest/bundled-lib'
import { expect, it } from 'vitest'
import { throwError } from './src/error'
diff --git a/test/browser/fixtures/locators-custom/basic.test.tsx b/test/browser/fixtures/locators-custom/basic.test.tsx
index ac2dabf83..e82d0846e 100644
--- a/test/browser/fixtures/locators-custom/basic.test.tsx
+++ b/test/browser/fixtures/locators-custom/basic.test.tsx
@@ -1,8 +1,8 @@
-import { type Locator, locators, page } from '@vitest/browser/context';
+import type { BrowserPage, Locator } from 'vitest/browser';
+import { locators, page, utils } from 'vitest/browser';
import { beforeEach, expect, test } from 'vitest';
-import { getElementLocatorSelectors } from '@vitest/browser/utils'
-declare module '@vitest/browser/context' {
+declare module 'vitest/browser' {
interface LocatorSelectors {
getByCustomTitle: (title: string) => Locator
getByNestedTitle: (title: string) => Locator
@@ -87,7 +87,7 @@ test('new added method works on the page', async () => {
})
test('locators are available from getElementLocatorSelectors', () => {
- const locators = getElementLocatorSelectors(document.body)
+ const locators = utils.getElementLocatorSelectors(document.body)
expect(locators.updateHtml).toBeTypeOf('function')
expect(locators.getByCustomTitle).toBeTypeOf('function')
diff --git a/test/browser/fixtures/locators/blog.test.tsx b/test/browser/fixtures/locators/blog.test.tsx
index 65faef58c..5502c3a9f 100644
--- a/test/browser/fixtures/locators/blog.test.tsx
+++ b/test/browser/fixtures/locators/blog.test.tsx
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest'
-import { page, userEvent } from '@vitest/browser/context'
+import { page, userEvent } from 'vitest/browser'
import Blog from '../../src/blog-app/blog'
test('renders blog posts', async () => {
diff --git a/test/browser/fixtures/locators/query.test.ts b/test/browser/fixtures/locators/query.test.ts
index 68a8d5f96..2c5e288bf 100644
--- a/test/browser/fixtures/locators/query.test.ts
+++ b/test/browser/fixtures/locators/query.test.ts
@@ -1,4 +1,4 @@
-import { page } from '@vitest/browser/context';
+import { page } from 'vitest/browser';
import { afterEach, describe, expect, test } from 'vitest';
afterEach(() => {
diff --git a/test/browser/fixtures/multiple-different-configs/basic.test.js b/test/browser/fixtures/multiple-different-configs/basic.test.js
index f5480f919..a9d31f958 100644
--- a/test/browser/fixtures/multiple-different-configs/basic.test.js
+++ b/test/browser/fixtures/multiple-different-configs/basic.test.js
@@ -1,5 +1,5 @@
import { test as baseTest, expect, inject } from 'vitest';
-import { server } from '@vitest/browser/context'
+import { server } from 'vitest/browser'
const test = baseTest.extend({
// chromium should inject the value as "true"
diff --git a/test/browser/fixtures/timeout-hooks/hooks-timeout.test.ts b/test/browser/fixtures/timeout-hooks/hooks-timeout.test.ts
index c7c51c55b..430a831cc 100644
--- a/test/browser/fixtures/timeout-hooks/hooks-timeout.test.ts
+++ b/test/browser/fixtures/timeout-hooks/hooks-timeout.test.ts
@@ -1,4 +1,4 @@
-import { page, server } from '@vitest/browser/context';
+import { page, server } from 'vitest/browser';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, onTestFailed, onTestFinished } from 'vitest';
describe.runIf(server.provider === 'playwright')('timeouts are failing correctly', () => {
diff --git a/test/browser/fixtures/timeout/timeout.test.ts b/test/browser/fixtures/timeout/timeout.test.ts
index 2ea998280..ec55d9bfb 100644
--- a/test/browser/fixtures/timeout/timeout.test.ts
+++ b/test/browser/fixtures/timeout/timeout.test.ts
@@ -1,4 +1,4 @@
-import { page } from '@vitest/browser/context';
+import { page } from 'vitest/browser';
import { afterEach, expect, test } from 'vitest';
afterEach(() => {
diff --git a/test/browser/fixtures/trace-view/vitest.config.ts b/test/browser/fixtures/trace-view/vitest.config.ts
index e7a19d7a1..ca61d24c7 100644
--- a/test/browser/fixtures/trace-view/vitest.config.ts
+++ b/test/browser/fixtures/trace-view/vitest.config.ts
@@ -1,6 +1,6 @@
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
diff --git a/test/browser/fixtures/user-event/cleanup-retry.test.ts b/test/browser/fixtures/user-event/cleanup-retry.test.ts
index a2e68d579..a0938152f 100644
--- a/test/browser/fixtures/user-event/cleanup-retry.test.ts
+++ b/test/browser/fixtures/user-event/cleanup-retry.test.ts
@@ -1,5 +1,5 @@
import { expect, onTestFinished, test } from 'vitest'
-import { userEvent } from '@vitest/browser/context'
+import { userEvent } from 'vitest/browser'
test('cleanup retry', { retry: 1 }, async (ctx) => {
let logs: any[] = [];
diff --git a/test/browser/fixtures/user-event/cleanup1.test.ts b/test/browser/fixtures/user-event/cleanup1.test.ts
index 9b6ef3996..ee88a9bab 100644
--- a/test/browser/fixtures/user-event/cleanup1.test.ts
+++ b/test/browser/fixtures/user-event/cleanup1.test.ts
@@ -1,5 +1,5 @@
import { expect, onTestFinished, test } from 'vitest'
-import { userEvent } from '@vitest/browser/context'
+import { userEvent } from 'vitest/browser'
test('cleanup1', async () => {
let logs: any[] = [];
diff --git a/test/browser/fixtures/user-event/cleanup2.test.ts b/test/browser/fixtures/user-event/cleanup2.test.ts
index 75e66d124..abf69feae 100644
--- a/test/browser/fixtures/user-event/cleanup2.test.ts
+++ b/test/browser/fixtures/user-event/cleanup2.test.ts
@@ -1,5 +1,5 @@
import { expect, onTestFinished, test } from 'vitest'
-import { userEvent } from '@vitest/browser/context'
+import { userEvent } from 'vitest/browser'
// test per-test-file cleanup just in case
diff --git a/test/browser/fixtures/user-event/clipboard.test.ts b/test/browser/fixtures/user-event/clipboard.test.ts
index 18f286a7e..0b65562e4 100644
--- a/test/browser/fixtures/user-event/clipboard.test.ts
+++ b/test/browser/fixtures/user-event/clipboard.test.ts
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest';
-import { page, userEvent } from '@vitest/browser/context';
+import { page, userEvent } from 'vitest/browser';
test('clipboard', async () => {
// make it smaller since webdriverio fails when scaled
diff --git a/test/browser/fixtures/user-event/keyboard.test.ts b/test/browser/fixtures/user-event/keyboard.test.ts
index 8e0f3cadc..aced4c255 100644
--- a/test/browser/fixtures/user-event/keyboard.test.ts
+++ b/test/browser/fixtures/user-event/keyboard.test.ts
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest'
-import { userEvent, page, server } from '@vitest/browser/context'
+import { userEvent, page, server } from 'vitest/browser'
test('non US keys', async () => {
document.body.innerHTML = `
diff --git a/test/browser/fixtures/viewport/basic.test.ts b/test/browser/fixtures/viewport/basic.test.ts
index 56746956d..6791cdfb5 100644
--- a/test/browser/fixtures/viewport/basic.test.ts
+++ b/test/browser/fixtures/viewport/basic.test.ts
@@ -1,4 +1,4 @@
-import { page, userEvent, server } from "@vitest/browser/context";
+import { page, userEvent, server } from "vitest/browser";
import { expect, test } from "vitest";
test("drag and drop over large viewport", async () => {
diff --git a/test/browser/package.json b/test/browser/package.json
index 114b02163..b2c58d7d0 100644
--- a/test/browser/package.json
+++ b/test/browser/package.json
@@ -33,6 +33,9 @@
"@types/react": "^19.1.13",
"@vitejs/plugin-basic-ssl": "^2.1.0",
"@vitest/browser": "workspace:*",
+ "@vitest/browser-playwright": "workspace:*",
+ "@vitest/browser-preview": "workspace:*",
+ "@vitest/browser-webdriverio": "workspace:*",
"@vitest/bundled-lib": "link:./bundled-lib",
"@vitest/cjs-lib": "link:./cjs-lib",
"playwright": "^1.55.0",
diff --git a/test/browser/settings.ts b/test/browser/settings.ts
index 3da09eed6..a23a43185 100644
--- a/test/browser/settings.ts
+++ b/test/browser/settings.ts
@@ -1,7 +1,7 @@
import type { BrowserInstanceOption } from 'vitest/node'
-import { playwright } from '@vitest/browser/providers/playwright'
-import { preview } from '@vitest/browser/providers/preview'
-import { webdriverio } from '@vitest/browser/providers/webdriverio'
+import { playwright } from '@vitest/browser-playwright'
+import { preview } from '@vitest/browser-preview'
+import { webdriverio } from '@vitest/browser-webdriverio'
const providerName = (process.env.PROVIDER || 'playwright') as 'playwright' | 'webdriverio' | 'preview'
export const providers = {
diff --git a/test/browser/setup.unit.ts b/test/browser/setup.unit.ts
index 91edd3c7d..551d41810 100644
--- a/test/browser/setup.unit.ts
+++ b/test/browser/setup.unit.ts
@@ -54,8 +54,7 @@ expect.extend({
})
declare module 'vitest' {
- // eslint-disable-next-line unused-imports/no-unused-vars
- interface Assertion {
+ interface Matchers {
// eslint-disable-next-line ts/method-signature-style
toReportPassedTest(testName: string, testProject?: string | BrowserInstanceOption[]): void
// eslint-disable-next-line ts/method-signature-style
diff --git a/test/browser/specs/locators.test.ts b/test/browser/specs/locators.test.ts
index 3b27e2579..e970b412e 100644
--- a/test/browser/specs/locators.test.ts
+++ b/test/browser/specs/locators.test.ts
@@ -1,13 +1,27 @@
-import { expect, test } from 'vitest'
+import { expect, test, vi } from 'vitest'
import { instances, runBrowserTests } from './utils'
test('locators work correctly', async () => {
+ const log = vi.fn()
const { stderr, stdout } = await runBrowserTests({
root: './fixtures/locators',
- reporters: [['verbose', { isTTY: false }]],
+ reporters: [
+ ['verbose', { isTTY: false }],
+ {
+ onInit(vitest) {
+ vitest.logger.deprecate = log
+ },
+ },
+ ],
})
expect(stderr).toReportNoErrors()
+ expect(log).toHaveBeenCalledWith(
+ expect.stringContaining(
+ `tries to load a deprecated "@vitest/browser/context" module. `
+ + `This import will stop working in the next major version. Please, use "vitest/browser" instead.`,
+ ),
+ )
instances.forEach(({ browser }) => {
expect(stdout).toReportPassedTest('blog.test.tsx', browser)
diff --git a/test/browser/specs/playwright-connect.test.ts b/test/browser/specs/playwright-connect.test.ts
index 1d3cf6555..97318ca47 100644
--- a/test/browser/specs/playwright-connect.test.ts
+++ b/test/browser/specs/playwright-connect.test.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { chromium } from 'playwright'
import { expect, test } from 'vitest'
import { provider } from '../settings'
@@ -27,9 +27,9 @@ test.runIf(provider.name === 'playwright')('[playwright] runs in connect mode',
await browserServer.close()
+ expect(stderr).toBe('')
expect(stdout).toContain('Tests 2 passed')
expect(exitCode).toBe(0)
- expect(stderr).toBe('')
})
test.runIf(provider.name === 'playwright')('[playwright] warns if both connect and launch mode are configured', async () => {
@@ -56,7 +56,7 @@ test.runIf(provider.name === 'playwright')('[playwright] warns if both connect a
await browserServer.close()
+ expect(stderr).toContain('Found both connect and launch options in browser instance configuration.')
expect(stdout).toContain('Tests 2 passed')
expect(exitCode).toBe(0)
- expect(stderr).toContain('Found both connect and launch options in browser instance configuration.')
})
diff --git a/test/browser/specs/to-match-screenshot.test.ts b/test/browser/specs/to-match-screenshot.test.ts
index 887357abc..e8b1feb41 100644
--- a/test/browser/specs/to-match-screenshot.test.ts
+++ b/test/browser/specs/to-match-screenshot.test.ts
@@ -1,8 +1,7 @@
-import type { ViteUserConfig } from 'vitest/config.js'
+import type { ViteUserConfig } from 'vitest/config'
import type { TestFsStructure } from '../../test-utils'
import { platform } from 'node:os'
import { resolve } from 'node:path'
-import { playwright } from '@vitest/browser/providers/playwright'
import { describe, expect, test } from 'vitest'
import { runVitestCli, useFS } from '../../test-utils'
import { extractToMatchScreenshotPaths } from '../fixtures/expect-dom/utils'
@@ -13,7 +12,7 @@ const testName = 'screenshot-snapshot'
const bgColor = '#fff'
const testContent = /* ts */`
-import { page, server } from '@vitest/browser/context'
+import { page, server } from 'vitest/browser'
import { describe, test } from 'vitest'
import { render } from './utils'
@@ -30,25 +29,27 @@ const browser = 'chromium'
export async function runInlineTests(
structure: TestFsStructure,
- config?: ViteUserConfig['test'],
+ config: ViteUserConfig['test'] = {},
) {
const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
const fs = useFS(root, {
...structure,
- 'vitest.config.ts': {
+ 'vitest.config.ts': `
+ import { playwright } from '@vitest/browser-playwright'
+ export default {
test: {
browser: {
enabled: true,
screenshotFailures: false,
provider: playwright(),
headless: true,
- instances: [{ browser }],
+ instances: [{ browser: ${JSON.stringify(browser)} }],
},
reporters: ['verbose'],
- ...config,
+ ...${JSON.stringify(config)},
},
- },
+ }`,
})
const vitest = await runVitestCli({
diff --git a/test/browser/test/cdp.test.ts b/test/browser/test/cdp.test.ts
index fce095e41..43c934a6e 100644
--- a/test/browser/test/cdp.test.ts
+++ b/test/browser/test/cdp.test.ts
@@ -1,5 +1,5 @@
-import { cdp, server } from '@vitest/browser/context'
import { describe, expect, it, onTestFinished, vi } from 'vitest'
+import { cdp, server } from 'vitest/browser'
describe.runIf(
server.provider === 'playwright' && server.browser === 'chromium',
diff --git a/test/browser/test/commands.test.ts b/test/browser/test/commands.test.ts
index 0276e0d30..247167536 100644
--- a/test/browser/test/commands.test.ts
+++ b/test/browser/test/commands.test.ts
@@ -1,5 +1,5 @@
-import { server } from '@vitest/browser/context'
import { expect, it } from 'vitest'
+import { server } from 'vitest/browser'
const { readFile, writeFile, removeFile, myCustomCommand } = server.commands
@@ -51,7 +51,7 @@ it('can run custom commands', async () => {
})
})
-declare module '@vitest/browser/context' {
+declare module 'vitest/browser' {
interface BrowserCommands {
myCustomCommand: (arg1: string, arg2: string) => Promise<{
testPath: string
diff --git a/test/browser/test/dom.test.ts b/test/browser/test/dom.test.ts
index 1f2828e58..a7115b93d 100644
--- a/test/browser/test/dom.test.ts
+++ b/test/browser/test/dom.test.ts
@@ -1,6 +1,6 @@
import { createNode } from '#src/createNode'
-import { page, server } from '@vitest/browser/context'
import { afterAll, beforeEach, describe, expect, test } from 'vitest'
+import { page, server } from 'vitest/browser'
import '../src/button.css'
afterAll(() => {
diff --git a/test/browser/test/expect-element.test.ts b/test/browser/test/expect-element.test.ts
index 22bcf5532..aba9ce83c 100644
--- a/test/browser/test/expect-element.test.ts
+++ b/test/browser/test/expect-element.test.ts
@@ -1,5 +1,5 @@
-import { page } from '@vitest/browser/context'
import { expect, test, vi } from 'vitest'
+import { page } from 'vitest/browser'
// element selector uses prettyDOM under the hood, which is an expensive call
// that should not be called on each failed locator attempt to avoid memory leak:
diff --git a/test/browser/test/iframe.test.ts b/test/browser/test/iframe.test.ts
index a9916323f..cea9145f6 100644
--- a/test/browser/test/iframe.test.ts
+++ b/test/browser/test/iframe.test.ts
@@ -1,5 +1,5 @@
-import { page, server } from '@vitest/browser/context'
import { expect, test } from 'vitest'
+import { page, server } from 'vitest/browser'
test.runIf(server.provider === 'playwright')('locates an iframe', async () => {
const iframe = document.createElement('iframe')
diff --git a/test/browser/test/userEvent.test.ts b/test/browser/test/userEvent.test.ts
index 370ef9e80..10a7f93c4 100644
--- a/test/browser/test/userEvent.test.ts
+++ b/test/browser/test/userEvent.test.ts
@@ -1,5 +1,5 @@
-import { userEvent as _uE, server } from '@vitest/browser/context'
import { beforeEach, describe, expect, test, vi } from 'vitest'
+import { userEvent as _uE, server } from 'vitest/browser'
import '../src/button.css'
beforeEach(() => {
@@ -615,7 +615,7 @@ describe.each(inputLike)('userEvent.fill', async (getInput) => {
expect(value()).toBe('Another Value')
})
- test('fill input in shadow root', async () => {
+ test.skipIf(server.provider === 'preview')('fill input in shadow root', async () => {
const input = getInput()
const shadowRoot = createShadowRoot()
shadowRoot.appendChild(input)
diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts
index 168e53066..edab7ae54 100644
--- a/test/browser/test/utils.test.ts
+++ b/test/browser/test/utils.test.ts
@@ -1,6 +1,5 @@
-import { commands } from '@vitest/browser/context'
-import { prettyDOM } from '@vitest/browser/utils'
import { afterEach, expect, it, test } from 'vitest'
+import { commands, utils } from 'vitest/browser'
import { inspect } from 'vitest/internal/browser'
@@ -13,13 +12,13 @@ it('utils package correctly uses loupe', async () => {
})
test('prints default document', async () => {
- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
const div = document.createElement('div')
div.innerHTML = 'hello '
document.body.append(div)
- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
})
test('prints the element', async () => {
@@ -27,7 +26,7 @@ test('prints the element', async () => {
div.innerHTML = 'hello '
document.body.append(div)
- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
})
test('prints the element with attributes', async () => {
@@ -35,7 +34,7 @@ test('prints the element with attributes', async () => {
div.innerHTML = 'hello '
document.body.append(div)
- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
})
test('should handle DOM content bigger than maxLength', async () => {
@@ -50,7 +49,7 @@ test('should handle DOM content bigger than maxLength', async () => {
parentDiv.innerHTML = domString
document.body.appendChild(parentDiv)
- expect(await commands.stripVTControlCharacters(prettyDOM(undefined, maxContent))).toMatchSnapshot()
+ expect(await commands.stripVTControlCharacters(utils.prettyDOM(undefined, maxContent))).toMatchSnapshot()
})
test('should handle shadow DOM content', async () => {
@@ -71,7 +70,7 @@ test('should handle shadow DOM content', async () => {
div.innerHTML = ' '
document.body.append(div)
- expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
+ expect(await commands.stripVTControlCharacters(utils.prettyDOM())).toMatchSnapshot()
})
test('should be able to opt out of shadow DOM content', async () => {
@@ -92,5 +91,5 @@ test('should be able to opt out of shadow DOM content', async () => {
div.innerHTML = ' '
document.body.append(div)
- expect(await commands.stripVTControlCharacters(prettyDOM(undefined, undefined, { printShadowRoot: false }))).toMatchSnapshot()
+ expect(await commands.stripVTControlCharacters(utils.prettyDOM(undefined, undefined, { printShadowRoot: false }))).toMatchSnapshot()
})
diff --git a/test/browser/test/viewport.test.ts b/test/browser/test/viewport.test.ts
index b3f564adf..5dbf073cf 100644
--- a/test/browser/test/viewport.test.ts
+++ b/test/browser/test/viewport.test.ts
@@ -1,5 +1,5 @@
-import { server } from '@vitest/browser/context'
import { describe, expect, it } from 'vitest'
+import { server } from 'vitest/browser'
describe.skipIf(
// preview cannot control viewport
diff --git a/test/browser/tsconfig.json b/test/browser/tsconfig.json
index 7c0b0f12c..dc59446e1 100644
--- a/test/browser/tsconfig.json
+++ b/test/browser/tsconfig.json
@@ -9,7 +9,6 @@
},
"types": [
"vite/client",
- "@vitest/browser/providers/playwright",
"vitest-browser-react",
"vitest/import-meta"
],
diff --git a/test/browser/vitest.config.mts b/test/browser/vitest.config.mts
index bb01b2175..daba54474 100644
--- a/test/browser/vitest.config.mts
+++ b/test/browser/vitest.config.mts
@@ -2,15 +2,15 @@ import type { BrowserCommand, BrowserInstanceOption } from 'vitest/node'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import * as util from 'node:util'
-import { playwright } from '@vitest/browser/providers/playwright'
-import { preview } from '@vitest/browser/providers/preview'
-import { webdriverio } from '@vitest/browser/providers/webdriverio'
+import { playwright } from '@vitest/browser-playwright'
+import { preview } from '@vitest/browser-preview'
+import { webdriverio } from '@vitest/browser-webdriverio'
import { defineConfig } from 'vitest/config'
const dir = dirname(fileURLToPath(import.meta.url))
const providerName = process.env.PROVIDER || 'playwright'
-const browser = process.env.BROWSER || (providerName === 'playwright' ? 'chromium' : 'chrome')
+const browser = process.env.BROWSER as 'firefox' || (providerName === 'playwright' ? 'chromium' : 'chrome')
const provider = {
playwright,
preview,
diff --git a/test/cli/fixtures/browser-init/package.json b/test/cli/fixtures/browser-init/package.json
index 2d786c6dc..0a29b1fae 100644
--- a/test/cli/fixtures/browser-init/package.json
+++ b/test/cli/fixtures/browser-init/package.json
@@ -6,6 +6,6 @@
"vitest": "latest"
},
"devDependencies": {
- "@vitest/browser": "latest"
+ "@vitest/browser-preview": "latest"
}
}
diff --git a/test/cli/fixtures/browser-multiple/vitest.config.ts b/test/cli/fixtures/browser-multiple/vitest.config.ts
index 1df074e7b..c525c6f32 100644
--- a/test/cli/fixtures/browser-multiple/vitest.config.ts
+++ b/test/cli/fixtures/browser-multiple/vitest.config.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright';
+import { playwright } from '@vitest/browser-playwright';
import { resolve } from 'pathe';
import { defineConfig } from 'vitest/config';
diff --git a/test/cli/fixtures/config-loader/browser/vitest.config.ts b/test/cli/fixtures/config-loader/browser/vitest.config.ts
index 4a0041e1b..0bde499fc 100644
--- a/test/cli/fixtures/config-loader/browser/vitest.config.ts
+++ b/test/cli/fixtures/config-loader/browser/vitest.config.ts
@@ -1,6 +1,6 @@
import { defineConfig } from "vitest/config"
import "@test/test-dep-linked/ts";
-import { playwright } from '@vitest/browser/providers/playwright';
+import { playwright } from '@vitest/browser-playwright';
export default defineConfig({
test: {
diff --git a/test/cli/fixtures/fails/node-browser-context.test.ts b/test/cli/fixtures/fails/node-browser-context.test.ts
index af854dc40..3bb99a56f 100644
--- a/test/cli/fixtures/fails/node-browser-context.test.ts
+++ b/test/cli/fixtures/fails/node-browser-context.test.ts
@@ -1,3 +1,3 @@
-import { page } from '@vitest/browser/context'
+import { page } from 'vitest/browser'
console.log(page)
diff --git a/test/cli/fixtures/list/vitest.config.ts b/test/cli/fixtures/list/vitest.config.ts
index 487485c4e..a99b87bb6 100644
--- a/test/cli/fixtures/list/vitest.config.ts
+++ b/test/cli/fixtures/list/vitest.config.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
diff --git a/test/cli/fixtures/public-api/vitest.config.ts b/test/cli/fixtures/public-api/vitest.config.ts
index 3fe34057d..8b5e2245d 100644
--- a/test/cli/fixtures/public-api/vitest.config.ts
+++ b/test/cli/fixtures/public-api/vitest.config.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { defineConfig } from 'vitest/config'
export default defineConfig({
diff --git a/test/cli/package.json b/test/cli/package.json
index cb1dd8153..53a4e4e4b 100644
--- a/test/cli/package.json
+++ b/test/cli/package.json
@@ -12,6 +12,8 @@
"@types/debug": "catalog:",
"@types/ws": "catalog:",
"@vitejs/plugin-basic-ssl": "^2.1.0",
+ "@vitest/browser-playwright": "workspace:*",
+ "@vitest/browser-preview": "workspace:*",
"@vitest/runner": "workspace:^",
"@vitest/utils": "workspace:*",
"debug": "^4.4.3",
diff --git a/test/cli/test/__snapshots__/fails.test.ts.snap b/test/cli/test/__snapshots__/fails.test.ts.snap
index 78cd736b7..cd2e1903c 100644
--- a/test/cli/test/__snapshots__/fails.test.ts.snap
+++ b/test/cli/test/__snapshots__/fails.test.ts.snap
@@ -61,7 +61,7 @@ exports[`should fail nested-suite.test.ts 1`] = `"AssertionError: expected true
exports[`should fail no-assertions.test.ts 1`] = `"Error: expected any number of assertion, but got none"`;
-exports[`should fail node-browser-context.test.ts 1`] = `"Error: @vitest/browser/context can be imported only inside the Browser Mode. Your test is running in forks pool. Make sure your regular tests are excluded from the "test.include" glob pattern."`;
+exports[`should fail node-browser-context.test.ts 1`] = `"Error: vitest/browser can be imported only inside the Browser Mode. Your test is running in forks pool. Make sure your regular tests are excluded from the "test.include" glob pattern."`;
exports[`should fail poll-no-awaited.test.ts 1`] = `
"Error: expect.poll(assertion).toBe() was not awaited. This assertion is asynchronous and must be awaited; otherwise, it is not executed to avoid unhandled rejections:
diff --git a/test/cli/test/annotations.test.ts b/test/cli/test/annotations.test.ts
index 414878805..34b59564e 100644
--- a/test/cli/test/annotations.test.ts
+++ b/test/cli/test/annotations.test.ts
@@ -1,5 +1,5 @@
import type { TestAnnotation } from 'vitest'
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { describe, expect, test } from 'vitest'
import { runInlineTests } from '../../test-utils'
@@ -40,7 +40,7 @@ describe('API', () => {
provider: playwright(),
headless: true,
instances: [
- { browser: 'chromium' },
+ { browser: 'chromium' as const },
],
},
},
diff --git a/test/cli/test/fails.test.ts b/test/cli/test/fails.test.ts
index 7efc4b4f9..664c0a1c4 100644
--- a/test/cli/test/fails.test.ts
+++ b/test/cli/test/fails.test.ts
@@ -1,5 +1,5 @@
import type { TestCase } from 'vitest/node'
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { resolve } from 'pathe'
import { glob } from 'tinyglobby'
@@ -109,16 +109,6 @@ it('prints a warning if the assertion is not awaited', async () => {
it('prints a warning if the assertion is not awaited in the browser mode', async () => {
const { stderr } = await runInlineTests({
- './vitest.config.js': {
- test: {
- browser: {
- enabled: true,
- instances: [{ browser: 'chromium' }],
- provider: playwright(),
- headless: true,
- },
- },
- },
'base.test.js': ts`
import { expect, test } from 'vitest';
@@ -126,6 +116,15 @@ it('prints a warning if the assertion is not awaited in the browser mode', async
expect(Promise.resolve(1)).resolves.toBe(1)
})
`,
+ }, {}, {}, {
+ test: {
+ browser: {
+ enabled: true,
+ instances: [{ browser: 'chromium' }],
+ provider: playwright(),
+ headless: true,
+ },
+ },
})
expect(stderr).toContain('Promise returned by \`expect(actual).resolves.toBe(expected)\` was not awaited')
expect(stderr).toContain('base.test.js:5:33')
diff --git a/test/cli/test/init.test.ts b/test/cli/test/init.test.ts
index c2c9f89ef..dc3f72550 100644
--- a/test/cli/test/init.test.ts
+++ b/test/cli/test/init.test.ts
@@ -54,7 +54,7 @@ test('initializes project', async () => {
expect(await getFileContent('/vitest.browser.config.ts')).toMatchInlineSnapshot(`
"import { defineConfig } from 'vitest/config'
- import { preview } from '@vitest/browser/providers/preview'
+ import { preview } from '@vitest/browser-preview'
export default defineConfig({
test: {
diff --git a/test/cli/test/scoped-fixtures.test.ts b/test/cli/test/scoped-fixtures.test.ts
index 40ce7785f..def153f03 100644
--- a/test/cli/test/scoped-fixtures.test.ts
+++ b/test/cli/test/scoped-fixtures.test.ts
@@ -4,7 +4,7 @@ import type { TestAPI } from 'vitest'
import type { ViteUserConfig } from 'vitest/config'
import type { TestSpecification, TestUserConfig } from 'vitest/node'
import type { TestFsStructure } from '../../test-utils'
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { runInlineTests } from '../../test-utils'
interface TestContext {
@@ -555,7 +555,7 @@ describe.for([
provider: playwright(),
headless: true,
instances: [
- { browser: 'chromium', name: '' },
+ { browser: 'chromium' as const, name: '' },
],
},
},
diff --git a/test/config/fixtures/browser-custom-html/vitest.config.correct.ts b/test/config/fixtures/browser-custom-html/vitest.config.correct.ts
index cc6645a06..cbd53f2d3 100644
--- a/test/config/fixtures/browser-custom-html/vitest.config.correct.ts
+++ b/test/config/fixtures/browser-custom-html/vitest.config.correct.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright';
+import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
export default defineConfig({
diff --git a/test/config/fixtures/browser-custom-html/vitest.config.custom-transformIndexHtml.ts b/test/config/fixtures/browser-custom-html/vitest.config.custom-transformIndexHtml.ts
index 136f218a6..71c7a8a48 100644
--- a/test/config/fixtures/browser-custom-html/vitest.config.custom-transformIndexHtml.ts
+++ b/test/config/fixtures/browser-custom-html/vitest.config.custom-transformIndexHtml.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright';
+import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
export default defineConfig({
diff --git a/test/config/fixtures/browser-custom-html/vitest.config.default-transformIndexHtml.ts b/test/config/fixtures/browser-custom-html/vitest.config.default-transformIndexHtml.ts
index f8fe90543..325eab879 100644
--- a/test/config/fixtures/browser-custom-html/vitest.config.default-transformIndexHtml.ts
+++ b/test/config/fixtures/browser-custom-html/vitest.config.default-transformIndexHtml.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright';
+import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
export default defineConfig({
diff --git a/test/config/fixtures/browser-custom-html/vitest.config.error-hook.ts b/test/config/fixtures/browser-custom-html/vitest.config.error-hook.ts
index 4ad8f42e4..452b517cd 100644
--- a/test/config/fixtures/browser-custom-html/vitest.config.error-hook.ts
+++ b/test/config/fixtures/browser-custom-html/vitest.config.error-hook.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright';
+import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
export default defineConfig({
diff --git a/test/config/fixtures/browser-custom-html/vitest.config.non-existing.ts b/test/config/fixtures/browser-custom-html/vitest.config.non-existing.ts
index 8a259fc9f..4e14f69e8 100644
--- a/test/config/fixtures/browser-custom-html/vitest.config.non-existing.ts
+++ b/test/config/fixtures/browser-custom-html/vitest.config.non-existing.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright';
+import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
export default defineConfig({
diff --git a/test/config/fixtures/browser-no-config/vitest.config.ts b/test/config/fixtures/browser-no-config/vitest.config.ts
index 742cc1027..ec42f4dc7 100644
--- a/test/config/fixtures/browser-no-config/vitest.config.ts
+++ b/test/config/fixtures/browser-no-config/vitest.config.ts
@@ -4,6 +4,7 @@ export default defineConfig({
test: {
browser: {
headless: true,
- } as any, // testing that instances is required
+ // testing that instances is required
+ },
},
})
\ No newline at end of file
diff --git a/test/config/package.json b/test/config/package.json
index dd7c8c7b2..43318e591 100644
--- a/test/config/package.json
+++ b/test/config/package.json
@@ -6,6 +6,9 @@
"test": "vitest --typecheck.enabled"
},
"devDependencies": {
+ "@vitest/browser-playwright": "workspace:*",
+ "@vitest/browser-preview": "workspace:*",
+ "@vitest/browser-webdriverio": "workspace:*",
"@vitest/test-dep-conditions": "file:./deps/test-dep-conditions",
"tinyexec": "^0.3.2",
"vite": "latest",
diff --git a/test/config/test/bail.test.ts b/test/config/test/bail.test.ts
index 62f509dca..9127851bd 100644
--- a/test/config/test/bail.test.ts
+++ b/test/config/test/bail.test.ts
@@ -1,6 +1,6 @@
import type { TestUserConfig } from 'vitest/node'
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { expect, test } from 'vitest'
import { runVitest } from '../../test-utils'
diff --git a/test/config/test/browser-configs.test.ts b/test/config/test/browser-configs.test.ts
index 87c6b004e..38ce4e41b 100644
--- a/test/config/test/browser-configs.test.ts
+++ b/test/config/test/browser-configs.test.ts
@@ -2,9 +2,9 @@ import type { ViteUserConfig } from 'vitest/config'
import type { TestUserConfig, VitestOptions } from 'vitest/node'
import type { TestFsStructure } from '../../test-utils'
import crypto from 'node:crypto'
-import { playwright } from '@vitest/browser/providers/playwright'
-import { webdriverio } from '@vitest/browser/providers/webdriverio'
-import { preview } from '@vitest/browser/src/node/providers/preview.js'
+import { playwright } from '@vitest/browser-playwright'
+import { preview } from '@vitest/browser-preview'
+import { webdriverio } from '@vitest/browser-webdriverio'
import { resolve } from 'pathe'
import { describe, expect, onTestFinished, test } from 'vitest'
import { createVitest } from 'vitest/node'
@@ -21,6 +21,7 @@ test('assigns names as browsers', async () => {
browser: {
enabled: true,
headless: true,
+ provider: preview(),
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
@@ -39,6 +40,7 @@ test('filters projects', async () => {
const { projects } = await vitest({ project: 'chromium' }, {
browser: {
enabled: true,
+ provider: preview(),
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
@@ -55,6 +57,7 @@ test('filters projects with a wildcard', async () => {
const { projects } = await vitest({ project: 'chrom*' }, {
browser: {
enabled: true,
+ provider: preview(),
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
@@ -76,6 +79,7 @@ test('assigns names as browsers in a custom project', async () => {
browser: {
enabled: true,
headless: true,
+ provider: preview(),
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
@@ -103,6 +107,7 @@ test('inherits browser options', async () => {
} as any,
browser: {
enabled: true,
+ provider: preview(),
headless: true,
screenshotFailures: false,
testerHtmlPath: '/custom-path.html',
@@ -244,6 +249,7 @@ test('browser instances with include/exclude/includeSource option override paren
includeSource: ['src/**/*.{js,ts}'],
browser: {
enabled: true,
+ provider: preview(),
headless: true,
instances: [
{ browser: 'chromium' },
@@ -299,6 +305,7 @@ test('browser instances with empty include array should get parent include patte
include: ['**/*.test.{js,ts}'],
browser: {
enabled: true,
+ provider: preview(),
headless: true,
instances: [
{ browser: 'chromium', include: [] },
@@ -349,6 +356,7 @@ test('can enable browser-cli options for multi-project workspace', async () => {
{
browser: {
enabled: true,
+ provider: preview(),
headless: true,
instances: [],
},
@@ -387,18 +395,6 @@ test('can enable browser-cli options for multi-project workspace', async () => {
expect(projects[1].config.browser.headless).toBe(true)
})
-test('core provider has no options if `provider` is not set', async () => {
- const v = await vitest({}, {
- browser: {
- enabled: true,
- instances: [{
- browser: 'chromium',
- }],
- },
- })
- expect(v.config.browser.provider).toEqual(undefined)
-})
-
test('core provider has options if `provider` is playwright', async () => {
const v = await vitest({}, {
browser: {
@@ -512,6 +508,7 @@ test('provider options can be changed dynamically in CLI', async () => {
}, {
browser: {
enabled: true,
+ provider: preview(),
instances: [
{ browser: 'chromium' },
],
@@ -785,6 +782,7 @@ describe('[e2e] workspace configs are affected by the CLI options', () => {
name: 'browser',
browser: {
enabled: true,
+ provider: preview(),
instances: [],
},
},
@@ -839,6 +837,7 @@ describe('[e2e] workspace configs are affected by the CLI options', () => {
name: 'browser',
browser: {
enabled: true,
+ provider: preview(),
instances: [],
},
},
diff --git a/test/config/test/failures.test.ts b/test/config/test/failures.test.ts
index cfb113b09..c78db1ab8 100644
--- a/test/config/test/failures.test.ts
+++ b/test/config/test/failures.test.ts
@@ -1,9 +1,9 @@
import type { UserConfig as ViteUserConfig } from 'vite'
import type { TestUserConfig } from 'vitest/node'
import type { VitestRunnerCLIOptions } from '../../test-utils'
-import { playwright } from '@vitest/browser/providers/playwright'
-import { preview } from '@vitest/browser/providers/preview'
-import { webdriverio } from '@vitest/browser/providers/webdriverio'
+import { playwright } from '@vitest/browser-playwright'
+import { preview } from '@vitest/browser-preview'
+import { webdriverio } from '@vitest/browser-webdriverio'
import { normalize, resolve } from 'pathe'
import { beforeEach, expect, test } from 'vitest'
import { version } from 'vitest/package.json'
@@ -405,13 +405,13 @@ test('browser.instances is empty', async () => {
},
},
})
- expect(stderr).toMatch('"browser.instances" was set in the config, but the array is empty. Define at least one browser config.')
+ expect(stderr).toMatch(`Vitest wasn't able to resolve any project. Please, check that you specified the "browser.instances" option.`)
})
test('browser.name or browser.instances are required', async () => {
const { stderr, exitCode } = await runVitestCli('--browser.enabled', '--root=./fixtures/browser-no-config')
expect(exitCode).toBe(1)
- expect(stderr).toMatch('Vitest Browser Mode requires "browser.name" (deprecated) or "browser.instances" options, none were set.')
+ expect(stderr).toMatch('Vitest received --browser flag, but no project had a browser configuration.')
})
test('--browser flag without browser configuration throws an error', async () => {
@@ -497,24 +497,6 @@ test('throws an error if name conflicts with a workspace name', async () => {
expect(stderr).toMatch('Cannot define a nested project for a firefox browser. The project name "1 (firefox)" was already defined. If you have multiple instances for the same browser, make sure to define a custom "name". All projects should have unique names. Make sure your configuration is correct.')
})
-test('throws an error if several browsers are headed in nonTTY mode', async () => {
- const { stderr } = await runVitest({}, {
- test: {
- browser: {
- enabled: true,
- provider: playwright(),
- headless: false,
- instances: [
- { browser: 'chromium' },
- { browser: 'firefox' },
- ],
- },
- },
- })
- expect(stderr).toContain('Found multiple projects that run browser tests in headed mode: "chromium", "firefox"')
- expect(stderr).toContain('Please, filter projects with --browser=name or --project=name flag or run tests with "headless: true" option')
-})
-
test('non existing project name will throw', async () => {
const { stderr } = await runVitest({ project: 'non-existing-project' })
expect(stderr).toMatch('No projects matched the filter "non-existing-project".')
diff --git a/test/core/test/exports.test.ts b/test/core/test/exports.test.ts
index c065f8ab5..800142ace 100644
--- a/test/core/test/exports.test.ts
+++ b/test/core/test/exports.test.ts
@@ -9,6 +9,9 @@ it('exports snapshot', async ({ skip, task }) => {
const manifest = await getPackageExportsManifest({
importMode: 'package', // or 'dist' or 'package'
cwd: resolve(import.meta.dirname, '../../../packages/vitest'),
+ resolveExportEntries(entries) {
+ return entries.filter(([key]) => key !== './browser')
+ },
})
if (rolldownVersion) {
@@ -59,6 +62,7 @@ it('exports snapshot', async ({ skip, task }) => {
"./internal/browser": {
"DecodedMap": "function",
"SpyModule": "object",
+ "__INTERNAL": "object",
"collectTests": "function",
"format": "function",
"getOriginalPosition": "function",
@@ -210,6 +214,7 @@ it('exports snapshot', async ({ skip, task }) => {
"./internal/browser": {
"DecodedMap": "function",
"SpyModule": "object",
+ "__INTERNAL": "object",
"collectTests": "function",
"format": "function",
"getOriginalPosition": "function",
diff --git a/test/coverage-test/package.json b/test/coverage-test/package.json
index 4e640f5fe..f40c3ddc2 100644
--- a/test/coverage-test/package.json
+++ b/test/coverage-test/package.json
@@ -10,7 +10,7 @@
"@types/istanbul-lib-coverage": "catalog:",
"@types/istanbul-lib-report": "catalog:",
"@vitejs/plugin-vue": "latest",
- "@vitest/browser": "workspace:*",
+ "@vitest/browser-playwright": "workspace:*",
"@vitest/coverage-istanbul": "workspace:*",
"@vitest/coverage-v8": "workspace:*",
"@vitest/web-worker": "workspace:*",
diff --git a/test/coverage-test/utils.ts b/test/coverage-test/utils.ts
index 764f13e2e..d34f79656 100644
--- a/test/coverage-test/utils.ts
+++ b/test/coverage-test/utils.ts
@@ -7,7 +7,7 @@ import { unlink } from 'node:fs/promises'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { stripVTControlCharacters } from 'node:util'
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import libCoverage from 'istanbul-lib-coverage'
import { normalize } from 'pathe'
import { vi, describe as vitestDescribe, test as vitestTest } from 'vitest'
diff --git a/test/dts-playwright/package.json b/test/dts-playwright/package.json
index c53fbd8f5..0b094aae7 100644
--- a/test/dts-playwright/package.json
+++ b/test/dts-playwright/package.json
@@ -6,7 +6,7 @@
"test": "tsc -b"
},
"devDependencies": {
- "@vitest/browser": "workspace:*",
+ "@vitest/browser-playwright": "workspace:*",
"vitest": "workspace:*"
}
}
diff --git a/test/dts-playwright/src/basic.test.ts b/test/dts-playwright/src/basic.test.ts
index 4aec7d2db..079bc3674 100644
--- a/test/dts-playwright/src/basic.test.ts
+++ b/test/dts-playwright/src/basic.test.ts
@@ -1,5 +1,5 @@
-import { page, userEvent } from '@vitest/browser/context'
import { test } from 'vitest'
+import { page, userEvent } from 'vitest/browser'
test('basic', async () => {
document.body.innerHTML = `hello `
diff --git a/test/dts-playwright/tsconfig.json b/test/dts-playwright/tsconfig.json
index 66b2370df..ef7768529 100644
--- a/test/dts-playwright/tsconfig.json
+++ b/test/dts-playwright/tsconfig.json
@@ -4,7 +4,6 @@
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
- "types": ["@vitest/browser/providers/playwright"],
"strict": true,
"noEmit": true
},
diff --git a/test/dts-playwright/vite.config.ts b/test/dts-playwright/vite.config.ts
index 0c32e8096..f471d86a3 100644
--- a/test/dts-playwright/vite.config.ts
+++ b/test/dts-playwright/vite.config.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { defineConfig } from 'vitest/config'
export default defineConfig({
diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts
index 0933f26ea..91b41e9ea 100644
--- a/test/test-utils/index.ts
+++ b/test/test-utils/index.ts
@@ -298,6 +298,15 @@ const results = await (${content[0]})({ ${imports.flatMap(([_, is]) => is).join(
${(content[1].exports || []).map(e => `export const ${e} = results["${e}"]`)}
`
}
+ if ('test' in content && content.test?.browser?.enabled && content.test?.browser?.provider?.name) {
+ const name = content.test.browser.provider.name
+ return `
+import { ${name} } from '@vitest/browser-${name}'
+const config = ${JSON.stringify(content)}
+config.test.browser.provider = ${name}(${JSON.stringify(content.test.browser.provider.options || {})})
+export default config
+ `
+ }
return `export default ${JSON.stringify(content)}`
}
diff --git a/test/watch/package.json b/test/watch/package.json
index d4fc79036..48a6ddf44 100644
--- a/test/watch/package.json
+++ b/test/watch/package.json
@@ -6,7 +6,8 @@
"test": "vitest"
},
"devDependencies": {
- "@vitest/browser": "workspace:*",
+ "@vitest/browser-playwright": "workspace:*",
+ "@vitest/browser-webdriverio": "workspace:*",
"vite": "latest",
"vitest": "workspace:*"
}
diff --git a/test/watch/test/config-watching.test.ts b/test/watch/test/config-watching.test.ts
index 9edc214e3..f4c8fc3d4 100644
--- a/test/watch/test/config-watching.test.ts
+++ b/test/watch/test/config-watching.test.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { expect, test } from 'vitest'
import { runInlineTests } from '../../test-utils'
diff --git a/test/watch/test/file-watching.test.ts b/test/watch/test/file-watching.test.ts
index 451ec6aa3..dbed60347 100644
--- a/test/watch/test/file-watching.test.ts
+++ b/test/watch/test/file-watching.test.ts
@@ -1,5 +1,5 @@
import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
-import { webdriverio } from '@vitest/browser/providers/webdriverio'
+import { webdriverio } from '@vitest/browser-webdriverio'
import { afterEach, describe, expect, test } from 'vitest'
import * as testUtils from '../../test-utils'
diff --git a/test/workspaces-browser/package.json b/test/workspaces-browser/package.json
index 5cd40bf42..1ef72c903 100644
--- a/test/workspaces-browser/package.json
+++ b/test/workspaces-browser/package.json
@@ -6,7 +6,7 @@
"test": "vitest run"
},
"devDependencies": {
- "@vitest/browser": "workspace:^",
+ "@vitest/browser-playwright": "workspace:^",
"vitest": "workspace:*"
}
}
diff --git a/test/workspaces-browser/space_browser/vitest.config.ts b/test/workspaces-browser/space_browser/vitest.config.ts
index 246901f82..71e9b087c 100644
--- a/test/workspaces-browser/space_browser/vitest.config.ts
+++ b/test/workspaces-browser/space_browser/vitest.config.ts
@@ -1,11 +1,11 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { defineProject } from 'vitest/config'
export default defineProject({
test: {
browser: {
enabled: true,
- instances: [{ browser: process.env.BROWSER || 'chromium' }],
+ instances: [{ browser: process.env.BROWSER as 'chromium' || 'chromium' }],
headless: true,
provider: playwright(),
},
diff --git a/test/workspaces-browser/vitest.config.ts b/test/workspaces-browser/vitest.config.ts
index b550364fc..5df82d415 100644
--- a/test/workspaces-browser/vitest.config.ts
+++ b/test/workspaces-browser/vitest.config.ts
@@ -1,4 +1,4 @@
-import { playwright } from '@vitest/browser/providers/playwright'
+import { playwright } from '@vitest/browser-playwright'
import { defineConfig } from 'vitest/config'
if (process.env.TEST_WATCH) {
@@ -21,7 +21,7 @@ export default defineConfig({
root: './space_browser_inline',
browser: {
enabled: true,
- instances: [{ browser: process.env.BROWSER || 'chromium' }],
+ instances: [{ browser: process.env.BROWSER as 'chromium' || 'chromium' }],
headless: true,
provider: playwright(),
},
diff --git a/tsconfig.base.json b/tsconfig.base.json
index da5ecf972..25bfb09d6 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -19,12 +19,15 @@
"@vitest/mocker/browser": ["./packages/mocker/src/browser/index.ts"],
"@vitest/runner": ["./packages/runner/src/index.ts"],
"@vitest/runner/*": ["./packages/runner/src/*"],
+ "@vitest/browser-playwright": ["./packages/browser-playwright/src/index.ts"],
"@vitest/browser": ["./packages/browser/src/node/index.ts"],
"@vitest/browser/client": ["./packages/browser/src/client/client.ts"],
"~/*": ["./packages/ui/client/*"],
"vitest": ["./packages/vitest/src/public/index.ts"],
+ "vitest/internal/browser": ["./packages/vitest/src/public/browser.ts"],
"vitest/internal/module-runner": ["./packages/vitest/src/public/module-runner.ts"],
"vitest/globals": ["./packages/vitest/globals.d.ts"],
+ "vitest/browser": ["./packages/vitest/browser/context.d.ts"],
"vitest/*": ["./packages/vitest/src/public/*"],
"vite-node": ["./packages/vite-node/src/index.ts"],
"vite-node/*": ["./packages/vite-node/src/*"]