From fad263fe8b4b77076dad08637ef4fc1d6352a0bc Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 18 Aug 2026 11:25:52 +0200 Subject: [PATCH] perf: initialize trace view in the orchestrator once per worker (#10984) --- packages/browser/src/client/orchestrator.ts | 7 ++++ packages/browser/src/client/tester/runner.ts | 4 +- packages/browser/src/client/utils.ts | 8 ++++ patches/rrweb-snapshot@2.1.1.patch | 43 +++++++++++++++++++- pnpm-lock.yaml | 8 ++-- 5 files changed, 63 insertions(+), 7 deletions(-) diff --git a/packages/browser/src/client/orchestrator.ts b/packages/browser/src/client/orchestrator.ts index 34d465d1b..72359d273 100644 --- a/packages/browser/src/client/orchestrator.ts +++ b/packages/browser/src/client/orchestrator.ts @@ -27,6 +27,13 @@ export class IframeOrchestrator { constructor() { debug('init orchestrator', getBrowserState().sessionId) + if (getConfig().browser.traceView.enabled) { + const domSnapshot = import('rrweb-snapshot') + // a load failure surfaces in the tester that awaits this + domSnapshot.catch(() => {}) + getBrowserState().browserTraceDomSnapshotPromise = domSnapshot + } + const otelConfig = getBrowserState().config.experimental.openTelemetry this.traces = new Traces({ enabled: !!(otelConfig?.enabled && otelConfig.browserSdkPath), diff --git a/packages/browser/src/client/tester/runner.ts b/packages/browser/src/client/tester/runner.ts index 509f3a35a..223d50b63 100644 --- a/packages/browser/src/client/tester/runner.ts +++ b/packages/browser/src/client/tester/runner.ts @@ -29,7 +29,7 @@ import { } from 'vitest/internal/browser' import { createStackString, parseStacktrace } from '../../../../utils/src/source-map' import { getTestName } from '../../../../vitest/src/utils/tasks' -import { getBrowserState, getWorkerState, moduleRunner, now } from '../utils' +import { getBrowserState, getOrchestratorState, getWorkerState, moduleRunner, now } from '../utils' import { rpc } from './rpc' import { VitestBrowserSnapshotEnvironment } from './snapshot' import { recordBrowserTraceEntry } from './trace' @@ -92,7 +92,7 @@ function createBrowserRunner( return } if (shouldTraceView) { - getBrowserState().browserTraceDomSnapshot = await import('rrweb-snapshot') + getBrowserState().browserTraceDomSnapshot ??= await getOrchestratorState().browserTraceDomSnapshotPromise getBrowserState().browserTraceAttempts.set(test.id, { retry, repeats, startTime: now() }) } else { diff --git a/packages/browser/src/client/utils.ts b/packages/browser/src/client/utils.ts index 2b020f74a..c61647b26 100644 --- a/packages/browser/src/client/utils.ts +++ b/packages/browser/src/client/utils.ts @@ -98,6 +98,8 @@ export interface BrowserRunnerState { browserTraceAttempts: Map // lazily loaded only when traceView is enabled browserTraceDomSnapshot?: typeof import('rrweb-snapshot') + // import started by the orchestrator so every tester reuses one module instance + browserTraceDomSnapshotPromise?: Promise selectorEngine: Ivya traces: Traces cleanups: Array<() => unknown> @@ -117,6 +119,12 @@ export function getBrowserState(): BrowserRunnerState { return window.__vitest_browser_runner__ } +/* @__NO_SIDE_EFFECTS__ */ +export function getOrchestratorState(): BrowserRunnerState { + // @ts-expect-error not typed global + return window.parent.__vitest_browser_runner__ +} + /* @__NO_SIDE_EFFECTS__ */ export function getWorkerState(): WorkerGlobalState { // @ts-expect-error not typed global diff --git a/patches/rrweb-snapshot@2.1.1.patch b/patches/rrweb-snapshot@2.1.1.patch index dff0b27a4..fe3baf8ed 100644 --- a/patches/rrweb-snapshot@2.1.1.patch +++ b/patches/rrweb-snapshot@2.1.1.patch @@ -1,7 +1,48 @@ diff --git a/dist/rrweb-snapshot.js b/dist/rrweb-snapshot.js +index 1c0fc62d7ede018a7797d3e707c73fa803166d2d..eadcceb088ac1deef08655eb2ed1cdb95fdb515b 100644 --- a/dist/rrweb-snapshot.js +++ b/dist/rrweb-snapshot.js -@@ -1536,6 +1536,18 @@ const pseudoClassPlugin = { +@@ -603,7 +603,7 @@ function genId() { + return _id++; + } + function getValidTagName(element) { +- if (element instanceof HTMLFormElement) { ++ if (Object.prototype.toString.call(element) === "[object HTMLFormElement]") { + return "form"; + } + const processedTagName = toLowerCase(element.tagName); +@@ -612,8 +612,16 @@ function getValidTagName(element) { + } + return processedTagName; + } +-let canvasService; +-let canvasCtx; ++const canvasServices = new WeakMap(); ++function getCanvasService(doc) { ++ let service = canvasServices.get(doc); ++ if (!service) { ++ const canvas = doc.createElement("canvas"); ++ service = { canvas, context: canvas.getContext("2d") }; ++ canvasServices.set(doc, service); ++ } ++ return service; ++} + const SRCSET_NOT_SPACES = /^[^ \t\n\r\u000c]+/; + const SRCSET_COMMAS_OR_SPACES = /^[, \t\n\r\u000c]+/; + function getAbsoluteSrcsetString(doc, attributeValue) { +@@ -1056,10 +1064,7 @@ function serializeElementNode(n, options) { + } + } + if (tagName === "img" && inlineImages) { +- if (!canvasService) { +- canvasService = doc.createElement("canvas"); +- canvasCtx = canvasService.getContext("2d"); +- } ++ const { canvas: canvasService, context: canvasCtx } = getCanvasService(doc); + const image = n; + const imageSrc = image.currentSrc || image.getAttribute("src") || ""; + const priorCrossOrigin = image.crossOrigin; +@@ -1536,6 +1541,18 @@ const pseudoClassPlugin = { if (selector.includes(":hover")) { rule2.selector += ",\n" + selector.replace(/:hover/g, ".\\:hover"); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44ebef58e..673cbca61 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,7 +196,7 @@ patchedDependencies: cac@6.7.14: a8f0f3517a47ce716ed90c0cfe6ae382ab763b021a664ada2a608477d0621588 istanbul-lib-instrument: fa76adf262fdb0bf545d5c529c5420e0ae33df7f5e344a6c8a727a2ffc0b0b11 istanbul-lib-source-maps: be977704c4b9838da456619fe2421b5e24df2103a25967bd383b2246c88ddf6e - rrweb-snapshot@2.1.1: 0b0dbc551d27a6f24438e9582c41a0ec34fd444760d383a4172013031d73c4e7 + rrweb-snapshot@2.1.1: b66b30796877352a5f887f3f4115c4e7265ddc11772af2223da4bd92293716f6 importers: @@ -597,7 +597,7 @@ importers: version: 2.0.3 rrweb-snapshot: specifier: 2.1.1 - version: 2.1.1(patch_hash=0b0dbc551d27a6f24438e9582c41a0ec34fd444760d383a4172013031d73c4e7) + version: 2.1.1(patch_hash=b66b30796877352a5f887f3f4115c4e7265ddc11772af2223da4bd92293716f6) vitest: specifier: workspace:* version: link:../vitest @@ -934,7 +934,7 @@ importers: version: 4.62.4 rrweb-snapshot: specifier: 2.1.1 - version: 2.1.1(patch_hash=0b0dbc551d27a6f24438e9582c41a0ec34fd444760d383a4172013031d73c4e7) + version: 2.1.1(patch_hash=b66b30796877352a5f887f3f4115c4e7265ddc11772af2223da4bd92293716f6) splitpanes: specifier: ^4.1.2 version: 4.1.2(vue@3.5.41(typescript@5.9.3)) @@ -18394,7 +18394,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.62.4 fsevents: 2.3.3 - rrweb-snapshot@2.1.1(patch_hash=0b0dbc551d27a6f24438e9582c41a0ec34fd444760d383a4172013031d73c4e7): + rrweb-snapshot@2.1.1(patch_hash=b66b30796877352a5f887f3f4115c4e7265ddc11772af2223da4bd92293716f6): dependencies: postcss: 8.5.14 -- 2.51.2