trace viewer

This commit is contained in:
Simon Knott 2024-12-20 12:51:51 +01:00
parent 3b0f0a9798
commit 4fe6927c5d
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
2 changed files with 16 additions and 11 deletions

View file

@ -27,12 +27,12 @@ import type { Annotation, FullConfigInternal, FullProjectInternal } from '../com
import type { FullConfig, Location } from '../../types/testReporter'; import type { FullConfig, Location } from '../../types/testReporter';
import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util'; import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util';
import { TestTracing } from './testTracing'; import { TestTracing } from './testTracing';
import type { Attachment } from './testTracing';
import type { StackFrame } from '@protocol/channels'; import type { StackFrame } from '@protocol/channels';
import { testInfoError } from './util'; import { testInfoError } from './util';
export interface TestStepInternal { export interface TestStepInternal {
complete(result: { error?: Error | unknown, attachments?: Attachment[], suggestedRebaseline?: string }): void; complete(result: { error?: Error | unknown, suggestedRebaseline?: string }): void;
attachmentIndexes: number[];
stepId: string; stepId: string;
title: string; title: string;
category: 'hook' | 'fixture' | 'test.step' | 'expect' | 'attach' | string; category: 'hook' | 'fixture' | 'test.step' | 'expect' | 'attach' | string;
@ -70,6 +70,7 @@ export class TestInfoImpl implements TestInfo {
readonly _projectInternal: FullProjectInternal; readonly _projectInternal: FullProjectInternal;
readonly _configInternal: FullConfigInternal; readonly _configInternal: FullConfigInternal;
private readonly _steps: TestStepInternal[] = []; private readonly _steps: TestStepInternal[] = [];
private readonly _stepMap = new Map<string, TestStepInternal>();
_onDidFinishTestFunction: (() => Promise<void>) | undefined; _onDidFinishTestFunction: (() => Promise<void>) | undefined;
_hasNonRetriableError = false; _hasNonRetriableError = false;
_hasUnhandledError = false; _hasUnhandledError = false;
@ -248,7 +249,7 @@ export class TestInfoImpl implements TestInfo {
return zones.zoneData<ExpectZone>('expectZone')?.stepId; return zones.zoneData<ExpectZone>('expectZone')?.stepId;
} }
_addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps' | 'attachments'>, parentStep?: TestStepInternal): TestStepInternal { _addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps' | 'attachmentIndexes'>, parentStep?: TestStepInternal): TestStepInternal {
const stepId = `${data.category}@${++this._lastStepId}`; const stepId = `${data.category}@${++this._lastStepId}`;
if (data.isStage) { if (data.isStage) {
@ -267,10 +268,12 @@ export class TestInfoImpl implements TestInfo {
} }
data.location = data.location || filteredStack[0]; data.location = data.location || filteredStack[0];
const attachmentIndexes: number[] = [];
const step: TestStepInternal = { const step: TestStepInternal = {
stepId, stepId,
...data, ...data,
steps: [], steps: [],
attachmentIndexes,
complete: result => { complete: result => {
if (step.endWallTime) if (step.endWallTime)
return; return;
@ -307,11 +310,13 @@ export class TestInfoImpl implements TestInfo {
}; };
this._onStepEnd(payload); this._onStepEnd(payload);
const errorForTrace = step.error ? { name: '', message: step.error.message || '', stack: step.error.stack } : undefined; const errorForTrace = step.error ? { name: '', message: step.error.message || '', stack: step.error.stack } : undefined;
this._tracing.appendAfterActionForStep(stepId, errorForTrace, result.attachments); const attachments = attachmentIndexes.map(i => this.attachments[i]);
this._tracing.appendAfterActionForStep(stepId, errorForTrace, attachments);
} }
}; };
const parentStepList = parentStep ? parentStep.steps : this._steps; const parentStepList = parentStep ? parentStep.steps : this._steps;
parentStepList.push(step); parentStepList.push(step);
this._stepMap.set(stepId, step);
const payload: StepBeginPayload = { const payload: StepBeginPayload = {
testId: this.testId, testId: this.testId,
stepId, stepId,
@ -410,13 +415,14 @@ export class TestInfoImpl implements TestInfo {
title: `attach "${name}"`, title: `attach "${name}"`,
category: 'attach', category: 'attach',
}); });
const attachment = await normalizeAndSaveAttachment(this.outputPath(), name, options); this._attach(await normalizeAndSaveAttachment(this.outputPath(), name, options), step.stepId);
this._attach(attachment, step.stepId); step.complete({});
step.complete({ attachments: [attachment] });
} }
private _attach(attachment: TestInfo['attachments'][0], stepId: string | undefined) { private _attach(attachment: TestInfo['attachments'][0], stepId: string | undefined) {
this._attachmentsPush(attachment); const index = this._attachmentsPush(attachment) - 1;
if (stepId)
this._stepMap.get(stepId)!.attachmentIndexes.push(index);
this._onAttach({ this._onAttach({
testId: this.testId, testId: this.testId,
name: attachment.name, name: attachment.name,

View file

@ -392,7 +392,7 @@ test('should show custom fixture titles in actions tree', async ({ runUITest })
]); ]);
}); });
test('all attachments are shown in attachments tab', async ({ runUITest }) => { test('attachments tab shows all but top-level .push attachments', async ({ runUITest }) => {
const { page } = await runUITest({ const { page } = await runUITest({
'a.test.ts': ` 'a.test.ts': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
@ -429,11 +429,10 @@ test('all attachments are shown in attachments tab', async ({ runUITest }) => {
- treeitem /attach \\"bar-attach\\"/ - treeitem /attach \\"bar-attach\\"/
`); `);
await page.getByRole('tab', { name: 'Attachments' }).click(); await page.getByRole('tab', { name: 'Attachments' }).click();
await expect(page.getByRole('tabpanel', { name: 'Attachments' }), 'attachments tab shows all').toMatchAriaSnapshot(` await expect(page.getByRole('tabpanel', { name: 'Attachments' })).toMatchAriaSnapshot(`
- tabpanel: - tabpanel:
- button /foo-push/ - button /foo-push/
- button /foo-attach/ - button /foo-attach/
- button /bar-push/
- button /bar-attach/ - button /bar-attach/
`); `);
}); });