diff --git a/packages/isomorphic/stackTrace.ts b/packages/isomorphic/stackTrace.ts index dca83848e9414..97447e425b4a4 100644 --- a/packages/isomorphic/stackTrace.ts +++ b/packages/isomorphic/stackTrace.ts @@ -30,9 +30,10 @@ export type StackFrame = { export function captureRawStack(): RawStack { const stackTraceLimit = Error.stackTraceLimit; - Error.stackTraceLimit = 50; - const error = new Error(); - const stack = error.stack || ''; + Error.stackTraceLimit = 200; + const obj: { stack?: string } = {}; + Error.captureStackTrace(obj); + const stack = obj.stack || ''; Error.stackTraceLimit = stackTraceLimit; return stack.split('\n'); } @@ -107,11 +108,17 @@ export function parseStackFrame(text: string, pathSeparator: string, showInterna } export function rewriteErrorMessage(e: E, newMessage: string): E { - const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at ')); + const originalStack = e.stack || ''; + e.message = newMessage; + const errorTitle = `${e.name}: ${e.message}`; - if (lines.length) - e.stack = `${errorTitle}\n${lines.join('\n')}`; + + if (originalStack) { + const stackWithoutTitle = originalStack.split('\n').slice(1).join('\n'); + e.stack = `${errorTitle}\n${stackWithoutTitle}`; + } + return e; } @@ -150,10 +157,16 @@ export function parseErrorStack(stack: string, pathSeparator: string, showIntern const frame = parseStackFrame(line, pathSeparator, showInternalStackFrames); if (!frame || !frame.file) continue; - if (belongsToNodeModules(frame.file, pathSeparator)) - continue; - location = { file: frame.file, column: frame.column || 0, line: frame.line || 0 }; - break; + // Prefer non-node_modules frames + if (!belongsToNodeModules(frame.file, pathSeparator)) { + location = { file: frame.file, column: frame.column || 0, line: frame.line || 0 }; + break; + } + + // fallback if only node_modules found + if (!location) { + location = { file: frame.file, column: frame.column || 0, line: frame.line || 0 }; + } } return { message, stackLines, location }; } diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 9d6bb0e087dd5..e52a5fb1db5b2 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -322,8 +322,6 @@ export class TestInfoImpl implements TestInfo { if (typeof result.error === 'object' && !(result.error as any)?.[stepSymbol]) (result.error as any)[stepSymbol] = step; const error = testInfoError(result.error); - if (step.boxedStack) - error.stack = `${error.message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`; step.error = error; } @@ -410,8 +408,13 @@ export class TestInfoImpl implements TestInfo { this.status = error instanceof TimeoutManagerError ? 'timedOut' : 'failed'; const serialized = testInfoError(error); const step: TestStepInternal | undefined = typeof error === 'object' ? (error as any)?.[stepSymbol] : undefined; - if (step && step.boxedStack) - serialized.stack = `${(error as Error).name}: ${(error as Error).message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`; + + if (step && step.boxedStack) { + const originalStack = (error as Error).stack || `${(error as Error).name}: ${(error as Error).message}`; + const stepStack = stringifyStackFrames(step.boxedStack).join('\n'); + + serialized.stack = `${originalStack}\n--- STEP ---\n${stepStack}`; + } this.errors.push(serialized); this._tracing.appendForError(serialized); }