chore(ui): hide top-level attach actions (#35040)

This commit is contained in:
Simon Knott 2025-03-05 15:37:25 +01:00 committed by GitHub
parent 3773a73855
commit f44cd3050b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 39 additions and 18 deletions

View File

@ -0,0 +1,17 @@
/**
* 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.
*/
export const kTopLevelAttachmentPrefix = '_attach';

View File

@ -4,3 +4,4 @@
../util.ts ../util.ts
../utilBundle.ts ../utilBundle.ts
../matchers/** ../matchers/**
../isomorphic/util.ts

View File

@ -414,14 +414,10 @@ export class TestInfoImpl implements TestInfo {
_attach(attachment: TestInfo['attachments'][0], stepId: string | undefined) { _attach(attachment: TestInfo['attachments'][0], stepId: string | undefined) {
const index = this._attachmentsPush(attachment) - 1; const index = this._attachmentsPush(attachment) - 1;
if (stepId) { if (stepId)
this._stepMap.get(stepId)!.attachmentIndices.push(index); this._stepMap.get(stepId)!.attachmentIndices.push(index);
} else { else
// trace viewer has no means of representing attachments outside of a step, so we create an artificial action this._tracing.appendTopLevelAttachment(attachment);
const callId = `attach@${++this._lastStepId}`;
this._tracing.appendBeforeActionForStep(callId, this._findLastPredefinedStep(this._steps)?.stepId, 'attach', `attach "${attachment.name}"`, undefined, []);
this._tracing.appendAfterActionForStep(callId, undefined, [attachment]);
}
this._onAttach({ this._onAttach({
testId: this.testId, testId: this.testId,

View File

@ -20,6 +20,7 @@ import path from 'path';
import { ManualPromise, SerializedFS, calculateSha1, createGuid, monotonicTime } from 'playwright-core/lib/utils'; import { ManualPromise, SerializedFS, calculateSha1, createGuid, monotonicTime } from 'playwright-core/lib/utils';
import { yauzl, yazl } from 'playwright-core/lib/zipBundle'; import { yauzl, yazl } from 'playwright-core/lib/zipBundle';
import { kTopLevelAttachmentPrefix } from '../isomorphic/util';
import { filteredStackTrace } from '../util'; import { filteredStackTrace } from '../util';
import type { TestInfoImpl } from './testInfo'; import type { TestInfoImpl } from './testInfo';
@ -47,6 +48,7 @@ export class TestTracing {
private _tracesDir: string; private _tracesDir: string;
private _contextCreatedEvent: trace.ContextCreatedTraceEvent; private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
private _didFinishTestFunctionAndAfterEachHooks = false; private _didFinishTestFunctionAndAfterEachHooks = false;
private _lastActionId = 0;
constructor(testInfo: TestInfoImpl, artifactsDir: string) { constructor(testInfo: TestInfoImpl, artifactsDir: string) {
this._testInfo = testInfo; this._testInfo = testInfo;
@ -291,6 +293,16 @@ export class TestTracing {
}); });
} }
appendTopLevelAttachment(attachment: Attachment) {
// trace viewer has no means of representing attachments outside of a step,
// so we create an artificial action that's hidden in the UI
// the alternative would be to have one hidden step at the end with all top-level attachments,
// but that would delay useful information in live traces.
const callId = `${kTopLevelAttachmentPrefix}@${++this._lastActionId}`;
this.appendBeforeActionForStep(callId, undefined, kTopLevelAttachmentPrefix, `${kTopLevelAttachmentPrefix} "${attachment.name}"`, undefined, []);
this.appendAfterActionForStep(callId, undefined, [attachment]);
}
private _appendTraceEvent(event: trace.TraceEvent) { private _appendTraceEvent(event: trace.TraceEvent) {
this._traceEvents.push(event); this._traceEvents.push(event);
if (this._liveTraceFile) if (this._liveTraceFile)

View File

@ -20,6 +20,7 @@ import type * as trace from '@trace/trace';
import type { ActionTraceEvent } from '@trace/trace'; import type { ActionTraceEvent } from '@trace/trace';
import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries'; import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries';
import type { StackFrame } from '@protocol/channels'; import type { StackFrame } from '@protocol/channels';
import { kTopLevelAttachmentPrefix } from '@testIsomorphic/util';
const contextSymbol = Symbol('context'); const contextSymbol = Symbol('context');
const nextInContextSymbol = Symbol('next'); const nextInContextSymbol = Symbol('next');
@ -338,6 +339,8 @@ export function buildActionTree(actions: ActionTraceEventInContext[]): { rootIte
const rootItem: ActionTreeItem = { id: '', parent: undefined, children: [] }; const rootItem: ActionTreeItem = { id: '', parent: undefined, children: [] };
for (const item of itemMap.values()) { for (const item of itemMap.values()) {
if (item.action?.apiName.startsWith(kTopLevelAttachmentPrefix))
continue;
const parent = item.action!.parentId ? itemMap.get(item.action!.parentId) || rootItem : rootItem; const parent = item.action!.parentId ? itemMap.get(item.action!.parentId) || rootItem : rootItem;
parent.children.push(item); parent.children.push(item);
item.parent = parent; item.parent = parent;

View File

@ -223,7 +223,7 @@ test('should record trace', async ({ runInlineTest }) => {
'After Hooks', 'After Hooks',
'fixture: page', 'fixture: page',
'fixture: context', 'fixture: context',
'attach "_prompt-0"', '_attach "_prompt-0"',
'Worker Cleanup', 'Worker Cleanup',
'fixture: browser', 'fixture: browser',
]); ]);

View File

@ -129,7 +129,6 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => {
' fixture: context', ' fixture: context',
' fixture: request', ' fixture: request',
' apiRequestContext.dispose', ' apiRequestContext.dispose',
' attach "_prompt-0"',
'Worker Cleanup', 'Worker Cleanup',
' fixture: browser', ' fixture: browser',
]); ]);
@ -328,7 +327,6 @@ test('should not override trace file in afterAll', async ({ runInlineTest, serve
'After Hooks', 'After Hooks',
' fixture: page', ' fixture: page',
' fixture: context', ' fixture: context',
' attach "_prompt-0"',
' afterAll hook', ' afterAll hook',
' fixture: request', ' fixture: request',
' apiRequest.newContext', ' apiRequest.newContext',
@ -671,7 +669,6 @@ test('should show non-expect error in trace', async ({ runInlineTest }, testInfo
'After Hooks', 'After Hooks',
' fixture: page', ' fixture: page',
' fixture: context', ' fixture: context',
' attach "_prompt-0"',
'Worker Cleanup', 'Worker Cleanup',
' fixture: browser', ' fixture: browser',
]); ]);
@ -734,7 +731,7 @@ test('should not throw when attachment is missing', async ({ runInlineTest }, te
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-passes', 'trace.zip')); const trace = await parseTrace(testInfo.outputPath('test-results', 'a-passes', 'trace.zip'));
expect(trace.actionTree).toContain('attach "screenshot"'); expect(trace.apiNames).toContain('_attach "screenshot"');
}); });
test('should not throw when screenshot on failure fails', async ({ runInlineTest, server }, testInfo) => { test('should not throw when screenshot on failure fails', async ({ runInlineTest, server }, testInfo) => {
@ -983,7 +980,6 @@ test('should record nested steps, even after timeout', async ({ runInlineTest },
' page.setContent', ' page.setContent',
' fixture: page', ' fixture: page',
' fixture: context', ' fixture: context',
' attach "_prompt-0"',
' afterAll hook', ' afterAll hook',
' fixture: barPage', ' fixture: barPage',
' barPage setup', ' barPage setup',
@ -1043,7 +1039,6 @@ test('should attribute worker fixture teardown to the right test', async ({ runI
expect(trace2.actionTree).toEqual([ expect(trace2.actionTree).toEqual([
'Before Hooks', 'Before Hooks',
'After Hooks', 'After Hooks',
' attach "_prompt-0"',
'Worker Cleanup', 'Worker Cleanup',
' fixture: foo', ' fixture: foo',
' step in foo teardown', ' step in foo teardown',
@ -1153,7 +1148,6 @@ test('should not corrupt actions when no library trace is present', async ({ run
'After Hooks', 'After Hooks',
' fixture: foo', ' fixture: foo',
' expect.toBe', ' expect.toBe',
' attach "_prompt-0"',
'Worker Cleanup', 'Worker Cleanup',
]); ]);
}); });
@ -1184,7 +1178,6 @@ test('should record trace for manually created context in a failed test', async
'page.setContent', 'page.setContent',
'expect.toBe', 'expect.toBe',
'After Hooks', 'After Hooks',
' attach "_prompt-0"',
'Worker Cleanup', 'Worker Cleanup',
' fixture: browser', ' fixture: browser',
]); ]);
@ -1272,7 +1265,6 @@ test('should record trace after fixture teardown timeout', {
'page.evaluate', 'page.evaluate',
'After Hooks', 'After Hooks',
' fixture: fixture', ' fixture: fixture',
' attach "_prompt-0"',
'Worker Cleanup', 'Worker Cleanup',
' fixture: browser', ' fixture: browser',
]); ]);

View File

@ -458,7 +458,6 @@ test('attachments tab shows all but top-level .push attachments', async ({ runUI
- treeitem /step/: - treeitem /step/:
- group: - group:
- treeitem /attach \\"foo-attach\\"/ - treeitem /attach \\"foo-attach\\"/
- treeitem /attach \\"bar-push\\"/
- treeitem /attach \\"bar-attach\\"/ - treeitem /attach \\"bar-attach\\"/
`); `);
await page.getByRole('tab', { name: 'Attachments' }).click(); await page.getByRole('tab', { name: 'Attachments' }).click();

View File

@ -12,6 +12,7 @@
"baseUrl": "..", "baseUrl": "..",
"paths": { "paths": {
"@isomorphic/*": ["packages/playwright-core/src/utils/isomorphic/*"], "@isomorphic/*": ["packages/playwright-core/src/utils/isomorphic/*"],
"@testIsomorphic/*": ["packages/playwright/src/isomorphic/*"],
"@protocol/*": ["packages/protocol/src/*"], "@protocol/*": ["packages/protocol/src/*"],
"@recorder/*": ["packages/recorder/src/*"], "@recorder/*": ["packages/recorder/src/*"],
"@trace/*": ["packages/trace/src/*"], "@trace/*": ["packages/trace/src/*"],