+ return
{ onClick?.(); setExpanded(!expanded); }} >
{loadChildren && !!expanded && icons.downArrow()}
{loadChildren && !expanded && icons.rightArrow()}
diff --git a/packages/html-reporter/src/types.ts b/packages/html-reporter/src/types.ts
index 733e88e8b9..ea2cf453bc 100644
--- a/packages/html-reporter/src/types.ts
+++ b/packages/html-reporter/src/types.ts
@@ -102,6 +102,7 @@ export type TestResult = {
export type TestStep = {
title: string;
+ category: 'hook' | 'fixture' | 'test.step' | 'expect' | 'attach' | string;
startTime: string;
duration: number;
location?: Location;
diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts
index 584c11bae8..776a4ae69a 100644
--- a/packages/playwright/src/reporters/html.ts
+++ b/packages/playwright/src/reporters/html.ts
@@ -498,6 +498,7 @@ class HtmlBuilder {
const { step, duration, count } = dedupedStep;
const result: TestStep = {
title: step.title,
+ category: step.category,
startTime: step.startTime.toISOString(),
duration,
steps: dedupeSteps(step.steps).map(s => this._createTestStep(s)),
diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts
index 11169fadff..dc5da7e35e 100644
--- a/tests/playwright-test/reporter-html.spec.ts
+++ b/tests/playwright-test/reporter-html.spec.ts
@@ -845,7 +845,7 @@ for (const useIntermediateMergeReport of [true, false] as const) {
'a.test.js': `
import { test, expect } from '@playwright/test';
test('passing', async ({ page }, testInfo) => {
- testInfo.attach('axe-report.html', {
+ await testInfo.attach('axe-report.html', {
contentType: 'text/html',
body: 'Axe Report
',
});
@@ -914,6 +914,27 @@ for (const useIntermediateMergeReport of [true, false] as const) {
]));
});
+ test('should link from attach step to attachment view', async ({ runInlineTest, page, showReport }) => {
+ const result = await runInlineTest({
+ 'a.test.js': `
+ import { test, expect } from '@playwright/test';
+ test('passing', async ({ page }, testInfo) => {
+ await testInfo.attach('foo', { body: 'bar' });
+ });
+ `,
+ }, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
+ expect(result.exitCode).toBe(0);
+
+ await showReport();
+ await page.getByRole('link', { name: 'passing' }).click();
+ await page.getByLabel('attach "foo"').getByTitle('link to attachment').click();
+
+ await page.waitForURL(url => {
+ const navState = new URLSearchParams(url.hash.slice(1));
+ return navState.get('attachment') === 'foo';
+ });
+ });
+
test('should strikethrough textual diff', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({
'helper.ts': `