mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(trace-viewer): show test name (#9957)
This commit is contained in:
parent
3673776330
commit
56ca3a18f5
@ -133,11 +133,15 @@ Whether to capture DOM snapshot on every action.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
await context.tracing.start({ screenshots: true, snapshots: true });
|
||||
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
|
||||
|
||||
|
||||
@ -362,6 +362,11 @@ test.beforeEach(async ({ page }, testInfo) => {
|
||||
|
||||
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
|
||||
- type: <[int]>
|
||||
|
||||
|
||||
@ -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)
|
||||
this._context._instrumentation!.addListener(this._instrumentationListener);
|
||||
await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
||||
await channel.tracingStart(options);
|
||||
await channel.tracingStartChunk();
|
||||
await channel.tracingStartChunk({ title: options.title });
|
||||
});
|
||||
}
|
||||
|
||||
async startChunk() {
|
||||
async startChunk(options: { title?: string } = {}) {
|
||||
this._sources = new Set();
|
||||
await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
|
||||
await channel.tracingStartChunk();
|
||||
await channel.tracingStartChunk(options);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -192,7 +192,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
@ -558,7 +558,6 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
dir: string,
|
||||
size?: {
|
||||
@ -631,7 +630,6 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
dir: string,
|
||||
size?: {
|
||||
@ -725,7 +723,6 @@ export type BrowserNewContextParams = {
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
dir: string,
|
||||
size?: {
|
||||
@ -785,7 +782,6 @@ export type BrowserNewContextOptions = {
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
dir: string,
|
||||
size?: {
|
||||
@ -900,7 +896,7 @@ export interface BrowserContextChannel extends EventTargetChannel {
|
||||
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
|
||||
newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>;
|
||||
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>;
|
||||
tracingStop(params?: BrowserContextTracingStopParams, metadata?: Metadata): Promise<BrowserContextTracingStopResult>;
|
||||
harExport(params?: BrowserContextHarExportParams, metadata?: Metadata): Promise<BrowserContextHarExportResult>;
|
||||
@ -1113,8 +1109,12 @@ export type BrowserContextTracingStartOptions = {
|
||||
screenshots?: boolean,
|
||||
};
|
||||
export type BrowserContextTracingStartResult = void;
|
||||
export type BrowserContextTracingStartChunkParams = {};
|
||||
export type BrowserContextTracingStartChunkOptions = {};
|
||||
export type BrowserContextTracingStartChunkParams = {
|
||||
title?: string,
|
||||
};
|
||||
export type BrowserContextTracingStartChunkOptions = {
|
||||
title?: string,
|
||||
};
|
||||
export type BrowserContextTracingStartChunkResult = void;
|
||||
export type BrowserContextTracingStopChunkParams = {
|
||||
save: boolean,
|
||||
@ -3538,7 +3538,6 @@ export type AndroidDeviceLaunchBrowserParams = {
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
dir: string,
|
||||
size?: {
|
||||
@ -3585,7 +3584,6 @@ export type AndroidDeviceLaunchBrowserOptions = {
|
||||
reducedMotion?: 'reduce' | 'no-preference',
|
||||
forcedColors?: 'active' | 'none',
|
||||
acceptDownloads?: boolean,
|
||||
_debugName?: string,
|
||||
recordVideo?: {
|
||||
dir: string,
|
||||
size?: {
|
||||
|
||||
@ -403,7 +403,6 @@ ContextOptions:
|
||||
- none
|
||||
acceptDownloads: boolean?
|
||||
baseURL: string?
|
||||
_debugName: string?
|
||||
recordVideo:
|
||||
type: object?
|
||||
properties:
|
||||
@ -819,6 +818,8 @@ BrowserContext:
|
||||
screenshots: boolean?
|
||||
|
||||
tracingStartChunk:
|
||||
parameters:
|
||||
title: string?
|
||||
|
||||
tracingStopChunk:
|
||||
parameters:
|
||||
@ -2892,7 +2893,6 @@ AndroidDevice:
|
||||
- active
|
||||
- none
|
||||
acceptDownloads: boolean?
|
||||
_debugName: string?
|
||||
recordVideo:
|
||||
type: object?
|
||||
properties:
|
||||
|
||||
@ -329,7 +329,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
forcedColors: tOptional(tEnum(['active', 'none'])),
|
||||
acceptDownloads: tOptional(tBoolean),
|
||||
baseURL: tOptional(tString),
|
||||
_debugName: tOptional(tString),
|
||||
recordVideo: tOptional(tObject({
|
||||
dir: tString,
|
||||
size: tOptional(tObject({
|
||||
@ -389,7 +388,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
forcedColors: tOptional(tEnum(['active', 'none'])),
|
||||
acceptDownloads: tOptional(tBoolean),
|
||||
baseURL: tOptional(tString),
|
||||
_debugName: tOptional(tString),
|
||||
recordVideo: tOptional(tObject({
|
||||
dir: tString,
|
||||
size: tOptional(tObject({
|
||||
@ -505,7 +503,9 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
snapshots: tOptional(tBoolean),
|
||||
screenshots: tOptional(tBoolean),
|
||||
});
|
||||
scheme.BrowserContextTracingStartChunkParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextTracingStartChunkParams = tObject({
|
||||
title: tOptional(tString),
|
||||
});
|
||||
scheme.BrowserContextTracingStopChunkParams = tObject({
|
||||
save: tBoolean,
|
||||
skipCompress: tBoolean,
|
||||
@ -1343,7 +1343,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
reducedMotion: tOptional(tEnum(['reduce', 'no-preference'])),
|
||||
forcedColors: tOptional(tEnum(['active', 'none'])),
|
||||
acceptDownloads: tOptional(tBoolean),
|
||||
_debugName: tOptional(tString),
|
||||
recordVideo: tOptional(tObject({
|
||||
dir: tString,
|
||||
size: tOptional(tObject({
|
||||
|
||||
@ -428,8 +428,6 @@ export function validateBrowserContextOptions(options: types.BrowserContextOptio
|
||||
if (debugMode() === 'inspector')
|
||||
options.bypassCSP = true;
|
||||
verifyGeolocation(options.geolocation);
|
||||
if (!options._debugName)
|
||||
options._debugName = createGuid();
|
||||
}
|
||||
|
||||
export function verifyGeolocation(geolocation?: types.Geolocation) {
|
||||
|
||||
@ -24,13 +24,13 @@ export type BrowserContextEventOptions = {
|
||||
viewport?: Size,
|
||||
deviceScaleFactor?: number,
|
||||
isMobile?: boolean,
|
||||
_debugName?: string,
|
||||
};
|
||||
|
||||
export type ContextCreatedTraceEvent = {
|
||||
version: number,
|
||||
type: 'context-options',
|
||||
browserName: string,
|
||||
title?: string,
|
||||
options: BrowserContextEventOptions
|
||||
};
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
this._harTracer.start();
|
||||
}
|
||||
|
||||
async startChunk() {
|
||||
async startChunk(options: { title?: string } = {}) {
|
||||
if (this._state && this._state.recording)
|
||||
await this.stopChunk(false, false);
|
||||
|
||||
@ -124,7 +124,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
|
||||
this._appendTraceOperation(async () => {
|
||||
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);
|
||||
|
||||
@ -272,7 +272,6 @@ export type BrowserContextOptions = {
|
||||
strictSelectors?: boolean,
|
||||
proxy?: ProxySettings,
|
||||
baseURL?: string,
|
||||
_debugName?: string,
|
||||
};
|
||||
|
||||
export type EnvArray = { name: string, value: string }[];
|
||||
|
||||
@ -21,6 +21,7 @@ export type ContextEntry = {
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
browserName: string;
|
||||
title?: string;
|
||||
options: trace.BrowserContextEventOptions;
|
||||
pages: PageEntry[];
|
||||
resources: ResourceSnapshot[];
|
||||
@ -46,7 +47,6 @@ export function createEmptyContext(): ContextEntry {
|
||||
deviceScaleFactor: 1,
|
||||
isMobile: false,
|
||||
viewport: { width: 1280, height: 800 },
|
||||
_debugName: '<empty>',
|
||||
},
|
||||
pages: [],
|
||||
resources: [],
|
||||
|
||||
@ -103,6 +103,7 @@ export class TraceModel {
|
||||
switch (event.type) {
|
||||
case 'context-options': {
|
||||
this.contextEntry.browserName = event.browserName;
|
||||
this.contextEntry.title = event.title;
|
||||
this.contextEntry.options = event.options;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
flex-direction: column;
|
||||
padding: 20px 0 5px;
|
||||
cursor: text;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.timeline-divider {
|
||||
|
||||
@ -52,7 +52,6 @@
|
||||
|
||||
.workbench {
|
||||
contain: size;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.workbench .header {
|
||||
@ -86,6 +85,10 @@
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.workbench .title {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.workbench .spacer {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
@ -106,6 +106,7 @@ export const Workbench: React.FunctionComponent<{
|
||||
<div className='hbox header'>
|
||||
<div className='logo'>🎭</div>
|
||||
<div className='product'>Playwright</div>
|
||||
{contextEntry.title && <div className='title'>{contextEntry.title}</div>}
|
||||
<div className='spacer'></div>
|
||||
</div>
|
||||
<div style={{ background: 'white', paddingLeft: '20px', flex: 'none', borderBottom: '1px solid #ddd' }}>
|
||||
|
||||
24
packages/playwright-core/types/types.d.ts
vendored
24
packages/playwright-core/types/types.d.ts
vendored
@ -14399,12 +14399,17 @@ export interface Tracing {
|
||||
* Whether to include source files for trace actions.
|
||||
*/
|
||||
sources?: boolean;
|
||||
|
||||
/**
|
||||
* Trace name to be shown in the Trace Viewer.
|
||||
*/
|
||||
title?: string;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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).
|
||||
*
|
||||
* ```js
|
||||
@ -14423,8 +14428,14 @@ export interface Tracing {
|
||||
* 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.
|
||||
@ -14438,15 +14449,16 @@ export interface Tracing {
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Stop the trace chunk. See [tracing.startChunk()](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) for
|
||||
* more details about multiple trace chunks.
|
||||
* Stop the trace chunk. See
|
||||
* [tracing.startChunk([options])](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) for more details
|
||||
* about multiple trace chunks.
|
||||
* @param options
|
||||
*/
|
||||
stopChunk(options?: {
|
||||
/**
|
||||
* Export trace collected since the last
|
||||
* [tracing.startChunk()](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) call into the file with the
|
||||
* given path.
|
||||
* [tracing.startChunk([options])](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) call into the file
|
||||
* with the given path.
|
||||
*/
|
||||
path?: string;
|
||||
}): Promise<void>;
|
||||
|
||||
@ -186,11 +186,12 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
||||
context.setDefaultTimeout(actionTimeout || 0);
|
||||
context.setDefaultNavigationTimeout(navigationTimeout || actionTimeout || 0);
|
||||
if (captureTrace) {
|
||||
const title = [path.relative(testInfo.project.testDir, testInfo.file) + ':' + testInfo.line, ...testInfo.titlePath.slice(1)].join(' › ');
|
||||
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;
|
||||
} else {
|
||||
await context.tracing.startChunk();
|
||||
await context.tracing.startChunk({ title });
|
||||
}
|
||||
} else {
|
||||
(context.tracing as any)[kTracingStarted] = false;
|
||||
|
||||
@ -249,6 +249,7 @@ export class WorkerRunner extends EventEmitter {
|
||||
project: this._project.config,
|
||||
config: this._loader.fullConfig(),
|
||||
title: test.title,
|
||||
titlePath: test.titlePath(),
|
||||
file: test.location.file,
|
||||
line: test.location.line,
|
||||
column: test.location.column,
|
||||
|
||||
4
packages/playwright-test/types/test.d.ts
vendored
4
packages/playwright-test/types/test.d.ts
vendored
@ -1101,6 +1101,10 @@ export interface TestInfo {
|
||||
* The title of the currently running test as passed to `test(title, testFunction)`.
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* The full title path starting with the project.
|
||||
*/
|
||||
titlePath: string[];
|
||||
/**
|
||||
* Absolute path to a file where the currently running test is declared.
|
||||
*/
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
1
utils/generate_types/overrides-test.d.ts
vendored
1
utils/generate_types/overrides-test.d.ts
vendored
@ -178,6 +178,7 @@ export interface TestInfo {
|
||||
workerIndex: number;
|
||||
|
||||
title: string;
|
||||
titlePath: string[];
|
||||
file: string;
|
||||
line: number;
|
||||
column: number;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user