show in html reporter
This commit is contained in:
parent
13d305b959
commit
9a9d789e9b
|
|
@ -113,3 +113,11 @@ export const copy = () => {
|
||||||
<path d='M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z'></path>
|
<path d='M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z'></path>
|
||||||
</svg>;
|
</svg>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const paperclip = () => {
|
||||||
|
return (
|
||||||
|
<svg className='octicon' viewBox='0 0 16 16' width='16' height='16' aria-hidden='true'>
|
||||||
|
<path d='M12.212 3.02a1.753 1.753 0 0 0-2.478.003l-5.83 5.83a3.007 3.007 0 0 0-.88 2.127c0 .795.315 1.551.88 2.116.567.567 1.333.89 2.126.89.79 0 1.548-.321 2.116-.89l5.48-5.48a.75.75 0 0 1 1.061 1.06l-5.48 5.48a4.492 4.492 0 0 1-3.177 1.33c-1.2 0-2.345-.487-3.187-1.33a4.483 4.483 0 0 1-1.32-3.177c0-1.195.475-2.341 1.32-3.186l5.83-5.83a3.25 3.25 0 0 1 5.553 2.297c0 .863-.343 1.691-.953 2.301L7.439 12.39c-.375.377-.884.59-1.416.593a1.998 1.998 0 0 1-1.412-.593 1.992 1.992 0 0 1 0-2.828l5.48-5.48a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-5.48 5.48a.492.492 0 0 0 0 .707.499.499 0 0 0 .352.154.51.51 0 0 0 .356-.154l5.833-5.827a1.755 1.755 0 0 0 0-2.481Z'></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ const result: TestResult = {
|
||||||
location: { file: 'test.spec.ts', line: 82, column: 0 },
|
location: { file: 'test.spec.ts', line: 82, column: 0 },
|
||||||
steps: [],
|
steps: [],
|
||||||
count: 1,
|
count: 1,
|
||||||
|
attachments: [],
|
||||||
}],
|
}],
|
||||||
|
attachments: [],
|
||||||
}],
|
}],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
status: 'passed',
|
status: 'passed',
|
||||||
|
|
@ -142,6 +144,7 @@ const resultWithAttachment: TestResult = {
|
||||||
location: { file: 'test.spec.ts', line: 62, column: 0 },
|
location: { file: 'test.spec.ts', line: 62, column: 0 },
|
||||||
count: 1,
|
count: 1,
|
||||||
steps: [],
|
steps: [],
|
||||||
|
attachments: [1],
|
||||||
}],
|
}],
|
||||||
attachments: [{
|
attachments: [{
|
||||||
name: 'first attachment',
|
name: 'first attachment',
|
||||||
|
|
@ -183,3 +186,11 @@ test('should correctly render links in attachment name', async ({ mount }) => {
|
||||||
await expect(link).toHaveAttribute('href', 'https://github.com/microsoft/playwright/issues/31284');
|
await expect(link).toHaveAttribute('href', 'https://github.com/microsoft/playwright/issues/31284');
|
||||||
await expect(link).toHaveText('https://github.com/microsoft/playwright/issues/31284');
|
await expect(link).toHaveText('https://github.com/microsoft/playwright/issues/31284');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render attachments in step view', async ({ mount }) => {
|
||||||
|
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} run={0} anchor=''></TestCaseView>);
|
||||||
|
const steps = component.getByTestId('test-steps-chip');
|
||||||
|
await expect(steps).not.toContainText('attachment with inline link');
|
||||||
|
await steps.getByTitle('1 attachment').click();
|
||||||
|
await expect(steps).toContainText('attachment with inline link');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,8 @@
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachments-icon {
|
||||||
|
color: var(--color-fg-muted);
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import * as React from 'react';
|
||||||
import { TreeItem } from './treeItem';
|
import { TreeItem } from './treeItem';
|
||||||
import { msToString } from './utils';
|
import { msToString } from './utils';
|
||||||
import { AutoChip } from './chip';
|
import { AutoChip } from './chip';
|
||||||
|
import * as icons from './icons';
|
||||||
import { traceImage } from './images';
|
import { traceImage } from './images';
|
||||||
import { AttachmentLink, generateTraceUrl } from './links';
|
import { AttachmentLink, generateTraceUrl } from './links';
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
|
|
@ -188,23 +189,19 @@ const StepTreeItem: React.FC<{
|
||||||
depth: number,
|
depth: number,
|
||||||
attachments: TestAttachment[],
|
attachments: TestAttachment[],
|
||||||
}> = ({ step, depth, attachments }) => {
|
}> = ({ step, depth, attachments }) => {
|
||||||
if (step.category === 'attach') {
|
|
||||||
const attachmentName = step.title.match(/^attach "(.*)"$/)?.[1];
|
|
||||||
const matchingAttachments = attachments.filter(a => a.name === attachmentName);
|
|
||||||
if (matchingAttachments.length === 1) {
|
|
||||||
const [attachment] = matchingAttachments;
|
|
||||||
return <AttachmentLink attachment={attachment} depth={depth} openInNewTab={getAttachmentCategory(attachment) === 'html'} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <TreeItem title={<span>
|
return <TreeItem title={<span>
|
||||||
<span style={{ float: 'right' }}>{msToString(step.duration)}</span>
|
<span style={{ float: 'right' }}>{msToString(step.duration)}</span>
|
||||||
{statusIcon(step.error || step.duration === -1 ? 'failed' : 'passed')}
|
{statusIcon(step.error || step.duration === -1 ? 'failed' : 'passed')}
|
||||||
<span>{step.title}</span>
|
<span>{step.title}</span>
|
||||||
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>}
|
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>}
|
||||||
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>}
|
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>}
|
||||||
</span>} loadChildren={step.steps.length + (step.snippet ? 1 : 0) ? () => {
|
{step.attachments.length > 0 && <span className='attachments-icon' title={`${step.attachments} attachment${step.attachments.length > 1 ? 's' : ''}`}>{icons.paperclip()}</span>}
|
||||||
|
</span>} loadChildren={step.steps.length + step.attachments.length + (step.snippet ? 1 : 0) ? () => {
|
||||||
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} attachments={attachments}></StepTreeItem>);
|
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} attachments={attachments}></StepTreeItem>);
|
||||||
|
children.unshift(...step.attachments.map(a => {
|
||||||
|
const attachment = attachments[a];
|
||||||
|
return <AttachmentLink key={`attachment-${a}`} attachment={attachment} depth={depth + 1} openInNewTab={getAttachmentCategory(attachment) === 'html'}/>;
|
||||||
|
}));
|
||||||
if (step.snippet)
|
if (step.snippet)
|
||||||
children.unshift(<TestErrorView key='line' error={step.snippet}></TestErrorView>);
|
children.unshift(<TestErrorView key='line' error={step.snippet}></TestErrorView>);
|
||||||
return children;
|
return children;
|
||||||
|
|
|
||||||
|
|
@ -109,5 +109,6 @@ export type TestStep = {
|
||||||
snippet?: string;
|
snippet?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
steps: TestStep[];
|
steps: TestStep[];
|
||||||
|
attachments: number[];
|
||||||
count: number;
|
count: number;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -484,7 +484,7 @@ class HtmlBuilder {
|
||||||
duration: result.duration,
|
duration: result.duration,
|
||||||
startTime: result.startTime.toISOString(),
|
startTime: result.startTime.toISOString(),
|
||||||
retry: result.retry,
|
retry: result.retry,
|
||||||
steps: dedupeSteps(result.steps).map(s => this._createTestStep(s)),
|
steps: dedupeSteps(result.steps).map(s => this._createTestStep(s, result)),
|
||||||
errors: formatResultFailure(test, result, '', true).map(error => error.message),
|
errors: formatResultFailure(test, result, '', true).map(error => error.message),
|
||||||
status: result.status,
|
status: result.status,
|
||||||
attachments: this._serializeAttachments([
|
attachments: this._serializeAttachments([
|
||||||
|
|
@ -494,21 +494,27 @@ class HtmlBuilder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createTestStep(dedupedStep: DedupedStep): TestStep {
|
private _createTestStep(dedupedStep: DedupedStep, result: TestResultPublic): TestStep {
|
||||||
const { step, duration, count } = dedupedStep;
|
const { step, duration, count } = dedupedStep;
|
||||||
const result: TestStep = {
|
const testStep: TestStep = {
|
||||||
title: step.title,
|
title: step.title,
|
||||||
category: step.category,
|
category: step.category,
|
||||||
startTime: step.startTime.toISOString(),
|
startTime: step.startTime.toISOString(),
|
||||||
duration,
|
duration,
|
||||||
steps: dedupeSteps(step.steps).map(s => this._createTestStep(s)),
|
steps: dedupeSteps(step.steps).map(s => this._createTestStep(s, result)),
|
||||||
|
attachments: step.attachments.map(s => {
|
||||||
|
const index = result.attachments.indexOf(s);
|
||||||
|
if (index === -1)
|
||||||
|
throw new Error('Unexpected, attachment not found');
|
||||||
|
return index;
|
||||||
|
}),
|
||||||
location: this._relativeLocation(step.location),
|
location: this._relativeLocation(step.location),
|
||||||
error: step.error?.message,
|
error: step.error?.message,
|
||||||
count
|
count
|
||||||
};
|
};
|
||||||
if (result.location)
|
if (testStep.location)
|
||||||
this._stepsInFile.set(result.location.file, result);
|
this._stepsInFile.set(testStep.location.file, testStep);
|
||||||
return result;
|
return testStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _relativeLocation(location: Location | undefined): Location | undefined {
|
private _relativeLocation(location: Location | undefined): Location | undefined {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue