Skip to content

Commit e201234

Browse files
authored
feat(trace-viewer): show test annotations in the trace viewer (#42060)
1 parent 7c5b614 commit e201234

8 files changed

Lines changed: 39 additions & 6 deletions

File tree

packages/isomorphic/trace/entries.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type ContextEntry = {
4242
hasSource: boolean;
4343
contextId: string;
4444
testTimeout?: number;
45+
annotations?: trace.TraceEventAnnotation[];
4546
};
4647

4748
export type PageEntry = {

packages/isomorphic/trace/traceModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export class TraceModel {
8989
readonly actionCounters: Map<string, number>;
9090
readonly traceUri: string;
9191
readonly testTimeout?: number;
92+
readonly annotations?: trace.TraceEventAnnotation[];
9293
readonly pagerefToTitle = new Map<string, string>();
9394
readonly contextToTitle = new Map<ContextEntry, string>();
9495

@@ -106,6 +107,7 @@ export class TraceModel {
106107
this.title = libraryContext?.title || '';
107108
this.options = libraryContext?.options || {};
108109
this.testTimeout = contexts.find(c => c.origin === 'testRunner')?.testTimeout;
110+
this.annotations = contexts.find(c => c.origin === 'testRunner')?.annotations;
109111
// Next call updates all timestamps for all events in library contexts, so it must be done first.
110112
this.actions = mergeActionsAndUpdateTiming(contexts);
111113
this.pages = ([] as PageEntry[]).concat(...contexts.map(c => c.pages));

packages/isomorphic/trace/traceModernizer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export class TraceModernizer {
9999
contextEntry.testIdAttributeName = event.testIdAttributeName;
100100
contextEntry.contextId = event.contextId ?? '';
101101
contextEntry.testTimeout = event.testTimeout;
102+
contextEntry.annotations = event.annotations;
102103
break;
103104
}
104105
case 'screencast-frame': {

packages/playwright/src/worker/testTracing.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export class TestTracing {
172172

173173
async stopIfNeeded() {
174174
this._contextCreatedEvent.testTimeout = this._testInfo.timeout;
175+
this._contextCreatedEvent.annotations = this._testInfo.annotations.map(({ type, description }) => ({ type, description }));
175176

176177
if (!this._options)
177178
return;
@@ -290,7 +291,7 @@ export class TestTracing {
290291
});
291292
}
292293

293-
appendAfterActionForStep(callId: string, error?: trace.SerializedError['error'], attachments: Attachment[] = [], annotations?: trace.AfterActionTraceEventAnnotation[]) {
294+
appendAfterActionForStep(callId: string, error?: trace.SerializedError['error'], attachments: Attachment[] = [], annotations?: trace.TraceEventAnnotation[]) {
294295
this._appendTraceEvent({
295296
type: 'after',
296297
callId,

packages/trace-viewer/src/ui/uiModeTraceView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export const TraceView: React.FC<{
9696
fallbackLocation={item.testFile}
9797
isLive={model?.isLive}
9898
status={item.treeItem?.status}
99-
annotations={item.testCase?.annotations ?? []}
99+
defaultAnnotations={item.testCase?.annotations ?? []}
100100
onOpenExternally={onOpenExternally}
101101
revealSource={revealSource}
102102
/>;

packages/trace-viewer/src/ui/workbench.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export type WorkbenchProps = {
5757
isLive?: boolean;
5858
hideTimeline?: boolean;
5959
status?: UITestStatus;
60-
annotations?: TestAnnotation[];
60+
defaultAnnotations?: TestAnnotation[];
6161
inert?: boolean;
6262
onOpenExternally?: (location: SourceLocation) => void;
6363
revealSource?: boolean;
@@ -72,7 +72,9 @@ export const Workbench: React.FunctionComponent<WorkbenchProps> = props => {
7272
};
7373

7474
const PartitionedWorkbench: React.FunctionComponent<WorkbenchProps & { partition: string }> = props => {
75-
const { partition, model, showSourcesFirst, rootDir, fallbackLocation, isLive, hideTimeline, status, annotations, inert, onOpenExternally, revealSource, testRunMetadata } = props;
75+
const { partition, model, showSourcesFirst, rootDir, fallbackLocation, isLive, hideTimeline, status, inert, onOpenExternally, revealSource, testRunMetadata } = props;
76+
// Default annotations come from the test model before the test runs, shown for the empty workbench / trace.
77+
const annotations = model?.annotations ?? props.defaultAnnotations;
7678

7779
// UI settings, shared for all models.
7880
const [selectedNavigatorTab, setSelectedNavigatorTab] = useSetting<string>('navigatorTab', 'actions');

packages/trace/src/trace.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export type ContextCreatedTraceEvent = {
9494
testIdAttributeName?: string,
9595
contextId?: string,
9696
testTimeout?: number,
97+
annotations?: TraceEventAnnotation[],
9798
};
9899

99100
export type ScreencastFrameTraceEvent = {
@@ -137,7 +138,7 @@ export type AfterActionTraceEventAttachment = {
137138
base64?: string;
138139
};
139140

140-
export type AfterActionTraceEventAnnotation = {
141+
export type TraceEventAnnotation = {
141142
type: string,
142143
description?: string
143144
};
@@ -149,7 +150,7 @@ export type AfterActionTraceEvent = {
149150
afterSnapshot?: string;
150151
error?: SerializedError['error'];
151152
attachments?: AfterActionTraceEventAttachment[];
152-
annotations?: AfterActionTraceEventAnnotation[];
153+
annotations?: TraceEventAnnotation[];
153154
result?: any;
154155
point?: Point;
155156
};

tests/playwright-test/playwright.trace.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,3 +1427,28 @@ test('should record custom test timeout in trace', async ({ runInlineTest }, tes
14271427
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
14281428
expect(trace.model.testTimeout).toBe(120_000);
14291429
});
1430+
1431+
test('should record test annotations in trace', async ({ runInlineTest }, testInfo) => {
1432+
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42035' });
1433+
1434+
const result = await runInlineTest({
1435+
'a.spec.ts': `
1436+
import { test, expect } from '@playwright/test';
1437+
test('pass', {
1438+
annotation: { type: 'note1', description: 'static annotation' },
1439+
}, async ({}) => {
1440+
test.info().annotations.push({ type: 'note2', description: 'dynamic annotation' });
1441+
test.info().annotations.push({ type: 'note3' });
1442+
});
1443+
`,
1444+
}, { trace: 'on' });
1445+
1446+
expect(result.exitCode).toBe(0);
1447+
expect(result.passed).toBe(1);
1448+
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
1449+
expect(trace.model.annotations).toEqual([
1450+
{ type: 'note1', description: 'static annotation' },
1451+
{ type: 'note2', description: 'dynamic annotation' },
1452+
{ type: 'note3' },
1453+
]);
1454+
});

0 commit comments

Comments
 (0)