From edd437907899a3686c4779efdb269d3c2ff6d0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Sun, 13 Oct 2024 22:13:14 +0200 Subject: [PATCH 01/14] working proto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- .../playwright-core/src/client/tracing.ts | 8 +++++ .../playwright-core/src/protocol/validator.ts | 10 ++++++ .../server/dispatchers/tracingDispatcher.ts | 14 ++++++++ .../src/server/trace/recorder/tracing.ts | 32 +++++++++++++++++++ packages/protocol/src/channels.ts | 17 ++++++++++ packages/protocol/src/protocol.yml | 12 +++++++ 6 files changed, 93 insertions(+) diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index b5c411cc6533e..9692943f6a1c9 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -51,6 +51,14 @@ export class Tracing extends ChannelOwner implements ap await this._startCollectingStacks(traceName); } + async group(name: string, options: { location?: string } = {}) { + await this._channel.tracingGroup({ name, options }); + } + + async groupEnd() { + await this._channel.tracingGroupEnd(); + } + private async _startCollectingStacks(traceName: string) { if (!this._isTracing) { this._isTracing = true; diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 24ddf0014c0d1..0453a43481fca 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -2280,6 +2280,9 @@ scheme.DialogAcceptParams = tObject({ scheme.DialogAcceptResult = tOptional(tObject({})); scheme.DialogDismissParams = tOptional(tObject({})); scheme.DialogDismissResult = tOptional(tObject({})); +scheme.TracingGroupOptions = tObject({ + location: tString, +}); scheme.TracingInitializer = tOptional(tObject({})); scheme.TracingTracingStartParams = tObject({ name: tOptional(tString), @@ -2295,6 +2298,13 @@ scheme.TracingTracingStartChunkParams = tObject({ scheme.TracingTracingStartChunkResult = tObject({ traceName: tString, }); +scheme.TracingTracingGroupParams = tObject({ + name: tString, + options: tType('TracingGroupOptions'), +}); +scheme.TracingTracingGroupResult = tOptional(tObject({})); +scheme.TracingTracingGroupEndParams = tOptional(tObject({})); +scheme.TracingTracingGroupEndResult = tOptional(tObject({})); scheme.TracingTracingStopChunkParams = tObject({ mode: tEnum(['archive', 'discard', 'entries']), }); diff --git a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts index b8214fbe31b5f..0a1e39aa68903 100644 --- a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts @@ -33,14 +33,28 @@ export class TracingDispatcher extends Dispatcher { await this._object.start(params); } + // async startChunk(options: { name?: string, title?: string } = {}): Promise<{ traceName: string }> { async tracingStartChunk(params: channels.TracingTracingStartChunkParams): Promise { return await this._object.startChunk(params); } + + // async group(name: string, options: { location?: string } = {}): Promise { + async tracingGroup(params: channels.TracingTracingGroupParams): Promise { + const { name, options } = params; + await this._object.group(name, options); + } + + // async groupEnd(): Promise { + async tracingGroupEnd(params: channels.TracingTracingGroupEndParams): Promise { + await this._object.groupEnd(); + } + async tracingStopChunk(params: channels.TracingTracingStopChunkParams): Promise { const { artifact, entries } = await this._object.stopChunk(params); return { artifact: artifact ? ArtifactDispatcher.from(this, artifact) : undefined, entries }; diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 97476d4b3122a..9e8e5fd64228c 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -80,6 +80,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps private _allResources = new Set(); private _contextCreatedEvent: trace.ContextCreatedTraceEvent; private _pendingHarEntries = new Set(); + private _groupStack: string[] = []; + private _groupId = 0; constructor(context: BrowserContext | APIRequestContext, tracesDir: string | undefined) { super(context, 'tracing'); @@ -194,6 +196,34 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps return { traceName: this._state.traceName }; } + async group(name: string, options: { location?: string } = {}): Promise { + const location = options.location?.split(':', 2); + const file = location?.[0], line = location?.[1]; + const event: trace.BeforeActionTraceEvent = { + type: 'before', + callId: `group-${this._groupId++}`, + startTime: monotonicTime(), + apiName: name, + class: 'Tracing', + method: 'group', + params: { }, + stack: [{ file: file || '', line: line ? parseInt(line, 10) : 0, column: 0 }], + }; + this._groupStack.push(event.callId); + this._appendTraceEvent(event); + } + + async groupEnd(): Promise { + const callId = this._groupStack.pop(); + assert(callId, 'Cannot end group that has not started'); + const event: trace.AfterActionTraceEvent = { + type: 'after', + callId, + endTime: monotonicTime(), + }; + this._appendTraceEvent(event); + } + private _startScreencast() { if (!(this._context instanceof BrowserContext)) return; @@ -357,6 +387,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps const event = createBeforeActionTraceEvent(metadata); if (!event) return Promise.resolve(); + if (event.parentId === undefined && this._groupStack.length) + event.parentId = this._groupStack[this._groupStack.length - 1]; sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling(); event.beforeSnapshot = `before@${metadata.id}`; this._state?.callIds.add(metadata.id); diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 5ecc2f4077e07..9308c74261779 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -4077,6 +4077,10 @@ export type DialogDismissResult = void; export interface DialogEvents { } +export type TracingGroupOptions = { + location: string, +}; + // ----------- Tracing ----------- export type TracingInitializer = {}; export interface TracingEventTarget { @@ -4085,6 +4089,8 @@ export interface TracingChannel extends TracingEventTarget, Channel { _type_Tracing: boolean; tracingStart(params: TracingTracingStartParams, metadata?: CallMetadata): Promise; tracingStartChunk(params: TracingTracingStartChunkParams, metadata?: CallMetadata): Promise; + tracingGroup(params: TracingTracingGroupParams, metadata?: CallMetadata): Promise; + tracingGroupEnd(params?: TracingTracingGroupEndParams, metadata?: CallMetadata): Promise; tracingStopChunk(params: TracingTracingStopChunkParams, metadata?: CallMetadata): Promise; tracingStop(params?: TracingTracingStopParams, metadata?: CallMetadata): Promise; } @@ -4112,6 +4118,17 @@ export type TracingTracingStartChunkOptions = { export type TracingTracingStartChunkResult = { traceName: string, }; +export type TracingTracingGroupParams = { + name: string, + options: TracingGroupOptions, +}; +export type TracingTracingGroupOptions = { + +}; +export type TracingTracingGroupResult = void; +export type TracingTracingGroupEndParams = {}; +export type TracingTracingGroupEndOptions = {}; +export type TracingTracingGroupEndResult = void; export type TracingTracingStopChunkParams = { mode: 'archive' | 'discard' | 'entries', }; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index c91cecbe6cab7..b722401dd3573 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -3177,6 +3177,11 @@ Dialog: dismiss: +TracingGroupOptions: + type: object + properties: + location: string + Tracing: type: interface @@ -3196,6 +3201,13 @@ Tracing: returns: traceName: string + tracingGroup: + parameters: + name: string + options: TracingGroupOptions + + tracingGroupEnd: + tracingStopChunk: parameters: mode: From 58c51f1be2ebe68f9c2fc97feb15aced9b47adee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Sun, 13 Oct 2024 22:38:24 +0200 Subject: [PATCH 02/14] fixed Location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- packages/playwright-core/src/client/tracing.ts | 2 +- .../playwright-core/src/protocol/validator.ts | 13 +++++++------ .../src/server/trace/recorder/tracing.ts | 13 ++++++++----- packages/protocol/src/channels.ts | 14 ++++++++------ packages/protocol/src/protocol.yml | 16 +++++++++------- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index 9692943f6a1c9..5c7796c103e73 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -51,7 +51,7 @@ export class Tracing extends ChannelOwner implements ap await this._startCollectingStacks(traceName); } - async group(name: string, options: { location?: string } = {}) { + async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}) { await this._channel.tracingGroup({ name, options }); } diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 0453a43481fca..8b9ea68cdfd6a 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -26,12 +26,13 @@ scheme.StackFrame = tObject({ column: tNumber, function: tOptional(tString), }); +scheme.Location = tObject({ + file: tString, + line: tOptional(tNumber), + column: tOptional(tNumber), +}); scheme.Metadata = tObject({ - location: tOptional(tObject({ - file: tString, - line: tOptional(tNumber), - column: tOptional(tNumber), - })), + location: tOptional(tType('Location')), apiName: tOptional(tString), internal: tOptional(tBoolean), stepId: tOptional(tString), @@ -2281,7 +2282,7 @@ scheme.DialogAcceptResult = tOptional(tObject({})); scheme.DialogDismissParams = tOptional(tObject({})); scheme.DialogDismissResult = tOptional(tObject({})); scheme.TracingGroupOptions = tObject({ - location: tString, + location: tOptional(tType('Location')), }); scheme.TracingInitializer = tOptional(tObject({})); scheme.TracingTracingStartParams = tObject({ diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 9e8e5fd64228c..6a770ad2fa684 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -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'; @@ -196,9 +196,12 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps return { traceName: this._state.traceName }; } - async group(name: string, options: { location?: string } = {}): Promise { - const location = options.location?.split(':', 2); - const file = location?.[0], line = location?.[1]; + async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}): Promise { + const stackFrame: StackFrame = { + file: options.location?.file || '', + line: options.location?.line || 0, + column: options.location?.column || 0, + }; const event: trace.BeforeActionTraceEvent = { type: 'before', callId: `group-${this._groupId++}`, @@ -207,7 +210,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps class: 'Tracing', method: 'group', params: { }, - stack: [{ file: file || '', line: line ? parseInt(line, 10) : 0, column: 0 }], + stack: [stackFrame], }; this._groupStack.push(event.callId); this._appendTraceEvent(event); diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 9308c74261779..cf0bfa7caad39 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -144,12 +144,14 @@ export type StackFrame = { function?: string, }; +export type Location = { + file: string, + line?: number, + column?: number, +}; + export type Metadata = { - location?: { - file: string, - line?: number, - column?: number, - }, + location?: Location, apiName?: string, internal?: boolean, stepId?: string, @@ -4078,7 +4080,7 @@ export interface DialogEvents { } export type TracingGroupOptions = { - location: string, + location?: Location, }; // ----------- Tracing ----------- diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index b722401dd3573..faf0f458a17c9 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -20,17 +20,19 @@ StackFrame: column: number function: string? +Location: + type: object + properties: + file: string + line: number? + column: number? + # This object can be send with any rpc call in the "metadata" field. Metadata: type: object properties: - location: - type: object? - properties: - file: string - line: number? - column: number? + location: Location? apiName: string? internal: boolean? # Test runner step id. @@ -3180,7 +3182,7 @@ Dialog: TracingGroupOptions: type: object properties: - location: string + location: Location? Tracing: type: interface From 2177a0a4430247cb71300667ce5b8da3727d1c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Mon, 14 Oct 2024 01:10:21 +0200 Subject: [PATCH 03/14] fixed hirarchy added types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- docs/src/api/class-tracing.md | 23 ++++++++++++ .../playwright-core/src/client/tracing.ts | 6 ++++ .../src/server/trace/recorder/tracing.ts | 36 ++++++++++++++----- packages/playwright-core/types/types.d.ts | 30 ++++++++++++++++ 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/docs/src/api/class-tracing.md b/docs/src/api/class-tracing.md index 065896925fa74..d7e2721936c7a 100644 --- a/docs/src/api/class-tracing.md +++ b/docs/src/api/class-tracing.md @@ -281,6 +281,29 @@ 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 in the trace log, causing any subsequent calls to be indented by an additional level, until [`method: Tracing.groupEnd`] is called. + +### param: Tracing.group.name +* since: v1.49 +- `name` <[string]> + +Group name shown in the 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 + +## async method: Tracing.groupEnd +* since: v1.49 + +Closes the last opened inline group in the trace log. + ## async method: Tracing.stop * since: v1.12 diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index 5c7796c103e73..1911d60d487fb 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -18,6 +18,8 @@ import type * as api from '../../types/types'; import type * as channels from '@protocol/channels'; import { Artifact } from './artifact'; import { ChannelOwner } from './channelOwner'; +import { captureRawStack } from '../utils'; +import { filteredStackTrace } from 'playwright/lib/util'; export class Tracing extends ChannelOwner implements api.Tracing { private _includeSources = false; @@ -52,6 +54,10 @@ export class Tracing extends ChannelOwner implements ap } async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}) { + if (!options.location) { + const filteredStack = filteredStackTrace(captureRawStack()); + options.location = filteredStack[0]; + } await this._channel.tracingGroup({ name, options }); } diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 6a770ad2fa684..484fe6cf32419 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -61,6 +61,8 @@ type RecordingState = { traceSha1s: Set, recording: boolean; callIds: Set; + groupStack: string[]; + groupId: number; }; const kScreencastOptions = { width: 800, height: 600, quality: 90 }; @@ -80,8 +82,6 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps private _allResources = new Set(); private _contextCreatedEvent: trace.ContextCreatedTraceEvent; private _pendingHarEntries = new Set(); - private _groupStack: string[] = []; - private _groupId = 0; constructor(context: BrowserContext | APIRequestContext, tracesDir: string | undefined) { super(context, 'tracing'); @@ -150,6 +150,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps networkSha1s: new Set(), recording: false, callIds: new Set(), + groupStack: [], + groupId: 0, }; this._fs.mkdir(this._state.resourcesDir); this._fs.writeFile(this._state.networkFile, ''); @@ -197,6 +199,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps } async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}): Promise { + if (!this._state) + return; const stackFrame: StackFrame = { file: options.location?.file || '', line: options.location?.line || 0, @@ -204,7 +208,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps }; const event: trace.BeforeActionTraceEvent = { type: 'before', - callId: `group-${this._groupId++}`, + callId: `group-${this._state.groupId++}`, startTime: monotonicTime(), apiName: name, class: 'Tracing', @@ -212,13 +216,18 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps params: { }, stack: [stackFrame], }; - this._groupStack.push(event.callId); + 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 { - const callId = this._groupStack.pop(); - assert(callId, 'Cannot end group that has not started'); + if (!this._state) + return; + const callId = this._state.groupStack.pop(); + if (!callId) + return; const event: trace.AfterActionTraceEvent = { type: 'after', callId, @@ -297,6 +306,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`); @@ -309,6 +323,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) @@ -390,8 +406,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps const event = createBeforeActionTraceEvent(metadata); if (!event) return Promise.resolve(); - if (event.parentId === undefined && this._groupStack.length) - event.parentId = this._groupStack[this._groupStack.length - 1]; + this._applyOpenGroup(event); sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling(); event.beforeSnapshot = `before@${metadata.id}`; this._state?.callIds.add(metadata.id); @@ -399,6 +414,11 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps return this._captureSnapshot(event.beforeSnapshot, sdkObject, metadata); } + private _applyOpenGroup(event: trace.BeforeActionTraceEvent) { + if (event.parentId === undefined && this._state?.groupStack.length) + event.parentId = this._state?.groupStack[this._state.groupStack.length - 1]; + } + onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata) { if (!this._state?.callIds.has(metadata.id)) return Promise.resolve(); diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 092f75b2636bc..42ce1fb7adbfb 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -21058,6 +21058,36 @@ export interface Touchscreen { * */ export interface Tracing { + /** + * Creates a new inline group in the trace log, causing any subsequent calls to be indented by an additional level, + * until [tracing.groupEnd()](https://playwright.dev/docs/api/class-tracing#tracing-group-end) is called. + * @param name Group name shown in the trace viewer. + * @param options + */ + group(name: string, options?: { + 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; + + /** + * Closes the last opened inline group in the trace log. + */ + groupEnd(): Promise; + /** * Start tracing. * From 472602475f7e98bdd22e0b79b20fa1ccbeacdf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Mon, 14 Oct 2024 09:47:27 +0200 Subject: [PATCH 04/14] added StackTrace filtering to core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- .../playwright-core/src/client/tracing.ts | 3 +-- .../server/dispatchers/tracingDispatcher.ts | 5 ----- .../playwright-core/src/utils/stackTrace.ts | 19 +++++++++++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index 1911d60d487fb..a5746aed5bf89 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -18,8 +18,7 @@ import type * as api from '../../types/types'; import type * as channels from '@protocol/channels'; import { Artifact } from './artifact'; import { ChannelOwner } from './channelOwner'; -import { captureRawStack } from '../utils'; -import { filteredStackTrace } from 'playwright/lib/util'; +import { captureRawStack, filteredStackTrace } from '../utils'; export class Tracing extends ChannelOwner implements api.Tracing { private _includeSources = false; diff --git a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts index 0a1e39aa68903..fc1ea3c1ad719 100644 --- a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts @@ -33,24 +33,19 @@ export class TracingDispatcher extends Dispatcher { await this._object.start(params); } - // async startChunk(options: { name?: string, title?: string } = {}): Promise<{ traceName: string }> { async tracingStartChunk(params: channels.TracingTracingStartChunkParams): Promise { return await this._object.startChunk(params); } - - // async group(name: string, options: { location?: string } = {}): Promise { async tracingGroup(params: channels.TracingTracingGroupParams): Promise { const { name, options } = params; await this._object.group(name, options); } - // async groupEnd(): Promise { async tracingGroupEnd(params: channels.TracingTracingGroupEndParams): Promise { await this._object.groupEnd(); } diff --git a/packages/playwright-core/src/utils/stackTrace.ts b/packages/playwright-core/src/utils/stackTrace.ts index 77e1365b3f876..4317ab6159d69 100644 --- a/packages/playwright-core/src/utils/stackTrace.ts +++ b/packages/playwright-core/src/utils/stackTrace.ts @@ -47,6 +47,25 @@ export function captureRawStack(): RawStack { return stack.split('\n'); } +export function filterStackFile(file: string) { + if (!process.env.PWDEBUGIMPL && file.startsWith(CORE_DIR)) + return false; + return true; +} + +export function filteredStackTrace(rawStack: RawStack): StackFrame[] { + const frames: StackFrame[] = []; + for (const line of rawStack) { + const frame = parseStackTraceLine(line); + if (!frame || !frame.file) + continue; + if (!filterStackFile(frame.file)) + continue; + frames.push(frame); + } + return frames; +} + export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: string } { const stack = captureRawStack(); From 87cf5bb16d15a9c14e8ab8b236bed5966968e8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <41592183+Snooz82@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:39:22 +0200 Subject: [PATCH 05/14] Update docs/src/api/class-tracing.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dmitry Gozman Signed-off-by: René <41592183+Snooz82@users.noreply.github.com> --- docs/src/api/class-tracing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/api/class-tracing.md b/docs/src/api/class-tracing.md index d7e2721936c7a..f9c764f2f4d2b 100644 --- a/docs/src/api/class-tracing.md +++ b/docs/src/api/class-tracing.md @@ -284,7 +284,7 @@ To specify the final trace zip file name, you need to pass `path` option to ## async method: Tracing.group * since: v1.49 -Creates a new inline group in the trace log, causing any subsequent calls to be indented by an additional level, until [`method: Tracing.groupEnd`] is called. +Creates a new inline group in the trace, causing any subsequent calls to belong to this group, until [`method: Tracing.groupEnd`] is called. ### param: Tracing.group.name * since: v1.49 From c39e5f90c99dda5b187bc7be029cb7fd73ca0fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <41592183+Snooz82@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:39:28 +0200 Subject: [PATCH 06/14] Update docs/src/api/class-tracing.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dmitry Gozman Signed-off-by: René <41592183+Snooz82@users.noreply.github.com> --- docs/src/api/class-tracing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/api/class-tracing.md b/docs/src/api/class-tracing.md index f9c764f2f4d2b..7607b3c53b1e3 100644 --- a/docs/src/api/class-tracing.md +++ b/docs/src/api/class-tracing.md @@ -302,7 +302,7 @@ Group name shown in the trace viewer. ## async method: Tracing.groupEnd * since: v1.49 -Closes the last opened inline group in the trace log. +Closes the last opened inline group in the trace. ## async method: Tracing.stop * since: v1.12 From 6f45a0d0a95870e0705d912f7f901300ac6a6554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Mon, 14 Oct 2024 18:53:19 +0200 Subject: [PATCH 07/14] fixed review comments and made the code way easier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- .../playwright-core/src/client/tracing.ts | 5 --- .../server/dispatchers/tracingDispatcher.ts | 5 ++- .../src/server/trace/recorder/tracing.ts | 42 ++++++++++--------- .../playwright-core/src/utils/stackTrace.ts | 19 --------- packages/playwright-core/types/types.d.ts | 6 +-- 5 files changed, 28 insertions(+), 49 deletions(-) diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index a5746aed5bf89..5c7796c103e73 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -18,7 +18,6 @@ import type * as api from '../../types/types'; import type * as channels from '@protocol/channels'; import { Artifact } from './artifact'; import { ChannelOwner } from './channelOwner'; -import { captureRawStack, filteredStackTrace } from '../utils'; export class Tracing extends ChannelOwner implements api.Tracing { private _includeSources = false; @@ -53,10 +52,6 @@ export class Tracing extends ChannelOwner implements ap } async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}) { - if (!options.location) { - const filteredStack = filteredStackTrace(captureRawStack()); - options.location = filteredStack[0]; - } await this._channel.tracingGroup({ name, options }); } diff --git a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts index fc1ea3c1ad719..adfcda84af5ae 100644 --- a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts @@ -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'; @@ -41,9 +42,9 @@ export class TracingDispatcher extends Dispatcher { + async tracingGroup(params: channels.TracingTracingGroupParams, metadata: CallMetadata): Promise { const { name, options } = params; - await this._object.group(name, options); + await this._object.group(name, options, metadata); } async tracingGroupEnd(params: channels.TracingTracingGroupEndParams): Promise { diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 484fe6cf32419..da603c72b8205 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -62,7 +62,6 @@ type RecordingState = { recording: boolean; callIds: Set; groupStack: string[]; - groupId: number; }; const kScreencastOptions = { width: 800, height: 600, quality: 90 }; @@ -151,7 +150,6 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps recording: false, callIds: new Set(), groupStack: [], - groupId: 0, }; this._fs.mkdir(this._state.resourcesDir); this._fs.writeFile(this._state.networkFile, ''); @@ -198,23 +196,27 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps return { traceName: this._state.traceName }; } - async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}): Promise { + async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}, metadata: CallMetadata): Promise { if (!this._state) return; - const stackFrame: StackFrame = { - file: options.location?.file || '', - line: options.location?.line || 0, - column: options.location?.column || 0, - }; + const stackFrames: StackFrame[] = []; + const { file, line, column } = options.location ?? metadata.location ?? {}; + if (file) { + stackFrames.push({ + file, + line: line ?? 0, + column: column ?? 0, + }); + } const event: trace.BeforeActionTraceEvent = { type: 'before', - callId: `group-${this._state.groupId++}`, - startTime: monotonicTime(), + callId: metadata.id, + startTime: metadata.startTime, apiName: name, class: 'Tracing', method: 'group', params: { }, - stack: [stackFrame], + stack: stackFrames, }; if (this._state.groupStack.length) event.parentId = this._state.groupStack[this._state.groupStack.length - 1]; @@ -403,10 +405,12 @@ 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(); - this._applyOpenGroup(event); sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling(); event.beforeSnapshot = `before@${metadata.id}`; this._state?.callIds.add(metadata.id); @@ -414,11 +418,6 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps return this._captureSnapshot(event.beforeSnapshot, sdkObject, metadata); } - private _applyOpenGroup(event: trace.BeforeActionTraceEvent) { - if (event.parentId === undefined && this._state?.groupStack.length) - event.parentId = this._state?.groupStack[this._state.groupStack.length - 1]; - } - onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata) { if (!this._state?.callIds.has(metadata.id)) return Promise.resolve(); @@ -626,10 +625,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, @@ -640,6 +639,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 { diff --git a/packages/playwright-core/src/utils/stackTrace.ts b/packages/playwright-core/src/utils/stackTrace.ts index 4317ab6159d69..77e1365b3f876 100644 --- a/packages/playwright-core/src/utils/stackTrace.ts +++ b/packages/playwright-core/src/utils/stackTrace.ts @@ -47,25 +47,6 @@ export function captureRawStack(): RawStack { return stack.split('\n'); } -export function filterStackFile(file: string) { - if (!process.env.PWDEBUGIMPL && file.startsWith(CORE_DIR)) - return false; - return true; -} - -export function filteredStackTrace(rawStack: RawStack): StackFrame[] { - const frames: StackFrame[] = []; - for (const line of rawStack) { - const frame = parseStackTraceLine(line); - if (!frame || !frame.file) - continue; - if (!filterStackFile(frame.file)) - continue; - frames.push(frame); - } - return frames; -} - export function captureLibraryStackTrace(): { frames: StackFrame[], apiName: string } { const stack = captureRawStack(); diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 42ce1fb7adbfb..3c8bf3061de1f 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -21059,8 +21059,8 @@ export interface Touchscreen { */ export interface Tracing { /** - * Creates a new inline group in the trace log, causing any subsequent calls to be indented by an additional level, - * until [tracing.groupEnd()](https://playwright.dev/docs/api/class-tracing#tracing-group-end) is called. + * Creates a new inline group in the trace, causing any subsequent calls to belong to this group, until + * [tracing.groupEnd()](https://playwright.dev/docs/api/class-tracing#tracing-group-end) is called. * @param name Group name shown in the trace viewer. * @param options */ @@ -21084,7 +21084,7 @@ export interface Tracing { }): Promise; /** - * Closes the last opened inline group in the trace log. + * Closes the last opened inline group in the trace. */ groupEnd(): Promise; From 1894e2834850148d8e17b4b9501c7fae55656815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Tue, 15 Oct 2024 00:00:06 +0200 Subject: [PATCH 08/14] added test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- tests/config/traceViewerFixtures.ts | 4 ++ tests/library/trace-viewer.spec.ts | 71 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/tests/config/traceViewerFixtures.ts b/tests/config/traceViewerFixtures.ts index 3eb3b11a153c7..837b953ef4f28 100644 --- a/tests/config/traceViewerFixtures.ts +++ b/tests/config/traceViewerFixtures.ts @@ -36,6 +36,7 @@ export type TraceViewerFixtures = { class TraceViewerPage { actionTitles: Locator; + actionsTree: Locator; callLines: Locator; consoleLines: Locator; logLines: Locator; @@ -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'); @@ -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) { diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 0eda4b092a111..bbebf5b41c9a3 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -62,6 +62,15 @@ test.beforeAll(async function recordTrace({ browser, browserName, browserType, s } await doClick(); + await context.tracing.group('High-level Group'); + await context.tracing.group('First Mid-level Group', { location: { file: `${__dirname}/tracing.spec.ts`, line: 100, column: 10 } }); + await page.locator('button >> nth=0').click(); + await context.tracing.groupEnd(); + await context.tracing.group('Second Mid-level Group', { location: { file: __filename } }); + await expect(page.getByText('Click')).toBeVisible(); + await context.tracing.groupEnd(); + await context.tracing.groupEnd(); + await Promise.all([ page.waitForNavigation(), page.waitForResponse(server.PREFIX + '/frames/frame.html'), @@ -102,6 +111,67 @@ test('should open trace viewer on specific host', async ({ showTraceViewer }, te await expect(traceViewer.page).toHaveURL(/127.0.0.1/); }); +test('should show groups as tree in trace viewer', async ({ showTraceViewer }) => { + const traceViewer = await showTraceViewer([traceFile]); + await expect(traceViewer.actionTitles).toHaveText([ + /browserContext.newPage/, + /page.gotodata:text\/html,Hello world<\/html>/, + /page.setContent/, + /expect.toHaveTextlocator\('button'\)/, + /expect.toBeHiddengetByTestId\('amazing-btn'\)/, + /expect.toBeHiddengetByTestId\(\/amazing-btn-regex\/\)/, + /page.evaluate/, + /page.evaluate/, + /locator.clickgetByText\('Click'\)/, + /High-level Group/, + /page.waitForNavigation/, + /page.waitForResponse/, + /page.waitForTimeout/, + /page.gotohttp:\/\/localhost:\d+\/frames\/frame.html/, + /page.setViewportSize/, + ]); + await traceViewer.actionsTree.locator('.list-view-entry:has-text("High-level Group") .codicon-chevron-right').click(); + await traceViewer.actionsTree.locator('.list-view-entry:has-text("First Mid-level Group") .codicon-chevron-right').click(); + await traceViewer.actionsTree.locator('.list-view-entry:has-text("Second Mid-level Group") .codicon-chevron-right').click(); + await expect(traceViewer.actionTitles).toHaveText([ + /browserContext.newPage/, + /page.gotodata:text\/html,Hello world<\/html>/, + /page.setContent/, + /expect.toHaveTextlocator\('button'\)/, + /expect.toBeHiddengetByTestId\('amazing-btn'\)/, + /expect.toBeHiddengetByTestId\(\/amazing-btn-regex\/\)/, + /page.evaluate/, + /page.evaluate/, + /locator.clickgetByText\('Click'\)/, + /High-level Group/, + /First Mid-level Group/, + /locator\.clicklocator\('button'\)\.first\(\)/, + /Second Mid-level Group/, + /expect\.toBeVisiblegetByText\('Click'\)/, + /page.waitForNavigation/, + /page.waitForResponse/, + /page.waitForTimeout/, + /page.gotohttp:\/\/localhost:\d+\/frames\/frame.html/, + /page.setViewportSize/, + ]); + await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("First Mid-level Group") > .list-view-indent')).toHaveCount(1); + await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("Second Mid-level Group") > .list-view-indent')).toHaveCount(1); + await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("locator.clicklocator(\'button\').first()") > .list-view-indent')).toHaveCount(2); + await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("expect.toBeVisiblegetByText(\'Click\')") > .list-view-indent')).toHaveCount(2); + + await traceViewer.showSourceTab(); + await traceViewer.selectAction('High-level Group'); + await expect(traceViewer.sourceCodeTab.locator('.source-tab-file-name')).toHaveAttribute('title', __filename); + await expect(traceViewer.sourceCodeTab.locator('.source-line-running')).toHaveText(/\d+\s+await context.tracing.group\('High-level Group'\);/); + + await traceViewer.selectAction('First Mid-level Group'); + await expect(traceViewer.sourceCodeTab.locator('.source-tab-file-name')).toHaveAttribute('title', `${__dirname}/tracing.spec.ts`); + + await traceViewer.selectAction('Second Mid-level Group'); + await expect(traceViewer.sourceCodeTab.getByText(/Licensed under the Apache License/)).toBeVisible(); +}); + + test('should open simple trace viewer', async ({ showTraceViewer }) => { const traceViewer = await showTraceViewer([traceFile]); await expect(traceViewer.actionTitles).toHaveText([ @@ -114,6 +184,7 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => { /page.evaluate/, /page.evaluate/, /locator.clickgetByText\('Click'\)/, + /High-level Group/, /page.waitForNavigation/, /page.waitForResponse/, /page.waitForTimeout/, From 01829d9a58d8ad2e40ca9aa81237ae5a6c9745b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Wed, 16 Oct 2024 23:08:30 +0200 Subject: [PATCH 09/14] refactored review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- .../playwright-core/src/client/tracing.ts | 2 +- .../playwright-core/src/protocol/validator.ts | 20 ++++++------- .../server/dispatchers/tracingDispatcher.ts | 4 +-- .../src/server/trace/recorder/tracing.ts | 4 +-- packages/protocol/src/channels.ts | 28 ++++++++++--------- packages/protocol/src/protocol.yml | 27 ++++++++---------- 6 files changed, 42 insertions(+), 43 deletions(-) diff --git a/packages/playwright-core/src/client/tracing.ts b/packages/playwright-core/src/client/tracing.ts index 5c7796c103e73..a85d951388a89 100644 --- a/packages/playwright-core/src/client/tracing.ts +++ b/packages/playwright-core/src/client/tracing.ts @@ -52,7 +52,7 @@ export class Tracing extends ChannelOwner implements ap } async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}) { - await this._channel.tracingGroup({ name, options }); + await this._channel.tracingGroup({ name, location: options.location }); } async groupEnd() { diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 8b9ea68cdfd6a..1962826ae5041 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -26,13 +26,12 @@ scheme.StackFrame = tObject({ column: tNumber, function: tOptional(tString), }); -scheme.Location = tObject({ - file: tString, - line: tOptional(tNumber), - column: tOptional(tNumber), -}); scheme.Metadata = tObject({ - location: tOptional(tType('Location')), + location: tOptional(tObject({ + file: tString, + line: tOptional(tNumber), + column: tOptional(tNumber), + })), apiName: tOptional(tString), internal: tOptional(tBoolean), stepId: tOptional(tString), @@ -2281,9 +2280,6 @@ scheme.DialogAcceptParams = tObject({ scheme.DialogAcceptResult = tOptional(tObject({})); scheme.DialogDismissParams = tOptional(tObject({})); scheme.DialogDismissResult = tOptional(tObject({})); -scheme.TracingGroupOptions = tObject({ - location: tOptional(tType('Location')), -}); scheme.TracingInitializer = tOptional(tObject({})); scheme.TracingTracingStartParams = tObject({ name: tOptional(tString), @@ -2301,7 +2297,11 @@ scheme.TracingTracingStartChunkResult = tObject({ }); scheme.TracingTracingGroupParams = tObject({ name: tString, - options: tType('TracingGroupOptions'), + location: tOptional(tObject({ + file: tString, + line: tOptional(tNumber), + column: tOptional(tNumber), + })), }); scheme.TracingTracingGroupResult = tOptional(tObject({})); scheme.TracingTracingGroupEndParams = tOptional(tObject({})); diff --git a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts index adfcda84af5ae..5555de15d1e1b 100644 --- a/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/tracingDispatcher.ts @@ -43,8 +43,8 @@ export class TracingDispatcher extends Dispatcher { - const { name, options } = params; - await this._object.group(name, options, metadata); + const { name, location } = params; + await this._object.group(name, location, metadata); } async tracingGroupEnd(params: channels.TracingTracingGroupEndParams): Promise { diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index da603c72b8205..6566ab8f2ff42 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -196,11 +196,11 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps return { traceName: this._state.traceName }; } - async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}, metadata: CallMetadata): Promise { + async group(name: string, location: { file: string, line?: number, column?: number } | undefined, metadata: CallMetadata): Promise { if (!this._state) return; const stackFrames: StackFrame[] = []; - const { file, line, column } = options.location ?? metadata.location ?? {}; + const { file, line, column } = location ?? metadata.location ?? {}; if (file) { stackFrames.push({ file, diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index cf0bfa7caad39..073f9c5486684 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -144,14 +144,12 @@ export type StackFrame = { function?: string, }; -export type Location = { - file: string, - line?: number, - column?: number, -}; - export type Metadata = { - location?: Location, + location?: { + file: string, + line?: number, + column?: number, + }, apiName?: string, internal?: boolean, stepId?: string, @@ -4079,10 +4077,6 @@ export type DialogDismissResult = void; export interface DialogEvents { } -export type TracingGroupOptions = { - location?: Location, -}; - // ----------- Tracing ----------- export type TracingInitializer = {}; export interface TracingEventTarget { @@ -4122,10 +4116,18 @@ export type TracingTracingStartChunkResult = { }; export type TracingTracingGroupParams = { name: string, - options: TracingGroupOptions, + location?: { + file: string, + line?: number, + column?: number, + }, }; export type TracingTracingGroupOptions = { - + location?: { + file: string, + line?: number, + column?: number, + }, }; export type TracingTracingGroupResult = void; export type TracingTracingGroupEndParams = {}; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index faf0f458a17c9..95c2d6f8bba91 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -20,19 +20,17 @@ StackFrame: column: number function: string? -Location: - type: object - properties: - file: string - line: number? - column: number? - # This object can be send with any rpc call in the "metadata" field. Metadata: type: object properties: - location: Location? + location: + type: object? + properties: + file: string + line: number? + column: number? apiName: string? internal: boolean? # Test runner step id. @@ -3178,12 +3176,6 @@ Dialog: dismiss: - -TracingGroupOptions: - type: object - properties: - location: Location? - Tracing: type: interface @@ -3206,7 +3198,12 @@ Tracing: tracingGroup: parameters: name: string - options: TracingGroupOptions + location: + type: object? + properties: + file: string + line: number? + column: number? tracingGroupEnd: From 159bdb031297f0f6d31e8b9205294367f1dcca65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Thu, 17 Oct 2024 00:34:57 +0200 Subject: [PATCH 10/14] improved docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- .../src/server/trace/recorder/tracing.ts | 1 + packages/playwright-core/types/types.d.ts | 29 +++++++++++++++++-- packages/protocol/src/protocol.yml | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 6566ab8f2ff42..0184c1a2a5322 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -280,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(); diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 3c8bf3061de1f..e0ab9632e7027 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -21061,10 +21061,34 @@ export interface Tracing { /** * Creates a new inline group in the trace, causing any subsequent calls to belong to this group, until * [tracing.groupEnd()](https://playwright.dev/docs/api/class-tracing#tracing-group-end) is called. - * @param name Group name shown in the trace viewer. + * + * Groups can be nested and are similar to [test.step()](https://playwright.dev/docs/api/class-test#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()](https://playwright.dev/docs/api/class-test#test-step). + * + * + * **Usage** + * ```js + * await context.tracing.start({ screenshots: true, snapshots: true }); + * await context.tracing.group('Open Playwright.dev'); + * 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(); + * ``` + * + * @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. @@ -21084,7 +21108,8 @@ export interface Tracing { }): Promise; /** - * Closes the last opened inline group in the trace. + * Closes the currently open inline group in the trace. + * */ groupEnd(): Promise; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 95c2d6f8bba91..f46e973a7b713 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -3176,6 +3176,7 @@ Dialog: dismiss: + Tracing: type: interface From d1ff9ff0f406cdf724f073f02661564ae3bf87d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Thu, 17 Oct 2024 01:12:21 +0200 Subject: [PATCH 11/14] NOW improved docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- docs/src/api/class-tracing.md | 31 +++++++++++++++++++++-- packages/playwright-core/types/types.d.ts | 15 ++++++----- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/docs/src/api/class-tracing.md b/docs/src/api/class-tracing.md index 7607b3c53b1e3..7a19b9ef40bc4 100644 --- a/docs/src/api/class-tracing.md +++ b/docs/src/api/class-tracing.md @@ -286,11 +286,35 @@ To specify the final trace zip file name, you need to pass `path` option to Creates a new inline group in the trace, causing any subsequent calls to belong to this group, until [`method: Tracing.groupEnd`] is called. +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 trace viewer. +Group name shown in the actions tree in trace viewer. ### option: Tracing.group.location * since: v1.49 @@ -299,10 +323,13 @@ Group name shown in the trace viewer. - `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 last opened inline group in the trace. +Closes the currently open inline group in the trace. ## async method: Tracing.stop * since: v1.12 diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index e0ab9632e7027..8a40902206796 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -21062,16 +21062,17 @@ export interface Tracing { * Creates a new inline group in the trace, causing any subsequent calls to belong to this group, until * [tracing.groupEnd()](https://playwright.dev/docs/api/class-tracing#tracing-group-end) is called. * - * Groups can be nested and are similar to [test.step()](https://playwright.dev/docs/api/class-test#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()](https://playwright.dev/docs/api/class-test#test-step). + * 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(); @@ -21079,6 +21080,7 @@ export interface 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. @@ -21086,8 +21088,8 @@ export interface Tracing { */ 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. + * 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?: { /** @@ -21109,7 +21111,6 @@ export interface Tracing { /** * Closes the currently open inline group in the trace. - * */ groupEnd(): Promise; From 7c465c99702ebdee52941ab9e1c640b7e053f37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Thu, 17 Oct 2024 10:37:04 +0200 Subject: [PATCH 12/14] small change in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- docs/src/api/class-tracing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/api/class-tracing.md b/docs/src/api/class-tracing.md index 7a19b9ef40bc4..353a220516698 100644 --- a/docs/src/api/class-tracing.md +++ b/docs/src/api/class-tracing.md @@ -284,7 +284,7 @@ To specify the final trace zip file name, you need to pass `path` option to ## async method: Tracing.group * since: v1.49 -Creates a new inline group in the trace, causing any subsequent calls to belong to this group, until [`method: Tracing.groupEnd`] is called. +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. From 23cea2e1de3b67af168cb54b4c55f6e99f5cf449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Thu, 17 Oct 2024 10:43:01 +0200 Subject: [PATCH 13/14] small change in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- packages/playwright-core/types/types.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 8a40902206796..ee8cb8b1016a7 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -21059,8 +21059,8 @@ export interface Touchscreen { */ export interface Tracing { /** - * Creates a new inline group in the trace, causing any subsequent calls to belong to this group, until - * [tracing.groupEnd()](https://playwright.dev/docs/api/class-tracing#tracing-group-end) is called. + * 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. From 906faf7c8dbf1427e5eec5cde908be5dad423e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81?= Date: Fri, 18 Oct 2024 19:02:04 +0200 Subject: [PATCH 14/14] adopted to changes in main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: René --- tests/library/trace-viewer.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index bbebf5b41c9a3..10c0e490fa70b 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -130,9 +130,9 @@ test('should show groups as tree in trace viewer', async ({ showTraceViewer }) = /page.gotohttp:\/\/localhost:\d+\/frames\/frame.html/, /page.setViewportSize/, ]); - await traceViewer.actionsTree.locator('.list-view-entry:has-text("High-level Group") .codicon-chevron-right').click(); - await traceViewer.actionsTree.locator('.list-view-entry:has-text("First Mid-level Group") .codicon-chevron-right').click(); - await traceViewer.actionsTree.locator('.list-view-entry:has-text("Second Mid-level Group") .codicon-chevron-right').click(); + await traceViewer.actionsTree.locator('.tree-view-entry:has-text("High-level Group") .codicon-chevron-right').click(); + await traceViewer.actionsTree.locator('.tree-view-entry:has-text("First Mid-level Group") .codicon-chevron-right').click(); + await traceViewer.actionsTree.locator('.tree-view-entry:has-text("Second Mid-level Group") .codicon-chevron-right').click(); await expect(traceViewer.actionTitles).toHaveText([ /browserContext.newPage/, /page.gotodata:text\/html,Hello world<\/html>/, @@ -154,10 +154,10 @@ test('should show groups as tree in trace viewer', async ({ showTraceViewer }) = /page.gotohttp:\/\/localhost:\d+\/frames\/frame.html/, /page.setViewportSize/, ]); - await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("First Mid-level Group") > .list-view-indent')).toHaveCount(1); - await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("Second Mid-level Group") > .list-view-indent')).toHaveCount(1); - await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("locator.clicklocator(\'button\').first()") > .list-view-indent')).toHaveCount(2); - await expect(traceViewer.actionsTree.locator('.list-view-entry:has-text("expect.toBeVisiblegetByText(\'Click\')") > .list-view-indent')).toHaveCount(2); + await expect(traceViewer.actionsTree.locator('.tree-view-entry:has-text("First Mid-level Group") > .tree-view-indent')).toHaveCount(1); + await expect(traceViewer.actionsTree.locator('.tree-view-entry:has-text("Second Mid-level Group") > .tree-view-indent')).toHaveCount(1); + await expect(traceViewer.actionsTree.locator('.tree-view-entry:has-text("locator.clicklocator(\'button\').first()") > .tree-view-indent')).toHaveCount(2); + await expect(traceViewer.actionsTree.locator('.tree-view-entry:has-text("expect.toBeVisiblegetByText(\'Click\')") > .tree-view-indent')).toHaveCount(2); await traceViewer.showSourceTab(); await traceViewer.selectAction('High-level Group');