mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(tracing) Adding groups to trace via pw-api (#33081)
Signed-off-by: René <snooz@posteo.de> Signed-off-by: René <41592183+Snooz82@users.noreply.github.com> Co-authored-by: Dmitry Gozman <dgozman@gmail.com>
This commit is contained in:
		
							parent
							
								
									da4614ea7c
								
							
						
					
					
						commit
						fa10bcd5a3
					
				@ -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
 | 
					To specify the final trace zip file name, you need to pass `path` option to
 | 
				
			||||||
[`method: Tracing.stopChunk`] instead.
 | 
					[`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
 | 
					## async method: Tracing.stop
 | 
				
			||||||
* since: v1.12
 | 
					* since: v1.12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -168,7 +168,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
 | 
				
			|||||||
    return channel;
 | 
					    return channel;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async _wrapApiCall<R>(func: (apiZone: ApiZone) => Promise<R>, isInternal = false): Promise<R> {
 | 
					  async _wrapApiCall<R>(func: (apiZone: ApiZone) => Promise<R>, isInternal?: boolean): Promise<R> {
 | 
				
			||||||
    const logger = this._logger;
 | 
					    const logger = this._logger;
 | 
				
			||||||
    const apiZone = zones.zoneData<ApiZone>('apiZone');
 | 
					    const apiZone = zones.zoneData<ApiZone>('apiZone');
 | 
				
			||||||
    if (apiZone)
 | 
					    if (apiZone)
 | 
				
			||||||
@ -178,7 +178,8 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
 | 
				
			|||||||
    let apiName: string | undefined = stackTrace.apiName;
 | 
					    let apiName: string | undefined = stackTrace.apiName;
 | 
				
			||||||
    const frames: channels.StackFrame[] = stackTrace.frames;
 | 
					    const frames: channels.StackFrame[] = stackTrace.frames;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isInternal = isInternal || this._isInternalType;
 | 
					    if (isInternal === undefined)
 | 
				
			||||||
 | 
					      isInternal = this._isInternalType;
 | 
				
			||||||
    if (isInternal)
 | 
					    if (isInternal)
 | 
				
			||||||
      apiName = undefined;
 | 
					      apiName = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,18 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
 | 
				
			|||||||
    await this._startCollectingStacks(traceName);
 | 
					    await this._startCollectingStacks(traceName);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async group(name: string, options: { location?: { file: string, line?: number, column?: number } } = {}) {
 | 
				
			||||||
 | 
					    await this._wrapApiCall(async () => {
 | 
				
			||||||
 | 
					      await this._channel.tracingGroup({ name, location: options.location });
 | 
				
			||||||
 | 
					    }, false);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async groupEnd() {
 | 
				
			||||||
 | 
					    await this._wrapApiCall(async () => {
 | 
				
			||||||
 | 
					      await this._channel.tracingGroupEnd();
 | 
				
			||||||
 | 
					    }, false);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _startCollectingStacks(traceName: string) {
 | 
					  private async _startCollectingStacks(traceName: string) {
 | 
				
			||||||
    if (!this._isTracing) {
 | 
					    if (!this._isTracing) {
 | 
				
			||||||
      this._isTracing = true;
 | 
					      this._isTracing = true;
 | 
				
			||||||
 | 
				
			|||||||
@ -2297,6 +2297,17 @@ scheme.TracingTracingStartChunkParams = tObject({
 | 
				
			|||||||
scheme.TracingTracingStartChunkResult = tObject({
 | 
					scheme.TracingTracingStartChunkResult = tObject({
 | 
				
			||||||
  traceName: tString,
 | 
					  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({
 | 
					scheme.TracingTracingStopChunkParams = tObject({
 | 
				
			||||||
  mode: tEnum(['archive', 'discard', 'entries']),
 | 
					  mode: tEnum(['archive', 'discard', 'entries']),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type * as channels from '@protocol/channels';
 | 
					import type * as channels from '@protocol/channels';
 | 
				
			||||||
 | 
					import type { CallMetadata } from '@protocol/callMetadata';
 | 
				
			||||||
import type { Tracing } from '../trace/recorder/tracing';
 | 
					import type { Tracing } from '../trace/recorder/tracing';
 | 
				
			||||||
import { ArtifactDispatcher } from './artifactDispatcher';
 | 
					import { ArtifactDispatcher } from './artifactDispatcher';
 | 
				
			||||||
import { Dispatcher, existingDispatcher } from './dispatcher';
 | 
					import { Dispatcher, existingDispatcher } from './dispatcher';
 | 
				
			||||||
@ -41,6 +42,15 @@ export class TracingDispatcher extends Dispatcher<Tracing, channels.TracingChann
 | 
				
			|||||||
    return await this._object.startChunk(params);
 | 
					    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> {
 | 
					  async tracingStopChunk(params: channels.TracingTracingStopChunkParams): Promise<channels.TracingTracingStopChunkResult> {
 | 
				
			||||||
    const { artifact, entries } = await this._object.stopChunk(params);
 | 
					    const { artifact, entries } = await this._object.stopChunk(params);
 | 
				
			||||||
    return { artifact: artifact ? ArtifactDispatcher.from(this, artifact) : undefined, entries };
 | 
					    return { artifact: artifact ? ArtifactDispatcher.from(this, artifact) : undefined, entries };
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ import fs from 'fs';
 | 
				
			|||||||
import os from 'os';
 | 
					import os from 'os';
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
import type { NameValue } from '../../../common/types';
 | 
					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 { commandsWithTracingSnapshots } from '../../../protocol/debug';
 | 
				
			||||||
import { assert, createGuid, monotonicTime, SerializedFS, removeFolders, eventsHelper, type RegisteredListener } from '../../../utils';
 | 
					import { assert, createGuid, monotonicTime, SerializedFS, removeFolders, eventsHelper, type RegisteredListener } from '../../../utils';
 | 
				
			||||||
import { Artifact } from '../../artifact';
 | 
					import { Artifact } from '../../artifact';
 | 
				
			||||||
@ -61,6 +61,7 @@ type RecordingState = {
 | 
				
			|||||||
  traceSha1s: Set<string>,
 | 
					  traceSha1s: Set<string>,
 | 
				
			||||||
  recording: boolean;
 | 
					  recording: boolean;
 | 
				
			||||||
  callIds: Set<string>;
 | 
					  callIds: Set<string>;
 | 
				
			||||||
 | 
					  groupStack: string[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const kScreencastOptions = { width: 800, height: 600, quality: 90 };
 | 
					const kScreencastOptions = { width: 800, height: 600, quality: 90 };
 | 
				
			||||||
@ -148,6 +149,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
 | 
				
			|||||||
      networkSha1s: new Set(),
 | 
					      networkSha1s: new Set(),
 | 
				
			||||||
      recording: false,
 | 
					      recording: false,
 | 
				
			||||||
      callIds: new Set(),
 | 
					      callIds: new Set(),
 | 
				
			||||||
 | 
					      groupStack: [],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    this._fs.mkdir(this._state.resourcesDir);
 | 
					    this._fs.mkdir(this._state.resourcesDir);
 | 
				
			||||||
    this._fs.writeFile(this._state.networkFile, '');
 | 
					    this._fs.writeFile(this._state.networkFile, '');
 | 
				
			||||||
@ -194,6 +196,53 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
 | 
				
			|||||||
    return { traceName: this._state.traceName };
 | 
					    return { traceName: this._state.traceName };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _currentGroupId(): string | undefined {
 | 
				
			||||||
 | 
					    return this._state?.groupStack.length ? this._state.groupStack[this._state.groupStack.length - 1] : undefined;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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: 'tracingGroup',
 | 
				
			||||||
 | 
					      params: { },
 | 
				
			||||||
 | 
					      stepId: metadata.stepId,
 | 
				
			||||||
 | 
					      stack: stackFrames,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (this._currentGroupId())
 | 
				
			||||||
 | 
					      event.parentId = this._currentGroupId();
 | 
				
			||||||
 | 
					    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() {
 | 
					  private _startScreencast() {
 | 
				
			||||||
    if (!(this._context instanceof BrowserContext))
 | 
					    if (!(this._context instanceof BrowserContext))
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@ -236,6 +285,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
 | 
				
			|||||||
      throw new Error(`Tracing is already stopping`);
 | 
					      throw new Error(`Tracing is already stopping`);
 | 
				
			||||||
    if (this._state.recording)
 | 
					    if (this._state.recording)
 | 
				
			||||||
      throw new Error(`Must stop trace file before stopping tracing`);
 | 
					      throw new Error(`Must stop trace file before stopping tracing`);
 | 
				
			||||||
 | 
					    await this._closeAllGroups();
 | 
				
			||||||
    this._harTracer.stop();
 | 
					    this._harTracer.stop();
 | 
				
			||||||
    this.flushHarEntries();
 | 
					    this.flushHarEntries();
 | 
				
			||||||
    await this._fs.syncAndGetError();
 | 
					    await this._fs.syncAndGetError();
 | 
				
			||||||
@ -264,6 +314,11 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
 | 
				
			|||||||
    await this._fs.syncAndGetError();
 | 
					    await this._fs.syncAndGetError();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async _closeAllGroups() {
 | 
				
			||||||
 | 
					    while (this._currentGroupId())
 | 
				
			||||||
 | 
					      await this.groupEnd();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async stopChunk(params: TracingTracingStopChunkParams): Promise<{ artifact?: Artifact, entries?: NameValue[] }> {
 | 
					  async stopChunk(params: TracingTracingStopChunkParams): Promise<{ artifact?: Artifact, entries?: NameValue[] }> {
 | 
				
			||||||
    if (this._isStopping)
 | 
					    if (this._isStopping)
 | 
				
			||||||
      throw new Error(`Tracing is already stopping`);
 | 
					      throw new Error(`Tracing is already stopping`);
 | 
				
			||||||
@ -276,6 +331,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
 | 
				
			|||||||
      return {};
 | 
					      return {};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await this._closeAllGroups();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this._context.instrumentation.removeListener(this);
 | 
					    this._context.instrumentation.removeListener(this);
 | 
				
			||||||
    eventsHelper.removeEventListeners(this._eventListeners);
 | 
					    eventsHelper.removeEventListeners(this._eventListeners);
 | 
				
			||||||
    if (this._state.options.screenshots)
 | 
					    if (this._state.options.screenshots)
 | 
				
			||||||
@ -354,7 +411,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
 | 
					  onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
 | 
				
			||||||
    // IMPORTANT: no awaits before this._appendTraceEvent in this method.
 | 
					    // IMPORTANT: no awaits before this._appendTraceEvent in this method.
 | 
				
			||||||
    const event = createBeforeActionTraceEvent(metadata);
 | 
					    const event = createBeforeActionTraceEvent(metadata, this._currentGroupId());
 | 
				
			||||||
    if (!event)
 | 
					    if (!event)
 | 
				
			||||||
      return Promise.resolve();
 | 
					      return Promise.resolve();
 | 
				
			||||||
    sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling();
 | 
					    sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling();
 | 
				
			||||||
@ -571,10 +628,10 @@ export function shouldCaptureSnapshot(metadata: CallMetadata): boolean {
 | 
				
			|||||||
  return commandsWithTracingSnapshots.has(metadata.type + '.' + metadata.method);
 | 
					  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'))
 | 
					  if (metadata.internal || metadata.method.startsWith('tracing'))
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  return {
 | 
					  const event: trace.BeforeActionTraceEvent = {
 | 
				
			||||||
    type: 'before',
 | 
					    type: 'before',
 | 
				
			||||||
    callId: metadata.id,
 | 
					    callId: metadata.id,
 | 
				
			||||||
    startTime: metadata.startTime,
 | 
					    startTime: metadata.startTime,
 | 
				
			||||||
@ -585,6 +642,9 @@ function createBeforeActionTraceEvent(metadata: CallMetadata): trace.BeforeActio
 | 
				
			|||||||
    stepId: metadata.stepId,
 | 
					    stepId: metadata.stepId,
 | 
				
			||||||
    pageId: metadata.pageId,
 | 
					    pageId: metadata.pageId,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  if (parentId)
 | 
				
			||||||
 | 
					    event.parentId = parentId;
 | 
				
			||||||
 | 
					  return event;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createInputActionTraceEvent(metadata: CallMetadata): trace.InputActionTraceEvent | null {
 | 
					function createInputActionTraceEvent(metadata: CallMetadata): trace.InputActionTraceEvent | null {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										56
									
								
								packages/playwright-core/types/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										56
									
								
								packages/playwright-core/types/types.d.ts
									
									
									
									
										vendored
									
									
								
							@ -21055,6 +21055,62 @@ export interface Touchscreen {
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export interface Tracing {
 | 
					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.
 | 
					   * Start tracing.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions,
 | 
				
			|||||||
import * as playwrightLibrary from 'playwright-core';
 | 
					import * as playwrightLibrary from 'playwright-core';
 | 
				
			||||||
import { createGuid, debugMode, addInternalStackPrefix, isString, asLocator, jsonStringifyForceASCII } from 'playwright-core/lib/utils';
 | 
					import { createGuid, debugMode, addInternalStackPrefix, isString, asLocator, jsonStringifyForceASCII } from 'playwright-core/lib/utils';
 | 
				
			||||||
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
 | 
					import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
 | 
				
			||||||
import type { TestInfoImpl } from './worker/testInfo';
 | 
					import type { TestInfoImpl, TestStepInternal } from './worker/testInfo';
 | 
				
			||||||
import { rootTestType } from './common/testType';
 | 
					import { rootTestType } from './common/testType';
 | 
				
			||||||
import type { ContextReuseMode } from './common/config';
 | 
					import type { ContextReuseMode } from './common/config';
 | 
				
			||||||
import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation';
 | 
					import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation';
 | 
				
			||||||
@ -255,20 +255,28 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
 | 
					    const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
 | 
				
			||||||
    await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
 | 
					    await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const tracingGroupSteps: TestStepInternal[] = [];
 | 
				
			||||||
    const csiListener: ClientInstrumentationListener = {
 | 
					    const csiListener: ClientInstrumentationListener = {
 | 
				
			||||||
      onApiCallBegin: (apiName: string, params: Record<string, any>, frames: StackFrame[], userData: any, out: { stepId?: string }) => {
 | 
					      onApiCallBegin: (apiName: string, params: Record<string, any>, frames: StackFrame[], userData: any, out: { stepId?: string }) => {
 | 
				
			||||||
        const testInfo = currentTestInfo();
 | 
					        const testInfo = currentTestInfo();
 | 
				
			||||||
        if (!testInfo || apiName.includes('setTestIdAttribute'))
 | 
					        if (!testInfo || apiName.includes('setTestIdAttribute'))
 | 
				
			||||||
          return { userObject: null };
 | 
					          return { userObject: null };
 | 
				
			||||||
 | 
					        if (apiName === 'tracing.groupEnd') {
 | 
				
			||||||
 | 
					          tracingGroupSteps.pop();
 | 
				
			||||||
 | 
					          return { userObject: null };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        const step = testInfo._addStep({
 | 
					        const step = testInfo._addStep({
 | 
				
			||||||
          location: frames[0] as any,
 | 
					          location: frames[0] as any,
 | 
				
			||||||
          category: 'pw:api',
 | 
					          category: 'pw:api',
 | 
				
			||||||
          title: renderApiCall(apiName, params),
 | 
					          title: renderApiCall(apiName, params),
 | 
				
			||||||
          apiName,
 | 
					          apiName,
 | 
				
			||||||
          params,
 | 
					          params,
 | 
				
			||||||
        });
 | 
					        }, tracingGroupSteps[tracingGroupSteps.length - 1]);
 | 
				
			||||||
        userData.userObject = step;
 | 
					        userData.userObject = step;
 | 
				
			||||||
        out.stepId = step.stepId;
 | 
					        out.stepId = step.stepId;
 | 
				
			||||||
 | 
					        if (apiName === 'tracing.group')
 | 
				
			||||||
 | 
					          tracingGroupSteps.push(step);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      onApiCallEnd: (userData: any, error?: Error) => {
 | 
					      onApiCallEnd: (userData: any, error?: Error) => {
 | 
				
			||||||
        const step = userData.userObject;
 | 
					        const step = userData.userObject;
 | 
				
			||||||
 | 
				
			|||||||
@ -238,15 +238,15 @@ export class TestInfoImpl implements TestInfo {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps'>): TestStepInternal {
 | 
					  _addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps'>, parentStep?: TestStepInternal): TestStepInternal {
 | 
				
			||||||
    const stepId = `${data.category}@${++this._lastStepId}`;
 | 
					    const stepId = `${data.category}@${++this._lastStepId}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let parentStep: TestStepInternal | undefined;
 | 
					 | 
				
			||||||
    if (data.isStage) {
 | 
					    if (data.isStage) {
 | 
				
			||||||
      // Predefined stages form a fixed hierarchy - use the current one as parent.
 | 
					      // Predefined stages form a fixed hierarchy - use the current one as parent.
 | 
				
			||||||
      parentStep = this._findLastStageStep(this._steps);
 | 
					      parentStep = this._findLastStageStep(this._steps);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      parentStep = zones.zoneData<TestStepInternal>('stepZone');
 | 
					      if (!parentStep)
 | 
				
			||||||
 | 
					        parentStep = zones.zoneData<TestStepInternal>('stepZone');
 | 
				
			||||||
      if (!parentStep) {
 | 
					      if (!parentStep) {
 | 
				
			||||||
        // If no parent step on stack, assume the current stage as parent.
 | 
					        // If no parent step on stack, assume the current stage as parent.
 | 
				
			||||||
        parentStep = this._findLastStageStep(this._steps);
 | 
					        parentStep = this._findLastStageStep(this._steps);
 | 
				
			||||||
 | 
				
			|||||||
@ -4086,6 +4086,8 @@ export interface TracingChannel extends TracingEventTarget, Channel {
 | 
				
			|||||||
  _type_Tracing: boolean;
 | 
					  _type_Tracing: boolean;
 | 
				
			||||||
  tracingStart(params: TracingTracingStartParams, metadata?: CallMetadata): Promise<TracingTracingStartResult>;
 | 
					  tracingStart(params: TracingTracingStartParams, metadata?: CallMetadata): Promise<TracingTracingStartResult>;
 | 
				
			||||||
  tracingStartChunk(params: TracingTracingStartChunkParams, metadata?: CallMetadata): Promise<TracingTracingStartChunkResult>;
 | 
					  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>;
 | 
					  tracingStopChunk(params: TracingTracingStopChunkParams, metadata?: CallMetadata): Promise<TracingTracingStopChunkResult>;
 | 
				
			||||||
  tracingStop(params?: TracingTracingStopParams, metadata?: CallMetadata): Promise<TracingTracingStopResult>;
 | 
					  tracingStop(params?: TracingTracingStopParams, metadata?: CallMetadata): Promise<TracingTracingStopResult>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -4113,6 +4115,25 @@ export type TracingTracingStartChunkOptions = {
 | 
				
			|||||||
export type TracingTracingStartChunkResult = {
 | 
					export type TracingTracingStartChunkResult = {
 | 
				
			||||||
  traceName: string,
 | 
					  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 = {
 | 
					export type TracingTracingStopChunkParams = {
 | 
				
			||||||
  mode: 'archive' | 'discard' | 'entries',
 | 
					  mode: 'archive' | 'discard' | 'entries',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -3198,6 +3198,18 @@ Tracing:
 | 
				
			|||||||
      returns:
 | 
					      returns:
 | 
				
			||||||
        traceName: string
 | 
					        traceName: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tracingGroup:
 | 
				
			||||||
 | 
					      parameters:
 | 
				
			||||||
 | 
					        name: string
 | 
				
			||||||
 | 
					        location:
 | 
				
			||||||
 | 
					          type: object?
 | 
				
			||||||
 | 
					          properties:
 | 
				
			||||||
 | 
					            file: string
 | 
				
			||||||
 | 
					            line: number?
 | 
				
			||||||
 | 
					            column: number?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tracingGroupEnd:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tracingStopChunk:
 | 
					    tracingStopChunk:
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
        mode:
 | 
					        mode:
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,7 @@ export type TraceViewerFixtures = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class TraceViewerPage {
 | 
					class TraceViewerPage {
 | 
				
			||||||
  actionTitles: Locator;
 | 
					  actionTitles: Locator;
 | 
				
			||||||
 | 
					  actionsTree: Locator;
 | 
				
			||||||
  callLines: Locator;
 | 
					  callLines: Locator;
 | 
				
			||||||
  consoleLines: Locator;
 | 
					  consoleLines: Locator;
 | 
				
			||||||
  logLines: Locator;
 | 
					  logLines: Locator;
 | 
				
			||||||
@ -46,9 +47,11 @@ class TraceViewerPage {
 | 
				
			|||||||
  networkRequests: Locator;
 | 
					  networkRequests: Locator;
 | 
				
			||||||
  metadataTab: Locator;
 | 
					  metadataTab: Locator;
 | 
				
			||||||
  snapshotContainer: Locator;
 | 
					  snapshotContainer: Locator;
 | 
				
			||||||
 | 
					  sourceCodeTab: Locator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(public page: Page) {
 | 
					  constructor(public page: Page) {
 | 
				
			||||||
    this.actionTitles = page.locator('.action-title');
 | 
					    this.actionTitles = page.locator('.action-title');
 | 
				
			||||||
 | 
					    this.actionsTree = page.getByTestId('actions-tree');
 | 
				
			||||||
    this.callLines = page.locator('.call-tab .call-line');
 | 
					    this.callLines = page.locator('.call-tab .call-line');
 | 
				
			||||||
    this.logLines = page.getByTestId('log-list').locator('.list-view-entry');
 | 
					    this.logLines = page.getByTestId('log-list').locator('.list-view-entry');
 | 
				
			||||||
    this.consoleLines = page.locator('.console-line');
 | 
					    this.consoleLines = page.locator('.console-line');
 | 
				
			||||||
@ -59,6 +62,7 @@ class TraceViewerPage {
 | 
				
			|||||||
    this.networkRequests = page.getByTestId('network-list').locator('.list-view-entry');
 | 
					    this.networkRequests = page.getByTestId('network-list').locator('.list-view-entry');
 | 
				
			||||||
    this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]');
 | 
					    this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]');
 | 
				
			||||||
    this.metadataTab = page.getByTestId('metadata-view');
 | 
					    this.metadataTab = page.getByTestId('metadata-view');
 | 
				
			||||||
 | 
					    this.sourceCodeTab = page.getByTestId('source-code');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async actionIconsText(action: string) {
 | 
					  async actionIconsText(action: string) {
 | 
				
			||||||
 | 
				
			|||||||
@ -103,6 +103,106 @@ test('should open trace viewer on specific host', async ({ showTraceViewer }, te
 | 
				
			|||||||
  await expect(traceViewer.page).toHaveURL(/127.0.0.1/);
 | 
					  await expect(traceViewer.page).toHaveURL(/127.0.0.1/);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('should show groups as tree in trace viewer', async ({ runAndTrace, page, context }) => {
 | 
				
			||||||
 | 
					  const outerGroup = 'Outer Group';
 | 
				
			||||||
 | 
					  const outerGroupContent = 'locator.clickgetByText(\'Click\')';
 | 
				
			||||||
 | 
					  const firstInnerGroup = 'First Inner Group';
 | 
				
			||||||
 | 
					  const firstInnerGroupContent = 'locator.clicklocator(\'button\').first()';
 | 
				
			||||||
 | 
					  const secondInnerGroup = 'Second Inner Group';
 | 
				
			||||||
 | 
					  const secondInnerGroupContent = 'expect.toBeVisiblegetByText(\'Click\')';
 | 
				
			||||||
 | 
					  const expandedFailure = 'Expanded Failure';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const traceViewer = await test.step('create trace with groups', async () => {
 | 
				
			||||||
 | 
					    return await runAndTrace(async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await page.goto(`data:text/html,<!DOCTYPE html><html>Hello world</html>`);
 | 
				
			||||||
 | 
					        await page.setContent('<!DOCTYPE html><button>Click</button>');
 | 
				
			||||||
 | 
					        async function doClick() {
 | 
				
			||||||
 | 
					          await page.getByText('Click').click();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await context.tracing.group(outerGroup);  // Outer group
 | 
				
			||||||
 | 
					        await doClick();
 | 
				
			||||||
 | 
					        await context.tracing.group(firstInnerGroup, { 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(secondInnerGroup, { location: { file: __filename } });
 | 
				
			||||||
 | 
					        await expect(page.getByText('Click')).toBeVisible();
 | 
				
			||||||
 | 
					        await context.tracing.groupEnd();
 | 
				
			||||||
 | 
					        await context.tracing.groupEnd();
 | 
				
			||||||
 | 
					        await context.tracing.group(expandedFailure);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          await expect(page.getByText('Click')).toBeHidden({ timeout: 1 });
 | 
				
			||||||
 | 
					        } catch (e) {}
 | 
				
			||||||
 | 
					        await context.tracing.groupEnd();
 | 
				
			||||||
 | 
					        await page.evaluate(() => console.log('ungrouped'), null);
 | 
				
			||||||
 | 
					      } catch (e) {}
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }, { box: true });
 | 
				
			||||||
 | 
					  const treeViewEntries = traceViewer.actionsTree.locator('.tree-view-entry');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await test.step('check automatic expansion of groups on failure', async () => {
 | 
				
			||||||
 | 
					    await expect(traceViewer.actionTitles).toHaveText([
 | 
				
			||||||
 | 
					      /page.gotodata:text\/html,<!DOCTYPE html><html>Hello world<\/html>/,
 | 
				
			||||||
 | 
					      /page.setContent/,
 | 
				
			||||||
 | 
					      outerGroup,
 | 
				
			||||||
 | 
					      expandedFailure,
 | 
				
			||||||
 | 
					      /expect.toBeHiddengetByText\('Click'\)/,
 | 
				
			||||||
 | 
					      /page.evaluate/,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    await expect(traceViewer.actionsTree.locator('.tree-view-entry.selected > .tree-view-indent')).toHaveCount(1);
 | 
				
			||||||
 | 
					    await expect(traceViewer.actionsTree.locator('.tree-view-entry.selected')).toHaveText(/expect.toBeHiddengetByText\('Click'\)/);
 | 
				
			||||||
 | 
					    await treeViewEntries.filter({ hasText: expandedFailure }).locator('.codicon-chevron-down').click();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  await test.step('check outer group', async () => {
 | 
				
			||||||
 | 
					    await treeViewEntries.filter({ hasText: outerGroup }).locator('.codicon-chevron-right').click();
 | 
				
			||||||
 | 
					    await expect(traceViewer.actionTitles).toHaveText([
 | 
				
			||||||
 | 
					      /page.gotodata:text\/html,<!DOCTYPE html><html>Hello world<\/html>/,
 | 
				
			||||||
 | 
					      /page.setContent/,
 | 
				
			||||||
 | 
					      outerGroup,
 | 
				
			||||||
 | 
					      outerGroupContent,
 | 
				
			||||||
 | 
					      firstInnerGroup,
 | 
				
			||||||
 | 
					      secondInnerGroup,
 | 
				
			||||||
 | 
					      expandedFailure,
 | 
				
			||||||
 | 
					      /page.evaluate/,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    await expect(treeViewEntries.filter({ hasText: firstInnerGroup }).locator(' > .tree-view-indent')).toHaveCount(1);
 | 
				
			||||||
 | 
					    await expect(treeViewEntries.filter({ hasText: secondInnerGroup }).locator(' > .tree-view-indent')).toHaveCount(1);
 | 
				
			||||||
 | 
					    await test.step('check automatic location of groups', async () => {
 | 
				
			||||||
 | 
					      await traceViewer.showSourceTab();
 | 
				
			||||||
 | 
					      await traceViewer.selectAction(outerGroup);
 | 
				
			||||||
 | 
					      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\(outerGroup\);  \/\/ Outer group/);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  await test.step('check inner groups', async () => {
 | 
				
			||||||
 | 
					    await treeViewEntries.filter({ hasText: firstInnerGroup }).locator('.codicon-chevron-right').click();
 | 
				
			||||||
 | 
					    await treeViewEntries.filter({ hasText: secondInnerGroup }).locator('.codicon-chevron-right').click();
 | 
				
			||||||
 | 
					    await expect(traceViewer.actionTitles).toHaveText([
 | 
				
			||||||
 | 
					      /page.gotodata:text\/html,<!DOCTYPE html><html>Hello world<\/html>/,
 | 
				
			||||||
 | 
					      /page.setContent/,
 | 
				
			||||||
 | 
					      outerGroup,
 | 
				
			||||||
 | 
					      outerGroupContent,
 | 
				
			||||||
 | 
					      firstInnerGroup,
 | 
				
			||||||
 | 
					      firstInnerGroupContent,
 | 
				
			||||||
 | 
					      secondInnerGroup,
 | 
				
			||||||
 | 
					      secondInnerGroupContent,
 | 
				
			||||||
 | 
					      expandedFailure,
 | 
				
			||||||
 | 
					      /page.evaluate/,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    await expect(treeViewEntries.filter({ hasText: firstInnerGroupContent }).locator(' > .tree-view-indent')).toHaveCount(2);
 | 
				
			||||||
 | 
					    await expect(treeViewEntries.filter({ hasText: secondInnerGroupContent }).locator(' > .tree-view-indent')).toHaveCount(2);
 | 
				
			||||||
 | 
					    await test.step('check location with file, line, column', async () => {
 | 
				
			||||||
 | 
					      await traceViewer.selectAction(firstInnerGroup);
 | 
				
			||||||
 | 
					      await expect(traceViewer.sourceCodeTab.locator('.source-tab-file-name')).toHaveAttribute('title', `${__dirname}/tracing.spec.ts`);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await test.step('check location with file', async () => {
 | 
				
			||||||
 | 
					      await traceViewer.selectAction(secondInnerGroup);
 | 
				
			||||||
 | 
					      await expect(traceViewer.sourceCodeTab.getByText(/Licensed under the Apache License/)).toBeVisible();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('should open simple trace viewer', async ({ showTraceViewer }) => {
 | 
					test('should open simple trace viewer', async ({ showTraceViewer }) => {
 | 
				
			||||||
  const traceViewer = await showTraceViewer([traceFile]);
 | 
					  const traceViewer = await showTraceViewer([traceFile]);
 | 
				
			||||||
  await expect(traceViewer.actionTitles).toHaveText([
 | 
					  await expect(traceViewer.actionTitles).toHaveText([
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user