diff --git a/packages/playwright-core/src/utils/traceUtils.ts b/packages/playwright-core/src/utils/traceUtils.ts
index 2f7cb1cda2..b39d7c9998 100644
--- a/packages/playwright-core/src/utils/traceUtils.ts
+++ b/packages/playwright-core/src/utils/traceUtils.ts
@@ -119,12 +119,12 @@ export async function saveTraceFile(fileName: string, traceEvents: TraceEvent[],
for (const attachment of (event.attachments || []).filter(a => !!a.path)) {
await fs.promises.readFile(attachment.path!).then(content => {
const sha1 = calculateSha1(content);
+ attachment.sha1 = sha1;
+ delete attachment.path;
if (sha1s.has(sha1))
return;
sha1s.add(sha1);
zipFile.addBuffer(content, 'resources/' + sha1);
- attachment.sha1 = sha1;
- delete attachment.path;
}).catch();
}
}
diff --git a/packages/playwright-test/src/worker/testInfo.ts b/packages/playwright-test/src/worker/testInfo.ts
index 957f64d760..110657222a 100644
--- a/packages/playwright-test/src/worker/testInfo.ts
+++ b/packages/playwright-test/src/worker/testInfo.ts
@@ -331,7 +331,13 @@ export class TestInfoImpl implements TestInfo {
// ------------ TestInfo methods ------------
async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) {
+ const step = this._addStep({
+ title: 'attach',
+ category: 'attach',
+ wallTime: Date.now(),
+ });
this.attachments.push(await normalizeAndSaveAttachment(this.outputPath(), name, options));
+ step.complete({});
}
outputPath(...pathSegments: string[]){
diff --git a/packages/trace-viewer/src/ui/attachmentsTab.css b/packages/trace-viewer/src/ui/attachmentsTab.css
new file mode 100644
index 0000000000..46eae4326b
--- /dev/null
+++ b/packages/trace-viewer/src/ui/attachmentsTab.css
@@ -0,0 +1,40 @@
+/*
+ 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.
+*/
+
+.attachments-tab {
+ flex: auto;
+ line-height: 24px;
+ white-space: pre;
+ overflow: auto;
+ user-select: text;
+}
+
+.attachments-section {
+ padding-left: 6px;
+ font-weight: bold;
+ text-transform: uppercase;
+ font-size: 10px;
+ color: var(--vscode-sideBarTitle-foreground);
+ line-height: 24px;
+}
+
+.attachments-section:not(:first-child) {
+ border-top: 1px solid var(--vscode-panel-border);
+}
+
+.attachment-item {
+ margin: 4px 8px;
+}
diff --git a/packages/trace-viewer/src/ui/attachmentsTab.tsx b/packages/trace-viewer/src/ui/attachmentsTab.tsx
new file mode 100644
index 0000000000..c74cea8b6f
--- /dev/null
+++ b/packages/trace-viewer/src/ui/attachmentsTab.tsx
@@ -0,0 +1,59 @@
+/**
+ * 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.
+ */
+
+import type { ActionTraceEvent } from '@trace/trace';
+import * as React from 'react';
+import './attachmentsTab.css';
+import { ImageDiffView } from '@web/components/imageDiffView';
+import type { TestAttachment } from '@web/components/imageDiffView';
+
+export const AttachmentsTab: React.FunctionComponent<{
+ action: ActionTraceEvent | undefined,
+}> = ({ action }) => {
+ if (!action)
+ return null;
+ const expected = action.attachments?.find(a => a.name.endsWith('-expected.png') && (a.path || a.sha1)) as TestAttachment | undefined;
+ const actual = action.attachments?.find(a => a.name.endsWith('-actual.png') && (a.path || a.sha1)) as TestAttachment | undefined;
+ const diff = action.attachments?.find(a => a.name.endsWith('-diff.png') && (a.path || a.sha1)) as TestAttachment | undefined;
+
+ return