Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions packages/isomorphic/stackTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function captureRawStack(): RawStack {
return stack.split('\n');
}

export function parseStackFrame(text: string, pathSeparator: string, showInternalStackFrames: boolean): StackFrame | null {
export function parseStackFrame(text: string, pathSeparator: string): StackFrame | null {
const match = text && text.match(re);
if (!match)
return null;
Expand All @@ -46,7 +46,7 @@ export function parseStackFrame(text: string, pathSeparator: string, showInterna
let file = match[7];
if (!file)
return null;
if (!showInternalStackFrames && (file.startsWith('internal') || file.startsWith('node:')))
if (!_showInternalStackFrames && (file.startsWith('internal') || file.startsWith('node:')))
return null;

const line = match[8];
Expand Down Expand Up @@ -134,7 +134,7 @@ export function splitErrorMessage(message: string): { name: string, message: str
};
}

export function parseErrorStack(stack: string, pathSeparator: string, showInternalStackFrames: boolean = false): {
export function parseErrorStack(stack: string, pathSeparator: string): {
message: string;
stackLines: string[];
location?: StackFrame;
Expand All @@ -147,7 +147,7 @@ export function parseErrorStack(stack: string, pathSeparator: string, showIntern
const stackLines = lines.slice(firstStackLine);
let location: StackFrame | undefined;
for (const line of stackLines) {
const frame = parseStackFrame(line, pathSeparator, showInternalStackFrames);
const frame = parseStackFrame(line, pathSeparator);
if (!frame || !frame.file)
continue;
if (belongsToNodeModules(frame.file, pathSeparator))
Expand All @@ -162,6 +162,29 @@ function belongsToNodeModules(file: string, pathSeparator: string) {
return file.includes(`${pathSeparator}node_modules${pathSeparator}`);
}

export function filterStackFile(file: string) {
if (_showInternalStackFrames)
return true;
if (!!_coreDir && file.startsWith(_coreDir))
return false;
if (_boxedStackPrefixes.some(prefix => file.startsWith(prefix)))
return false;
return true;
}

export function filteredStackTrace(rawStack: RawStack, pathSeparator: string): StackFrame[] {
const frames: StackFrame[] = [];
for (const line of rawStack) {
const frame = parseStackFrame(line, pathSeparator);
if (!frame || !frame.file)
continue;
if (!filterStackFile(frame.file))
continue;
frames.push(frame);
}
return frames;
}

const re = new RegExp('^' +
// Sometimes we strip out the ' at' because it's noisy
'(?:\\s*at )?' +
Expand Down Expand Up @@ -216,12 +239,6 @@ export function setBoxedStackPrefixes(prefixes: string[]) {
_boxedStackPrefixes = prefixes;
}

export function boxedStackPrefixes(): string[] {
if (_showInternalStackFrames)
return [];
return _coreDir ? [_coreDir, ..._boxedStackPrefixes] : _boxedStackPrefixes.slice();
}

export function setShowInternalStackFrames(value: boolean) {
_showInternalStackFrames = value;
}
Expand Down
11 changes: 3 additions & 8 deletions packages/playwright-core/src/client/clientStackTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import path from 'path';

import { boxedStackPrefixes, captureRawStack, coreDir, parseStackFrame, showInternalStackFrames } from '@isomorphic/stackTrace';
import { captureRawStack, coreDir, filterStackFile, parseStackFrame } from '@isomorphic/stackTrace';

import type { StackFrame } from '@isomorphic/stackTrace';

Expand All @@ -30,7 +30,7 @@ export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: str
isPlaywrightLibrary: boolean;
};
let parsedFrames = stack.map(line => {
const frame = parseStackFrame(line, path.sep, showInternalStackFrames());
const frame = parseStackFrame(line, path.sep);
if (!frame || !frame.file)
return null;
const isPlaywrightLibrary = !!playwrightCoreDir && frame.file.startsWith(playwrightCoreDir);
Expand Down Expand Up @@ -65,12 +65,7 @@ export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: str
}

// This is for the inspector so that it did not include the test runner stack frames.
const filterPrefixes = boxedStackPrefixes();
parsedFrames = parsedFrames.filter(f => {
if (filterPrefixes.some(prefix => f.frame.file.startsWith(prefix)))
return false;
return true;
});
parsedFrames = parsedFrames.filter(f => filterStackFile(f.frame.file));

return {
frames: parsedFrames.map(p => p.frame),
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright/src/common/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
*/

import crypto from 'crypto';
import { filterStackFile } from '@isomorphic/stackTrace';

import { filterStackFile, formatLocation } from '../util';
import { formatLocation } from '../util';

import type { FixturesWithLocation } from './config';
import type { Fixtures } from '../../types/test';
Expand Down
3 changes: 3 additions & 0 deletions packages/playwright/src/common/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import 'playwright-core/lib/bootstrap';
import { ManualPromise } from '@isomorphic/manualPromise';
import { setTimeOrigin } from '@isomorphic/time';
import { startProfiling, stopProfiling } from '@utils/profiler';
import { setBoxedStackPrefixes } from '@isomorphic/stackTrace';

import { packageRoot } from '../package';
import { serializeError } from '../util';

import type { EnvProducedPayload, ProcessInitParams, TestInfoErrorPayload } from './ipc';
Expand Down Expand Up @@ -61,6 +63,7 @@ let forceExitInitiated = false;
let processRunner: ProcessRunner | undefined;
let processName: string | undefined;
const startingEnv = { ...process.env };
setBoxedStackPrefixes([packageRoot]);

export function startProcessRunner(create: (params: any) => ProcessRunner) {
sendMessageToParent({ method: 'ready' });
Expand Down
4 changes: 0 additions & 4 deletions packages/playwright/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import path from 'path';
import * as playwrightLibrary from 'playwright-core';
import { asLocatorDescription } from '@isomorphic/locatorGenerators';
import { getActionGroup, renderTitleForCall } from '@isomorphic/protocolFormatter';
import { setBoxedStackPrefixes } from '@isomorphic/stackTrace';
import { escapeHTML } from '@isomorphic/stringUtils';
import { jsonStringifyForceASCII } from '@utils/ascii';
import { createGuid } from '@utils/crypto';
Expand All @@ -29,7 +28,6 @@ import { currentZone } from '@utils/zones';
import { buildErrorContext } from './errorContext';
import { config, testType } from './common';
import * as globals from './globals';
import { packageRoot } from './package';
import { createCustomMessageHandler, runDaemonForContext } from './mcp/test/browserBackend';

import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
Expand All @@ -46,8 +44,6 @@ import type { BrowserContext, BrowserContextOptions, LaunchOptions, Page, Tracin
export { expect } from './matchers/expect';
export const _baseTest: TestType<{}, {}> = testType.rootTestType.test;

setBoxedStackPrefixes([packageRoot]);

if ((process as any)['__pw_initiator__']) {
const originalStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 200;
Expand Down
8 changes: 4 additions & 4 deletions packages/playwright/src/matchers/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export interface ExpectTestInfo {

export type ExpectConfig = {
testInfo: ExpectTestInfo | null;
filteredStackTrace: (rawStack: string[]) => StackFrame[];
filteredStackTrace: (rawStack: string[], pathSeparator: string) => StackFrame[];
ignoreSnapshots: boolean;
updateSnapshots: 'all' | 'changed' | 'missing' | 'none';
timeout?: number;
Expand Down Expand Up @@ -143,8 +143,8 @@ export type ExpectConfig = {
toPass?: { timeout?: number; intervals?: number[] };
};

function unfilteredStackTrace(rawStack: string[]): StackFrame[] {
return rawStack.map(frame => parseStackFrame(frame, path.sep, !!process.env.PWDEBUGIMPL)).filter(f => !!f);
function unfilteredStackTrace(rawStack: string[], pathSeparator: string): StackFrame[] {
return rawStack.map(frame => parseStackFrame(frame, pathSeparator)).filter(f => !!f);
}

let _expectConfig: ExpectConfig = { testInfo: null, filteredStackTrace: unfilteredStackTrace, ignoreSnapshots: false, updateSnapshots: 'missing' };
Expand Down Expand Up @@ -339,7 +339,7 @@ function callMatcherAsStep(matcherName: string, info: ExpectMetaInfo, actual: un

// This looks like it is unnecessary, but it isn't - we need to filter
// out all the frames that belong to the test runner from caught runtime errors.
const stackFrames = expectConfig().filteredStackTrace(captureRawStack());
const stackFrames = expectConfig().filteredStackTrace(captureRawStack(), path.sep);
const stepData = {
category: 'expect' as const,
apiName,
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright/src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ import 'playwright-core/lib/bootstrap';

import { libCli, tools } from 'playwright-core/lib/coreBundle';
import { program } from 'commander';
import { setBoxedStackPrefixes } from '@isomorphic/stackTrace';
import { gracefullyProcessExitDoNotHang } from '@utils/processLauncher';
import { builtInReporters, config, configLoader } from './common';
import { runTests, clearCache, runTestServerAction } from './cli/testActions';
import { showReport, mergeReports } from './cli/reportActions';
import { TestServerBackend, testServerBackendTools } from './mcp/test/testBackend';
import { ClaudeGenerator, CodexGenerator, OpencodeGenerator, VSCodeGenerator, CopilotGenerator } from './agents/generateAgents';
import { packageJSON } from './package';
import { packageRoot, packageJSON } from './package';

export { program };

import type { TraceMode } from '../types/test';
import type { Command } from 'commander';

setBoxedStackPrefixes([packageRoot]);
libCli.decorateProgram(program);

function addTestCommand(program: Command) {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ export function prepareErrorStack(stack: string): {
stackLines: string[];
location?: Location;
} {
return parseErrorStack(stack, path.sep, !!process.env.PWDEBUGIMPL);
return parseErrorStack(stack, path.sep);
}

function resolveFromEnv(name: string): string | undefined {
Expand Down
33 changes: 3 additions & 30 deletions packages/playwright/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,54 +24,27 @@ import minimatch from 'minimatch';
import { calculateSha1 } from '@utils/crypto';
import { sanitizeForFilePath } from '@utils/fileUtils';
import { isRegExp } from '@isomorphic/rtti';
import { parseStackFrame, stringifyStackFrames } from '@isomorphic/stackTrace';
import { stringifyStackFrames, filteredStackTrace } from '@isomorphic/stackTrace';
import { ansiRegex, isString, stripAnsiEscapes } from '@isomorphic/stringUtils';

import type { RawStack, StackFrame } from '@isomorphic/stackTrace';
import type { Location } from './../types/testReporter';
import type { TestInfoError } from './../types/test';
import type { TestCase } from './common/test';

const PLAYWRIGHT_TEST_PATH = path.join(__dirname, '..');
const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core/package.json'));

export function filterStackTrace(e: Error): { message: string, stack: string, cause?: ReturnType<typeof filterStackTrace> } {
export function filterStackTrace(e: Error): TestInfoError {
const name = e.name ? e.name + ': ' : '';
const cause = e.cause instanceof Error ? filterStackTrace(e.cause) : undefined;
if (process.env.PWDEBUGIMPL)
return { message: name + e.message, stack: e.stack || '', cause };

const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || []));
const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || [], path.sep));
return {
message: name + e.message,
stack: `${name}${e.message}${stackLines.map(line => '\n' + line).join('')}`,
cause,
};
}

export function filterStackFile(file: string) {
if (process.env.PWDEBUGIMPL)
return true;
if (file.startsWith(PLAYWRIGHT_TEST_PATH))
return false;
if (file.startsWith(PLAYWRIGHT_CORE_PATH))
return false;
return true;
}

export function filteredStackTrace(rawStack: RawStack): StackFrame[] {
const frames: StackFrame[] = [];
for (const line of rawStack) {
const frame = parseStackFrame(line, path.sep, !!process.env.PWDEBUGIMPL);
if (!frame || !frame.file)
continue;
if (!filterStackFile(frame.file))
continue;
frames.push(frame);
}
return frames;
}

export function serializeError(error: Error | any): TestInfoError {
if (error instanceof Error)
return filterStackTrace(error);
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright/src/worker/fixtureRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

import { ManualPromise } from '@isomorphic/manualPromise';
import { escapeWithQuotes } from '@isomorphic/stringUtils';
import { filterStackFile } from '@isomorphic/stackTrace';

import { fixtures } from '../common';
import { filterStackFile, formatLocation } from '../util';
import { formatLocation } from '../util';

import type { TestInfoImpl } from './testInfo';
import type { FixtureDescription, RunnableDescription } from './timeoutManager';
Expand Down
6 changes: 3 additions & 3 deletions packages/playwright/src/worker/testInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import fs from 'fs';
import path from 'path';

import { ManualPromise } from '@isomorphic/manualPromise';
import { captureRawStack, stringifyStackFrames } from '@isomorphic/stackTrace';
import { captureRawStack, stringifyStackFrames, filteredStackTrace } from '@isomorphic/stackTrace';
import { escapeWithQuotes } from '@isomorphic/stringUtils';
import { monotonicTime } from '@isomorphic/time';
import { createGuid } from '@utils/crypto';
import { sanitizeForFilePath, trimLongString } from '@utils/fileUtils';
import { currentZone } from '@utils/zones';

import { TimeoutManager, TimeoutManagerError } from './timeoutManager';
import { addSuffixToFilePath, filteredStackTrace, getContainedPath, normalizeAndSaveAttachment, sanitizeFilePathBeforeExtension, windowsFilesystemFriendlyLength } from '../util';
import { addSuffixToFilePath, getContainedPath, normalizeAndSaveAttachment, sanitizeFilePathBeforeExtension, windowsFilesystemFriendlyLength } from '../util';
import { TestTracing } from './testTracing';
import { testInfoError } from './util';
import { ipc, transform } from '../common';
Expand Down Expand Up @@ -295,7 +295,7 @@ export class TestInfoImpl implements TestInfo {
parentStep = this._parentStep();
}

const filteredStack = filteredStackTrace(captureRawStack());
const filteredStack = filteredStackTrace(captureRawStack(), path.sep);
let boxedStack = parentStep?.boxedStack;
let location = data.location;
if (!boxedStack && data.box) {
Expand Down
5 changes: 2 additions & 3 deletions packages/playwright/src/worker/testTracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import { monotonicTime } from '@isomorphic/time';
import { calculateSha1, createGuid } from '@utils/crypto';
import { SerializedFS } from '@utils/serializedFS';
import { getPlaywrightVersion } from 'playwright-core/lib/coreBundle';

import { filteredStackTrace } from '../util';
import { filteredStackTrace } from '@isomorphic/stackTrace';

import type { TestStepCategory, TestInfoImpl } from './testInfo';
import type { PlaywrightWorkerOptions, TestInfo, TestInfoError, TraceMode } from '../../types/test';
Expand Down Expand Up @@ -251,7 +250,7 @@ export class TestTracing {

appendForError(error: TestInfoError) {
const rawStack = error.stack?.split('\n') || [];
const stack = rawStack ? filteredStackTrace(rawStack) : [];
const stack = rawStack ? filteredStackTrace(rawStack, path.sep) : [];
this._appendTraceEvent({
type: 'error',
message: this._formatError(error),
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright/src/worker/workerMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import colors from 'colors/safe';
import { ManualPromise } from '@isomorphic/manualPromise';
import { removeFolders } from '@utils/fileUtils';
import { gracefullyCloseAll } from '@utils/processLauncher';
import { filteredStackTrace } from '@isomorphic/stackTrace';

import { configLoader, fixtures, ipc, poolBuilder, ProcessRunner, suiteUtils, testLoader } from '../common';
import * as globals from '../globals';
import { setExpectConfig } from '../matchers/expect';
import { debugTest, filteredStackTrace, relativeFilePath } from '../util';
import { debugTest, relativeFilePath } from '../util';
import { FixtureRunner } from './fixtureRunner';
import { TestSkipError, TestInfoImpl, emtpyTestInfoCallbacks } from './testInfo';
import { testInfoError } from './util';
Expand Down
Loading