diff --git a/README.md b/README.md index c6ad0b2c..6048f5c9 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ export default defineConfig({ }) ``` -After running a browser test through the extension, right-click the test in the Testing view or its editor gutter icon and select **Open Trace View**. The extension opens the trace view of a selected test from the generated HTML report inside Visual Studio Code's Integrated Browser. +After running a browser test through the extension, right-click the test in the Testing view or its editor gutter icon and select **Open Trace View**. The extension opens the trace view of the selected test from the generated HTML report in an editor tab. ![Vitest Trace View open in Visual Studio Code](./img/trace-view.png) diff --git a/img/trace-view.png b/img/trace-view.png index a89f4f94..0244c712 100644 Binary files a/img/trace-view.png and b/img/trace-view.png differ diff --git a/packages/extension/src/extension.ts b/packages/extension/src/extension.ts index d7444f14..846071e8 100644 --- a/packages/extension/src/extension.ts +++ b/packages/extension/src/extension.ts @@ -646,6 +646,7 @@ class VitestExtension { this.testController.dispose() this.schemaProvider.dispose() this.importsBreakdownProvider.dispose() + this.traceViewManager.dispose() this.runProfiles.forEach((p) => p.dispose()) this.runProfiles.clear() this.disposables.forEach((d) => d.dispose()) diff --git a/packages/extension/src/traceView.ts b/packages/extension/src/traceView.ts index 895e1bd8..392ea295 100644 --- a/packages/extension/src/traceView.ts +++ b/packages/extension/src/traceView.ts @@ -1,5 +1,6 @@ import type { RunnerTask, RunnerTestFile } from 'vitest' import type { TestTree } from './testTree' +import { randomBytes } from 'node:crypto' import * as vscode from 'vscode' interface TraceViewTarget { @@ -9,8 +10,32 @@ interface TraceViewTarget { testId: string } +type TraceSelectionMessage = { + type: 'traceSelection' + revision: number + testId: string | null + traceAttempt: string | null + traceStep: number +} + +interface TraceViewState { + panel: vscode.WebviewPanel + target: TraceViewTarget + watcher: vscode.FileSystemWatcher + traceAttempt?: string + traceStep: number + refreshTimer?: ReturnType +} + export class TraceViewManager { private targets = new Map() + private viewState?: TraceViewState + // Invalidate older documents on refresh, target changes, and panel disposal. + private revision = 0 + + dispose() { + this.viewState?.panel.dispose() + } clear() { this.targets.clear() @@ -18,13 +43,38 @@ export class TraceViewManager { } async update(apiId: string, reportPath: string, files: RunnerTestFile[], tree: TestTree) { + // Remove Open Trace View actions from the previous run of this process. for (const [item, target] of this.targets) { if (target.apiId === apiId) { this.targets.delete(item) } } - for (const target of findTraceViewTargets(apiId, reportPath, files)) { + // Keep the selected test when available, otherwise follow the first traced test. + // HTML report file writes trigger the reload. + const targets = findTraceViewTargets(apiId, reportPath, files) + const viewState = this.viewState + const target = + targets.find( + (target) => + target.apiId === viewState?.target.apiId && target.testId === viewState.target.testId, + ) ?? targets[0] + if (viewState && target) { + if (viewState.target.reportPath !== target.reportPath) { + clearTimeout(viewState.refreshTimer) + viewState.watcher.dispose() + viewState.watcher = this.watchReport(target.reportPath) + } + if (viewState.target.apiId !== target.apiId || viewState.target.testId !== target.testId) { + this.revision++ + viewState.target = target + viewState.traceAttempt = undefined + viewState.traceStep = 0 + } + } + + // Register Open Trace View actions for the latest results. + for (const target of targets) { const item = tree.getTestItemByTaskId(target.testId) if (item) { this.targets.set(item, target) @@ -43,7 +93,9 @@ export class TraceViewManager { } async open(testItem: vscode.TestItem) { - const target = this.targets.get(testItem)! + // Resolve the requested test and check that its report exists. + const target = this.targets.get(testItem) + if (!target) return const reportUri = vscode.Uri.file(target.reportPath) try { @@ -55,16 +107,105 @@ export class TraceViewManager { return } - const commands = await vscode.commands.getCommands(true) - const url = createTraceViewUrl(target) - // The Integrated Browser command is internal; follow Simple Browser's feature detection before using it. - // https://github.com/microsoft/vscode/blob/008427a901bf4aa79b47f175ccc8da1731750f78/extensions/simple-browser/src/extension.ts#L15-L35 - if (commands.includes('workbench.action.browser.open')) { - await vscode.commands.executeCommand('workbench.action.browser.open', url) + // Create the panel and its listeners on the first open. + let viewState = this.viewState + if (!viewState) { + const panel = vscode.window.createWebviewPanel( + 'vitest.traceView', + 'Vitest Trace View', + { viewColumn: vscode.ViewColumn.Beside, preserveFocus: true }, + { enableScripts: true, retainContextWhenHidden: true }, + ) + viewState = { + panel, + target, + watcher: this.watchReport(target.reportPath), + traceStep: 0, + } + this.viewState = viewState + // Remember attempt and step changes for the next report reload. + panel.webview.onDidReceiveMessage((message: TraceSelectionMessage) => { + const viewState = this.viewState + if (viewState && message.type === 'traceSelection' && message.revision === this.revision) { + viewState.traceAttempt = message.traceAttempt ?? undefined + viewState.traceStep = message.traceStep + } + }) + // Release the watcher and pending reload when the panel closes. + panel.onDidDispose(() => { + const viewState = this.viewState + if (viewState?.panel !== panel) return + viewState.watcher.dispose() + clearTimeout(viewState.refreshTimer) + this.viewState = undefined + this.revision++ + }) } else { - // TODO: Support a single-file fallback because external browsers may block file:// metadata requests. - await vscode.env.openExternal(vscode.Uri.parse(url)) + // Reuse the panel for the requested test with its initial attempt and step. + clearTimeout(viewState.refreshTimer) + viewState.watcher.dispose() + viewState.watcher = this.watchReport(target.reportPath) + viewState.target = target + viewState.traceAttempt = undefined + viewState.traceStep = 0 + } + // Explicit opens load immediately. Later report changes reload through the watcher. + viewState.panel.reveal(undefined, true) + await this.refresh() + } + + private watchReport(reportPath: string) { + const watcher = vscode.workspace.createFileSystemWatcher( + new vscode.RelativePattern( + vscode.Uri.joinPath(vscode.Uri.file(reportPath), '..'), + '{index.html,ui/html.meta.json.gz}', + ), + ) + const debouncedRefresh = () => { + const viewState = this.viewState + if (viewState?.watcher !== watcher) return + clearTimeout(viewState.refreshTimer) + viewState.refreshTimer = setTimeout(() => void this.refresh(), 150) } + watcher.onDidChange(debouncedRefresh) + watcher.onDidCreate(debouncedRefresh) + return watcher + } + + private async refresh() { + // Capture the active view and invalidate older loads. + const viewState = this.viewState + if (!viewState) return + const { panel, target } = viewState + const revision = ++this.revision + + // Restore the current test, attempt, and step in the new document. + const traceViewUrlHash = createTraceViewUrlHash( + target, + viewState.traceStep, + viewState.traceAttempt, + ) + const reportUri = vscode.Uri.file(target.reportPath) + // Read the generated report and discard it if the view changed while loading. + let reportHtml: Uint8Array + try { + reportHtml = await vscode.workspace.fs.readFile(reportUri) + } catch (error) { + await vscode.window.showWarningMessage(`Failed to load Vitest trace report: ${String(error)}`) + return + } + if (revision !== this.revision) return + + // Adapt report resources and bootstrap code for the webview. + const directory = vscode.Uri.joinPath(reportUri, '..') + panel.webview.options = { enableScripts: true, localResourceRoots: [directory] } + panel.webview.html = transformTraceViewHtml( + Buffer.from(reportHtml).toString('utf8'), + panel.webview, + directory, + traceViewUrlHash, + revision, + ) } } @@ -94,13 +235,102 @@ function findTraceViewTargets( return targets } -function createTraceViewUrl(target: TraceViewTarget) { +function createTraceViewUrlHash(target: TraceViewTarget, traceStep = 0, traceAttempt?: string) { // https://github.com/vitest-dev/vitest/blob/decfeb61c71a93372f84b6d43893df86a1756308/packages/ui/client/composables/params.ts#L3-L24 const params = new URLSearchParams({ file: target.fileId, + layout: 'trace', view: 'editor', test: target.testId, - traceStep: '0', + traceStep: String(traceStep), }) - return `${vscode.Uri.file(target.reportPath).toString(true)}#/?${params}` + if (traceAttempt) { + params.set('traceAttempt', traceAttempt) + } + return `/?${params}` +} + +function transformTraceViewHtml( + html: string, + webview: vscode.Webview, + directory: vscode.Uri, + traceViewUrlHash: string, + revision: number, +) { + const base = `${webview.asWebviewUri(directory).toString()}/` + const nonce = randomBytes(16).toString('hex') + const csp = [ + `default-src 'none'`, + `script-src ${webview.cspSource} 'nonce-${nonce}'`, + `style-src ${webview.cspSource} https://fonts.googleapis.com 'unsafe-inline'`, + `img-src ${webview.cspSource} data: blob: https:`, + `font-src ${webview.cspSource} data: https:`, + `connect-src ${webview.cspSource}`, + `frame-src 'self' blob: data:;`, + ].join('; ') + // Resolve metadata from the report directory instead of the webview URL. + const metadata = webview.asWebviewUri(vscode.Uri.joinPath(directory, 'ui', 'html.meta.json.gz')) + html = html.replace( + /new URL\("\.\/ui\/html\.meta\.json\.gz", window\.location\.href\)/g, + JSON.stringify(metadata.toString()), + ) + // Resolve relative asset URLs from the report directory. + html = html.replace(/<(script|link|img|source)\b[^>]*>/gi, (tag) => + tag.replace(/(\s)(src|href)\s*=\s*(['"])(.*?)\3/gi, (attribute, space, name, quote, value) => { + if (!value || /^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(value)) return attribute + const url = new URL(value.replace(/&/g, '&'), base).href + return `${space}${name}=${quote}${url.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''')}${quote}` + }), + ) + html = html.replace(/]*>/i, + `$& + + + `, + ) + return html +} + +const TRACE_VIEW_CSS = ` + body { + padding: 0; + color: var(--color-text); + } + html:not(.dark) { + background-color: white; + color-scheme: light; + } +` + +function initializeTraceView(vscode: any, window: any, traceViewUrlHash: string, revision: number) { + const reportSelection = () => { + const params = new URLSearchParams(window.location.hash.split('?')[1]) + const step = params.get('traceStep') + if (step !== null) { + vscode.postMessage({ + type: 'traceSelection', + revision, + testId: params.get('test'), + traceAttempt: params.get('traceAttempt'), + traceStep: Number(step), + } satisfies TraceSelectionMessage) + } + } + // Vitest updates URL parameters through History, which does not + // emit hashchange events. + for (const method of ['replaceState', 'pushState']) { + const original = window.history[method] + window.history[method] = function (...args: any[]) { + const result = original.apply(this, args) + reportSelection() + return result + } + } + window.addEventListener('hashchange', reportSelection) + window.addEventListener('popstate', reportSelection) + window.location.hash = traceViewUrlHash } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19649d53..c306338c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,13 +157,13 @@ catalogs: version: 3.2.4 v5: '@vitest/browser-playwright': - specifier: 5.0.0 + specifier: https://pkg.pr.new/@vitest/browser-playwright@0ae5d17 version: 5.0.0 '@vitest/ui': - specifier: 5.0.0 + specifier: https://pkg.pr.new/@vitest/ui@0ae5d17 version: 5.0.0 vitest: - specifier: 5.0.0 + specifier: https://pkg.pr.new/vitest@0ae5d17 version: 5.0.0 importers: @@ -425,10 +425,10 @@ importers: devDependencies: '@vitest/browser-playwright': specifier: catalog:v5 - version: 5.0.0(playwright@1.62.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0) + version: https://pkg.pr.new/@vitest/browser-playwright@0ae5d17(playwright@1.62.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0) '@vitest/ui': specifier: catalog:v5 - version: 5.0.0(vitest@5.0.0) + version: https://pkg.pr.new/@vitest/ui@0ae5d17(vitest@5.0.0) playwright: specifier: 1.62.1 version: 1.62.1 @@ -437,7 +437,7 @@ importers: version: 8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: catalog:v5 - version: 5.0.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + version: https://pkg.pr.new/vitest@0ae5d17(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) samples/continuous: devDependencies: @@ -1998,8 +1998,9 @@ packages: playwright: '*' vitest: 4.1.0 - '@vitest/browser-playwright@5.0.0': - resolution: {integrity: sha512-N+gED9y4/8pypaHjz/x0ah3CjoBr+N0hWWT+Gq4VXtMzyB+rUIdHni0ulzpD1j4H77iWy5Jg8PRMlVn6kjxo6g==} + '@vitest/browser-playwright@https://pkg.pr.new/@vitest/browser-playwright@0ae5d17': + resolution: {integrity: sha512-k3JT+oQKVXTpRVzxBWrJ04LE1nv7O/dEqwpBSIGcQPcIZmmsJvmosVwDKOh9U2V8OfY5nlfBMkJINXen8D17KA==, tarball: https://pkg.pr.new/@vitest/browser-playwright@0ae5d17} + version: 5.0.0 peerDependencies: playwright: '*' vitest: 5.0.0 @@ -2024,8 +2025,9 @@ packages: peerDependencies: vitest: 4.1.0 - '@vitest/browser@5.0.0': - resolution: {integrity: sha512-JC9FG5xIRxPHXJPcdCaluIJcEoeM0IwGQ3xneuJk09LXKHRNs40BqWDWymQikbb20yOpYvzpKzmYgPRuSzKmvg==} + '@vitest/browser@https://pkg.pr.new/vitest-dev/vitest/@vitest/browser@0ae5d17': + resolution: {integrity: sha512-lG12dbjSu+9JoUwzkaA8CHJbezaeARbv2HhGw7UecOm6S91iBE1HfecNIX0Div5fg2cXZGllEiO0HlumlWXDbw==, tarball: https://pkg.pr.new/vitest-dev/vitest/@vitest/browser@0ae5d17} + version: 5.0.0 peerDependencies: vitest: 5.0.0 @@ -2080,8 +2082,9 @@ packages: vite: optional: true - '@vitest/mocker@5.0.0': - resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} + '@vitest/mocker@https://pkg.pr.new/vitest-dev/vitest/@vitest/mocker@0ae5d17': + resolution: {integrity: sha512-RMYv9xs/dO/l5oV8+adMjoVNKRUN5K/cBvOoba1DsJTjPjqwoG3J5hPBIX8oWAq4gEuPNbwi2eYBoBFLZ12dCA==, tarball: https://pkg.pr.new/vitest-dev/vitest/@vitest/mocker@0ae5d17} + version: 5.0.0 peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2097,8 +2100,9 @@ packages: '@vitest/pretty-format@4.1.0': resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} - '@vitest/pretty-format@5.0.0': - resolution: {integrity: sha512-PVRNuB3wpReb4SQEs4zTKM4KWFhQ5pw3spE8naoDJNB5T5aWRzGKHwXcLUllr0WeOTXpB6bSr3CJLo5+7XQSSQ==} + '@vitest/pretty-format@https://pkg.pr.new/vitest-dev/vitest/@vitest/pretty-format@0ae5d17': + resolution: {integrity: sha512-PVRNuB3wpReb4SQEs4zTKM4KWFhQ5pw3spE8naoDJNB5T5aWRzGKHwXcLUllr0WeOTXpB6bSr3CJLo5+7XQSSQ==, tarball: https://pkg.pr.new/vitest-dev/vitest/@vitest/pretty-format@0ae5d17} + version: 5.0.0 '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} @@ -2118,11 +2122,19 @@ packages: '@vitest/spy@4.1.0': resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} - '@vitest/spy@5.0.0': - resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} + '@vitest/spy@https://pkg.pr.new/vitest-dev/vitest/@vitest/spy@0ae5d17': + resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==, tarball: https://pkg.pr.new/vitest-dev/vitest/@vitest/spy@0ae5d17} + version: 5.0.0 - '@vitest/ui@5.0.0': - resolution: {integrity: sha512-h2FIFwggCY2GxUd2UdQoYNVQkOIqEQLPhNREcl3FUiRsdzQep7NWwYbSmhGEA9nFLPDq5pXzRMcBZQU8Py83sg==} + '@vitest/ui@https://pkg.pr.new/@vitest/ui@0ae5d17': + resolution: {integrity: sha512-s09V2poailZE3fNw9lSyAIKZQUQGoizyru8I7P/TE5mbT/ET4ip203Uy1PlKvbkuQ9HxXILuIXTHLcPsjLiNHA==, tarball: https://pkg.pr.new/@vitest/ui@0ae5d17} + version: 5.0.0 + peerDependencies: + vitest: 5.0.0 + + '@vitest/ui@https://pkg.pr.new/vitest-dev/vitest/@vitest/ui@0ae5d17': + resolution: {integrity: sha512-s09V2poailZE3fNw9lSyAIKZQUQGoizyru8I7P/TE5mbT/ET4ip203Uy1PlKvbkuQ9HxXILuIXTHLcPsjLiNHA==, tarball: https://pkg.pr.new/vitest-dev/vitest/@vitest/ui@0ae5d17} + version: 5.0.0 peerDependencies: vitest: 5.0.0 @@ -2132,8 +2144,9 @@ packages: '@vitest/utils@4.1.0': resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} - '@vitest/utils@5.0.0': - resolution: {integrity: sha512-dO++xL3vDfvhTAVimfkuQUA3k+JClIF1i1vAkPqpcGAthRmeWnXmHB7YPViPvgCwviX8u7Y5W1u2N//AaQr3fw==} + '@vitest/utils@https://pkg.pr.new/vitest-dev/vitest/@vitest/utils@0ae5d17': + resolution: {integrity: sha512-QtW27LH02mO4CPR1UIV1U/25TUm1XisCPF/4tnucrmWUZrL1ZXlEWrdABrgYxQLhLfUO02Rhtx0/fwHZ2G+Otg==, tarball: https://pkg.pr.new/vitest-dev/vitest/@vitest/utils@0ae5d17} + version: 5.0.0 '@vscode/test-cli@0.0.6': resolution: {integrity: sha512-4i61OUv5PQr3GxhHOuUgHdgBDfIO/kXTPCsEyFiMaY4SOqQTgkTmyZLagHehjOgCfsXdcrJa3zgQ7zoc+Dh6hQ==} @@ -5087,8 +5100,9 @@ packages: jsdom: optional: true - vitest@5.0.0: - resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==} + vitest@https://pkg.pr.new/vitest@0ae5d17: + resolution: {integrity: sha512-ftyomCQf1wpgz+JFU7yXbW/bGfxFJ17YphoM+fD+Kk3rUtS/m2wsnZBHBbdRA9g+qbswIE/Uhd5khDkhccaB/A==, tarball: https://pkg.pr.new/vitest@0ae5d17} + version: 5.0.0 engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: @@ -6547,13 +6561,13 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@5.0.0(playwright@1.62.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0)': + '@vitest/browser-playwright@https://pkg.pr.new/@vitest/browser-playwright@0ae5d17(playwright@1.62.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0)': dependencies: - '@vitest/browser': 5.0.0(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0) - '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/browser': https://pkg.pr.new/vitest-dev/vitest/@vitest/browser@0ae5d17(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0) + '@vitest/mocker': https://pkg.pr.new/vitest-dev/vitest/@vitest/mocker@0ae5d17(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) playwright: 1.62.1 tinyrainbow: 3.1.1 - vitest: 5.0.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + vitest: https://pkg.pr.new/vitest@0ae5d17(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - bufferutil - msw @@ -6614,17 +6628,17 @@ snapshots: - vite optional: true - '@vitest/browser@5.0.0(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0)': + '@vitest/browser@https://pkg.pr.new/vitest-dev/vitest/@vitest/browser@0ae5d17(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/ui': 5.0.0(vitest@5.0.0) - '@vitest/utils': 5.0.0 + '@vitest/mocker': https://pkg.pr.new/vitest-dev/vitest/@vitest/mocker@0ae5d17(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/ui': https://pkg.pr.new/vitest-dev/vitest/@vitest/ui@0ae5d17(vitest@5.0.0) + '@vitest/utils': https://pkg.pr.new/vitest-dev/vitest/@vitest/utils@0ae5d17 magic-string: 1.2.3 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.1 - vitest: 5.0.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + vitest: https://pkg.pr.new/vitest@0ae5d17(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) ws: 8.21.3 transitivePeerDependencies: - bufferutil @@ -6742,10 +6756,10 @@ snapshots: optionalDependencies: vite: 8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@5.0.0(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@https://pkg.pr.new/vitest-dev/vitest/@vitest/mocker@0ae5d17(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.0 + '@vitest/spy': https://pkg.pr.new/vitest-dev/vitest/@vitest/spy@0ae5d17 estree-walker: 3.0.3 magic-string: 1.2.3 optionalDependencies: @@ -6759,7 +6773,7 @@ snapshots: dependencies: tinyrainbow: 3.0.3 - '@vitest/pretty-format@5.0.0': + '@vitest/pretty-format@https://pkg.pr.new/vitest-dev/vitest/@vitest/pretty-format@0ae5d17': dependencies: tinyrainbow: 3.1.1 @@ -6793,17 +6807,27 @@ snapshots: '@vitest/spy@4.1.0': {} - '@vitest/spy@5.0.0': {} + '@vitest/spy@https://pkg.pr.new/vitest-dev/vitest/@vitest/spy@0ae5d17': {} + + '@vitest/ui@https://pkg.pr.new/@vitest/ui@0ae5d17(vitest@5.0.0)': + dependencies: + '@vitest/utils': https://pkg.pr.new/vitest-dev/vitest/@vitest/utils@0ae5d17 + fflate: 0.8.3 + flatted: 3.4.4 + pathe: 2.0.3 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: https://pkg.pr.new/vitest@0ae5d17(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/ui@5.0.0(vitest@5.0.0)': + '@vitest/ui@https://pkg.pr.new/vitest-dev/vitest/@vitest/ui@0ae5d17(vitest@5.0.0)': dependencies: - '@vitest/utils': 5.0.0 + '@vitest/utils': https://pkg.pr.new/vitest-dev/vitest/@vitest/utils@0ae5d17 fflate: 0.8.3 flatted: 3.4.4 pathe: 2.0.3 sirv: 3.0.2 tinyrainbow: 3.1.1 - vitest: 5.0.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + vitest: https://pkg.pr.new/vitest@0ae5d17(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/utils@3.2.4': dependencies: @@ -6817,9 +6841,9 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.0.3 - '@vitest/utils@5.0.0': + '@vitest/utils@https://pkg.pr.new/vitest-dev/vitest/@vitest/utils@0ae5d17': dependencies: - '@vitest/pretty-format': 5.0.0 + '@vitest/pretty-format': https://pkg.pr.new/vitest-dev/vitest/@vitest/pretty-format@0ae5d17 convert-source-map: 2.0.0 tinyrainbow: 3.1.1 @@ -10047,10 +10071,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@5.0.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)): + vitest@https://pkg.pr.new/vitest@0ae5d17(@types/node@24.10.1)(@vitest/browser-playwright@5.0.0)(@vitest/ui@5.0.0)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': https://pkg.pr.new/vitest-dev/vitest/@vitest/mocker@0ae5d17(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -10065,8 +10089,8 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.1 - '@vitest/browser-playwright': 5.0.0(playwright@1.62.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0) - '@vitest/ui': 5.0.0(vitest@5.0.0) + '@vitest/browser-playwright': https://pkg.pr.new/@vitest/browser-playwright@0ae5d17(playwright@1.62.1)(vite@8.2.2(@types/node@24.10.1)(esbuild@0.27.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@5.0.0) + '@vitest/ui': https://pkg.pr.new/@vitest/ui@0ae5d17(vitest@5.0.0) happy-dom: 20.9.0 jsdom: 29.1.1 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cea83a03..5432774f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -57,9 +57,9 @@ catalogs: vite: ^7.2.6 vitest: ^3.2.4 v5: - '@vitest/browser-playwright': 5.0.0 - '@vitest/ui': 5.0.0 - vitest: 5.0.0 + '@vitest/browser-playwright': https://pkg.pr.new/@vitest/browser-playwright@0ae5d17 + '@vitest/ui': https://pkg.pr.new/@vitest/ui@0ae5d17 + vitest: https://pkg.pr.new/vitest@0ae5d17 onlyBuiltDependencies: - '@vscode/vsce-sign' - esbuild diff --git a/samples/browser-v5/basic.test.ts b/samples/browser-v5/basic.test.ts index e7272579..38acd598 100644 --- a/samples/browser-v5/basic.test.ts +++ b/samples/browser-v5/basic.test.ts @@ -2,7 +2,13 @@ import { expect, test } from 'vitest' import { page } from 'vitest/browser' test('records a trace', async () => { - document.body.innerHTML = '' + document.body.innerHTML = '' await page.getByRole('button').mark('Render button') await expect.element(page.getByRole('button')).toBeVisible() }) + +test('records a second trace', async () => { + document.body.innerHTML = '' + await page.getByRole('button').mark('Render cancel button') + await expect.element(page.getByRole('button')).toHaveTextContent('Cancel BAR') +})