Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tracing) Adding groups to trace via pw-api #33081

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
50 changes: 50 additions & 0 deletions docs/src/api/class-tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,56 @@ given name prefix inside the [`option: BrowserType.launch.tracesDir`] directory
To specify the final trace zip file name, you need to pass `path` option to
[`method: Tracing.stopChunk`] instead.

## async method: Tracing.group
* since: v1.49

Creates a new inline group within the trace, assigning any subsequent calls to this group until [method: Tracing.groupEnd] is invoked.

Groups can be nested and are similar to `test.step` in trace.
However, groups are only visualized in the trace viewer and, unlike test.step, have no effect on the test reports.

:::note Groups should not be used with Playwright Test!

This API is intended for Playwright API users that can not use `test.step`.
:::

**Usage**

```js
await context.tracing.start({ screenshots: true, snapshots: true });
await context.tracing.group('Open Playwright.dev');
// All actions between group and groupEnd will be shown in the trace viewer as a group.
const page = await context.newPage();
await page.goto('https://playwright.dev/');
await context.tracing.groupEnd();
await context.tracing.group('Open API Docs of Tracing');
await page.getByRole('link', { name: 'API' }).click();
await page.getByRole('link', { name: 'Tracing' }).click();
await context.tracing.groupEnd();
// This Trace will have two groups: 'Open Playwright.dev' and 'Open API Docs of Tracing'.
```

### param: Tracing.group.name
* since: v1.49
- `name` <[string]>

Group name shown in the actions tree in trace viewer.

### option: Tracing.group.location
* since: v1.49
- `location` ?<[Object]>
- `file` <[string]> Source file path to be shown in the trace viewer source tab.
- `line` ?<[int]> Line number in the source file.
- `column` ?<[int]> Column number in the source file

Specifies a custom location for the group start to be shown in source tab in trace viewer.
By default, location of the tracing.group() call is shown.

## async method: Tracing.groupEnd
* since: v1.49

Closes the currently open inline group in the trace.

## async method: Tracing.stop
* since: v1.12

Expand Down
8 changes: 8 additions & 0 deletions packages/playwright-core/src/client/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
await this._startCollectingStacks(traceName);
}

async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}) {
await this._channel.tracingGroup({ name, location: options.location });
}

async groupEnd() {
await this._channel.tracingGroupEnd();
}

private async _startCollectingStacks(traceName: string) {
if (!this._isTracing) {
this._isTracing = true;
Expand Down
11 changes: 11 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2295,6 +2295,17 @@ scheme.TracingTracingStartChunkParams = tObject({
scheme.TracingTracingStartChunkResult = tObject({
traceName: tString,
});
scheme.TracingTracingGroupParams = tObject({
name: tString,
location: tOptional(tObject({
file: tString,
line: tOptional(tNumber),
column: tOptional(tNumber),
})),
});
scheme.TracingTracingGroupResult = tOptional(tObject({}));
scheme.TracingTracingGroupEndParams = tOptional(tObject({}));
scheme.TracingTracingGroupEndResult = tOptional(tObject({}));
scheme.TracingTracingStopChunkParams = tObject({
mode: tEnum(['archive', 'discard', 'entries']),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import type * as channels from '@protocol/channels';
import type { CallMetadata } from '@protocol/callMetadata';
import type { Tracing } from '../trace/recorder/tracing';
import { ArtifactDispatcher } from './artifactDispatcher';
import { Dispatcher, existingDispatcher } from './dispatcher';
Expand All @@ -41,6 +42,15 @@ export class TracingDispatcher extends Dispatcher<Tracing, channels.TracingChann
return await this._object.startChunk(params);
}

async tracingGroup(params: channels.TracingTracingGroupParams, metadata: CallMetadata): Promise<channels.TracingTracingGroupResult> {
const { name, location } = params;
await this._object.group(name, location, metadata);
}

async tracingGroupEnd(params: channels.TracingTracingGroupEndParams): Promise<channels.TracingTracingGroupEndResult> {
await this._object.groupEnd();
}

async tracingStopChunk(params: channels.TracingTracingStopChunkParams): Promise<channels.TracingTracingStopChunkResult> {
const { artifact, entries } = await this._object.stopChunk(params);
return { artifact: artifact ? ArtifactDispatcher.from(this, artifact) : undefined, entries };
Expand Down
66 changes: 62 additions & 4 deletions packages/playwright-core/src/server/trace/recorder/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import fs from 'fs';
import os from 'os';
import path from 'path';
import type { NameValue } from '../../../common/types';
import type { TracingTracingStopChunkParams } from '@protocol/channels';
import type { TracingTracingStopChunkParams, StackFrame } from '@protocol/channels';
import { commandsWithTracingSnapshots } from '../../../protocol/debug';
import { assert, createGuid, monotonicTime, SerializedFS, removeFolders, eventsHelper, type RegisteredListener } from '../../../utils';
import { Artifact } from '../../artifact';
Expand Down Expand Up @@ -61,6 +61,7 @@ type RecordingState = {
traceSha1s: Set<string>,
recording: boolean;
callIds: Set<string>;
groupStack: string[];
};

const kScreencastOptions = { width: 800, height: 600, quality: 90 };
Expand Down Expand Up @@ -148,6 +149,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
networkSha1s: new Set(),
recording: false,
callIds: new Set(),
groupStack: [],
};
this._fs.mkdir(this._state.resourcesDir);
this._fs.writeFile(this._state.networkFile, '');
Expand Down Expand Up @@ -194,6 +196,48 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
return { traceName: this._state.traceName };
}

async group(name: string, location: { file: string, line?: number, column?: number } | undefined, metadata: CallMetadata): Promise<void> {
if (!this._state)
return;
const stackFrames: StackFrame[] = [];
const { file, line, column } = location ?? metadata.location ?? {};
if (file) {
stackFrames.push({
file,
line: line ?? 0,
column: column ?? 0,
});
}
const event: trace.BeforeActionTraceEvent = {
type: 'before',
callId: metadata.id,
startTime: metadata.startTime,
apiName: name,
class: 'Tracing',
method: 'group',
params: { },
stack: stackFrames,
};
if (this._state.groupStack.length)
event.parentId = this._state.groupStack[this._state.groupStack.length - 1];
this._state.groupStack.push(event.callId);
this._appendTraceEvent(event);
}

async groupEnd(): Promise<void> {
if (!this._state)
return;
const callId = this._state.groupStack.pop();
if (!callId)
return;
const event: trace.AfterActionTraceEvent = {
type: 'after',
callId,
endTime: monotonicTime(),
};
this._appendTraceEvent(event);
}

private _startScreencast() {
if (!(this._context instanceof BrowserContext))
return;
Expand Down Expand Up @@ -236,6 +280,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
throw new Error(`Tracing is already stopping`);
if (this._state.recording)
throw new Error(`Must stop trace file before stopping tracing`);
await this._closeAllGroups();
this._harTracer.stop();
this.flushHarEntries();
await this._fs.syncAndGetError();
Expand Down Expand Up @@ -264,6 +309,11 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
await this._fs.syncAndGetError();
}

async _closeAllGroups() {
while (this._state?.groupStack.length)
await this.groupEnd();
}

async stopChunk(params: TracingTracingStopChunkParams): Promise<{ artifact?: Artifact, entries?: NameValue[] }> {
if (this._isStopping)
throw new Error(`Tracing is already stopping`);
Expand All @@ -276,6 +326,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
return {};
}

await this._closeAllGroups();

this._context.instrumentation.removeListener(this);
eventsHelper.removeEventListeners(this._eventListeners);
if (this._state.options.screenshots)
Expand Down Expand Up @@ -354,7 +406,10 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps

onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
// IMPORTANT: no awaits before this._appendTraceEvent in this method.
const event = createBeforeActionTraceEvent(metadata);
const event = createBeforeActionTraceEvent(
metadata,
this._state?.groupStack.length ? this._state.groupStack[this._state.groupStack.length - 1] : undefined
);
if (!event)
return Promise.resolve();
sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling();
Expand Down Expand Up @@ -571,10 +626,10 @@ export function shouldCaptureSnapshot(metadata: CallMetadata): boolean {
return commandsWithTracingSnapshots.has(metadata.type + '.' + metadata.method);
}

function createBeforeActionTraceEvent(metadata: CallMetadata): trace.BeforeActionTraceEvent | null {
function createBeforeActionTraceEvent(metadata: CallMetadata, parentId?: string): trace.BeforeActionTraceEvent | null {
if (metadata.internal || metadata.method.startsWith('tracing'))
return null;
return {
const event: trace.BeforeActionTraceEvent = {
type: 'before',
callId: metadata.id,
startTime: metadata.startTime,
Expand All @@ -585,6 +640,9 @@ function createBeforeActionTraceEvent(metadata: CallMetadata): trace.BeforeActio
stepId: metadata.stepId,
pageId: metadata.pageId,
};
if (parentId)
event.parentId = parentId;
return event;
}

function createInputActionTraceEvent(metadata: CallMetadata): trace.InputActionTraceEvent | null {
Expand Down
56 changes: 56 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21058,6 +21058,62 @@ export interface Touchscreen {
*
*/
export interface Tracing {
/**
* Creates a new inline group within the trace, assigning any subsequent calls to this group until
* [method: Tracing.groupEnd] is invoked.
*
* Groups can be nested and are similar to `test.step` in trace. However, groups are only visualized in the trace
* viewer and, unlike test.step, have no effect on the test reports.
*
* **NOTE** This API is intended for Playwright API users that can not use `test.step`.
*
* **Usage**
*
* ```js
* await context.tracing.start({ screenshots: true, snapshots: true });
* await context.tracing.group('Open Playwright.dev');
* // All actions between group and groupEnd will be shown in the trace viewer as a group.
* const page = await context.newPage();
* await page.goto('https://playwright.dev/');
* await context.tracing.groupEnd();
* await context.tracing.group('Open API Docs of Tracing');
* await page.getByRole('link', { name: 'API' }).click();
* await page.getByRole('link', { name: 'Tracing' }).click();
* await context.tracing.groupEnd();
* // This Trace will have two groups: 'Open Playwright.dev' and 'Open API Docs of Tracing'.
* ```
*
* @param name Group name shown in the actions tree in trace viewer.
* @param options
*/
group(name: string, options?: {
/**
* Specifies a custom location for the group start to be shown in source tab in trace viewer. By default, location of
* the tracing.group() call is shown.
*/
location?: {
/**
* Source file path to be shown in the trace viewer source tab.
*/
file: string;

/**
* Line number in the source file.
*/
line?: number;

/**
* Column number in the source file
*/
column?: number;
};
}): Promise<void>;

/**
* Closes the currently open inline group in the trace.
*/
groupEnd(): Promise<void>;

/**
* Start tracing.
*
Expand Down
21 changes: 21 additions & 0 deletions packages/protocol/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4085,6 +4085,8 @@ export interface TracingChannel extends TracingEventTarget, Channel {
_type_Tracing: boolean;
tracingStart(params: TracingTracingStartParams, metadata?: CallMetadata): Promise<TracingTracingStartResult>;
tracingStartChunk(params: TracingTracingStartChunkParams, metadata?: CallMetadata): Promise<TracingTracingStartChunkResult>;
tracingGroup(params: TracingTracingGroupParams, metadata?: CallMetadata): Promise<TracingTracingGroupResult>;
tracingGroupEnd(params?: TracingTracingGroupEndParams, metadata?: CallMetadata): Promise<TracingTracingGroupEndResult>;
tracingStopChunk(params: TracingTracingStopChunkParams, metadata?: CallMetadata): Promise<TracingTracingStopChunkResult>;
tracingStop(params?: TracingTracingStopParams, metadata?: CallMetadata): Promise<TracingTracingStopResult>;
}
Expand Down Expand Up @@ -4112,6 +4114,25 @@ export type TracingTracingStartChunkOptions = {
export type TracingTracingStartChunkResult = {
traceName: string,
};
export type TracingTracingGroupParams = {
name: string,
location?: {
file: string,
line?: number,
column?: number,
},
};
export type TracingTracingGroupOptions = {
location?: {
file: string,
line?: number,
column?: number,
},
};
export type TracingTracingGroupResult = void;
export type TracingTracingGroupEndParams = {};
export type TracingTracingGroupEndOptions = {};
export type TracingTracingGroupEndResult = void;
export type TracingTracingStopChunkParams = {
mode: 'archive' | 'discard' | 'entries',
};
Expand Down
12 changes: 12 additions & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3196,6 +3196,18 @@ Tracing:
returns:
traceName: string

tracingGroup:
parameters:
name: string
location:
type: object?
properties:
file: string
line: number?
column: number?

tracingGroupEnd:

tracingStopChunk:
parameters:
mode:
Expand Down
4 changes: 4 additions & 0 deletions tests/config/traceViewerFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type TraceViewerFixtures = {

class TraceViewerPage {
actionTitles: Locator;
actionsTree: Locator;
callLines: Locator;
consoleLines: Locator;
logLines: Locator;
Expand All @@ -46,9 +47,11 @@ class TraceViewerPage {
networkRequests: Locator;
metadataTab: Locator;
snapshotContainer: Locator;
sourceCodeTab: Locator;

constructor(public page: Page) {
this.actionTitles = page.locator('.action-title');
this.actionsTree = page.getByTestId('actions-tree');
this.callLines = page.locator('.call-tab .call-line');
this.logLines = page.getByTestId('log-list').locator('.list-view-entry');
this.consoleLines = page.locator('.console-line');
Expand All @@ -59,6 +62,7 @@ class TraceViewerPage {
this.networkRequests = page.getByTestId('network-list').locator('.list-view-entry');
this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]');
this.metadataTab = page.getByTestId('metadata-view');
this.sourceCodeTab = page.getByTestId('source-code');
}

async actionIconsText(action: string) {
Expand Down
Loading
Loading