feat(trace-viewer): show test name (#9957)

This commit is contained in:
Pavel Feldman 2021-11-01 20:23:35 -08:00 committed by GitHub
parent 3673776330
commit 56ca3a18f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 91 additions and 37 deletions

View file

@ -133,11 +133,15 @@ Whether to capture DOM snapshot on every action.
Whether to include source files for trace actions. Whether to include source files for trace actions.
### option: Tracing.start.title
- `title` <[string]>
Trace name to be shown in the Trace Viewer.
## async method: Tracing.startChunk ## async method: Tracing.startChunk
Start a new trace chunk. If you'd like to record multiple traces on the same [BrowserContext], use [`method: Tracing.start`] once, and then create multiple trace chunks with [`method: Tracing.startChunk`] and [`method: Tracing.stopChunk`]. Start a new trace chunk. If you'd like to record multiple traces on the same [BrowserContext], use [`method: Tracing.start`] once, and then create multiple trace chunks with [`method: Tracing.startChunk`] and [`method: Tracing.stopChunk`].
```js ```js
await context.tracing.start({ screenshots: true, snapshots: true }); await context.tracing.start({ screenshots: true, snapshots: true });
const page = await context.newPage(); const page = await context.newPage();
@ -234,6 +238,11 @@ await context.Tracing.StopChunkAsync(new TracingStopChunkOptions
}); });
``` ```
### option: Tracing.startChunk.title
- `title` <[string]>
Trace name to be shown in the Trace Viewer.
## async method: Tracing.stop ## async method: Tracing.stop

View file

@ -362,6 +362,11 @@ test.beforeEach(async ({ page }, testInfo) => {
The title of the currently running test as passed to `test(title, testFunction)`. The title of the currently running test as passed to `test(title, testFunction)`.
## property: TestInfo.titlePath
- type: <[Array]<[string]>>
The full title path starting with the project.
## property: TestInfo.workerIndex ## property: TestInfo.workerIndex
- type: <[int]> - type: <[int]>

View file

@ -43,19 +43,19 @@ export class Tracing implements api.Tracing {
}; };
} }
async start(options: { name?: string, snapshots?: boolean, screenshots?: boolean, sources?: boolean } = {}) { async start(options: { name?: string, title?: string, snapshots?: boolean, screenshots?: boolean, sources?: boolean } = {}) {
if (options.sources) if (options.sources)
this._context._instrumentation!.addListener(this._instrumentationListener); this._context._instrumentation!.addListener(this._instrumentationListener);
await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => { await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.tracingStart(options); await channel.tracingStart(options);
await channel.tracingStartChunk(); await channel.tracingStartChunk({ title: options.title });
}); });
} }
async startChunk() { async startChunk(options: { title?: string } = {}) {
this._sources = new Set(); this._sources = new Set();
await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => { await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.tracingStartChunk(); await channel.tracingStartChunk(options);
}); });
} }

View file

@ -192,7 +192,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
} }
async tracingStartChunk(params: channels.BrowserContextTracingStartChunkParams): Promise<channels.BrowserContextTracingStartChunkResult> { async tracingStartChunk(params: channels.BrowserContextTracingStartChunkParams): Promise<channels.BrowserContextTracingStartChunkResult> {
await this._context.tracing.startChunk(); await this._context.tracing.startChunk(params);
} }
async tracingStopChunk(params: channels.BrowserContextTracingStopChunkParams): Promise<channels.BrowserContextTracingStopChunkResult> { async tracingStopChunk(params: channels.BrowserContextTracingStopChunkParams): Promise<channels.BrowserContextTracingStopChunkResult> {

View file

@ -558,7 +558,6 @@ export type BrowserTypeLaunchPersistentContextParams = {
forcedColors?: 'active' | 'none', forcedColors?: 'active' | 'none',
acceptDownloads?: boolean, acceptDownloads?: boolean,
baseURL?: string, baseURL?: string,
_debugName?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
size?: { size?: {
@ -631,7 +630,6 @@ export type BrowserTypeLaunchPersistentContextOptions = {
forcedColors?: 'active' | 'none', forcedColors?: 'active' | 'none',
acceptDownloads?: boolean, acceptDownloads?: boolean,
baseURL?: string, baseURL?: string,
_debugName?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
size?: { size?: {
@ -725,7 +723,6 @@ export type BrowserNewContextParams = {
forcedColors?: 'active' | 'none', forcedColors?: 'active' | 'none',
acceptDownloads?: boolean, acceptDownloads?: boolean,
baseURL?: string, baseURL?: string,
_debugName?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
size?: { size?: {
@ -785,7 +782,6 @@ export type BrowserNewContextOptions = {
forcedColors?: 'active' | 'none', forcedColors?: 'active' | 'none',
acceptDownloads?: boolean, acceptDownloads?: boolean,
baseURL?: string, baseURL?: string,
_debugName?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
size?: { size?: {
@ -900,7 +896,7 @@ export interface BrowserContextChannel extends EventTargetChannel {
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>; recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>; newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>;
tracingStart(params: BrowserContextTracingStartParams, metadata?: Metadata): Promise<BrowserContextTracingStartResult>; tracingStart(params: BrowserContextTracingStartParams, metadata?: Metadata): Promise<BrowserContextTracingStartResult>;
tracingStartChunk(params?: BrowserContextTracingStartChunkParams, metadata?: Metadata): Promise<BrowserContextTracingStartChunkResult>; tracingStartChunk(params: BrowserContextTracingStartChunkParams, metadata?: Metadata): Promise<BrowserContextTracingStartChunkResult>;
tracingStopChunk(params: BrowserContextTracingStopChunkParams, metadata?: Metadata): Promise<BrowserContextTracingStopChunkResult>; tracingStopChunk(params: BrowserContextTracingStopChunkParams, metadata?: Metadata): Promise<BrowserContextTracingStopChunkResult>;
tracingStop(params?: BrowserContextTracingStopParams, metadata?: Metadata): Promise<BrowserContextTracingStopResult>; tracingStop(params?: BrowserContextTracingStopParams, metadata?: Metadata): Promise<BrowserContextTracingStopResult>;
harExport(params?: BrowserContextHarExportParams, metadata?: Metadata): Promise<BrowserContextHarExportResult>; harExport(params?: BrowserContextHarExportParams, metadata?: Metadata): Promise<BrowserContextHarExportResult>;
@ -1113,8 +1109,12 @@ export type BrowserContextTracingStartOptions = {
screenshots?: boolean, screenshots?: boolean,
}; };
export type BrowserContextTracingStartResult = void; export type BrowserContextTracingStartResult = void;
export type BrowserContextTracingStartChunkParams = {}; export type BrowserContextTracingStartChunkParams = {
export type BrowserContextTracingStartChunkOptions = {}; title?: string,
};
export type BrowserContextTracingStartChunkOptions = {
title?: string,
};
export type BrowserContextTracingStartChunkResult = void; export type BrowserContextTracingStartChunkResult = void;
export type BrowserContextTracingStopChunkParams = { export type BrowserContextTracingStopChunkParams = {
save: boolean, save: boolean,
@ -3538,7 +3538,6 @@ export type AndroidDeviceLaunchBrowserParams = {
reducedMotion?: 'reduce' | 'no-preference', reducedMotion?: 'reduce' | 'no-preference',
forcedColors?: 'active' | 'none', forcedColors?: 'active' | 'none',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
size?: { size?: {
@ -3585,7 +3584,6 @@ export type AndroidDeviceLaunchBrowserOptions = {
reducedMotion?: 'reduce' | 'no-preference', reducedMotion?: 'reduce' | 'no-preference',
forcedColors?: 'active' | 'none', forcedColors?: 'active' | 'none',
acceptDownloads?: boolean, acceptDownloads?: boolean,
_debugName?: string,
recordVideo?: { recordVideo?: {
dir: string, dir: string,
size?: { size?: {

View file

@ -403,7 +403,6 @@ ContextOptions:
- none - none
acceptDownloads: boolean? acceptDownloads: boolean?
baseURL: string? baseURL: string?
_debugName: string?
recordVideo: recordVideo:
type: object? type: object?
properties: properties:
@ -819,6 +818,8 @@ BrowserContext:
screenshots: boolean? screenshots: boolean?
tracingStartChunk: tracingStartChunk:
parameters:
title: string?
tracingStopChunk: tracingStopChunk:
parameters: parameters:
@ -2892,7 +2893,6 @@ AndroidDevice:
- active - active
- none - none
acceptDownloads: boolean? acceptDownloads: boolean?
_debugName: string?
recordVideo: recordVideo:
type: object? type: object?
properties: properties:

View file

@ -329,7 +329,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
forcedColors: tOptional(tEnum(['active', 'none'])), forcedColors: tOptional(tEnum(['active', 'none'])),
acceptDownloads: tOptional(tBoolean), acceptDownloads: tOptional(tBoolean),
baseURL: tOptional(tString), baseURL: tOptional(tString),
_debugName: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
dir: tString, dir: tString,
size: tOptional(tObject({ size: tOptional(tObject({
@ -389,7 +388,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
forcedColors: tOptional(tEnum(['active', 'none'])), forcedColors: tOptional(tEnum(['active', 'none'])),
acceptDownloads: tOptional(tBoolean), acceptDownloads: tOptional(tBoolean),
baseURL: tOptional(tString), baseURL: tOptional(tString),
_debugName: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
dir: tString, dir: tString,
size: tOptional(tObject({ size: tOptional(tObject({
@ -505,7 +503,9 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
snapshots: tOptional(tBoolean), snapshots: tOptional(tBoolean),
screenshots: tOptional(tBoolean), screenshots: tOptional(tBoolean),
}); });
scheme.BrowserContextTracingStartChunkParams = tOptional(tObject({})); scheme.BrowserContextTracingStartChunkParams = tObject({
title: tOptional(tString),
});
scheme.BrowserContextTracingStopChunkParams = tObject({ scheme.BrowserContextTracingStopChunkParams = tObject({
save: tBoolean, save: tBoolean,
skipCompress: tBoolean, skipCompress: tBoolean,
@ -1343,7 +1343,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])), reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
forcedColors: tOptional(tEnum(['active', 'none'])), forcedColors: tOptional(tEnum(['active', 'none'])),
acceptDownloads: tOptional(tBoolean), acceptDownloads: tOptional(tBoolean),
_debugName: tOptional(tString),
recordVideo: tOptional(tObject({ recordVideo: tOptional(tObject({
dir: tString, dir: tString,
size: tOptional(tObject({ size: tOptional(tObject({

View file

@ -428,8 +428,6 @@ export function validateBrowserContextOptions(options: types.BrowserContextOptio
if (debugMode() === 'inspector') if (debugMode() === 'inspector')
options.bypassCSP = true; options.bypassCSP = true;
verifyGeolocation(options.geolocation); verifyGeolocation(options.geolocation);
if (!options._debugName)
options._debugName = createGuid();
} }
export function verifyGeolocation(geolocation?: types.Geolocation) { export function verifyGeolocation(geolocation?: types.Geolocation) {

View file

@ -24,13 +24,13 @@ export type BrowserContextEventOptions = {
viewport?: Size, viewport?: Size,
deviceScaleFactor?: number, deviceScaleFactor?: number,
isMobile?: boolean, isMobile?: boolean,
_debugName?: string,
}; };
export type ContextCreatedTraceEvent = { export type ContextCreatedTraceEvent = {
version: number, version: number,
type: 'context-options', type: 'context-options',
browserName: string, browserName: string,
title?: string,
options: BrowserContextEventOptions options: BrowserContextEventOptions
}; };

View file

@ -107,7 +107,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
this._harTracer.start(); this._harTracer.start();
} }
async startChunk() { async startChunk(options: { title?: string } = {}) {
if (this._state && this._state.recording) if (this._state && this._state.recording)
await this.stopChunk(false, false); await this.stopChunk(false, false);
@ -124,7 +124,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
this._appendTraceOperation(async () => { this._appendTraceOperation(async () => {
await mkdirIfNeeded(state.traceFile); await mkdirIfNeeded(state.traceFile);
await fs.promises.appendFile(state.traceFile, JSON.stringify(this._contextCreatedEvent) + '\n'); await fs.promises.appendFile(state.traceFile, JSON.stringify({ ...this._contextCreatedEvent, title: options.title }) + '\n');
}); });
this._context.instrumentation.addListener(this); this._context.instrumentation.addListener(this);

View file

@ -272,7 +272,6 @@ export type BrowserContextOptions = {
strictSelectors?: boolean, strictSelectors?: boolean,
proxy?: ProxySettings, proxy?: ProxySettings,
baseURL?: string, baseURL?: string,
_debugName?: string,
}; };
export type EnvArray = { name: string, value: string }[]; export type EnvArray = { name: string, value: string }[];

View file

@ -21,6 +21,7 @@ export type ContextEntry = {
startTime: number; startTime: number;
endTime: number; endTime: number;
browserName: string; browserName: string;
title?: string;
options: trace.BrowserContextEventOptions; options: trace.BrowserContextEventOptions;
pages: PageEntry[]; pages: PageEntry[];
resources: ResourceSnapshot[]; resources: ResourceSnapshot[];
@ -46,7 +47,6 @@ export function createEmptyContext(): ContextEntry {
deviceScaleFactor: 1, deviceScaleFactor: 1,
isMobile: false, isMobile: false,
viewport: { width: 1280, height: 800 }, viewport: { width: 1280, height: 800 },
_debugName: '<empty>',
}, },
pages: [], pages: [],
resources: [], resources: [],

View file

@ -103,6 +103,7 @@ export class TraceModel {
switch (event.type) { switch (event.type) {
case 'context-options': { case 'context-options': {
this.contextEntry.browserName = event.browserName; this.contextEntry.browserName = event.browserName;
this.contextEntry.title = event.title;
this.contextEntry.options = event.options; this.contextEntry.options = event.options;
break; break;
} }

View file

@ -22,6 +22,7 @@
flex-direction: column; flex-direction: column;
padding: 20px 0 5px; padding: 20px 0 5px;
cursor: text; cursor: text;
user-select: none;
} }
.timeline-divider { .timeline-divider {

View file

@ -52,7 +52,6 @@
.workbench { .workbench {
contain: size; contain: size;
user-select: none;
} }
.workbench .header { .workbench .header {
@ -86,6 +85,10 @@
margin-left: 16px; margin-left: 16px;
} }
.workbench .title {
margin-left: 16px;
}
.workbench .spacer { .workbench .spacer {
flex: auto; flex: auto;
} }

View file

@ -106,6 +106,7 @@ export const Workbench: React.FunctionComponent<{
<div className='hbox header'> <div className='hbox header'>
<div className='logo'>🎭</div> <div className='logo'>🎭</div>
<div className='product'>Playwright</div> <div className='product'>Playwright</div>
{contextEntry.title && <div className='title'>{contextEntry.title}</div>}
<div className='spacer'></div> <div className='spacer'></div>
</div> </div>
<div style={{ background: 'white', paddingLeft: '20px', flex: 'none', borderBottom: '1px solid #ddd' }}> <div style={{ background: 'white', paddingLeft: '20px', flex: 'none', borderBottom: '1px solid #ddd' }}>

View file

@ -14399,12 +14399,17 @@ export interface Tracing {
* Whether to include source files for trace actions. * Whether to include source files for trace actions.
*/ */
sources?: boolean; sources?: boolean;
/**
* Trace name to be shown in the Trace Viewer.
*/
title?: string;
}): Promise<void>; }): Promise<void>;
/** /**
* Start a new trace chunk. If you'd like to record multiple traces on the same [BrowserContext], use * Start a new trace chunk. If you'd like to record multiple traces on the same [BrowserContext], use
* [tracing.start([options])](https://playwright.dev/docs/api/class-tracing#tracing-start) once, and then create multiple * [tracing.start([options])](https://playwright.dev/docs/api/class-tracing#tracing-start) once, and then create multiple
* trace chunks with [tracing.startChunk()](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) and * trace chunks with [tracing.startChunk([options])](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) and
* [tracing.stopChunk([options])](https://playwright.dev/docs/api/class-tracing#tracing-stop-chunk). * [tracing.stopChunk([options])](https://playwright.dev/docs/api/class-tracing#tracing-stop-chunk).
* *
* ```js * ```js
@ -14423,8 +14428,14 @@ export interface Tracing {
* await context.tracing.stopChunk({ path: 'trace2.zip' }); * await context.tracing.stopChunk({ path: 'trace2.zip' });
* ``` * ```
* *
* @param options
*/ */
startChunk(): Promise<void>; startChunk(options?: {
/**
* Trace name to be shown in the Trace Viewer.
*/
title?: string;
}): Promise<void>;
/** /**
* Stop tracing. * Stop tracing.
@ -14438,15 +14449,16 @@ export interface Tracing {
}): Promise<void>; }): Promise<void>;
/** /**
* Stop the trace chunk. See [tracing.startChunk()](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) for * Stop the trace chunk. See
* more details about multiple trace chunks. * [tracing.startChunk([options])](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) for more details
* about multiple trace chunks.
* @param options * @param options
*/ */
stopChunk(options?: { stopChunk(options?: {
/** /**
* Export trace collected since the last * Export trace collected since the last
* [tracing.startChunk()](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) call into the file with the * [tracing.startChunk([options])](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) call into the file
* given path. * with the given path.
*/ */
path?: string; path?: string;
}): Promise<void>; }): Promise<void>;

View file

@ -186,11 +186,12 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
context.setDefaultTimeout(actionTimeout || 0); context.setDefaultTimeout(actionTimeout || 0);
context.setDefaultNavigationTimeout(navigationTimeout || actionTimeout || 0); context.setDefaultNavigationTimeout(navigationTimeout || actionTimeout || 0);
if (captureTrace) { if (captureTrace) {
const title = [path.relative(testInfo.project.testDir, testInfo.file) + ':' + testInfo.line, ...testInfo.titlePath.slice(1)].join(' ');
if (!(context.tracing as any)[kTracingStarted]) { if (!(context.tracing as any)[kTracingStarted]) {
await context.tracing.start({ screenshots: true, snapshots: true, sources: true }); await context.tracing.start({ screenshots: true, snapshots: true, sources: true, title });
(context.tracing as any)[kTracingStarted] = true; (context.tracing as any)[kTracingStarted] = true;
} else { } else {
await context.tracing.startChunk(); await context.tracing.startChunk({ title });
} }
} else { } else {
(context.tracing as any)[kTracingStarted] = false; (context.tracing as any)[kTracingStarted] = false;

View file

@ -249,6 +249,7 @@ export class WorkerRunner extends EventEmitter {
project: this._project.config, project: this._project.config,
config: this._loader.fullConfig(), config: this._loader.fullConfig(),
title: test.title, title: test.title,
titlePath: test.titlePath(),
file: test.location.file, file: test.location.file,
line: test.location.line, line: test.location.line,
column: test.location.column, column: test.location.column,

View file

@ -1101,6 +1101,10 @@ export interface TestInfo {
* The title of the currently running test as passed to `test(title, testFunction)`. * The title of the currently running test as passed to `test(title, testFunction)`.
*/ */
title: string; title: string;
/**
* The full title path starting with the project.
*/
titlePath: string[];
/** /**
* Absolute path to a file where the currently running test is declared. * Absolute path to a file where the currently running test is declared.
*/ */

View file

@ -221,3 +221,24 @@ test('should show trace source', async ({ runInlineTest, page, showReport }) =>
]); ]);
await expect(page.locator('.stack-trace-frame.selected')).toContainText('a.test.js'); await expect(page.locator('.stack-trace-frame.selected')).toContainText('a.test.js');
}); });
test('should show trace title', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = { use: { trace: 'on' } };
`,
'a.test.js': `
const { test } = pwt;
test('passes', async ({ page }) => {
await page.evaluate('2 + 2');
});
`,
}, { reporter: 'dot,html' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
await showReport();
await page.click('text=passes');
await page.click('img');
await expect(page.locator('.workbench .title')).toHaveText('a.test.js:6 passes');
});

View file

@ -178,6 +178,7 @@ export interface TestInfo {
workerIndex: number; workerIndex: number;
title: string; title: string;
titlePath: string[];
file: string; file: string;
line: number; line: number;
column: number; column: number;