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' import { isFileLoadingAllowed } from 'vitest/node' 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, ): Promise { const browser = project.config.browser.name const name = project.name ? `[${project.name}] ` : '' if (!browser) { throw new Error( `${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`, ) } 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)) { throw new Error( `${name}Browser "${browser}" is not supported by the browser provider "${ options.provider.name }". Supported browsers: ${supportedBrowsers.join(', ')}.`, ) } 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.providerFactory(project) } export function slash(path: string): string { return path.replace(/\\/g, '/').replace(/\/+/g, '/') } export function assertBrowserFileAccess(project: TestProject, path: string): void { const normalized = slash(path) if ( !isFileLoadingAllowed(project.vite.config, normalized) && !isFileLoadingAllowed(project.vitest.vite.config, normalized) ) { throw new Error( `Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`, ) } } export function assertBrowserApiWrite(project: TestProject, path: string): void { if (!project.config.api.allowWrite || !project.vitest.config.api.allowWrite) { throw new Error( `Cannot modify file "${path}". File writing is disabled because the server is exposed to the internet, see https://vitest.dev/config/browser/api.`, ) } }