Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/html-reporter/src/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ export const attachment = () => {
</svg>;
};

export const indirectAttachment = () => {
return <svg aria-hidden='true' height='16' viewBox='0 0 16 16' version='1.1' width='16' data-view-component='true' className='octicon color-fg-muted indirect-attachment-indicator'>
<path fillRule='evenodd' d='M3.5 1.75a.25.25 0 01.25-.25h3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h2.086a.25.25 0 01.177.073l2.914 2.914a.25.25 0 01.073.177v8.586a.25.25 0 01-.25.25h-.5a.75.75 0 000 1.5h.5A1.75 1.75 0 0014 13.25V4.664c0-.464-.184-.909-.513-1.237L10.573.513A1.75 1.75 0 009.336 0H3.75A1.75 1.75 0 002 1.75v11.5c0 .649.353 1.214.874 1.515a.75.75 0 10.752-1.298.25.25 0 01-.126-.217V1.75zM8.75 3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM6 5.25a.75.75 0 01.75-.75h.5a.75.75 0 010 1.5h-.5A.75.75 0 016 5.25zm2 1.5A.75.75 0 018.75 6h.5a.75.75 0 010 1.5h-.5A.75.75 0 018 6.75zm-1.25.75a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM8 9.75A.75.75 0 018.75 9h.5a.75.75 0 010 1.5h-.5A.75.75 0 018 9.75zm-.75.75a1.75 1.75 0 00-1.75 1.75v3c0 .414.336.75.75.75h2.5a.75.75 0 00.75-.75v-3a1.75 1.75 0 00-1.75-1.75h-.5zM7 12.25a.25.25 0 01.25-.25h.5a.25.25 0 01.25.25v2.25H7v-2.25z'></path>
</svg>;
};

export const cross = () => {
return <svg className='octicon color-text-danger' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'>
<path fillRule='evenodd' d='M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z'></path>
Expand Down
11 changes: 11 additions & 0 deletions packages/html-reporter/src/testResultView.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@
margin-right: 0;
}

.step-indirect-attachment-indicator {
display: flex;
flex: none;
padding: 4px;
opacity: 0.5;
}

.step-indirect-attachment-indicator .octicon {
margin-right: 0;
}

.step-duration {
flex: none;
white-space: nowrap;
Expand Down
10 changes: 10 additions & 0 deletions packages/html-reporter/src/testResultView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ function stepChildrenMatchFilter(step: TestStep, filterText: string): boolean {
return step.steps.some(s => stepMatchesFilter(s, filterText) || stepChildrenMatchFilter(s, filterText));
}

function stepHasDescendantAttachments(step: TestStep): boolean {
return step.steps.some(s => s.attachments.length > 0 || stepHasDescendantAttachments(s));
}

const StepTreeItem: React.FC<{
test: TestCase;
result: TestResult;
Expand Down Expand Up @@ -265,6 +269,12 @@ const StepTreeItem: React.FC<{
onClick={evt => { evt.stopPropagation(); }}>
{icons.attachment()}
</a>}
{step.attachments.length === 0 && stepHasDescendantAttachments(step) && <span
className='step-indirect-attachment-indicator'
title='contains attachment'
aria-label='contains attachment'>
{icons.indirectAttachment()}
</span>}
<span className='step-duration'>{msToString(step.duration)}</span>
</div>} loadChildren={step.steps.length || step.snippet ? () => {
const snippet = step.snippet ? [<CodeSnippet testId='test-snippet' key='line' code={step.snippet} />] : [];
Expand Down
27 changes: 27 additions & 0 deletions tests/playwright-test/reporter-html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,33 @@ for (const useIntermediateMergeReport of [true, false] as const) {
await expect(attachment).toBeInViewport();
});

test('parent step shows indirect attachment indicator', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'a.test.js': `
import { test, expect } from '@playwright/test';
test('passing', async ({}, testInfo) => {
await test.step('outer', async () => {
await test.step('inner', async () => {
await testInfo.attach('attachment', { body: 'content', contentType: 'text/plain' });
});
});
});
`,
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
expect(result.exitCode).toBe(0);

await showReport();
await page.getByRole('link', { name: 'passing' }).click();

// Collapsed parents show the indirect indicator.
await expect(page.getByLabel('outer').getByLabel('contains attachment')).toBeVisible();
// Expand outer; inner still shows the indicator (the attached leaf is below it).
await page.getByLabel('outer').click();
await expect(page.getByLabel('inner').getByLabel('contains attachment')).toBeVisible();
// The indirect indicator is non-interactive (no link/button role).
await expect(page.getByLabel('outer').getByLabel('contains attachment')).not.toHaveAttribute('href', /.+/);
});

test('step.attach have links', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'a.test.js': `
Expand Down
Loading