From 9cdc8030d05fe9d3c3cb0ceef1f74b401f6f8fa9 Mon Sep 17 00:00:00 2001 From: Mohammad Bagher Abiat Date: Mon, 20 Mar 2023 17:07:23 +0330 Subject: [PATCH] feat: webdriverio (+ custom providers) integration for browser mode (#2999) Co-authored-by: Christian Bromann Co-authored-by: Vladimir Co-authored-by: userquin Co-authored-by: Anjorin Damilare --- .github/workflows/ci.yml | 58 ++ .gitignore | 1 + docs/.vitepress/config.ts | 4 + docs/config/index.md | 80 +- docs/guide/browser.md | 89 +++ docs/guide/cli.md | 2 +- package.json | 5 +- packages/browser/package.json | 7 +- packages/browser/src/client/main.ts | 48 +- packages/browser/src/client/runner.ts | 19 +- packages/browser/src/node/index.ts | 12 +- .../ui/client/composables/client/static.ts | 7 +- packages/vitest/package.json | 11 +- packages/vitest/rollup.config.js | 2 + packages/vitest/src/api/setup.ts | 12 +- packages/vitest/src/api/types.ts | 3 +- packages/vitest/src/constants.ts | 7 - packages/vitest/src/defaults.ts | 6 + packages/vitest/src/integrations/browser.ts | 25 + .../vitest/src/integrations/browser/server.ts | 66 ++ packages/vitest/src/node/browser/webdriver.ts | 126 ++++ packages/vitest/src/node/cli-api.ts | 16 +- packages/vitest/src/node/cli.ts | 6 +- packages/vitest/src/node/config.ts | 13 +- packages/vitest/src/node/core.ts | 72 +- packages/vitest/src/node/logger.ts | 6 +- packages/vitest/src/node/plugins/index.ts | 95 ++- packages/vitest/src/node/pool.ts | 10 +- packages/vitest/src/node/pools/child.ts | 2 +- packages/vitest/src/node/pools/threads.ts | 2 +- packages/vitest/src/paths.ts | 5 + packages/vitest/src/runtime/entry.ts | 2 +- packages/vitest/src/runtime/execute.ts | 2 +- packages/vitest/src/types/browser.ts | 57 ++ packages/vitest/src/types/config.ts | 13 +- packages/vitest/src/utils/graph.ts | 2 +- pnpm-lock.yaml | 700 +++++++++++++++++- test/browser/package.json | 4 +- test/browser/test.mjs | 32 + .../test/__snapshots__/snapshot.test.ts.snap | 2 +- test/browser/test/dom.test.ts | 8 + test/browser/vitest.config.ts | 24 +- 42 files changed, 1505 insertions(+), 158 deletions(-) create mode 100644 docs/guide/browser.md create mode 100644 packages/vitest/src/integrations/browser.ts create mode 100644 packages/vitest/src/integrations/browser/server.ts create mode 100644 packages/vitest/src/node/browser/webdriver.ts create mode 100644 packages/vitest/src/paths.ts create mode 100644 packages/vitest/src/types/browser.ts create mode 100644 test/browser/test.mjs create mode 100644 test/browser/test/dom.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46f16d9b33db..bc12538cb8ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,3 +101,61 @@ jobs: - name: Test UI run: pnpm run ui:test + + test-browser: + runs-on: ubuntu-latest + strategy: + matrix: + browser: [chrome, firefox, edge] + + timeout-minutes: 10 + + env: + BROWSER: ${{ matrix.browser }} + steps: + - uses: actions/checkout@v3 + + - uses: ./.github/actions/setup-and-cache + with: + node-version: 18 + + - uses: browser-actions/setup-chrome@v1 + - uses: browser-actions/setup-firefox@v1 + - uses: browser-actions/setup-edge@v1 + + - name: Install + run: pnpm i + + - name: Build + run: pnpm run build + + - name: Test Browser + run: pnpm run browser:test + + test-browser-safari: + runs-on: macos-latest + timeout-minutes: 10 + + env: + BROWSER: safari + steps: + - uses: actions/checkout@v3 + + - uses: ./.github/actions/setup-and-cache + with: + node-version: 18 + + - name: Info + run: system_profiler SPSoftwareDataType + + - name: Install + run: pnpm i + + - name: Build + run: pnpm run build + + - name: Enable + run: sudo safaridriver --enable + + - name: Test Browser + run: sudo pnpm run browser:test diff --git a/.gitignore b/.gitignore index 40b7ba1ce6ee..65f85c9967dc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ ltex* .DS_Store bench/test/*/*/ **/benchmark/bench.json +**/browser/browser.json cypress/videos cypress/downloads cypress/screenshots diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 3fb66bce6908..1ce0d4e9c78c 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -175,6 +175,10 @@ export default withPwa(defineConfig({ text: 'Vitest UI', link: '/guide/ui', }, + { + text: 'Browser Mode', + link: '/guide/browser', + }, { text: 'In-source Testing', link: '/guide/in-source', diff --git a/docs/config/index.md b/docs/config/index.md index 0ed0d3e9984e..f346ce99431e 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -383,7 +383,7 @@ export default defineConfig({ ### poolMatchGlobs -- **Type:** `[string, 'threads' | 'child_process'][]` +- **Type:** `[string, 'browser' | 'threads' | 'child_process'][]` - **Default:** `[]` - **Version:** Since Vitest 0.29.4 @@ -399,7 +399,9 @@ export default defineConfig({ poolMatchGlobs: [ // all tests in "worker-specific" directory will run inside a worker as if you enabled `--threads` for them, ['**/tests/worker-specific/**', 'threads'], - // all other tests will run based on "threads" option, if you didn't specify other globs + // run all tests in "browser" directory in an actual browser + ['**/tests/browser/**', 'browser'], + // all other tests will run based on "browser.enabled" and "threads" options, if you didn't specify other globs // ... ] } @@ -992,6 +994,80 @@ Open Vitest UI (WIP) Listen to port and serve API. When set to true, the default port is 51204 +### browser + +- **Type:** `{ enabled?, name?, provider?, headless?, api? }` +- **Default:** `{ enabled: false, headless: process.env.CI, api: 63315 }` +- **Version:** Since Vitest 0.30.0 +- **CLI:** `--browser`, `--browser=`, `--browser.name=chrome --browser.headless` + +Run Vitest tests in a browser. If the browser name is not specified, Vitest will try to determine your default browser automatically. We use [WebdriverIO](https://webdriver.io/) for running tests by default, but it can be configured with [browser.provider](/config/#browser-provider) option. + +::: tip NOTE +Read more about testing in a real browser in the [guide page](/guide/browser). +::: + +::: warning +This is an experimental feature. Breaking changes might not follow semver, please pin Vitest's version when using it. +::: + +#### browser.enabled + +- **Type:** `boolean` +- **Default:** `false` +- **CLI:** `--browser`, `--browser.enabled=false` + +Run all tests inside a browser by default. Can be overriden with [`poolMatchGlobs`](/config/#poolmatchglobs) option. + +#### browser.name + +- **Type:** `string` +- **Default:** _tries to find default browser automatically_ +- **CLI:** `--browser=safari` + +Run all tests in a specific browser. If not specified, tries to find a browser automatically. + + +#### browser.headless + +- **Type:** `boolean` +- **Default:** `process.env.CI` +- **CLI:** `--browser.headless`, `--brower.headless=false` + +Run the browser in a `headless` mode. If you are running Vitest in CI, it will be enabled by default. + +#### browser.api + +- **Type:** `number | { port?, strictPort?, host? }` +- **Default:** `63315` +- **CLI:** `--browser.api=63315`, `--browser.api.port=1234, --browser.api.host=example.com` + +Configure options for Vite server that serves code in the browser. Does not affect [`test.api`](/config/#api) option. + +#### browser.provider + +- **Type:** `string` +- **Default:** `'webdriverio'` +- **CLI:** `--browser.provider=./custom-provider.ts` + +Path to a provider that will be used when running browser tests. Provider should be exported using `default` export and have this shape: + +```ts +export interface BrowserProvider { + initialize(ctx: Vitest): Awaitable + createPool(): { + runTests: (files: string[], invalidated: string[]) => void + close: () => Awaited + } + // signals that test file stopped running, if it was opened with `id=` query + testFinished?(testId: string): Awaitable +} +``` + +::: warning +This is an advanced API for library authors. If you just need to run tests in a browser, use the [browser](/config/#browser) option. +::: + ### clearMocks - **Type:** `boolean` diff --git a/docs/guide/browser.md b/docs/guide/browser.md new file mode 100644 index 000000000000..34ffbb76f2ac --- /dev/null +++ b/docs/guide/browser.md @@ -0,0 +1,89 @@ +--- +title: Browser mode | Guide +--- + +# Browser mode (experimental) + +This page provides information about the experimental browser mode feature in the Vitest API, which allows you to run your tests in the browser natively, providing access to browser globals like window and document. This feature is currently under development, and APIs may change in the future. + +## Configuration + +To activate browser mode in your Vitest configuration, you can use the `--browser` flag or set the `browser.enabled` field to `true` in your Vitest configuration file. Here is an example configuration using the browser field: + +```ts +export default defineConfig({ + test: { + browser: { + enabled: true + }, + } +}) +``` + +## Browser Option Types: + +The browser option in Vitest can be set to either a boolean or a string type. If set to `true`, Vitest will try to automatically find your default browser. You can also specify a browser by providing its name as a `string`. The available browser options are: +- `firefox` +- `chrome` +- `edge` +- `safari` + +Here's an example configuration setting chrome as the browser option: + +```ts +export default defineConfig({ + test: { + browser: { + enabled: true, + name: 'chrome', + }, + } +}) +``` + +## Cross-browser Testing: + +When you specify a browser name in the browser option, Vitest will try to run the specified browser using [WebdriverIO](https://webdriver.io/) by default, and then run the tests there. This feature makes cross-browser testing easy to use and configure in environments like a CI. If you don't want to use WebdriverIO, you can configure the custom browser provider by using `browser.provider` option. + +To specify a browser using the CLI, use the `--browser` flag followed by the browser name, like this: + +```sh +npx vitest --browser=chrome +``` + +Or if you pass down several options, you dot-syntax: + +```sh +npx vitest --browser.name=chrome --browser.headless +``` + +::: tip NOTE +When using the Safari browser option, the `safaridriver` needs to be activated by running `sudo safaridriver --enable` on your device. + +Additionally, when running your tests, Vitest will attempt to install some drivers for compatibility with `safaridriver`. +::: + +## Headless + +Headless mode is another option available in the browser mode. In headless mode, the browser runs in the background without a user interface, which makes it useful for running automated tests. The headless option in Vitest can be set to a boolean value to enable or disable headless mode. + +Here's an example configuration enabling headless mode: + +```ts +export default defineConfig({ + test: { + browser: { + enabled: true, + headless: true, + }, + } +}) +``` + +You can also set headless mode using the `--browser.headless` flag in the CLI, like this: + +```sh +npx vitest --browser.name=chrome --browser.headless +``` + +In this case, Vitest will run in headless mode using the Chrome browser. diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 49843ada7c14..20a04cca487c 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -84,7 +84,7 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim | `--mode ` | Override Vite mode (default: `test`) | | `--globals` | Inject APIs globally | | `--dom` | Mock browser api with happy-dom | -| `--browser` | Run tests in browser | +| `--browser [options]` | Run tests in [the browser](/guide/browser) (default: `false`) | | `--environment ` | Runner environment (default: `node`) | | `--passWithNoTests` | Pass when no tests found | | `--logHeapUsage` | Show the size of heap for each test | diff --git a/package.json b/package.json index f56ab5f7b228..1bff9eea6ef0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "description": "A blazing fast unit test framework powered by Vite", "scripts": { "ci": "ni && nr typecheck && nr lint && nr build && nr test:all", - "build": "pnpm -r --filter='./packages/**' run build", + "build": "pnpm -r --filter='./packages/**' run build && pnpm -C ./packages/browser run copy", "dev": "NODE_OPTIONS=\"--max-old-space-size=8192\" pnpm -r --parallel --filter='./packages/**' run dev", "docs": "pnpm -C docs run dev", "docs:build": "pnpm -C docs run build", @@ -28,7 +28,8 @@ "typecheck:why": "tsc --noEmit --explainFiles > explainTypes.txt", "ui:build": "vite build packages/ui", "ui:dev": "vite packages/ui", - "ui:test": "npm -C packages/ui run test:run" + "ui:test": "npm -C packages/ui run test:run", + "browser:test": "npm -C test/browser run test" }, "devDependencies": { "@antfu/eslint-config": "^0.34.1", diff --git a/packages/browser/package.json b/packages/browser/package.json index b653ec5ce7f1..7648f2eff565 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -24,7 +24,7 @@ "stubs" ], "scripts": { - "build": "rimraf dist && pnpm build:node && pnpm build:client && pnpm copy", + "build": "rimraf dist && pnpm build:node && pnpm build:client", "build:client": "vite build src/client", "build:node": "rollup -c", "dev:client": "vite build src/client --watch", @@ -33,11 +33,14 @@ "copy": "esno scripts/copy-ui-to-browser.ts", "prepublishOnly": "pnpm build" }, + "peerDependencies": { + "vitest": ">=0.30.0" + }, "dependencies": { "@vitest/runner": "workspace:*", "local-pkg": "^0.4.2", "mlly": "^1.1.0", - "modern-node-polyfills": "0.0.9", + "modern-node-polyfills": "0.1.0", "rollup-plugin-node-polyfills": "^0.2.1", "sirv": "^2.0.2" }, diff --git a/packages/browser/src/client/main.ts b/packages/browser/src/client/main.ts index 05ff52cc63ee..c89721f3075d 100644 --- a/packages/browser/src/client/main.ts +++ b/packages/browser/src/client/main.ts @@ -7,7 +7,7 @@ import { createBrowserRunner } from './runner' import { BrowserSnapshotEnvironment } from './snapshot' // @ts-expect-error mocking some node apis -globalThis.process = { env: {}, argv: [], stdout: { write: () => {} } } +globalThis.process = { env: {}, argv: [], cwd: () => '/', stdout: { write: () => {} }, nextTick: cb => cb() } globalThis.global = globalThis export const PORT = import.meta.hot ? '51204' : location.port @@ -20,20 +20,14 @@ let config: ResolvedConfig | undefined let runner: VitestRunner | undefined const browserHashMap = new Map() -export const client = createClient(ENTRY_URL, { - handlers: { - async onPathsCollected(paths) { - if (!paths) - return - // const config = __vitest_worker__.config - const now = `${new Date().getTime()}` - paths.forEach((i) => { - browserHashMap.set(i, now) - }) - await runTests(paths, config, client) - }, - }, -}) +const url = new URL(location.href) +const testId = url.searchParams.get('id') || 'unknown' + +const getQueryPaths = () => { + return url.searchParams.getAll('path') +} + +export const client = createClient(ENTRY_URL) const ws = client.ws @@ -61,15 +55,13 @@ ws.addEventListener('open', async () => { globalThis.__vitest_worker__ = { config, browserHashMap, + moduleCache: new Map(), rpc: client.rpc, } // @ts-expect-error mocking vitest apis globalThis.__vitest_mocker__ = {} - const paths = await client.rpc.getPaths() - - const now = `${new Date().getTime()}` - paths.forEach(i => browserHashMap.set(i, now)) + const paths = getQueryPaths() const iFrame = document.getElementById('vitest-ui') as HTMLIFrameElement iFrame.setAttribute('src', '/__vitest__/') @@ -95,9 +87,19 @@ async function runTests(paths: string[], config: any, client: VitestClient) { setupSnapshotEnvironment(new BrowserSnapshotEnvironment(client)) hasSnapshot = true } - await setupCommonEnv(config) - await startTests(paths, runner) - await client.rpc.onFinished() - await client.rpc.onWatcherStart() + try { + await setupCommonEnv(config) + const files = paths.map((path) => { + return (`${config.root}/${path}`).replace(/\/+/g, '/') + }) + + const now = `${new Date().getTime()}` + files.forEach(i => browserHashMap.set(i, now)) + + await startTests(files, runner) + } + finally { + await client.rpc.onDone(testId) + } } diff --git a/packages/browser/src/client/runner.ts b/packages/browser/src/client/runner.ts index b3f7f93fd465..db6a72af9bc4 100644 --- a/packages/browser/src/client/runner.ts +++ b/packages/browser/src/client/runner.ts @@ -1,4 +1,4 @@ -import type { File, TaskResult } from '@vitest/runner' +import type { File, TaskResult, Test } from '@vitest/runner' import type { VitestClient } from '@vitest/ws-client' import type { ResolvedConfig } from '#types' @@ -11,16 +11,23 @@ interface BrowserRunnerOptions { export function createBrowserRunner(original: any) { return class BrowserTestRunner extends original { public config: ResolvedConfig - hasMap = new Map() + hashMap = new Map() client: VitestClient constructor(options: BrowserRunnerOptions) { super(options.config) this.config = options.config - this.hasMap = options.browserHashMap + this.hashMap = options.browserHashMap this.client = options.client } + async onAfterRunTest(task: Test) { + await super.onAfterRunTest?.() + task.result?.errors?.forEach((error) => { + console.error(error.message) + }) + } + onCollected(files: File[]): unknown { return this.client.rpc.onCollected(files) } @@ -31,7 +38,11 @@ export function createBrowserRunner(original: any) { async importFile(filepath: string) { const match = filepath.match(/^(\w:\/)/) - const hash = this.hasMap.get(filepath) + let hash = this.hashMap.get(filepath) + if (!hash) { + hash = Date.now().toString() + this.hashMap.set(filepath, hash) + } const importpath = match ? `/@fs/${filepath.slice(match[1].length)}?v=${hash}` : `${filepath}?v=${hash}` diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts index a2cfb830bbcd..39bf632b082a 100644 --- a/packages/browser/src/node/index.ts +++ b/packages/browser/src/node/index.ts @@ -18,16 +18,16 @@ export default (base = '/'): Plugin[] => { { enforce: 'pre', name: 'vitest:browser', - async resolveId(id, _, ctx) { - if (ctx.ssr) - return - + async resolveId(id) { if (id === '/__vitest_index__') return this.resolve('vitest/browser') if (id === '/__vitest_runners__') return this.resolve('vitest/runners') + if (id.startsWith('node:')) + id = id.slice(5) + if (polyfills.includes(id)) return polyfillPath(normalizeId(id)) @@ -45,8 +45,8 @@ export default (base = '/'): Plugin[] => { }, { name: 'modern-node-polyfills', - async resolveId(id, _, ctx) { - if (ctx.ssr || !builtinModules.includes(id)) + async resolveId(id) { + if (!builtinModules.includes(id)) return id = normalizeId(id) diff --git a/packages/ui/client/composables/client/static.ts b/packages/ui/client/composables/client/static.ts index ac459e9a4259..3756fa45b104 100644 --- a/packages/ui/client/composables/client/static.ts +++ b/packages/ui/client/composables/client/static.ts @@ -50,13 +50,16 @@ export function createStaticClient(): VitestClient { readFile: async (id) => { return Promise.resolve(id) }, - onWatcherStart: asyncNoop, - onFinished: asyncNoop, + onDone: noop, onCollected: asyncNoop, onTaskUpdate: noop, writeFile: asyncNoop, rerun: asyncNoop, updateSnapshot: asyncNoop, + removeFile: asyncNoop, + createDirectory: asyncNoop, + resolveSnapshotPath: asyncNoop, + snapshotSaved: asyncNoop, } as WebSocketHandlers ctx.rpc = rpc as any as BirpcReturn diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 1f11c5c3db9c..26e3b7d57f9c 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -113,6 +113,12 @@ "jsdom": { "optional": true }, + "webdriverio": { + "optional": true + }, + "safaridriver": { + "optional": true + }, "@edge-runtime/vm": { "optional": true } @@ -179,8 +185,11 @@ "pretty-format": "^27.5.1", "prompts": "^2.4.2", "rollup": "^2.79.1", + "safaridriver": "^0.0.4", "strip-ansi": "^7.0.1", "typescript": "^4.9.4", - "ws": "^8.12.0" + "webdriverio": "^8.5.5", + "ws": "^8.12.0", + "x-default-browser": "^0.5.2" } } diff --git a/packages/vitest/rollup.config.js b/packages/vitest/rollup.config.js index c2139ae77643..fe021e67f5ff 100644 --- a/packages/vitest/rollup.config.js +++ b/packages/vitest/rollup.config.js @@ -49,6 +49,8 @@ const external = [ 'node:worker_threads', 'node:fs', 'inspector', + 'webdriverio', + 'safaridriver', 'vite-node/source-map', 'vite-node/client', 'vite-node/server', diff --git a/packages/vitest/src/api/setup.ts b/packages/vitest/src/api/setup.ts index 004819945d31..342be15b6508 100644 --- a/packages/vitest/src/api/setup.ts +++ b/packages/vitest/src/api/setup.ts @@ -5,6 +5,7 @@ import { createBirpc } from 'birpc' import { parse, stringify } from 'flatted' import type { WebSocket } from 'ws' import { WebSocketServer } from 'ws' +import type { ViteDevServer } from 'vite' import { API_PATH } from '../constants' import type { Vitest } from '../node' import type { File, ModuleGraphData, Reporter, TaskResultPack, UserConsoleLog } from '../types' @@ -12,12 +13,12 @@ import { getModuleGraph } from '../utils' import { parseErrorStacktrace } from '../utils/source-map' import type { TransformResultWithSource, WebSocketEvents, WebSocketHandlers } from './types' -export function setup(ctx: Vitest) { +export function setup(ctx: Vitest, server?: ViteDevServer) { const wss = new WebSocketServer({ noServer: true }) const clients = new Map>() - ctx.server.httpServer?.on('upgrade', (request, socket, head) => { + ;(server || ctx.server).httpServer?.on('upgrade', (request, socket, head) => { if (!request.url) return @@ -34,11 +35,8 @@ export function setup(ctx: Vitest) { function setupClient(ws: WebSocket) { const rpc = createBirpc( { - async onWatcherStart() { - await ctx.report('onWatcherStart') - }, - async onFinished() { - await ctx.report('onFinished') + async onDone(testId) { + await ctx.browserProvider?.testFinished?.(testId) }, async onCollected(files) { ctx.state.collectFiles(files) diff --git a/packages/vitest/src/api/types.ts b/packages/vitest/src/api/types.ts index 4275d9d9ca66..d56d4cf8fd8f 100644 --- a/packages/vitest/src/api/types.ts +++ b/packages/vitest/src/api/types.ts @@ -6,10 +6,9 @@ export interface TransformResultWithSource extends TransformResult { } export interface WebSocketHandlers { - onWatcherStart: () => Promise - onFinished(files?: File[]): Promise onCollected(files?: File[]): Promise onTaskUpdate(packs: TaskResultPack[]): void + onDone(name: string): void getFiles(): File[] getPaths(): string[] getConfig(): ResolvedConfig diff --git a/packages/vitest/src/constants.ts b/packages/vitest/src/constants.ts index 5727be5cb674..a97d0c48fc4c 100644 --- a/packages/vitest/src/constants.ts +++ b/packages/vitest/src/constants.ts @@ -1,10 +1,3 @@ -import url from 'node:url' -import { resolve } from 'pathe' -import { isNode } from './utils/env' - -export const rootDir = isNode ? resolve(url.fileURLToPath(import.meta.url), '../../') : import.meta.url -export const distDir = isNode ? resolve(url.fileURLToPath(import.meta.url), '../../dist') : import.meta.url - // if changed, update also jsdocs and docs export const defaultPort = 51204 diff --git a/packages/vitest/src/defaults.ts b/packages/vitest/src/defaults.ts index f6ed961c2e82..b960898cc6f1 100644 --- a/packages/vitest/src/defaults.ts +++ b/packages/vitest/src/defaults.ts @@ -39,6 +39,11 @@ export const coverageConfigDefaults: ResolvedCoverageOptions = { extension: ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte'], } +export const browserConfigDefaults = { + enabled: false, + headless: isCI, +} as const + export const fakeTimersDefaults = { loopLimit: 10_000, shouldClearNativeTimers: true, @@ -68,6 +73,7 @@ const config = { hookTimeout: 10000, teardownTimeout: 10000, isolate: true, + browser: browserConfigDefaults, watchExclude: ['**/node_modules/**', '**/dist/**'], forceRerunTriggers: [ '**/package.json/**', diff --git a/packages/vitest/src/integrations/browser.ts b/packages/vitest/src/integrations/browser.ts new file mode 100644 index 000000000000..9782ec2724c6 --- /dev/null +++ b/packages/vitest/src/integrations/browser.ts @@ -0,0 +1,25 @@ +import { WebdriverBrowserProvider } from '../node/browser/webdriver' +import type { BrowserProviderModule, ResolvedBrowserOptions } from '../types/browser' + +interface Loader { + executeId: (id: string) => Promise<{ default: BrowserProviderModule }> +} + +export async function getBrowserProvider(options: ResolvedBrowserOptions, loader: Loader): Promise { + if (!options.provider || options.provider === 'webdriverio') + return WebdriverBrowserProvider + + let customProviderModule + + try { + customProviderModule = await loader.executeId(options.provider) + } + catch (error) { + throw new Error(`Failed to load custom BrowserProvider from ${options.provider}`, { cause: error }) + } + + if (customProviderModule.default == null) + throw new Error(`Custom BrowserProvider loaded from ${options.provider} was not the default export`) + + return customProviderModule.default +} diff --git a/packages/vitest/src/integrations/browser/server.ts b/packages/vitest/src/integrations/browser/server.ts new file mode 100644 index 000000000000..61c471ee1a02 --- /dev/null +++ b/packages/vitest/src/integrations/browser/server.ts @@ -0,0 +1,66 @@ +import { createServer } from 'vite' +import { resolve } from 'pathe' +import { findUp } from 'find-up' +import { configFiles } from '../../constants' +import type { Vitest } from '../../node' +import type { UserConfig } from '../../types/config' +import { ensurePackageInstalled } from '../../node/pkg' +import { resolveApiServerConfig } from '../../node/config' + +export async function createBrowserServer(ctx: Vitest, options: UserConfig) { + const root = ctx.config.root + + await ensurePackageInstalled('@vitest/browser', root) + + const configPath = options.config === false + ? false + : options.config + ? resolve(root, options.config) + : await findUp(configFiles, { cwd: root } as any) + + const server = await createServer({ + logLevel: 'error', + mode: ctx.config.mode, + configFile: configPath, + // watch is handled by Vitest + server: { + hmr: false, + watch: { + ignored: ['**/**'], + }, + }, + plugins: [ + (await import('@vitest/browser')).default('/'), + { + enforce: 'post', + name: 'vitest:browser:config', + async config(config) { + const server = resolveApiServerConfig(config.test?.browser || {}) || { + port: 63315, + } + + config.server = server + + config.optimizeDeps ??= {} + config.optimizeDeps.entries ??= [] + + const root = config.root || process.cwd() + const [...entries] = await ctx.globAllTestFiles(ctx.config, ctx.config.dir || root) + entries.push(...ctx.config.setupFiles) + + if (typeof config.optimizeDeps.entries === 'string') + config.optimizeDeps.entries = [config.optimizeDeps.entries] + + config.optimizeDeps.entries.push(...entries) + }, + }, + ], + }) + + await server.listen() + await server.watcher.close() + + ;(await import('../../api/setup')).setup(ctx, server) + + return server +} diff --git a/packages/vitest/src/node/browser/webdriver.ts b/packages/vitest/src/node/browser/webdriver.ts new file mode 100644 index 000000000000..8e643e3cb9d1 --- /dev/null +++ b/packages/vitest/src/node/browser/webdriver.ts @@ -0,0 +1,126 @@ +import { promisify } from 'util' +import type { Browser } from 'webdriverio' +import type { Awaitable } from '@vitest/utils' +// @ts-expect-error doesn't have types +import detectBrowser from 'x-default-browser' +import { createDefer } from '@vitest/utils' +import { relative } from 'pathe' +import type { BrowserProvider } from '../../types/browser' +import { ensurePackageInstalled } from '../pkg' +import type { Vitest } from '../core' + +export class WebdriverBrowserProvider implements BrowserProvider { + private cachedBrowser: Browser | null = null + private testDefers = new Map>() + private stopSafari: () => void = () => {} + private host = '' + private browser = 'unknown' + private ctx!: Vitest + + async initialize(ctx: Vitest) { + this.ctx = ctx + this.host = `http://${ctx.config.browser.api?.host || 'localhost'}:${ctx.browser.config.server.port}` + + const root = this.ctx.config.root + const browser = await this.getBrowserName() + + this.browser = browser + + if (browser === 'unknown' || !browser) + throw new Error('Cannot detect browser. Please specify it in the config file.') + + if (!await ensurePackageInstalled('webdriverio', root)) + throw new Error('Cannot find "webdriverio" package. Please install it manually.') + + if (browser === 'safari' && !await ensurePackageInstalled('safaridriver', root)) + throw new Error('Cannot find "safaridriver" package. Please install it manually.') + } + + private async resolveBrowserName(): Promise { + const browser = await promisify(detectBrowser)() + return browser.commonName + } + + async getBrowserName(): Promise { + return this.ctx.config.browser.name ?? await this.resolveBrowserName() + } + + async openBrowser() { + if (this.cachedBrowser) + return this.cachedBrowser + + const options = this.ctx.config.browser + + if (this.browser === 'safari') { + const safaridriver = await import('safaridriver') + safaridriver.start({ diagnose: true }) + this.stopSafari = () => safaridriver.stop() + + process.on('beforeExit', () => { + safaridriver.stop() + }) + } + + const { remote } = await import('webdriverio') + + // TODO: close everything, if browser is closed from the outside + this.cachedBrowser = await remote({ + logLevel: 'error', + capabilities: { + 'browserName': this.browser, + 'wdio:devtoolsOptions': { headless: options.headless }, + }, + }) + + return this.cachedBrowser + } + + testFinished(id: string): Awaitable { + this.testDefers.get(id)?.resolve(true) + } + + private waitForTest(id: string) { + const defer = createDefer() + this.testDefers.set(id, defer) + return defer + } + + createPool() { + const runTests = async (files: string[]) => { + const paths = files.map(file => relative(this.ctx.config.root, file)) + const browserInstance = await this.openBrowser() + + const isolate = this.ctx.config.isolate + if (isolate) { + for (const path of paths) { + const url = new URL(this.host) + url.searchParams.append('path', path) + url.searchParams.set('id', path) + await browserInstance.url(url.toString()) + await this.waitForTest(path) + } + } + else { + const url = new URL(this.host) + url.searchParams.set('id', 'no-isolate') + paths.forEach(path => url.searchParams.append('path', path)) + await browserInstance.url(url.toString()) + await this.waitForTest('no-isolate') + } + } + + return { + runTests, + close: async () => { + this.testDefers.clear() + await Promise.all([ + this.stopSafari(), + this.cachedBrowser?.sessionId ? this.cachedBrowser?.deleteSession?.() : null, + ]) + // TODO: right now process can only exit with timeout, if we use browser + // needs investigating + process.exit() + }, + } + } +} diff --git a/packages/vitest/src/node/cli-api.ts b/packages/vitest/src/node/cli-api.ts index b891429c638a..0bf39e8a76b4 100644 --- a/packages/vitest/src/node/cli-api.ts +++ b/packages/vitest/src/node/cli-api.ts @@ -32,8 +32,6 @@ export async function startVitest( if (options.run) options.watch = false - if (options.browser) // enabling threads in browser mode causes inconsistences - options.threads = false // this shouldn't affect _application root_ that can be changed inside config const root = resolve(options.root || process.cwd()) @@ -46,6 +44,18 @@ export async function startVitest( if (typeof options.coverage === 'boolean') options.coverage = { enabled: options.coverage } + // running "vitest --browser" + if (typeof options.browser === 'boolean') + options.browser = { enabled: options.browser } + + // running "vitest --browser=chrome" + if (typeof options.browser === 'string') + options.browser = { enabled: true, name: options.browser } + + // running "vitest --browser.headless" + if (typeof options.browser === 'object' && !('enabled' in options.browser)) + options.browser.enabled = true + const ctx = await createVitest(mode, options, viteOverrides) if (mode === 'test' && ctx.config.coverage.enabled) { @@ -92,7 +102,7 @@ export async function startVitest( return ctx } - if (ctx.config.watch) + if (ctx.shouldKeepServer()) return ctx await ctx.close() diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts index d0afc779192a..e70c8f52580d 100644 --- a/packages/vitest/src/node/cli.ts +++ b/packages/vitest/src/node/cli.ts @@ -35,8 +35,8 @@ cli .option('--mode ', 'Override Vite mode (default: test)') .option('--globals', 'Inject apis globally') .option('--dom', 'Mock browser api with happy-dom') - .option('--browser', 'Run tests in browser') - .option('--environment ', 'Specify runner environment (default: node)') + .option('--browser [options]', 'Run tests in the browser (default: false)') + .option('--environment ', 'Specify runner environment, if not running in the browser (default: node)') .option('--passWithNoTests', 'Pass when no tests found') .option('--logHeapUsage', 'Show the size of heap for each test') .option('--allowOnly', 'Allow tests and suites that are marked as only (default: !process.env.CI)') @@ -129,7 +129,7 @@ function normalizeCliOptions(argv: CliOptions): CliOptions { async function start(mode: VitestRunMode, cliFilters: string[], options: CliOptions): Promise { try { const ctx = await startVitest(mode, cliFilters.map(normalize), normalizeCliOptions(options)) - if (!ctx?.config.watch) + if (!ctx?.shouldKeepServer()) await ctx?.exit() return ctx } diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index 34dac6bb1af2..17ff3e747268 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -21,12 +21,12 @@ const extraInlineDeps = [ '@nuxt/test-utils', ] -export function resolveApiConfig( +export function resolveApiServerConfig( options: Options, ): ApiConfig | undefined { let api: ApiConfig | undefined - if ((options.ui || options.browser) && !options.api) + if (options.ui && !options.api) api = { port: defaultPort } else if (options.api === true) api = { port: defaultPort } @@ -203,7 +203,7 @@ export function resolveConfig( ] // the server has been created, we don't need to override vite.server options - resolved.api = resolveApiConfig(options) + resolved.api = resolveApiServerConfig(options) if (options.related) resolved.related = toArray(options.related).map(file => resolve(resolved.root, file)) @@ -253,5 +253,12 @@ export function resolveConfig( resolved.exclude = resolved.typecheck.exclude } + resolved.browser.enabled ??= false + resolved.browser.headless ??= isCI + + resolved.browser.api = resolveApiServerConfig(resolved.browser) || { + port: 63315, + } + return resolved } diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index b8318932b778..814227a73951 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -4,12 +4,16 @@ import { normalize, relative, toNamespacedPath } from 'pathe' import fg from 'fast-glob' import mm from 'micromatch' import c from 'picocolors' +import { normalizeRequestId } from 'vite-node/utils' import { ViteNodeRunner } from 'vite-node/client' import type { ArgumentsType, CoverageProvider, OnServerRestartHandler, Reporter, ResolvedConfig, UserConfig, VitestRunMode } from '../types' import { SnapshotManager } from '../integrations/snapshot/manager' import { deepMerge, hasFailed, noop, slash, toArray } from '../utils' import { getCoverageProvider } from '../integrations/coverage' import { Typechecker } from '../typecheck/typechecker' +import type { BrowserProvider } from '../types/browser' +import { getBrowserProvider } from '../integrations/browser' +import { createBrowserServer } from '../integrations/browser/server' import { createPool } from './pool' import type { ProcessPool } from './pool' import { createBenchmarkReporters, createReporters } from './reporters/utils' @@ -25,12 +29,14 @@ export class Vitest { config: ResolvedConfig = undefined! configOverride: Partial | undefined + browser: ViteDevServer = undefined! server: ViteDevServer = undefined! state: StateManager = undefined! snapshot: SnapshotManager = undefined! cache: VitestCache = undefined! reporters: Reporter[] = undefined! coverageProvider: CoverageProvider | null | undefined + browserProvider: BrowserProvider | undefined logger: Logger pool: ProcessPool | undefined typechecker: Typechecker | undefined @@ -56,6 +62,13 @@ export class Vitest { private _onRestartListeners: OnServerRestartHandler[] = [] private _onSetServer: OnServerRestartHandler[] = [] + async initBrowserServer(options: UserConfig) { + if (!this.isBrowserEnabled()) + return + await this.browser?.close() + this.browser = await createBrowserServer(this, options) + } + async setServer(options: UserConfig, server: ViteDevServer) { this.unregisterWatcher?.() clearTimeout(this._rerunTimer) @@ -132,6 +145,15 @@ export class Vitest { return this.coverageProvider } + async initBrowserProvider() { + if (this.browserProvider) + return this.browserProvider + const Provider = await getBrowserProvider(this.config.browser, this.runner) + this.browserProvider = new Provider() + await this.browserProvider.initialize(this as any) + return this.browserProvider + } + getSerializableConfig() { return deepMerge({ ...this.config, @@ -220,6 +242,9 @@ export class Vitest { try { await this.initCoverageProvider() await this.coverageProvider?.clean(this.config.coverage.clean) + + if (this.isBrowserEnabled()) + await this.initBrowserProvider() } catch (e) { this.logger.error(e) @@ -247,7 +272,7 @@ export class Vitest { await this.reportCoverage(true) - if (this.config.watch && !this.config.browser) + if (this.config.watch) await this.report('onWatcherStart') } @@ -326,9 +351,6 @@ export class Vitest { await this.report('onPathsCollected', paths) - if (this.config.browser) - return - // previous run await this.runningPromise @@ -357,8 +379,7 @@ export class Vitest { await this.cache.results.writeToCache() })() .finally(async () => { - if (!this.config.browser) - await this.report('onFinished', this.state.getFiles(paths), this.state.getUnhandledErrors()) + await this.report('onFinished', this.state.getFiles(paths), this.state.getUnhandledErrors()) this.runningPromise = undefined }) @@ -379,8 +400,7 @@ export class Vitest { await this.reportCoverage(!trigger) - if (!this.config.browser) - await this.report('onWatcherStart', this.state.getFiles(files)) + await this.report('onWatcherStart', this.state.getFiles(files)) } async changeNamePattern(pattern: string, files: string[] = this.state.getFilepaths(), trigger?: string) { @@ -472,15 +492,14 @@ export class Vitest { await this.reportCoverage(false) - if (!this.config.browser) - await this.report('onWatcherStart', this.state.getFiles(files)) + await this.report('onWatcherStart', this.state.getFiles(files)) }, WATCHER_DEBOUNCE) } private unregisterWatcher = noop private registerWatcher() { const updateLastChanged = (id: string) => { - const mod = this.server.moduleGraph.getModuleById(id) + const mod = this.server.moduleGraph.getModuleById(id) || this.browser?.moduleGraph.getModuleById(id) if (mod) mod.lastHMRTimestamp = Date.now() } @@ -544,9 +563,22 @@ export class Vitest { return true } - const mod = this.server.moduleGraph.getModuleById(id) - if (!mod) - return false + const mod = this.server.moduleGraph.getModuleById(id) || this.browser?.moduleGraph.getModuleById(id) + if (!mod) { + // files with `?v=` query from the browser + const mods = this.browser?.moduleGraph.getModulesByFile(id) + if (!mods?.size) + return false + let rerun = false + mods.forEach((m) => { + if (m.id && this.handleFileChanged(m.id)) + rerun = true + }) + return rerun + } + + // remove queries from id + id = normalizeRequestId(id, this.server.config.base) this.invalidates.add(id) @@ -579,6 +611,7 @@ export class Vitest { if (!this.closingPromise) { this.closingPromise = Promise.allSettled([ this.pool?.close(), + this.browser?.close(), this.server.close(), this.typechecker?.stop(), ].filter(Boolean)).then((results) => { @@ -685,6 +718,17 @@ export class Vitest { return false } + isBrowserEnabled() { + if (this.config.browser.enabled) + return true + return (this.config.poolMatchGlobs || []).some(([, pool]) => pool === 'browser') + } + + // The server needs to be running for communication + shouldKeepServer() { + return !!this.config?.watch + } + isInSourceTestFile(code: string) { return code.includes('import.meta.vitest') } diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 7897002509f2..fe9d7406c998 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -109,9 +109,9 @@ export class Logger { if (this.ctx.config.sequence.sequencer === RandomSequencer) this.log(c.gray(` Running tests with seed "${this.ctx.config.sequence.seed}"`)) - if (this.ctx.config.browser) - this.log(c.dim(c.green(` Browser runner started at http://${this.ctx.config.api?.host || 'localhost'}:${c.bold(`${this.ctx.server.config.server.port}`)}`))) - else if (this.ctx.config.ui) + if (this.ctx.config.browser.enabled) + this.log(c.dim(c.green(` Browser runner started at http://${this.ctx.config.browser.api?.host || 'localhost'}:${c.bold(`${this.ctx.browser.config.server.port}`)}`))) + if (this.ctx.config.ui) this.log(c.dim(c.green(` UI started at http://${this.ctx.config.api?.host || 'localhost'}:${c.bold(`${this.ctx.server.config.server.port}`)}${this.ctx.config.uiBase}`))) else if (this.ctx.config.api) this.log(c.dim(c.green(` API started at http://${this.ctx.config.api?.host || 'localhost'}:${c.bold(`${this.ctx.config.api.port}`)}`))) diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts index 3754f1aeafac..5fb4e1ed1280 100644 --- a/packages/vitest/src/node/plugins/index.ts +++ b/packages/vitest/src/node/plugins/index.ts @@ -7,7 +7,7 @@ import { configDefaults } from '../../defaults' import type { ResolvedConfig, UserConfig } from '../../types' import { deepMerge, notNullish, removeUndefinedValues } from '../../utils' import { ensurePackageInstalled } from '../pkg' -import { resolveApiConfig } from '../config' +import { resolveApiServerConfig } from '../config' import { Vitest } from '../core' import { generateScopedClassName } from '../../integrations/css/css-modules' import { EnvReplacerPlugin } from './envReplacer' @@ -25,11 +25,6 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t return (await import('@vitest/ui')).default(options.uiBase) } - async function BrowserPlugin() { - await ensurePackageInstalled('@vitest/browser', getRoot()) - return (await import('@vitest/browser')).default('/') - } - return [ { name: 'vitest', @@ -53,7 +48,7 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t options, removeUndefinedValues(viteConfig.test ?? {}), ) - preOptions.api = resolveApiConfig(preOptions) + preOptions.api = resolveApiServerConfig(preOptions) if (viteConfig.define) { delete viteConfig.define['import.meta.vitest'] @@ -97,8 +92,6 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t if (preOptions.ui && preOptions.open) open = preOptions.uiBase ?? '/__vitest__/' - else if (preOptions.browser) - open = '/' const config: ViteConfig = { root: viteConfig.test?.root || options.root, @@ -140,50 +133,48 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t } } - if (!options.browser) { - const optimizeConfig: Partial = {} - const optimizer = preOptions.deps?.experimentalOptimizer - if (!optimizer?.enabled) { - optimizeConfig.cacheDir = undefined - optimizeConfig.optimizeDeps = { - // experimental in Vite >2.9.2, entries remains to help with older versions - disabled: true, - entries: [], - } + const optimizeConfig: Partial = {} + const optimizer = preOptions.deps?.experimentalOptimizer + if (!optimizer?.enabled) { + optimizeConfig.cacheDir = undefined + optimizeConfig.optimizeDeps = { + // experimental in Vite >2.9.2, entries remains to help with older versions + disabled: true, + entries: [], } - else { - const root = config.root || process.cwd() - const [...entries] = await ctx.globAllTestFiles(preOptions as ResolvedConfig, preOptions.dir || root) - if (preOptions?.setupFiles) { - const setupFiles = toArray(preOptions.setupFiles).map((file: string) => - normalize( - resolveModule(file, { paths: [root] }) + } + else { + const root = config.root || process.cwd() + const [...entries] = await ctx.globAllTestFiles(preOptions as ResolvedConfig, preOptions.dir || root) + if (preOptions?.setupFiles) { + const setupFiles = toArray(preOptions.setupFiles).map((file: string) => + normalize( + resolveModule(file, { paths: [root] }) ?? resolve(root, file), - ), - ) - entries.push(...setupFiles) - } - const cacheDir = preOptions.cache !== false ? preOptions.cache?.dir : null - optimizeConfig.cacheDir = cacheDir ?? 'node_modules/.vitest' - optimizeConfig.optimizeDeps = { - ...viteConfig.optimizeDeps, - ...optimizer, - disabled: false, - entries: [...(viteConfig.optimizeDeps?.entries || []), ...entries], - exclude: ['vitest', ...builtinModules, ...(optimizer.exclude || viteConfig.optimizeDeps?.exclude || [])], - include: (optimizer.include || viteConfig.optimizeDeps?.include || []).filter((n: string) => n !== 'vitest'), - } - // Vite throws an error that it cannot rename "deps_temp", but optimization still works - // let's not show this error to users - const { error: logError } = console - console.error = (...args) => { - if (typeof args[0] === 'string' && args[0].includes('/deps_temp')) - return - return logError(...args) - } + ), + ) + entries.push(...setupFiles) + } + const cacheDir = preOptions.cache !== false ? preOptions.cache?.dir : null + optimizeConfig.cacheDir = cacheDir ?? 'node_modules/.vitest' + optimizeConfig.optimizeDeps = { + ...viteConfig.optimizeDeps, + ...optimizer, + disabled: false, + entries: [...(viteConfig.optimizeDeps?.entries || []), ...entries], + exclude: ['vitest', ...builtinModules, ...(optimizer.exclude || viteConfig.optimizeDeps?.exclude || [])], + include: (optimizer.include || viteConfig.optimizeDeps?.include || []).filter((n: string) => n !== 'vitest'), + } + // Vite throws an error that it cannot rename "deps_temp", but optimization still works + // let's not show this error to users + const { error: logError } = console + console.error = (...args) => { + if (typeof args[0] === 'string' && args[0].includes('/deps_temp')) + return + return logError(...args) } - Object.assign(config, optimizeConfig) } + Object.assign(config, optimizeConfig) return config }, @@ -202,7 +193,7 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t viteConfigTest, options, ) - options.api = resolveApiConfig(options) + options.api = resolveApiServerConfig(options) // we replace every "import.meta.env" with "process.env" // to allow reassigning, so we need to put all envs on process.env @@ -229,6 +220,7 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t async configureServer(server) { try { await ctx.setServer(options, server) + await ctx.initBrowserServer(options) if (options.api && options.watch) (await import('../../api/setup')).setup(ctx) } @@ -244,9 +236,6 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t }, EnvReplacerPlugin(), GlobalSetupPlugin(ctx), - ...(options.browser - ? await BrowserPlugin() - : []), ...CSSEnablerPlugin(ctx), CoverageTransform(ctx), options.ui diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts index 5b5cf234fbdb..3f0a6e18d450 100644 --- a/packages/vitest/src/node/pool.ts +++ b/packages/vitest/src/node/pool.ts @@ -1,7 +1,7 @@ import { pathToFileURL } from 'node:url' import mm from 'micromatch' import { resolve } from 'pathe' -import { distDir, rootDir } from '../constants' +import { distDir, rootDir } from '../paths' import type { VitestPool } from '../types' import type { Vitest } from './core' import { createChildProcessPool } from './pools/child' @@ -26,9 +26,12 @@ export function createPool(ctx: Vitest): ProcessPool { const pools: Record = { child_process: null, threads: null, + browser: null, } function getDefaultPoolName() { + if (ctx.config.browser.enabled) + return 'browser' if (ctx.config.threads) return 'threads' return 'child_process' @@ -95,6 +98,11 @@ export function createPool(ctx: Vitest): ProcessPool { if (!files.length) return null + if (ctx.browserProvider && pool === 'browser') { + pools.browser ??= ctx.browserProvider.createPool() + return pools.browser.runTests(files, invalidate) + } + if (pool === 'threads') { pools.threads ??= createThreadsPool(ctx, options) return pools.threads.runTests(files, invalidate) diff --git a/packages/vitest/src/node/pools/child.ts b/packages/vitest/src/node/pools/child.ts index 6f51193192c5..b6be56a0cfb8 100644 --- a/packages/vitest/src/node/pools/child.ts +++ b/packages/vitest/src/node/pools/child.ts @@ -8,7 +8,7 @@ import type { ContextTestEnvironment, ResolvedConfig, RuntimeRPC } from '../../t import type { Vitest } from '../core' import type { ChildContext } from '../../types/child' import type { PoolProcessOptions, ProcessPool } from '../pool' -import { distDir } from '../../constants' +import { distDir } from '../../paths' import { groupBy } from '../../utils/base' import { envsOrder, groupFilesByEnv } from '../../utils/test-helpers' import { createMethodsRPC } from './rpc' diff --git a/packages/vitest/src/node/pools/threads.ts b/packages/vitest/src/node/pools/threads.ts index 66c897343c34..8ac53377ecdc 100644 --- a/packages/vitest/src/node/pools/threads.ts +++ b/packages/vitest/src/node/pools/threads.ts @@ -5,7 +5,7 @@ import { createBirpc } from 'birpc' import { resolve } from 'pathe' import type { Options as TinypoolOptions } from 'tinypool' import Tinypool from 'tinypool' -import { distDir } from '../../constants' +import { distDir } from '../../paths' import type { ContextTestEnvironment, ResolvedConfig, RuntimeRPC, WorkerContext } from '../../types' import type { Vitest } from '../core' import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool' diff --git a/packages/vitest/src/paths.ts b/packages/vitest/src/paths.ts new file mode 100644 index 000000000000..9bc74dd12216 --- /dev/null +++ b/packages/vitest/src/paths.ts @@ -0,0 +1,5 @@ +import url from 'node:url' +import { resolve } from 'pathe' + +export const rootDir = resolve(url.fileURLToPath(import.meta.url), '../../') +export const distDir = resolve(url.fileURLToPath(import.meta.url), '../../dist') diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts index f3575ddbcd98..901709ddcb9e 100644 --- a/packages/vitest/src/runtime/entry.ts +++ b/packages/vitest/src/runtime/entry.ts @@ -4,7 +4,7 @@ import { resolve } from 'pathe' import type { ContextTestEnvironment, ResolvedConfig } from '../types' import { getWorkerState, resetModules } from '../utils' import { vi } from '../integrations/vi' -import { distDir } from '../constants' +import { distDir } from '../paths' import { startCoverageInsideWorker, stopCoverageInsideWorker, takeCoverageInsideWorker } from '../integrations/coverage' import { setupGlobalEnv, withEnv } from './setup.node' import { rpc } from './rpc' diff --git a/packages/vitest/src/runtime/execute.ts b/packages/vitest/src/runtime/execute.ts index 333707685ecf..23a466f331ee 100644 --- a/packages/vitest/src/runtime/execute.ts +++ b/packages/vitest/src/runtime/execute.ts @@ -8,7 +8,7 @@ import { processError } from '@vitest/runner/utils' import type { MockMap } from '../types/mocker' import { getCurrentEnvironment, getWorkerState } from '../utils/global' import type { ContextRPC, ContextTestEnvironment, ResolvedConfig } from '../types' -import { distDir } from '../constants' +import { distDir } from '../paths' import { VitestMocker } from './mocker' import { rpc } from './rpc' diff --git a/packages/vitest/src/types/browser.ts b/packages/vitest/src/types/browser.ts new file mode 100644 index 000000000000..88e54ed6bd08 --- /dev/null +++ b/packages/vitest/src/types/browser.ts @@ -0,0 +1,57 @@ +import type { Awaitable } from '@vitest/utils' +import type { Vitest } from '../node' +import type { ProcessPool } from '../node/pool' +import type { ApiConfig } from './config' + +export interface BrowserProvider { + initialize(ctx: Vitest): Awaitable + createPool(): ProcessPool + testFinished?(testId: string): Awaitable +} + +export interface BrowserProviderModule { + new (): BrowserProvider +} + +export interface BrowserConfigOptions { + /** + * if running tests in the broweser should be the default + * + * @default false + */ + enabled?: boolean + + /** + * Name of the browser + * + * @default tries to find the first available browser + */ + name?: 'firefox' | 'chrome' | 'edge' | 'safari' + + /** + * browser provider + * + * @default 'webdriver' + */ + provider?: string + + /** + * enable headless mode + * + * @default process.env.CI + */ + headless?: boolean + + /** + * Serve API options. + * + * The default port is 63315. + */ + api?: ApiConfig | number +} + +export interface ResolvedBrowserOptions extends BrowserConfigOptions { + enabled: boolean + headless: boolean + api: ApiConfig +} diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index 277b209a9b5c..d5b843dd6b66 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -10,13 +10,14 @@ import type { Reporter } from './reporter' import type { SnapshotStateOptions } from './snapshot' import type { Arrayable } from './general' import type { BenchmarkUserOptions } from './benchmark' +import type { BrowserConfigOptions, ResolvedBrowserOptions } from './browser' export type { SequenceHooks, SequenceSetupFiles } from '@vitest/runner' export type BuiltinEnvironment = 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' // Record is used, so user can get intellisense for builtin environments, but still allow custom environments export type VitestEnvironment = BuiltinEnvironment | (string & Record) -export type VitestPool = 'threads' | 'child_process' +export type VitestPool = 'browser' | 'threads' | 'child_process' export type CSSModuleScopeStrategy = 'stable' | 'scoped' | 'non-scoped' export type ApiConfig = Pick @@ -390,10 +391,12 @@ export interface InlineConfig { ui?: boolean /** - * Use in browser environment + * options for test in a browser environment * @experimental + * + * @default false */ - browser?: boolean + browser?: BrowserConfigOptions /** * Open UI automatically. @@ -652,7 +655,7 @@ export interface UserConfig extends InlineConfig { shard?: string } -export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck' | 'runner'> { +export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'browser' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck' | 'runner'> { mode: VitestRunMode base?: string @@ -665,6 +668,8 @@ export interface ResolvedConfig extends Omit, 'config' | 'f coverage: ResolvedCoverageOptions snapshotOptions: SnapshotStateOptions + browser: ResolvedBrowserOptions + reporters: (Reporter | BuiltinReporters)[] defines: Record diff --git a/packages/vitest/src/utils/graph.ts b/packages/vitest/src/utils/graph.ts index aa8f795c6a8f..962adeb6d151 100644 --- a/packages/vitest/src/utils/graph.ts +++ b/packages/vitest/src/utils/graph.ts @@ -29,7 +29,7 @@ export async function getModuleGraph(ctx: Vitest, id: string): Promise get(m, seen)))).filter(Boolean) as string[] return id } - await get(ctx.server.moduleGraph.getModuleById(id)) + await get(ctx.server.moduleGraph.getModuleById(id) || ctx.browser?.moduleGraph.getModuleById(id)) return { graph, externalized: Array.from(externalized), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8841ebeb4c34..c2f6ef7ce2ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -628,7 +628,7 @@ importers: '@vitest/ws-client': workspace:* local-pkg: ^0.4.2 mlly: ^1.1.0 - modern-node-polyfills: 0.0.9 + modern-node-polyfills: 0.1.0 rollup: ^2.79.1 rollup-plugin-node-polyfills: ^0.2.1 sirv: ^2.0.2 @@ -637,7 +637,7 @@ importers: '@vitest/runner': link:../runner local-pkg: 0.4.2 mlly: 1.1.0 - modern-node-polyfills: 0.0.9 + modern-node-polyfills: 0.1.0 rollup-plugin-node-polyfills: 0.2.1 sirv: 2.0.2 devDependencies: @@ -886,6 +886,7 @@ importers: pretty-format: ^27.5.1 prompts: ^2.4.2 rollup: ^2.79.1 + safaridriver: ^0.0.4 source-map: ^0.6.1 std-env: ^3.3.1 strip-ansi: ^7.0.1 @@ -896,8 +897,10 @@ importers: typescript: ^4.9.4 vite: ^4.0.0 vite-node: workspace:* + webdriverio: ^8.5.5 why-is-node-running: ^2.2.2 ws: ^8.12.0 + x-default-browser: ^0.5.2 dependencies: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 @@ -959,9 +962,12 @@ importers: pretty-format: 27.5.1 prompts: 2.4.2 rollup: 2.79.1 + safaridriver: 0.0.4 strip-ansi: 7.0.1 typescript: 4.9.4 + webdriverio: 8.6.3_typescript@4.9.4 ws: 8.12.0 + x-default-browser: 0.5.2 packages/web-worker: specifiers: @@ -1010,9 +1016,13 @@ importers: test/browser: specifiers: '@vitest/browser': workspace:* + execa: ^7.1.1 + safaridriver: ^0.0.4 vitest: workspace:* devDependencies: '@vitest/browser': link:../../packages/browser + execa: 7.1.1 + safaridriver: 0.0.4 vitest: link:../../packages/vitest test/cache: @@ -5841,6 +5851,11 @@ packages: resolution: {integrity: sha512-uWZaAsh9WFhcY1rWLLcMU/omiIIAQ/PmgqplaF6UWY6ULPH0ZO8hupJRAydzlTQZJIK3Voz8o8dYlEx+Cm6BAA==} dev: true + /@sindresorhus/is/5.3.0: + resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==} + engines: {node: '>=14.16'} + dev: true + /@sinonjs/commons/2.0.0: resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} dependencies: @@ -7234,6 +7249,13 @@ packages: - supports-color dev: true + /@szmarczak/http-timer/5.0.1: + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} + dependencies: + defer-to-connect: 2.0.1 + dev: true + /@testing-library/cypress/9.0.0_cypress@12.3.0: resolution: {integrity: sha512-c1XiCGeHGGTWn0LAU12sFUfoX3qfId5gcSE2yHode+vsyHDWraxDPALjVnHd4/Fa3j4KBcc5k++Ccy6A9qnkMA==} engines: {node: '>=12', npm: '>=6'} @@ -7532,6 +7554,10 @@ packages: resolution: {integrity: sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==} dev: true + /@types/http-cache-semantics/4.0.1: + resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} + dev: true + /@types/is-function/1.0.1: resolution: {integrity: sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q==} dev: true @@ -7860,6 +7886,10 @@ packages: source-map: 0.6.1 dev: true + /@types/which/2.0.2: + resolution: {integrity: sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==} + dev: true + /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: @@ -8856,6 +8886,58 @@ packages: - '@vue/composition-api' - vue + /@wdio/config/8.6.2: + resolution: {integrity: sha512-vmvau1rGqyWkmVLOeWtdw7Q1rE12BJDaGp//G9ZOVMEmpTLqjuof+wB9eCXjVvpxyOcl4GWiz7xpTnXUPwoydQ==} + engines: {node: ^16.13 || >=18} + dependencies: + '@wdio/logger': 8.1.0 + '@wdio/types': 8.4.0 + '@wdio/utils': 8.6.1 + decamelize: 6.0.0 + deepmerge-ts: 4.3.0 + glob: 9.3.0 + import-meta-resolve: 2.2.2 + read-pkg-up: 9.1.0 + dev: true + + /@wdio/logger/8.1.0: + resolution: {integrity: sha512-QRC5b7FF4JIYUCqggnVA0sZ80TwIUFN9JyBSbuGuMxaSLSLujSo7WfuSrnQXVvsRbnJ16wWwJWYigfLkxOW86Q==} + engines: {node: ^16.13 || >=18} + dependencies: + chalk: 5.2.0 + loglevel: 1.8.1 + loglevel-plugin-prefix: 0.8.4 + strip-ansi: 6.0.1 + dev: true + + /@wdio/protocols/8.5.7: + resolution: {integrity: sha512-ymdXSRqHugEptLdjLnvX7m7TY6cvae1B1yiFJVpaKwj88s4PaCUs7aISexC0ES9s6z8ZWRjZo3mrKPlwZ/IKCw==} + dev: true + + /@wdio/repl/8.1.0: + resolution: {integrity: sha512-96G4TzbYnRf95+GURo15FYt6iTYq85nbWU6YQedLRAV15RfSp4foKTbAnq++bKKMALNL6gdzTc+HGhQr3Q0sQg==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@wdio/types/8.4.0: + resolution: {integrity: sha512-1eA0D0jS8Ttg67zB2gsZJFUcHcRz4VRjLTjxdLKh70+ZfB1+YZr9tScLgQjc+qsjsK1wKSzOz03uZiidwNnN9g==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@wdio/utils/8.6.1: + resolution: {integrity: sha512-F/ITsHOG6Q1RxZaQuGcuti/noqxWmgkYQmRM8XdqZe0gSy4VdflGfc6TKCnfcn2oV3VPAX2HiORdTZnEGvzH2g==} + engines: {node: ^16.13 || >=18} + dependencies: + '@wdio/logger': 8.1.0 + '@wdio/types': 8.4.0 + import-meta-resolve: 2.2.2 + p-iteration: 1.1.8 + dev: true + /@webassemblyjs/ast/1.11.1: resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} dependencies: @@ -9253,6 +9335,7 @@ packages: resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} engines: {node: '>=0.4.0'} hasBin: true + dev: true /acorn/8.8.1: resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} @@ -9263,7 +9346,6 @@ packages: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /address/1.2.0: resolution: {integrity: sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==} @@ -9502,6 +9584,35 @@ packages: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} dev: true + /archiver-utils/2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.10 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.7 + dev: true + + /archiver/5.3.1: + resolution: {integrity: sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==} + engines: {node: '>= 10'} + dependencies: + archiver-utils: 2.1.0 + async: 3.2.4 + buffer-crc32: 0.2.13 + readable-stream: 3.6.0 + readdir-glob: 1.1.2 + tar-stream: 2.2.0 + zip-stream: 4.1.0 + dev: true + /archy/1.0.0: resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} dev: true @@ -10425,6 +10536,24 @@ packages: unset-value: 1.0.0 dev: true + /cacheable-lookup/7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} + dev: true + + /cacheable-request/10.2.8: + resolution: {integrity: sha512-IDVO5MJ4LItE6HKFQTqT2ocAQsisOoCTUDu1ddCmnhyiwFQjXNPp4081Xj23N4tO+AFEFNzGuNEf/c8Gwwt15A==} + engines: {node: '>=14.16'} + dependencies: + '@types/http-cache-semantics': 4.0.1 + get-stream: 6.0.1 + http-cache-semantics: 4.1.1 + keyv: 4.5.2 + mimic-response: 4.0.0 + normalize-url: 8.0.0 + responselike: 3.0.0 + dev: true + /cachedir/2.3.0: resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} engines: {node: '>=6'} @@ -10570,6 +10699,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk/5.2.0: + resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /character-entities-legacy/1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} dev: true @@ -10665,11 +10799,33 @@ packages: engines: {node: '>=10'} dev: true + /chrome-launcher/0.15.1: + resolution: {integrity: sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==} + engines: {node: '>=12.13.0'} + hasBin: true + dependencies: + '@types/node': 18.15.3 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /chrome-trace-event/1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} dev: true + /chromium-bidi/0.4.4_6o5gdkn34s2j2m26x63ssheuqa: + resolution: {integrity: sha512-4BX5cSaponuvVT1+SbLYTOAgDoVtX/Khoc9UsbFJ/AsPVUeFAM3RiIDFI6XFhLYMi9WmVJqh1ZH+dRpNKkKwiQ==} + peerDependencies: + devtools-protocol: '*' + dependencies: + devtools-protocol: 0.0.1094867 + mitt: 3.0.0 + dev: true + /ci-info/2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} dev: true @@ -10900,6 +11056,16 @@ packages: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} dev: true + /compress-commons/4.1.1: + resolution: {integrity: sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==} + engines: {node: '>= 10'} + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.2 + normalize-path: 3.0.0 + readable-stream: 3.6.0 + dev: true + /compressible/2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -11102,6 +11268,20 @@ packages: - supports-color dev: true + /crc-32/1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: true + + /crc32-stream/4.0.2: + resolution: {integrity: sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==} + engines: {node: '>= 10'} + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.0 + dev: true + /create-ecdh/4.0.4: resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} dependencies: @@ -11231,6 +11411,10 @@ packages: nth-check: 2.1.1 dev: true + /css-shorthand-properties/1.1.1: + resolution: {integrity: sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==} + dev: true + /css-tree/2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -11243,6 +11427,10 @@ packages: resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==} dev: false + /css-value/0.0.1: + resolution: {integrity: sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==} + dev: true + /css-what/6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -11585,6 +11773,11 @@ packages: dev: true optional: true + /decamelize/6.0.0: + resolution: {integrity: sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /decimal.js-light/2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} dev: false @@ -11606,6 +11799,13 @@ packages: engines: {node: '>=0.10'} dev: true + /decompress-response/6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: true + /dedent/0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -11641,6 +11841,11 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deepmerge-ts/4.3.0: + resolution: {integrity: sha512-if3ZYdkD2dClhnXR5reKtG98cwyaRT1NeugQoAPTTfsOpV9kqyeiBF9Qa5RHjemb3KzD5ulqygv6ED3t5j9eJw==} + engines: {node: '>=12.4.0'} + dev: true + /deepmerge/4.2.2: resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} engines: {node: '>=0.10.0'} @@ -11658,12 +11863,28 @@ packages: dev: true optional: true + /default-browser-id/2.0.0: + resolution: {integrity: sha512-+LePblg9HDIx3CIla8BxfI/zYUFs8Kp67U5feqb7iTJcAxBOvcZ7ZNXKFsBDnGE5x0ap66o848VHE0fq7cgpPg==} + engines: {node: '>=4'} + requiresBuild: true + dependencies: + bplist-parser: 0.1.1 + pify: 2.3.0 + untildify: 2.1.0 + dev: true + optional: true + /defaults/1.0.3: resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} dependencies: clone: 1.0.4 dev: true + /defer-to-connect/2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + dev: true + /define-lazy-prop/2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} @@ -11760,10 +11981,44 @@ packages: - supports-color dev: true + /devtools-protocol/0.0.1094867: + resolution: {integrity: sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==} + dev: true + + /devtools-protocol/0.0.1116775: + resolution: {integrity: sha512-jjWlaRN+ntWqV4bYlkg7lzIAXTnzFlznLiSj2kTPZVOHX+t0FW6gCPsgjjQA/4wray7PdbWZ09lMlkCuVWvtZA==} + dev: true + /devtools-protocol/0.0.981744: resolution: {integrity: sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==} dev: true + /devtools/8.6.2_typescript@4.9.4: + resolution: {integrity: sha512-dqR5SUsqpJBkY+SpzSPyRwAdwn3PdoCFjgaRqO2jkF0IXgvBkKsXSepAN2QV4/nbcFXB6mokJZ0rlHSzVl7GkQ==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 18.15.3 + '@wdio/config': 8.6.2 + '@wdio/logger': 8.1.0 + '@wdio/protocols': 8.5.7 + '@wdio/types': 8.4.0 + '@wdio/utils': 8.6.1 + chrome-launcher: 0.15.1 + edge-paths: 3.0.5 + import-meta-resolve: 2.2.2 + puppeteer-core: 19.7.4_typescript@4.9.4 + query-selector-shadow-dom: 1.0.1 + ua-parser-js: 1.0.34 + uuid: 9.0.0 + which: 3.0.0 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /dezalgo/1.0.3: resolution: {integrity: sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==} dependencies: @@ -11964,6 +12219,14 @@ packages: safer-buffer: 2.1.2 dev: true + /edge-paths/3.0.5: + resolution: {integrity: sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==} + engines: {node: '>=14.0.0'} + dependencies: + '@types/which': 2.0.2 + which: 2.0.2 + dev: true + /editorconfig/0.15.3: resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==} hasBin: true @@ -12864,6 +13127,21 @@ packages: strip-final-newline: 3.0.0 dev: true + /execa/7.1.1: + resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 4.3.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + /executable/4.1.1: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} @@ -13025,6 +13303,10 @@ packages: engines: {'0': node >=0.6.0} dev: true + /fast-deep-equal/2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + dev: true + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -13411,6 +13693,11 @@ packages: webpack: 4.46.0 dev: true + /form-data-encoder/2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} + dev: true + /form-data/2.3.3: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} @@ -13748,6 +14035,16 @@ packages: once: 1.4.0 dev: true + /glob/9.3.0: + resolution: {integrity: sha512-EAZejC7JvnQINayvB/7BJbpZpNOJ8Lrw2OZNEvQxe0vaLn1SuwMcfV7/MNaX8L/T0wmptBFI4YMtDvSBxYDc7w==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + fs.realpath: 1.0.0 + minimatch: 7.4.2 + minipass: 4.2.5 + path-scurry: 1.6.1 + dev: true + /global-dirs/3.0.0: resolution: {integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==} engines: {node: '>=10'} @@ -13812,6 +14109,23 @@ packages: resolution: {integrity: sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==} dev: true + /got/12.6.0: + resolution: {integrity: sha512-WTcaQ963xV97MN3x0/CbAriXFZcXCfgxVp91I+Ze6pawQOa7SgzwSx2zIJJsX+kTajMnVs0xcFD1TxZKFqhdnQ==} + engines: {node: '>=14.16'} + dependencies: + '@sindresorhus/is': 5.3.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.8 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.0 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + dev: true + /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true @@ -14094,6 +14408,13 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true + /hosted-git-info/4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + /hpack.js/2.1.6: resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} dependencies: @@ -14196,6 +14517,10 @@ packages: entities: 4.4.0 dev: true + /http-cache-semantics/4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + dev: true + /http-deceiver/1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} dev: true @@ -14231,6 +14556,14 @@ packages: sshpk: 1.17.0 dev: true + /http2-wrapper/2.2.0: + resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} + engines: {node: '>=10.19.0'} + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + dev: true + /https-browserify/1.0.0: resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} dev: true @@ -14338,6 +14671,10 @@ packages: parent-module: 1.0.1 resolve-from: 4.0.0 + /import-meta-resolve/2.2.2: + resolution: {integrity: sha512-f8KcQ1D80V7RnqVm+/lirO9zkOxjGxhaTC1IPrBGd3MEfNgmNG67tSUO9gTi2F3Blr2Az6g1vocaxzkVnWl9MA==} + dev: true + /imurmurhash/0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -14735,6 +15072,11 @@ packages: engines: {node: '>=8'} dev: true + /is-plain-obj/4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: true + /is-plain-object/2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -15416,6 +15758,10 @@ packages: hasBin: true dev: true + /json-buffer/3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + /json-parse-better-errors/1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} dev: true @@ -15502,6 +15848,12 @@ packages: engines: {node: '>=8'} dev: true + /keyv/4.5.2: + resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} + dependencies: + json-buffer: 3.0.1 + dev: true + /kind-of/3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} engines: {node: '>=0.10.0'} @@ -15545,6 +15897,11 @@ packages: resolution: {integrity: sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ==} dev: true + /ky/0.33.3: + resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} + engines: {node: '>=14.16'} + dev: true + /lazy-ass/1.6.0: resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} engines: {node: '> 0.8'} @@ -15561,6 +15918,13 @@ packages: dotenv-expand: 5.1.0 dev: true + /lazystream/1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + dependencies: + readable-stream: 2.3.7 + dev: true + /leven/3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -15590,6 +15954,15 @@ packages: set-cookie-parser: 2.5.1 dev: true + /lighthouse-logger/1.3.0: + resolution: {integrity: sha512-BbqAKApLb9ywUli+0a+PcV04SyJ/N1q/8qgCNe6U97KbPCS1BTksEuHFLYdvc8DltuhfxIUBqDZsC0bBGtl3lA==} + dependencies: + debug: 2.6.9 + marky: 1.2.5 + transitivePeerDependencies: + - supports-color + dev: true + /lilconfig/2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} engines: {node: '>=10'} @@ -15768,14 +16141,30 @@ packages: p-locate: 6.0.0 dev: true + /lodash.clonedeep/4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: true + /lodash.debounce/4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true + /lodash.defaults/4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: true + + /lodash.difference/4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + dev: true + /lodash.escape/4.0.1: resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==} dev: true + /lodash.flatten/4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + dev: true + /lodash.flattendeep/4.4.0: resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} dev: true @@ -15784,6 +16173,10 @@ packages: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} dev: true + /lodash.isplainobject/4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: true + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -15796,10 +16189,18 @@ packages: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true + /lodash.union/4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + dev: true + /lodash.uniq/4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true + /lodash.zip/4.2.0: + resolution: {integrity: sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==} + dev: true + /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -15832,6 +16233,15 @@ packages: wrap-ansi: 8.0.1 dev: true + /loglevel-plugin-prefix/0.8.4: + resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} + dev: true + + /loglevel/1.8.1: + resolution: {integrity: sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==} + engines: {node: '>= 0.6.0'} + dev: true + /loose-envify/1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -15865,6 +16275,11 @@ packages: tslib: 2.4.1 dev: true + /lowercase-keys/3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /lru-cache/4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: @@ -15885,6 +16300,11 @@ packages: yallist: 4.0.0 dev: true + /lru-cache/7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: true + /lz-string/1.4.4: resolution: {integrity: sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==} hasBin: true @@ -15966,6 +16386,10 @@ packages: resolution: {integrity: sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==} dev: true + /marky/1.2.5: + resolution: {integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==} + dev: true + /match-sorter/6.3.1: resolution: {integrity: sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==} dependencies: @@ -16200,6 +16624,16 @@ packages: engines: {node: '>=12'} dev: true + /mimic-response/3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: true + + /mimic-response/4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /min-document/2.19.0: resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} dependencies: @@ -16238,6 +16672,13 @@ packages: brace-expansion: 2.0.1 dev: true + /minimatch/7.4.2: + resolution: {integrity: sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist/1.2.7: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} dev: true @@ -16270,6 +16711,11 @@ packages: yallist: 4.0.0 dev: true + /minipass/4.2.5: + resolution: {integrity: sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==} + engines: {node: '>=8'} + dev: true + /minizlib/2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -16294,6 +16740,10 @@ packages: through2: 2.0.5 dev: true + /mitt/3.0.0: + resolution: {integrity: sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==} + dev: true + /mixin-deep/1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} engines: {node: '>=0.10.0'} @@ -16336,12 +16786,12 @@ packages: pkg-types: 1.0.1 ufo: 1.0.1 - /modern-node-polyfills/0.0.9: - resolution: {integrity: sha512-Z7sFaWAHCEAw44Ww1L0JEt4BaQ7/LVTbbqTtm3bNSfdWs0ZW7QwRN7Xy8RjSPOlZ9ZSkoXAa54neuvAC6KGRFg==} + /modern-node-polyfills/0.1.0: + resolution: {integrity: sha512-/Z9mlC56KBxjLZvdNSLqSEFw9jSav43dsUxhLYLN3bZgcSX5VFdixat+QGjb/4NxaGCwW09ABJhZA5oHFj4W4A==} dependencies: '@jspm/core': 2.0.0-beta.24 '@rollup/plugin-inject': 4.0.4_rollup@2.79.1 - acorn: 8.8.0 + acorn: 8.8.2 esbuild: 0.16.3 local-pkg: 0.4.2 rollup: 2.79.1 @@ -16679,6 +17129,16 @@ packages: validate-npm-package-license: 3.0.4 dev: true + /normalize-package-data/3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.11.0 + semver: 7.3.8 + validate-npm-package-license: 3.0.4 + dev: true + /normalize-path/2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} @@ -16696,6 +17156,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /normalize-url/8.0.0: + resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} + engines: {node: '>=14.16'} + dev: true + /notistack/2.0.5_adgxvao2osxf6z4nbcxc3gydr4: resolution: {integrity: sha512-Ig2T1Muqkc1PaSQcEDrK7diKv6cBxw02Iq6uv074ySfgq524TV5lK41diAb6OSsaiWfp3aRt+T3+0MF8m2EcJQ==} peerDependencies: @@ -17032,6 +17497,11 @@ packages: p-map: 2.1.0 dev: true + /p-cancelable/3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} + dev: true + /p-event/4.2.0: resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} engines: {node: '>=8'} @@ -17051,6 +17521,11 @@ packages: engines: {node: '>=4'} dev: true + /p-iteration/1.1.8: + resolution: {integrity: sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ==} + engines: {node: '>=8.0.0'} + dev: true + /p-limit/2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -17306,6 +17781,14 @@ packages: /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-scurry/1.6.1: + resolution: {integrity: sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==} + engines: {node: '>=14'} + dependencies: + lru-cache: 7.18.3 + minipass: 4.2.5 + dev: true + /path-to-regexp/0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: true @@ -17916,6 +18399,34 @@ packages: engines: {node: '>=6'} dev: true + /puppeteer-core/19.7.4_typescript@4.9.4: + resolution: {integrity: sha512-E8nVhqGF0ZM7s9pb5849gzKFG7282WYZaeGXo/eYDkpUpcPUgmkj7QrAoa8SpFXXyqHHilHJZPbfVBb4foDazQ==} + engines: {node: '>=14.14.0'} + peerDependencies: + typescript: '>= 4.7.4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + chromium-bidi: 0.4.4_6o5gdkn34s2j2m26x63ssheuqa + cross-fetch: 3.1.5 + debug: 4.3.4 + devtools-protocol: 0.0.1094867 + extract-zip: 2.0.1 + https-proxy-agent: 5.0.1 + proxy-from-env: 1.1.0 + rimraf: 4.4.0 + tar-fs: 2.1.1 + typescript: 4.9.4 + unbzip2-stream: 1.4.3 + ws: 8.12.1 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: true + /puppeteer/13.7.0: resolution: {integrity: sha512-U1uufzBjz3+PkpCxFrWzh4OrMIdIb2ztzCu0YEPfRHjHswcSwHZswnK+WdsOQJsRV8WeTg3jLhJR4D867+fjsA==} engines: {node: '>=10.18.1'} @@ -17964,6 +18475,10 @@ packages: engines: {node: '>=0.6'} dev: true + /query-selector-shadow-dom/1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + dev: true + /querystring-es3/0.2.1: resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} engines: {node: '>=0.4.x'} @@ -17986,6 +18501,11 @@ packages: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: true + /quick-lru/5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + dev: true + /raf/3.4.1: resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} dependencies: @@ -18324,6 +18844,15 @@ packages: type-fest: 0.8.1 dev: true + /read-pkg-up/9.1.0: + resolution: {integrity: sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + find-up: 6.3.0 + read-pkg: 7.1.0 + type-fest: 2.19.0 + dev: true + /read-pkg/1.1.0: resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==} engines: {node: '>=0.10.0'} @@ -18353,6 +18882,16 @@ packages: type-fest: 0.6.0 dev: true + /read-pkg/7.1.0: + resolution: {integrity: sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==} + engines: {node: '>=12.20'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 2.19.0 + dev: true + /readable-stream/2.3.7: resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} dependencies: @@ -18381,6 +18920,12 @@ packages: abort-controller: 3.0.0 dev: true + /readdir-glob/1.1.2: + resolution: {integrity: sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==} + dependencies: + minimatch: 5.1.1 + dev: true + /readdirp/2.2.1: resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} engines: {node: '>=0.10'} @@ -18663,6 +19208,10 @@ packages: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true + /resolve-alpn/1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + dev: true + /resolve-from/4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -18685,6 +19234,19 @@ packages: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + /responselike/3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} + dependencies: + lowercase-keys: 3.0.0 + dev: true + + /resq/1.11.0: + resolution: {integrity: sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==} + dependencies: + fast-deep-equal: 2.0.1 + dev: true + /restore-cursor/3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -18719,6 +19281,10 @@ packages: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} dev: true + /rgb2hex/0.2.5: + resolution: {integrity: sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==} + dev: true + /rifm/0.12.1_react@18.2.0: resolution: {integrity: sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==} peerDependencies: @@ -18746,6 +19312,14 @@ packages: hasBin: true dev: true + /rimraf/4.4.0: + resolution: {integrity: sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 9.3.0 + dev: true + /ripemd160/2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} dependencies: @@ -18882,6 +19456,10 @@ packages: tslib: 2.4.1 dev: true + /safaridriver/0.0.4: + resolution: {integrity: sha512-EgK9Vc2Bk4WaECq1E7ti9GyeQ6JKKQNs40vNOE/b5Ul5dDEt429G0Kj5Nzs4FRS5ZIVa7nBck1TyHuinOYdz2Q==} + dev: true + /safe-buffer/5.1.1: resolution: {integrity: sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==} dev: true @@ -19073,6 +19651,13 @@ packages: - supports-color dev: true + /serialize-error/8.1.0: + resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} + engines: {node: '>=10'} + dependencies: + type-fest: 0.20.2 + dev: true + /serialize-javascript/4.0.0: resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} dependencies: @@ -20558,6 +21143,10 @@ packages: hasBin: true dev: true + /ua-parser-js/1.0.34: + resolution: {integrity: sha512-K9mwJm/DaB6mRLZfw6q8IMXipcrmuT6yfhYmwhAkuh+81sChuYstYA+znlgaflUPaYUa3odxKPKGw6Vw/lANew==} + dev: true + /ufo/0.8.6: resolution: {integrity: sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==} dev: true @@ -21154,6 +21743,11 @@ packages: hasBin: true dev: true + /uuid/9.0.0: + resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + hasBin: true + dev: true + /v8-compile-cache-lib/3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true @@ -21659,6 +22253,63 @@ packages: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} dev: true + /webdriver/8.6.2: + resolution: {integrity: sha512-jgnu/hB7O8hrjCZm6zW77QhBEdwteQF+liWvjZcflQXUmS/YLdS/UAfCBQzt1CIUHfbGrlxHshwFm+ywjT9XJw==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 18.15.3 + '@types/ws': 8.5.4 + '@wdio/config': 8.6.2 + '@wdio/logger': 8.1.0 + '@wdio/protocols': 8.5.7 + '@wdio/types': 8.4.0 + '@wdio/utils': 8.6.1 + deepmerge-ts: 4.3.0 + got: 12.6.0 + ky: 0.33.3 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /webdriverio/8.6.3_typescript@4.9.4: + resolution: {integrity: sha512-XhS6lydSyCLnJcOBhLbSK6J0MQdfnSXaFcSvSEL10TMBnWcmETCfYIj7OX41weDI8F4YAasYyMH+mJUkWwC1Ww==} + engines: {node: ^16.13 || >=18} + dependencies: + '@types/node': 18.15.3 + '@wdio/config': 8.6.2 + '@wdio/logger': 8.1.0 + '@wdio/protocols': 8.5.7 + '@wdio/repl': 8.1.0 + '@wdio/types': 8.4.0 + '@wdio/utils': 8.6.1 + archiver: 5.3.1 + aria-query: 5.0.2 + css-shorthand-properties: 1.1.1 + css-value: 0.0.1 + devtools: 8.6.2_typescript@4.9.4 + devtools-protocol: 0.0.1116775 + grapheme-splitter: 1.0.4 + import-meta-resolve: 2.2.2 + is-plain-obj: 4.1.0 + lodash.clonedeep: 4.5.0 + lodash.zip: 4.2.0 + minimatch: 7.4.2 + puppeteer-core: 19.7.4_typescript@4.9.4 + query-selector-shadow-dom: 1.0.1 + resq: 1.11.0 + rgb2hex: 0.2.5 + serialize-error: 8.1.0 + webdriver: 8.6.2 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - typescript + - utf-8-validate + dev: true + /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -21905,6 +22556,14 @@ packages: dependencies: isexe: 2.0.0 + /which/3.0.0: + resolution: {integrity: sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + /why-is-node-running/2.2.2: resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} engines: {node: '>=8'} @@ -22157,6 +22816,19 @@ packages: utf-8-validate: optional: true + /ws/8.12.1: + resolution: {integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /ws/8.13.0: resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -22190,6 +22862,13 @@ packages: default-browser-id: 1.0.4 dev: true + /x-default-browser/0.5.2: + resolution: {integrity: sha512-OOC2l+bGtY2IVQPGTlXxmLqdIqGKwIH0g8phooVJHu6xVavegfgTPqeBmaL94IXyggBF59x2z3qAnTbVLS25PQ==} + hasBin: true + optionalDependencies: + default-browser-id: 2.0.0 + dev: true + /xml-name-validator/4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -22306,6 +22985,15 @@ packages: resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} dev: false + /zip-stream/4.1.0: + resolution: {integrity: sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==} + engines: {node: '>= 10'} + dependencies: + archiver-utils: 2.1.0 + compress-commons: 4.1.1 + readable-stream: 3.6.0 + dev: true + /zustand/4.1.1_react@18.2.0: resolution: {integrity: sha512-h4F3WMqsZgvvaE0n3lThx4MM81Ls9xebjvrABNzf5+jb3/03YjNTSgZXeyrvXDArMeV9untvWXRw1tY+ntPYbA==} engines: {node: '>=12.7.0'} diff --git a/test/browser/package.json b/test/browser/package.json index 05b46ca32dcf..d9bf628eb1d3 100644 --- a/test/browser/package.json +++ b/test/browser/package.json @@ -2,11 +2,13 @@ "name": "@vitest/test-browser", "private": true, "scripts": { - "test": "vitest --browser --open", + "test": "node test.mjs", "coverage": "vitest run --coverage" }, "devDependencies": { "@vitest/browser": "workspace:*", + "execa": "^7.1.1", + "safaridriver": "^0.0.4", "vitest": "workspace:*" } } diff --git a/test/browser/test.mjs b/test/browser/test.mjs new file mode 100644 index 000000000000..ee9e7d82786d --- /dev/null +++ b/test/browser/test.mjs @@ -0,0 +1,32 @@ +import assert from 'node:assert' +import { readFile } from 'node:fs/promises' +import { execa } from 'execa' + +const browser = process.env.BROWSER || 'chrome' + +let error +await execa('npx', ['vitest', `--browser=${browser}`], { + env: { + ...process.env, + CI: 'true', + NO_COLOR: 'true', + }, + stdout: 'inherit', + stderr: 'inherit', +}) + .catch((e) => { + error = e + }) + +if (error) { + console.error(error) + process.exit(1) +} + +const browserResult = await readFile('./browser.json', 'utf-8') +const browserResultJson = JSON.parse(browserResult) + +assert.ok(browserResultJson.testResults.length === 4, 'Not all the tests have been run') + +for (const result of browserResultJson.testResults) + assert.ok(result.status === 'passed') diff --git a/test/browser/test/__snapshots__/snapshot.test.ts.snap b/test/browser/test/__snapshots__/snapshot.test.ts.snap index 5f969b0acbdc..13300a736557 100644 --- a/test/browser/test/__snapshots__/snapshot.test.ts.snap +++ b/test/browser/test/__snapshots__/snapshot.test.ts.snap @@ -1,3 +1,3 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`file snapshot 1`] = `1`; diff --git a/test/browser/test/dom.test.ts b/test/browser/test/dom.test.ts new file mode 100644 index 000000000000..12fd4e0fbc33 --- /dev/null +++ b/test/browser/test/dom.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'vitest' + +test('render div', async () => { + const div = document.createElement('div') + div.textContent = 'Hello World' + document.body.appendChild(div) + expect(div.textContent).toBe('Hello World') +}) diff --git a/test/browser/vitest.config.ts b/test/browser/vitest.config.ts index 3608181f61e2..938b1a1a7885 100644 --- a/test/browser/vitest.config.ts +++ b/test/browser/vitest.config.ts @@ -1,8 +1,28 @@ import { defineConfig } from 'vitest/config' +const noop = () => {} + export default defineConfig({ test: { - browser: true, - open: true, + browser: { + enabled: true, + name: 'chrome', + headless: true, + }, + open: false, + isolate: false, + outputFile: './browser.json', + reporters: ['json', { + onInit: noop, + onPathsCollected: noop, + onCollected: noop, + onFinished: noop, + onTaskUpdate: noop, + onTestRemoved: noop, + onWatcherStart: noop, + onWatcherRerun: noop, + onServerRestart: noop, + onUserConsoleLog: noop, + }, 'default'], }, })