Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/src/test-api/class-testoptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,10 @@ export default defineConfig({
- `mode` <[TraceMode]<"off"|"on"|"retain-on-failure"|"on-first-retry"|"on-all-retries"|"retain-on-first-failure"|"retain-on-failure-and-retries">> Trace recording mode.
- `attachments` ?<[boolean]> Whether to include test attachments. Defaults to true. Optional.
- `screenshots` ?<[boolean]> Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview. Defaults to true. Optional.
- `snapshots` ?<[boolean]> Whether to capture DOM snapshot on every action. Defaults to true. Optional.
- `snapshots` ?<[boolean]|[Object]> Which snapshots to capture on every action. Passing `true` is a shortcut for `{ dom: true }`. Defaults to true. Optional.
- `dom` ?<[boolean]> Capture DOM snapshot on every action and record network activity. Optional.
- `aria` ?<[boolean]> Capture aria snapshot of the page on every action. Optional.
- `screen` ?<[boolean]> Capture a screenshot of the page on every action. Optional.
- `sources` ?<[boolean]> Whether to include source files for trace actions. Defaults to true. Optional.

Whether to record trace for each test. Defaults to `'off'`. The initial run of a test is the "first run"; subsequent runs caused by [retries](../test-retries.md) are "retries".
Expand Down
2 changes: 2 additions & 0 deletions packages/isomorphic/ariaSnapshotRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { AriaNodeJSON, AriaSnapshotJSON } from './ariaSnapshot';

export type AriaSnapshotYamlOptions = {
convertStringsToRegex?: boolean;
lineToNode?: Map<number, AriaNodeJSON>;
};

export function renderAriaSnapshotAsYaml(snapshot: AriaSnapshotJSON, options: AriaSnapshotYamlOptions = {}): string {
Expand Down Expand Up @@ -82,6 +83,7 @@ export function renderAriaSnapshotAsYaml(snapshot: AriaSnapshotJSON, options: Ar
return;
}

options.lineToNode?.set(lines.length, node);
const escapedKey = indent(depth) + '- ' + yamlEscapeKeyIfNeeded(createKey(node));
const props: [string, string][] = [];
if (node.url !== undefined)
Expand Down
2 changes: 2 additions & 0 deletions packages/isomorphic/trace/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export type ContextEntry = {
pages: PageEntry[];
resources: ResourceSnapshot[];
actions: ActionEntry[];
screenshots: trace.ScreenshotTraceEvent[];
ariaSnapshots: trace.AriaSnapshotTraceEvent[];
events: (trace.EventTraceEvent | trace.ConsoleMessageTraceEvent)[];
stdio: trace.StdioTraceEvent[];
errors: trace.ErrorTraceEvent[];
Expand Down
2 changes: 2 additions & 0 deletions packages/isomorphic/trace/traceLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ function createEmptyContext(): ContextEntry {
pages: [],
resources: [],
actions: [],
screenshots: [],
ariaSnapshots: [],
events: [],
errors: [],
stdio: [],
Expand Down
16 changes: 16 additions & 0 deletions packages/isomorphic/trace/traceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export class TraceModel {
readonly annotations?: trace.TraceEventAnnotation[];
readonly pagerefToTitle = new Map<string, string>();
private _eventsForAction = new Map<ActionEntry, (trace.EventTraceEvent | trace.ConsoleMessageTraceEvent)[]>();
private _screenshots = new Map<string, trace.ScreenshotTraceEvent>();
private _ariaSnapshots = new Map<string, trace.AriaSnapshotTraceEvent>();

constructor(traceUri: string, contexts: ContextEntry[]) {
const libraryContext = contexts.find(context => context.origin === 'library');
Expand Down Expand Up @@ -119,6 +121,12 @@ export class TraceModel {
for (const entry of context.resources)
this.resources.push({ ...entry, id: `${entry.pageref ?? lastApiContextId}-${entry.startedDateTime}-${entry.request.url}`, contextTitle });
}
for (const context of contexts) {
for (const event of context.screenshots || [])
this._screenshots.set(`${event.callId}/${event.phase}`, event);
for (const event of context.ariaSnapshots || [])
this._ariaSnapshots.set(`${event.callId}/${event.phase}`, event);
}
this.attachments = this.actions.flatMap(action => action.attachments?.map(attachment => ({ ...attachment, callId: action.callId, traceUri })) ?? []);
this.visibleAttachments = this.attachments.filter(attachment => !attachment.name.startsWith('_'));

Expand Down Expand Up @@ -148,6 +156,14 @@ export class TraceModel {
return this.actions.findLast(a => a.error);
}

screenshotForCall(callId: string, phase: trace.ActionPhase): trace.ScreenshotTraceEvent | undefined {
return this._screenshots.get(`${callId}/${phase}`);
}

ariaSnapshotForCall(callId: string, phase: trace.ActionPhase): trace.AriaSnapshotTraceEvent | undefined {
return this._ariaSnapshots.get(`${callId}/${phase}`);
}

eventsForAction(action: ActionEntry): (trace.EventTraceEvent | trace.ConsoleMessageTraceEvent)[] {
let result = this._eventsForAction.get(action);
if (result)
Expand Down
8 changes: 8 additions & 0 deletions packages/isomorphic/trace/traceModernizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ export class TraceModernizer {
this._pageEntry(event.pageId).screencastFrames.push(event);
break;
}
case 'screenshot': {
contextEntry.screenshots.push(event);
break;
}
case 'aria-snapshot': {
contextEntry.ariaSnapshots.push(event);
break;
}
case 'before': {
this._actionMap.set(event.callId, { ...event, type: 'action', endTime: 0, log: [] });
break;
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/trace/recorder/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
}

private async _captureScreenshot(progress: Progress, page: Page, phase: trace.ActionPhase): Promise<void> {
const buffer = await page.screenshot(progress, { type: 'png' }).catch(() => undefined);
const buffer = await page.screenshot(progress, { type: 'png', scale: 'css' }).catch(() => undefined);
if (!buffer || !this._state?.recording)
return;
const file = `screenshots/${progress.metadata.id}-${phase}.png`;
Expand All @@ -520,7 +520,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
}

private async _captureAriaSnapshot(progress: Progress, page: Page, phase: trace.ActionPhase): Promise<void> {
const snapshot = await ariaSnapshotJSONForFrame(progress, page.mainFrame(), undefined, { mode: 'default' }).catch(() => null);
const snapshot = await ariaSnapshotJSONForFrame(progress, page.mainFrame(), undefined, { mode: 'default', boxes: true }).catch(() => null);
if (!snapshot || !this._state?.recording)
return;
const buffer = Buffer.from(JSON.stringify(snapshot), 'utf8');
Expand Down
10 changes: 8 additions & 2 deletions packages/playwright/src/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import path from 'path';
import { packageJSON } from '../package';
import { getPackageJsonPath, mergeObjects, takeFirst } from '../util';

import type { Config, Fixtures, Metadata, Project, ReporterDescription } from '../../types/test';
import type { Config, Fixtures, Metadata, PlaywrightWorkerOptions, Project, ReporterDescription } from '../../types/test';
import type { TestRunnerPluginRegistration } from '../plugins';
import type { ConfigCLIOverrides } from './ipc';
import type { Location } from '../../types/testReporter';
Expand Down Expand Up @@ -171,6 +171,12 @@ export class FullProjectInternal {
const testDir = takeFirst(pathResolve(configDir, projectConfig.testDir), pathResolve(configDir, config.testDir), fullConfig.configDir);
this.snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate);

const use = mergeObjects(config.use, projectConfig.use, configCLIOverrides.use);
// `--trace <mode>` only forces the tracing mode, preserving other trace options from the config.
const configTrace = takeFirst((projectConfig.use as Partial<PlaywrightWorkerOptions> | undefined)?.trace, (config.use as Partial<PlaywrightWorkerOptions> | undefined)?.trace);
if (typeof configCLIOverrides.use?.trace === 'string' && typeof configTrace === 'object' && configTrace)
use.trace = { ...configTrace, mode: configCLIOverrides.use.trace };

this.project = {
grep: takeFirst(projectConfig.grep, config.grep, defaultGrep),
grepInvert: takeFirst(projectConfig.grepInvert, config.grepInvert, null),
Expand All @@ -186,7 +192,7 @@ export class FullProjectInternal {
testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/*.@(spec|test).?(c|m)[jt]s?(x)'),
timeout: takeFirst(configCLIOverrides.debug === 'inspector' ? 0 : undefined, configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
use: mergeObjects(config.use, projectConfig.use, configCLIOverrides.use),
use,
dependencies: projectConfig.dependencies || [],
teardown: projectConfig.teardown,
ignoreSnapshots: takeFirst(configCLIOverrides.ignoreSnapshots, projectConfig.ignoreSnapshots, config.ignoreSnapshots, false),
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/worker/testTracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const version: trace.VERSION = 8;
let traceOrdinal = 0;

type TraceFixtureValue = PlaywrightWorkerOptions['trace'] | undefined;
type TraceOptions = { screenshots: boolean, snapshots: boolean, sources: boolean, attachments: boolean, live: boolean, mode: TraceMode };
type TraceOptions = { screenshots: boolean, snapshots: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, sources: boolean, attachments: boolean, live: boolean, mode: TraceMode };

export class TestTracing {
private _testInfo: TestInfoImpl;
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7191,7 +7191,7 @@ export interface PlaywrightWorkerOptions {
*
* Learn more about [recording trace](https://playwright.dev/docs/test-use-options#recording-options).
*/
trace: TraceMode | /** deprecated */ 'retry-with-trace' | { mode: TraceMode, snapshots?: boolean, screenshots?: boolean, sources?: boolean, attachments?: boolean };
trace: TraceMode | /** deprecated */ 'retry-with-trace' | { mode: TraceMode, snapshots?: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, screenshots?: boolean, sources?: boolean, attachments?: boolean };
/**
* Whether to record video for each test. Defaults to `'off'`. The initial run of a test is the "first run";
* subsequent runs caused by [retries](https://playwright.dev/docs/test-retries) are "retries".
Expand Down
78 changes: 78 additions & 0 deletions packages/trace-viewer/src/ui/ariaModeView.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Copyright (c) Microsoft Corporation.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.aria-mode-view {
flex: auto;
}

.aria-mode-screenshot {
flex: 1 1 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 10px;
position: relative;
}

.aria-mode-screenshot img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
box-shadow: 0 12px 28px 0 rgba(0, 0, 0, .2), 0 2px 4px 0 rgba(0, 0, 0, .1);
}

.aria-mode-highlight {
position: absolute;
pointer-events: none;
background-color: #6fa8dc7f;
outline: 1px solid #6fa8dc;
}

.aria-mode-snapshot {
flex: 1 1 0;
border-left: 1px solid var(--vscode-panel-border);
}

.aria-mode-lines {
flex: auto;
overflow: auto;
padding: 8px 0;
font-family: var(--vscode-editor-font-family);
font-size: 12px;
line-height: 18px;
}

.aria-mode-line {
white-space: pre;
padding: 0 8px;
}

.aria-mode-line-hoverable:hover {
background-color: var(--vscode-list-hoverBackground);
}

.aria-mode-role {
color: var(--vscode-debugTokenExpression-name);
}

.aria-mode-string {
color: var(--vscode-debugTokenExpression-string);
}

.aria-mode-attribute {
color: var(--vscode-debugTokenExpression-number);
}
Loading
Loading