diff --git a/packages/browser/src/client/logger.ts b/packages/browser/src/client/logger.ts index c65eaa3f3290..8a4e615f0ea4 100644 --- a/packages/browser/src/client/logger.ts +++ b/packages/browser/src/client/logger.ts @@ -3,8 +3,8 @@ import { importId } from './utils' const { Date, console } = globalThis -export async function setupConsoleLogSpy() { - const { stringify, format, inspect } = await importId('vitest/utils') as typeof import('vitest/utils') +export async function setupConsoleLogSpy(basePath: string) { + const { stringify, format, inspect } = await importId('vitest/utils', basePath) as typeof import('vitest/utils') const { log, info, error, dir, dirxml, trace, time, timeEnd, timeLog, warn, debug, count, countReset } = console const formatInput = (input: unknown) => { if (input instanceof Node) diff --git a/packages/browser/src/client/main.ts b/packages/browser/src/client/main.ts index 1e8420e0867b..d0a9e029a352 100644 --- a/packages/browser/src/client/main.ts +++ b/packages/browser/src/client/main.ts @@ -3,7 +3,7 @@ import type { ResolvedConfig } from 'vitest' import type { CancelReason, VitestRunner } from '@vitest/runner' import type { VitestExecutor } from '../../../vitest/src/runtime/execute' import { createBrowserRunner } from './runner' -import { importId } from './utils' +import { importId as _importId } from './utils' import { setupConsoleLogSpy } from './logger' import { createSafeRpc, rpc, rpcDone } from './rpc' import { setupDialogsSpy } from './dialog' @@ -24,6 +24,10 @@ const url = new URL(location.href) const testId = url.searchParams.get('id') || 'unknown' const reloadTries = Number(url.searchParams.get('reloadTries') || '0') +const basePath = () => config?.base || '/' +const importId = (id: string) => _importId(id, basePath()) +const viteClientPath = () => `${basePath()}@vite/client` + function getQueryPaths() { return url.searchParams.getAll('path') } @@ -181,15 +185,14 @@ ws.addEventListener('open', async () => { const iFrame = document.getElementById('vitest-ui') as HTMLIFrameElement iFrame.setAttribute('src', '/__vitest__/') - await setupConsoleLogSpy() + await setupConsoleLogSpy(basePath()) setupDialogsSpy() await runTests(paths, config!) }) async function prepareTestEnvironment(config: ResolvedConfig) { // need to import it before any other import, otherwise Vite optimizer will hang - const viteClientPath = '/@vite/client' - await import(viteClientPath) + await import(viteClientPath()) const { startTests, diff --git a/packages/browser/src/client/runner.ts b/packages/browser/src/client/runner.ts index cfa79438d840..b43facae8f2c 100644 --- a/packages/browser/src/client/runner.ts +++ b/packages/browser/src/client/runner.ts @@ -12,10 +12,10 @@ interface CoverageHandler { } export function createBrowserRunner( - original: { new(config: ResolvedConfig): VitestRunner }, + VitestRunner: { new(config: ResolvedConfig): VitestRunner }, coverageModule: CoverageHandler | null, ): { new(options: BrowserRunnerOptions): VitestRunner } { - return class BrowserTestRunner extends original { + return class BrowserTestRunner extends VitestRunner { public config: ResolvedConfig hashMap = new Map() @@ -69,11 +69,12 @@ export function createBrowserRunner( hash = Date.now().toString() this.hashMap.set(filepath, [false, hash]) } + const base = this.config.base || '/' // on Windows we need the unit to resolve the test file - const importpath = /^\w:/.test(filepath) - ? `/@fs/${filepath}?${test ? 'browserv' : 'v'}=${hash}` - : `${filepath}?${test ? 'browserv' : 'v'}=${hash}` + const prefix = `${base}${/^\w:/.test(filepath) ? '@fs/' : ''}` + const query = `${test ? 'browserv' : 'v'}=${hash}` + const importpath = `${prefix}${filepath}?${query}`.replace(/\/+/g, '/') await import(importpath) } } diff --git a/packages/browser/src/client/utils.ts b/packages/browser/src/client/utils.ts index 27a390af601d..a4c1a856117f 100644 --- a/packages/browser/src/client/utils.ts +++ b/packages/browser/src/client/utils.ts @@ -1,5 +1,5 @@ -export async function importId(id: string) { - const name = `/@id/${id}` +export async function importId(id: string, basePath: string) { + const name = `${basePath}@id/${id}` // @ts-expect-error mocking vitest apis return __vi_wrap_module__(import(name)) } diff --git a/test/browser/package.json b/test/browser/package.json index a190c82e6808..5131b1c5c03c 100644 --- a/test/browser/package.json +++ b/test/browser/package.json @@ -4,9 +4,9 @@ "private": true, "scripts": { "test": "pnpm run test:webdriverio && pnpm run test:playwright", - "test:webdriverio": "PROVIDER=webdriverio node --test specs/", - "test:playwright": "PROVIDER=playwright node --test specs/", - "test:safaridriver": "PROVIDER=webdriverio BROWSER=safari node --test specs/", + "test:webdriverio": "PROVIDER=webdriverio node --test --test-concurrency=1 specs/", + "test:playwright": "PROVIDER=playwright node --test --test-concurrency=1 specs/", + "test:safaridriver": "PROVIDER=webdriverio BROWSER=safari node --test --test-concurrency=1 specs/", "coverage": "vitest --coverage.enabled --coverage.provider=istanbul --browser.headless=yes" }, "devDependencies": { diff --git a/test/browser/specs/fix-4686.test.mjs b/test/browser/specs/fix-4686.test.mjs new file mode 100644 index 000000000000..d95964ef866e --- /dev/null +++ b/test/browser/specs/fix-4686.test.mjs @@ -0,0 +1,20 @@ +// fix #4686 + +import assert from 'node:assert' +import test from 'node:test' +import runVitest from './run-vitest.mjs' + +const { + stderr, + browserResultJson, + passedTests, + failedTests, +} = await runVitest(['--config', 'vitest.config-basepath.mts', 'basic.test.ts']) + +await test('tests run in presence of config.base', async () => { + assert.ok(browserResultJson.testResults.length === 1, 'Not all the tests have been run') + assert.ok(passedTests.length === 1, 'Some tests failed') + assert.ok(failedTests.length === 0, 'Some tests have passed but should fail') + + assert.doesNotMatch(stderr, /Unhandled Error/, 'doesn\'t have any unhandled errors') +}) diff --git a/test/browser/specs/run-vitest.mjs b/test/browser/specs/run-vitest.mjs new file mode 100644 index 000000000000..79d2528201a7 --- /dev/null +++ b/test/browser/specs/run-vitest.mjs @@ -0,0 +1,30 @@ +import { readFile } from 'node:fs/promises' +import { execa } from 'execa' + +const browser = process.env.BROWSER || (process.env.PROVIDER === 'playwright' ? 'chromium' : 'chrome') + +export default async function runVitest(moreArgs = []) { + const argv = ['vitest', '--run', `--browser.name=${browser}`] + + if (browser !== 'safari') + argv.push('--browser.headless') + + const { stderr, stdout } = await execa('npx', argv.concat(moreArgs), { + env: { + ...process.env, + CI: 'true', + NO_COLOR: 'true', + }, + reject: false, + }) + const browserResult = await readFile('./browser.json', 'utf-8') + const browserResultJson = JSON.parse(browserResult) + + const getPassed = results => results.filter(result => result.status === 'passed') + const getFailed = results => results.filter(result => result.status === 'failed') + + const passedTests = getPassed(browserResultJson.testResults) + const failedTests = getFailed(browserResultJson.testResults) + + return { stderr, stdout, browserResultJson, passedTests, failedTests } +} diff --git a/test/browser/specs/runner.test.mjs b/test/browser/specs/runner.test.mjs index 556b749f2a14..ea67aee7c889 100644 --- a/test/browser/specs/runner.test.mjs +++ b/test/browser/specs/runner.test.mjs @@ -1,31 +1,14 @@ import assert from 'node:assert' -import { readFile } from 'node:fs/promises' import test from 'node:test' -import { execa } from 'execa' +import runVitest from './run-vitest.mjs' -const browser = process.env.BROWSER || (process.env.PROVIDER === 'playwright' ? 'chromium' : 'chrome') -const argv = ['vitest', '--run', `--browser.name=${browser}`] - -if (browser !== 'safari') - argv.push('--browser.headless') - -const { stderr, stdout } = await execa('npx', argv, { - env: { - ...process.env, - CI: 'true', - NO_COLOR: 'true', - }, - reject: false, -}) - -const browserResult = await readFile('./browser.json', 'utf-8') -const browserResultJson = JSON.parse(browserResult) - -const getPassed = results => results.filter(result => result.status === 'passed') -const getFailed = results => results.filter(result => result.status === 'failed') - -const passedTests = getPassed(browserResultJson.testResults) -const failedTests = getFailed(browserResultJson.testResults) +const { + stderr, + stdout, + browserResultJson, + passedTests, + failedTests, +} = await runVitest() await test('tests are actually running', async () => { assert.ok(browserResultJson.testResults.length === 10, 'Not all the tests have been run') diff --git a/test/browser/vitest.config-basepath.mts b/test/browser/vitest.config-basepath.mts new file mode 100644 index 000000000000..b772007fc8b8 --- /dev/null +++ b/test/browser/vitest.config-basepath.mts @@ -0,0 +1,4 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import baseConfig from './vitest.config.mjs' + +export default mergeConfig(baseConfig, defineConfig({ base: '/fix-4686' }))