Skip to content

Commit 18a0a4a

Browse files
committed
cherry-pick(9735): fix(stack): hide test runner stack frames
1 parent c75bdde commit 18a0a4a

File tree

4 files changed

+62
-32
lines changed

4 files changed

+62
-32
lines changed

packages/playwright-core/src/utils/stackTrace.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string):
3333
const CORE_DIR = path.resolve(__dirname, '..', '..');
3434
const CLIENT_LIB = path.join(CORE_DIR, 'lib', 'client');
3535
const CLIENT_SRC = path.join(CORE_DIR, 'src', 'client');
36+
const TEST_DIR_SRC = path.resolve(CORE_DIR, '..', 'playwright-test');
37+
const TEST_DIR_LIB = path.resolve(CORE_DIR, '..', '@playwright', 'test');
3638

3739
export type ParsedStackTrace = {
3840
allFrames: StackFrame[];
@@ -58,7 +60,11 @@ export function captureStackTrace(): ParsedStackTrace {
5860
const frame = stackUtils.parseLine(line);
5961
if (!frame || !frame.file)
6062
return null;
61-
if (frame.file.startsWith('internal'))
63+
// Node 16+ has node:internal.
64+
if (frame.file.startsWith('internal') || frame.file.startsWith('node:'))
65+
return null;
66+
// EventEmitter.emit has 'events.js' file.
67+
if (frame.file === 'events.js' && frame.function?.endsWith('.emit'))
6268
return null;
6369
const fileName = path.resolve(process.cwd(), frame.file);
6470
if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js')))
@@ -104,6 +110,15 @@ export function captureStackTrace(): ParsedStackTrace {
104110
}
105111
}
106112

113+
// Hide all test runner and library frames in the user stack (event handlers produce them).
114+
parsedFrames = parsedFrames.filter((f, i) => {
115+
if (f.frame.file.startsWith(TEST_DIR_SRC) || f.frame.file.startsWith(TEST_DIR_LIB))
116+
return false;
117+
if (i && f.frame.file.startsWith(CORE_DIR))
118+
return false;
119+
return true;
120+
});
121+
107122
return {
108123
allFrames: allFrames.map(p => p.frame),
109124
frames: parsedFrames.map(p => p.frame),

tests/playwright-test/reporter-html.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,6 @@ test('should show trace source', async ({ runInlineTest, page, showReport }) =>
287287

288288
await expect(page.locator('.stack-trace-frame')).toContainText([
289289
/a.test.js:[\d]+/,
290-
/fixtures.[tj]s:[\d]+/,
291290
]);
292291
await expect(page.locator('.stack-trace-frame.selected')).toContainText('a.test.js');
293292
});

tests/playwright-test/test-step.spec.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,6 @@ test('should report api step hierarchy', async ({ runInlineTest }) => {
8888
steps: [
8989
{
9090
category: 'pw:api',
91-
location: {
92-
column: 'number',
93-
file: 'index.ts',
94-
line: 'number',
95-
},
9691
title: 'browserContext.newPage',
9792
},
9893
],
@@ -161,11 +156,6 @@ test('should report api step hierarchy', async ({ runInlineTest }) => {
161156
steps: [
162157
{
163158
category: 'pw:api',
164-
location: {
165-
column: 'number',
166-
file: 'index.ts',
167-
line: 'number',
168-
},
169159
title: 'browserContext.close',
170160
},
171161
],
@@ -201,11 +191,6 @@ test('should not report nested after hooks', async ({ runInlineTest }) => {
201191
{
202192
category: 'pw:api',
203193
title: 'browserContext.newPage',
204-
location: {
205-
column: 'number',
206-
file: 'index.ts',
207-
line: 'number',
208-
},
209194
},
210195
],
211196
},
@@ -225,11 +210,6 @@ test('should not report nested after hooks', async ({ runInlineTest }) => {
225210
{
226211
category: 'pw:api',
227212
title: 'browserContext.close',
228-
location: {
229-
column: 'number',
230-
file: 'index.ts',
231-
line: 'number',
232-
},
233213
},
234214
],
235215
},
@@ -316,11 +296,6 @@ test('should report expect step locations', async ({ runInlineTest }) => {
316296
steps: [
317297
{
318298
category: 'pw:api',
319-
location: {
320-
column: 'number',
321-
file: 'index.ts',
322-
line: 'number',
323-
},
324299
title: 'browserContext.newPage',
325300
},
326301
],
@@ -340,11 +315,6 @@ test('should report expect step locations', async ({ runInlineTest }) => {
340315
steps: [
341316
{
342317
category: 'pw:api',
343-
location: {
344-
column: 'number',
345-
file: 'index.ts',
346-
line: 'number',
347-
},
348318
title: 'browserContext.close',
349319
},
350320
],

tests/tracing.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { expect, contextTest as test, browserTest } from './config/browserTest';
1818
import { ZipFileSystem } from '../packages/playwright-core/lib/utils/vfs';
1919
import jpeg from 'jpeg-js';
20+
import path from 'path';
2021

2122
test.skip(({ trace }) => !!trace);
2223

@@ -287,6 +288,47 @@ test('should not hang for clicks that open dialogs', async ({ context, page }) =
287288
await context.tracing.stop();
288289
});
289290

291+
test('should hide internal stack frames', async ({ context, page }, testInfo) => {
292+
await context.tracing.start({ screenshots: true, snapshots: true });
293+
let evalPromise;
294+
page.on('dialog', dialog => {
295+
evalPromise = page.evaluate('2+2');
296+
dialog.dismiss();
297+
});
298+
await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`);
299+
await page.click('div');
300+
await evalPromise;
301+
const tracePath = testInfo.outputPath('trace.zip');
302+
await context.tracing.stop({ path: tracePath });
303+
304+
const trace = await parseTrace(tracePath);
305+
const actions = trace.events.filter(e => e.type === 'action' && !e.metadata.apiName.startsWith('tracing.'));
306+
expect(actions).toHaveLength(4);
307+
for (const action of actions)
308+
expect(relativeStack(action)).toEqual(['tracing.spec.ts']);
309+
});
310+
311+
test('should hide internal stack frames in expect', async ({ context, page }, testInfo) => {
312+
await context.tracing.start({ screenshots: true, snapshots: true });
313+
let expectPromise;
314+
page.on('dialog', dialog => {
315+
expectPromise = expect(page).toHaveTitle('Hello');
316+
dialog.dismiss();
317+
});
318+
await page.setContent(`<title>Hello</title><div onclick='window.alert(123)'>Click me</div>`);
319+
await page.click('div');
320+
await expect(page.locator('div')).toBeVisible();
321+
await expectPromise;
322+
const tracePath = testInfo.outputPath('trace.zip');
323+
await context.tracing.stop({ path: tracePath });
324+
325+
const trace = await parseTrace(tracePath);
326+
const actions = trace.events.filter(e => e.type === 'action' && !e.metadata.apiName.startsWith('tracing.'));
327+
expect(actions).toHaveLength(5);
328+
for (const action of actions)
329+
expect(relativeStack(action)).toEqual(['tracing.spec.ts']);
330+
});
331+
290332
async function parseTrace(file: string): Promise<{ events: any[], resources: Map<string, Buffer> }> {
291333
const zipFS = new ZipFileSystem(file);
292334
const resources = new Map<string, Buffer>();
@@ -330,3 +372,7 @@ function expectBlue(pixels: Buffer, offset: number) {
330372
expect(b).toBeGreaterThan(200);
331373
expect(a).toBe(255);
332374
}
375+
376+
function relativeStack(action: any): string[] {
377+
return action.metadata.stack.map(f => f.file.replace(__dirname + path.sep, ''));
378+
}

0 commit comments

Comments
 (0)