From a967d8f39e2085fa1753a562d327eb9611359dab Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sat, 17 Oct 2026 01:17:37 +0900 Subject: [PATCH] perf(browser): map only first frame for trace locations (#11613) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- .../browser-playwright/src/commands/trace.ts | 2 +- packages/browser/src/node/commands/trace.ts | 2 +- packages/utils/src/source-map.ts | 95 +++++++++++-------- packages/vitest/src/node/types/browser.ts | 2 +- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/packages/browser-playwright/src/commands/trace.ts b/packages/browser-playwright/src/commands/trace.ts index 74e864da1..30ea95c83 100644 --- a/packages/browser-playwright/src/commands/trace.ts +++ b/packages/browser-playwright/src/commands/trace.ts @@ -133,7 +133,7 @@ function parseLocation(context: BrowserCommandContext, stack?: string): ParsedSt if (!stack) { return } - const parsedStacks = context.project.browser!.parseStacktrace(stack) + const parsedStacks = context.project.browser!.parseStacktrace(stack, { firstOnly: true }) return parsedStacks[0] } diff --git a/packages/browser/src/node/commands/trace.ts b/packages/browser/src/node/commands/trace.ts index 63589a69b..686e57d4d 100644 --- a/packages/browser/src/node/commands/trace.ts +++ b/packages/browser/src/node/commands/trace.ts @@ -66,7 +66,7 @@ export const _recordBrowserTrace: BrowserCommand< if (!entry.stack || entry.location) { return entry } - const stacks = project.browser!.parseStacktrace(entry.stack) + const stacks = project.browser!.parseStacktrace(entry.stack, { firstOnly: true }) if (stacks[0]) { entry.location = stacks[0] } diff --git a/packages/utils/src/source-map.ts b/packages/utils/src/source-map.ts index c49e8c878..a6892a2d0 100644 --- a/packages/utils/src/source-map.ts +++ b/packages/utils/src/source-map.ts @@ -9,6 +9,7 @@ export interface StackTraceParserOptions { getSourceMap?: (file: string) => unknown getUrlId?: (id: string) => string frameFilter?: (error: TestError, frame: ParsedStack) => boolean | void + firstOnly?: boolean } const CHROME_IE_STACK_REGEXP = /^\s*at .*(?:\S:\d+|\(native\))/m @@ -248,51 +249,67 @@ export function parseStacktrace( stacks = stacks.slice(helperIndex + 1) } - return stacks - .map((stack) => { - if (options.getUrlId) { - stack.file = options.getUrlId(stack.file) + if (options.firstOnly) { + for (const stack of stacks) { + const mapped = mapStackFrame(stack, options, ignoreStackEntries) + if (mapped) { + return [mapped] } + } + return [] + } - const map = options.getSourceMap?.(stack.file) as SourceMapLike | null | undefined - if (!map || typeof map !== 'object' || !map.version) { - return shouldFilter(ignoreStackEntries, stack.file) ? null : stack - } + return stacks + .map((stack) => mapStackFrame(stack, options, ignoreStackEntries)) + .filter((s) => s != null) +} - const traceMap = new DecodedMap(map, stack.file) - if (stack.line <= 0 || stack.column <= 0) { - return stack - } - const position = getOriginalPosition(traceMap, { - line: stack.line, - // stacktrace's column is 1-indexed, but sourcemap's one is 0-indexed - column: stack.column - 1, - }) - if (!position) { - return stack - } +function mapStackFrame( + stack: ParsedStack, + options: StackTraceParserOptions, + ignoreStackEntries: (string | RegExp)[], +): ParsedStack | null { + if (options.getUrlId) { + stack.file = options.getUrlId(stack.file) + } - const { line, column, source, name } = position - let file = source || stack.file - if (/\/\w:\//.test(file)) { - file = file.slice(1) - } + const map = options.getSourceMap?.(stack.file) as SourceMapLike | null | undefined + if (!map || typeof map !== 'object' || !map.version) { + return shouldFilter(ignoreStackEntries, stack.file) ? null : stack + } - if (shouldFilter(ignoreStackEntries, file)) { - return null - } + const traceMap = new DecodedMap(map, stack.file) + if (stack.line <= 0 || stack.column <= 0) { + return stack + } + const position = getOriginalPosition(traceMap, { + line: stack.line, + // stacktrace's column is 1-indexed, but sourcemap's one is 0-indexed + column: stack.column - 1, + }) + if (!position) { + return stack + } - if (line != null && column != null) { - return { - line, - column: column + 1, - file, - method: name || stack.method, - } - } - return stack - }) - .filter((s) => s != null) + const { line, column, source, name } = position + let file = source || stack.file + if (/\/\w:\//.test(file)) { + file = file.slice(1) + } + + if (shouldFilter(ignoreStackEntries, file)) { + return null + } + + if (line != null && column != null) { + return { + line, + column: column + 1, + file, + method: name || stack.method, + } + } + return stack } function shouldFilter(ignoreStackEntries: (string | RegExp)[], file: string): boolean { diff --git a/packages/vitest/src/node/types/browser.ts b/packages/vitest/src/node/types/browser.ts index bcccf03ce..607618381 100644 --- a/packages/vitest/src/node/types/browser.ts +++ b/packages/vitest/src/node/types/browser.ts @@ -474,7 +474,7 @@ export interface ProjectBrowser { close: () => Promise initBrowserProvider: (project: TestProject) => Promise closeBrowserProvider: (options?: BrowserProviderCloseOptions) => Promise - parseStacktrace: (stack: string) => ParsedStack[] + parseStacktrace: (stack: string, options?: StackTraceParserOptions) => ParsedStack[] parseErrorStacktrace: (error: TestError, options?: StackTraceParserOptions) => ParsedStack[] registerCommand: ( name: K, -- 2.54.0