Skip to content

Commit ee6f590

Browse files
authoredMar 22, 2023
feat: show browser console in the terminal (#3048)
1 parent 051bb65 commit ee6f590

File tree

21 files changed

+356
-90
lines changed

21 files changed

+356
-90
lines changed
 

‎packages/browser/src/client/logger.ts

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { rpc } from './rpc'
2+
import { importId } from './utils'
3+
4+
const { Date, console } = globalThis
5+
6+
export const setupConsoleLogSpy = async () => {
7+
const { stringify, format, utilInspect } = await importId('vitest/utils') as typeof import('vitest/utils')
8+
const { log, info, error, dir, dirxml, trace, time, timeEnd, timeLog, warn, debug, count, countReset } = console
9+
const formatInput = (input: unknown) => {
10+
if (input instanceof Node)
11+
return stringify(input)
12+
return format(input)
13+
}
14+
const processLog = (args: unknown[]) => args.map(formatInput).join(' ')
15+
const sendLog = (type: 'stdout' | 'stderr', content: string) => {
16+
if (content.startsWith('[vite]'))
17+
return
18+
const unknownTestId = '__vitest__unknown_test__'
19+
// @ts-expect-error untyped global
20+
const taskId = globalThis.__vitest_worker__?.current?.id ?? unknownTestId
21+
rpc().sendLog({
22+
content,
23+
time: Date.now(),
24+
taskId,
25+
type,
26+
size: content.length,
27+
})
28+
}
29+
const stdout = (base: (...args: unknown[]) => void) => (...args: unknown[]) => {
30+
sendLog('stdout', processLog(args))
31+
return base(...args)
32+
}
33+
const stderr = (base: (...args: unknown[]) => void) => (...args: unknown[]) => {
34+
sendLog('stderr', processLog(args))
35+
return base(...args)
36+
}
37+
console.log = stdout(log)
38+
console.debug = stdout(debug)
39+
console.info = stdout(info)
40+
41+
console.error = stderr(error)
42+
console.warn = stderr(warn)
43+
44+
console.dir = (item, options) => {
45+
sendLog('stdout', utilInspect(item, options))
46+
return dir(item, options)
47+
}
48+
49+
console.dirxml = (...args) => {
50+
sendLog('stdout', processLog(args))
51+
return dirxml(...args)
52+
}
53+
54+
console.trace = (...args: unknown[]) => {
55+
const content = processLog(args)
56+
const error = new Error('Trace')
57+
const stack = (error.stack || '').split('\n').slice(2).join('\n')
58+
sendLog('stdout', `${content}\n${stack}`)
59+
return trace(...args)
60+
}
61+
62+
const timeLabels: Record<string, number> = {}
63+
64+
console.time = (label = 'default') => {
65+
const now = performance.now()
66+
time(label)
67+
timeLabels[label] = now
68+
}
69+
70+
console.timeLog = (label = 'default') => {
71+
timeLog(label)
72+
if (!(label in timeLabels))
73+
sendLog('stderr', `Timer "${label}" does not exist`)
74+
else
75+
sendLog('stdout', `${label}: ${timeLabels[label]} ms`)
76+
}
77+
78+
console.timeEnd = (label = 'default') => {
79+
const end = performance.now()
80+
timeEnd(label)
81+
const start = timeLabels[label]
82+
if (!(label in timeLabels)) {
83+
sendLog('stderr', `Timer "${label}" does not exist`)
84+
}
85+
else if (start) {
86+
const duration = end - start
87+
sendLog('stdout', `${label}: ${duration} ms`)
88+
}
89+
}
90+
91+
const countLabels: Record<string, number> = {}
92+
93+
console.count = (label = 'default') => {
94+
const counter = (countLabels[label] ?? 0) + 1
95+
countLabels[label] = counter
96+
sendLog('stdout', `${label}: ${counter}`)
97+
return count(label)
98+
}
99+
100+
console.countReset = (label = 'default') => {
101+
countLabels[label] = 0
102+
return countReset(label)
103+
}
104+
}

‎packages/browser/src/client/main.ts

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import type { VitestClient } from '@vitest/ws-client'
21
import { createClient } from '@vitest/ws-client'
32
// eslint-disable-next-line no-restricted-imports
43
import type { ResolvedConfig } from 'vitest'
54
import type { VitestRunner } from '@vitest/runner'
65
import { createBrowserRunner } from './runner'
76
import { BrowserSnapshotEnvironment } from './snapshot'
7+
import { importId } from './utils'
8+
import { setupConsoleLogSpy } from './logger'
9+
import { createSafeRpc, rpc, rpcDone } from './rpc'
810

911
// @ts-expect-error mocking some node apis
1012
globalThis.process = { env: {}, argv: [], cwd: () => '/', stdout: { write: () => {} }, nextTick: cb => cb() }
@@ -51,42 +53,43 @@ async function loadConfig() {
5153
ws.addEventListener('open', async () => {
5254
await loadConfig()
5355

56+
const { getSafeTimers } = await importId('vitest/utils') as typeof import('vitest/utils')
57+
const safeRpc = createSafeRpc(client, getSafeTimers)
58+
5459
// @ts-expect-error mocking vitest apis
5560
globalThis.__vitest_worker__ = {
5661
config,
5762
browserHashMap,
5863
moduleCache: new Map(),
5964
rpc: client.rpc,
65+
safeRpc,
6066
}
6167

6268
const paths = getQueryPaths()
6369

6470
const iFrame = document.getElementById('vitest-ui') as HTMLIFrameElement
6571
iFrame.setAttribute('src', '/__vitest__/')
6672

67-
await runTests(paths, config, client)
73+
await setupConsoleLogSpy()
74+
await runTests(paths, config)
6875
})
6976

7077
let hasSnapshot = false
71-
async function runTests(paths: string[], config: any, client: VitestClient) {
78+
async function runTests(paths: string[], config: any) {
7279
// need to import it before any other import, otherwise Vite optimizer will hang
7380
const viteClientPath = '/@vite/client'
7481
await import(viteClientPath)
7582

76-
// we use dynamic import here, because this file is bundled with UI,
77-
// but we need to resolve correct path at runtime
78-
const path = '/__vitest_index__'
79-
const { startTests, setupCommonEnv, setupSnapshotEnvironment } = await import(path) as typeof import('vitest/browser')
83+
const { startTests, setupCommonEnv, setupSnapshotEnvironment } = await importId('vitest/browser') as typeof import('vitest/browser')
8084

8185
if (!runner) {
82-
const runnerPath = '/__vitest_runners__'
83-
const { VitestTestRunner } = await import(runnerPath) as typeof import('vitest/runners')
86+
const { VitestTestRunner } = await importId('vitest/runners') as typeof import('vitest/runners')
8487
const BrowserRunner = createBrowserRunner(VitestTestRunner)
85-
runner = new BrowserRunner({ config, client, browserHashMap })
88+
runner = new BrowserRunner({ config, browserHashMap })
8689
}
8790

8891
if (!hasSnapshot) {
89-
setupSnapshotEnvironment(new BrowserSnapshotEnvironment(client))
92+
setupSnapshotEnvironment(new BrowserSnapshotEnvironment())
9093
hasSnapshot = true
9194
}
9295

@@ -102,6 +105,7 @@ async function runTests(paths: string[], config: any, client: VitestClient) {
102105
await startTests(files, runner)
103106
}
104107
finally {
105-
await client.rpc.onDone(testId)
108+
await rpcDone()
109+
await rpc().onDone(testId)
106110
}
107111
}

‎packages/browser/src/client/rpc.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type {
2+
getSafeTimers,
3+
} from '@vitest/utils'
4+
import type { VitestClient } from '@vitest/ws-client'
5+
6+
const { get } = Reflect
7+
const safeRandom = Math.random
8+
9+
function withSafeTimers(getTimers: typeof getSafeTimers, fn: () => void) {
10+
const { setTimeout, clearTimeout, nextTick, setImmediate, clearImmediate } = getTimers()
11+
12+
const currentSetTimeout = globalThis.setTimeout
13+
const currentClearTimeout = globalThis.clearTimeout
14+
const currentRandom = globalThis.Math.random
15+
const currentNextTick = globalThis.process.nextTick
16+
const currentSetImmediate = globalThis.setImmediate
17+
const currentClearImmediate = globalThis.clearImmediate
18+
19+
try {
20+
globalThis.setTimeout = setTimeout
21+
globalThis.clearTimeout = clearTimeout
22+
globalThis.Math.random = safeRandom
23+
globalThis.process.nextTick = nextTick
24+
globalThis.setImmediate = setImmediate
25+
globalThis.clearImmediate = clearImmediate
26+
27+
const result = fn()
28+
return result
29+
}
30+
finally {
31+
globalThis.setTimeout = currentSetTimeout
32+
globalThis.clearTimeout = currentClearTimeout
33+
globalThis.Math.random = currentRandom
34+
globalThis.setImmediate = currentSetImmediate
35+
globalThis.clearImmediate = currentClearImmediate
36+
nextTick(() => {
37+
globalThis.process.nextTick = currentNextTick
38+
})
39+
}
40+
}
41+
42+
const promises = new Set<Promise<unknown>>()
43+
44+
export const rpcDone = async () => {
45+
if (!promises.size)
46+
return
47+
const awaitable = Array.from(promises)
48+
return Promise.all(awaitable)
49+
}
50+
51+
export const createSafeRpc = (client: VitestClient, getTimers: () => any): VitestClient['rpc'] => {
52+
return new Proxy(client.rpc, {
53+
get(target, p, handler) {
54+
const sendCall = get(target, p, handler)
55+
const safeSendCall = (...args: any[]) => withSafeTimers(getTimers, async () => {
56+
const result = sendCall(...args)
57+
promises.add(result)
58+
try {
59+
return await result
60+
}
61+
finally {
62+
promises.delete(result)
63+
}
64+
})
65+
safeSendCall.asEvent = sendCall.asEvent
66+
return safeSendCall
67+
},
68+
})
69+
}
70+
71+
export const rpc = (): VitestClient['rpc'] => {
72+
// @ts-expect-error not typed global
73+
return globalThis.__vitest_worker__.safeRpc
74+
}

‎packages/browser/src/client/runner.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
import type { File, TaskResult, Test } from '@vitest/runner'
2-
import type { VitestClient } from '@vitest/ws-client'
2+
import { rpc } from './rpc'
33
import type { ResolvedConfig } from '#types'
44

55
interface BrowserRunnerOptions {
66
config: ResolvedConfig
7-
client: VitestClient
87
browserHashMap: Map<string, string>
98
}
109

1110
export function createBrowserRunner(original: any) {
1211
return class BrowserTestRunner extends original {
1312
public config: ResolvedConfig
1413
hashMap = new Map<string, string>()
15-
client: VitestClient
1614

1715
constructor(options: BrowserRunnerOptions) {
1816
super(options.config)
1917
this.config = options.config
2018
this.hashMap = options.browserHashMap
21-
this.client = options.client
2219
}
2320

2421
async onAfterRunTest(task: Test) {
@@ -29,11 +26,11 @@ export function createBrowserRunner(original: any) {
2926
}
3027

3128
onCollected(files: File[]): unknown {
32-
return this.client.rpc.onCollected(files)
29+
return rpc().onCollected(files)
3330
}
3431

3532
onTaskUpdate(task: [string, TaskResult | undefined][]): Promise<void> {
36-
return this.client.rpc.onTaskUpdate(task)
33+
return rpc().onTaskUpdate(task)
3734
}
3835

3936
async importFile(filepath: string) {
+6-8
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
1-
import type { VitestClient } from '@vitest/ws-client'
1+
import { rpc } from './rpc'
22
import type { SnapshotEnvironment } from '#types'
33

44
export class BrowserSnapshotEnvironment implements SnapshotEnvironment {
5-
constructor(private client: VitestClient) {}
6-
75
readSnapshotFile(filepath: string): Promise<string | null> {
8-
return this.client.rpc.readFile(filepath)
6+
return rpc().readFile(filepath)
97
}
108

119
saveSnapshotFile(filepath: string, snapshot: string): Promise<void> {
12-
return this.client.rpc.writeFile(filepath, snapshot)
10+
return rpc().writeFile(filepath, snapshot)
1311
}
1412

1513
resolvePath(filepath: string): Promise<string> {
16-
return this.client.rpc.resolveSnapshotPath(filepath)
14+
return rpc().resolveSnapshotPath(filepath)
1715
}
1816

1917
removeSnapshotFile(filepath: string): Promise<void> {
20-
return this.client.rpc.removeFile(filepath)
18+
return rpc().removeFile(filepath)
2119
}
2220

2321
async prepareDirectory(filepath: string): Promise<void> {
24-
await this.client.rpc.createDirectory(filepath)
22+
await rpc().createDirectory(filepath)
2523
}
2624
}

‎packages/browser/src/client/utils.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const importId = (id: string) => {
2+
const name = `/@id/${id}`
3+
return import(name)
4+
}

‎packages/browser/src/node/index.ts

+9-16
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,6 @@ export default (base = '/'): Plugin[] => {
1818
{
1919
enforce: 'pre',
2020
name: 'vitest:browser',
21-
async resolveId(id) {
22-
if (id === '/__vitest_index__')
23-
return this.resolve('vitest/browser')
24-
25-
if (id === '/__vitest_runners__')
26-
return this.resolve('vitest/runners')
27-
28-
if (id.startsWith('node:'))
29-
id = id.slice(5)
30-
31-
if (polyfills.includes(id))
32-
return polyfillPath(normalizeId(id))
33-
34-
return null
35-
},
3621
async configureServer(server) {
3722
server.middlewares.use(
3823
base,
@@ -45,8 +30,16 @@ export default (base = '/'): Plugin[] => {
4530
},
4631
{
4732
name: 'modern-node-polyfills',
33+
enforce: 'pre',
34+
config() {
35+
return {
36+
optimizeDeps: {
37+
exclude: [...polyfills, ...builtinModules],
38+
},
39+
}
40+
},
4841
async resolveId(id) {
49-
if (!builtinModules.includes(id))
42+
if (!builtinModules.includes(id) && !polyfills.includes(id) && !id.startsWith('node:'))
5043
return
5144

5245
id = normalizeId(id)

‎packages/runner/src/utils/collect.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Suite, TaskBase } from '../types'
2+
import { processError } from './error'
23

34
/**
45
* If any tasks been marked as `only`, mark all other tasks as `skip`.
@@ -65,7 +66,7 @@ function skipAllTasks(suite: Suite) {
6566
function checkAllowOnly(task: TaskBase, allowOnly?: boolean) {
6667
if (allowOnly)
6768
return
68-
const error = new Error('[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly argument to bypass this error')
69+
const error = processError(new Error('[Vitest] Unexpected .only modifier. Remove it or pass --allowOnly argument to bypass this error'))
6970
task.result = {
7071
state: 'fail',
7172
error,

‎packages/utils/src/display.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ export function format(...args: any[]) {
88
return util.format(...args)
99
}
1010

11+
export function utilInspect(item: unknown, options?: util.InspectOptions) {
12+
return util.inspect(item, options)
13+
}
14+
1115
// chai utils
12-
export function inspect(obj: unknown): string {
16+
export function loupeInspect(obj: unknown): string {
1317
return loupe(obj, {
1418
depth: 2,
1519
truncate: 40,
@@ -18,7 +22,7 @@ export function inspect(obj: unknown): string {
1822

1923
export function objDisplay(obj: unknown) {
2024
const truncateThreshold = 40
21-
const str = inspect(obj)
25+
const str = loupeInspect(obj)
2226
const type = Object.prototype.toString.call(obj)
2327

2428
if (str.length >= truncateThreshold) {

‎packages/vitest/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
"types": "./dist/environments.d.ts",
6262
"import": "./dist/environments.js"
6363
},
64+
"./utils": {
65+
"types": "./dist/utils.d.ts",
66+
"import": "./dist/utils.js"
67+
},
6468
"./config": {
6569
"types": "./config.d.ts",
6670
"require": "./dist/config.cjs",

‎packages/vitest/rollup.config.js

+12-10
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,20 @@ const entries = [
2828
'src/runtime/entry.ts',
2929
'src/integrations/spy.ts',
3030
'src/coverage.ts',
31+
'src/public/utils.ts',
3132
]
3233

33-
const dtsEntries = [
34-
'src/index.ts',
35-
'src/node.ts',
36-
'src/environments.ts',
37-
'src/browser.ts',
38-
'src/runners.ts',
39-
'src/suite.ts',
40-
'src/config.ts',
41-
'src/coverage.ts',
42-
]
34+
const dtsEntries = {
35+
index: 'src/index.ts',
36+
node: 'src/node.ts',
37+
environments: 'src/environments.ts',
38+
browser: 'src/browser.ts',
39+
runners: 'src/runners.ts',
40+
suite: 'src/suite.ts',
41+
config: 'src/config.ts',
42+
coverage: 'src/coverage.ts',
43+
utils: 'src/public/utils.ts',
44+
}
4345

4446
const external = [
4547
...builtinModules,

‎packages/vitest/src/api/setup.ts

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export function setup(ctx: Vitest, server?: ViteDevServer) {
5252
getPaths() {
5353
return ctx.state.getPaths()
5454
},
55+
sendLog(log) {
56+
return ctx.report('onUserConsoleLog', log)
57+
},
5558
resolveSnapshotPath(testPath) {
5659
return ctx.snapshot.resolvePath(testPath)
5760
},

‎packages/vitest/src/api/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { TransformResult } from 'vite'
2-
import type { File, ModuleGraphData, Reporter, ResolvedConfig, SnapshotResult, TaskResultPack } from '../types'
2+
import type { File, ModuleGraphData, Reporter, ResolvedConfig, SnapshotResult, TaskResultPack, UserConsoleLog } from '../types'
33

44
export interface TransformResultWithSource extends TransformResult {
55
source?: string
@@ -9,6 +9,7 @@ export interface WebSocketHandlers {
99
onCollected(files?: File[]): Promise<void>
1010
onTaskUpdate(packs: TaskResultPack[]): void
1111
onDone(name: string): void
12+
sendLog(log: UserConsoleLog): void
1213
getFiles(): File[]
1314
getPaths(): string[]
1415
getConfig(): ResolvedConfig

‎packages/vitest/src/public/utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@vitest/utils'

‎packages/vitest/src/runtime/rpc.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
getSafeTimers,
33
} from '@vitest/utils'
4-
import { getWorkerState } from '../utils'
4+
import { getWorkerState } from '../utils/global'
55

66
const { get } = Reflect
77
const safeRandom = Math.random

‎packages/vitest/utils.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './dist/utils.js'

‎test/browser/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"name": "@vitest/test-browser",
33
"private": true,
4+
"module": "true",
45
"scripts": {
5-
"test": "node test.mjs",
6+
"test": "node --test specs/",
67
"coverage": "vitest run --coverage"
78
},
89
"devDependencies": {

‎test/browser/specs/runner.test.mjs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import assert from 'node:assert'
2+
import { readFile } from 'node:fs/promises'
3+
import test from 'node:test'
4+
import { execa } from 'execa'
5+
6+
const browser = process.env.BROWSER || 'chrome'
7+
8+
const { stderr, stdout } = await execa('npx', ['vitest', `--browser=${browser}`], {
9+
env: {
10+
...process.env,
11+
CI: 'true',
12+
NO_COLOR: 'true',
13+
},
14+
})
15+
16+
test('tests are actually running', async () => {
17+
const browserResult = await readFile('./browser.json', 'utf-8')
18+
const browserResultJson = JSON.parse(browserResult)
19+
20+
assert.ok(browserResultJson.testResults.length === 5, 'Not all the tests have been run')
21+
22+
for (const result of browserResultJson.testResults)
23+
assert.ok(result.status === 'passed', `${result.name} has failed`)
24+
})
25+
26+
test('logs are redirected to stdout', async () => {
27+
assert.match(stdout, /stdout | test\/logs.test.ts > logging to stdout/)
28+
assert.match(stdout, /hello from console.log/, 'prints console.log')
29+
assert.match(stdout, /hello from console.info/, 'prints console.info')
30+
assert.match(stdout, /hello from console.debug/, 'prints console.debug')
31+
assert.match(stdout, /{ hello: 'from dir' }/, 'prints console.dir')
32+
assert.match(stdout, /{ hello: 'from dirxml' }/, 'prints console.dixml')
33+
assert.match(stdout, /hello from console.trace\s+\w+/, 'prints console.trace')
34+
assert.match(stdout, /dom <div \/>/, 'prints dom')
35+
assert.match(stdout, /default: 1/, 'prints first default count')
36+
assert.match(stdout, /default: 2/, 'prints second default count')
37+
assert.match(stdout, /default: 3/, 'prints third default count')
38+
assert.match(stdout, /count: 1/, 'prints first custom count')
39+
assert.match(stdout, /count: 2/, 'prints second custom count')
40+
assert.match(stdout, /count: 3/, 'prints third custom count')
41+
assert.match(stdout, /default: [\d.]+ ms/, 'prints default time')
42+
assert.match(stdout, /time: [\d.]+ ms/, 'prints custom time')
43+
})
44+
45+
test('logs are redirected to stderr', async () => {
46+
assert.match(stderr, /stderr | test\/logs.test.ts > logging to stderr/)
47+
assert.match(stderr, /hello from console.error/, 'prints console.log')
48+
assert.match(stderr, /hello from console.warn/, 'prints console.info')
49+
assert.match(stderr, /Timer "invalid timeLog" does not exist/, 'prints errored timeLog')
50+
assert.match(stderr, /Timer "invalid timeEnd" does not exist/, 'prints errored timeEnd')
51+
})

‎test/browser/test.mjs

-32
This file was deleted.

‎test/browser/test/logs.test.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* eslint-disable no-console */
2+
import { test } from 'vitest'
3+
4+
test('logging to stdout', () => {
5+
console.log('hello from console.log')
6+
console.info('hello from console.info')
7+
console.debug('hello from console.debug')
8+
console.dir({ hello: 'from dir' })
9+
console.dirxml({ hello: 'from dirxml' })
10+
console.trace('hello from console.trace')
11+
})
12+
13+
test('logging to stderr', () => {
14+
console.error('hello from console.error')
15+
console.warn('hello from console.warn')
16+
})
17+
18+
test('logging DOM element', () => {
19+
const element = document.createElement('div')
20+
console.log('dom', element)
21+
})
22+
23+
test('logging default counter', () => {
24+
console.count()
25+
console.count()
26+
console.count()
27+
console.countReset()
28+
console.count()
29+
})
30+
31+
test('logging custom counter', () => {
32+
console.count('count')
33+
console.count('count')
34+
console.count('count')
35+
console.countReset('count')
36+
console.count('count')
37+
})
38+
39+
test('logging default time', () => {
40+
console.time()
41+
console.timeLog()
42+
console.timeEnd()
43+
})
44+
45+
test('logging custom time', () => {
46+
console.time('time')
47+
console.timeLog('time')
48+
console.timeEnd('time')
49+
})
50+
51+
test('logging invalid time', () => {
52+
console.timeLog('invalid timeLog')
53+
console.timeEnd('invalid timeEnd')
54+
})

‎test/browser/vitest.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const noop = () => {}
44

55
export default defineConfig({
66
test: {
7+
include: ['test/**.test.{ts,js}'],
78
browser: {
89
enabled: true,
910
name: 'chrome',

0 commit comments

Comments
 (0)
Please sign in to comment.