feat(html): group similar items in the report (#11160)
This commit is contained in:
parent
976dedda45
commit
7bfa6f9b5f
|
|
@ -31,12 +31,14 @@ const result: TestResult = {
|
||||||
startTime: new Date(100).toUTCString(),
|
startTime: new Date(100).toUTCString(),
|
||||||
duration: 10,
|
duration: 10,
|
||||||
location: { file: 'test.spec.ts', line: 62, column: 0 },
|
location: { file: 'test.spec.ts', line: 62, column: 0 },
|
||||||
|
count: 1,
|
||||||
steps: [{
|
steps: [{
|
||||||
title: 'Inner step',
|
title: 'Inner step',
|
||||||
startTime: new Date(200).toUTCString(),
|
startTime: new Date(200).toUTCString(),
|
||||||
duration: 10,
|
duration: 10,
|
||||||
location: { file: 'test.spec.ts', line: 82, column: 0 },
|
location: { file: 'test.spec.ts', line: 82, column: 0 },
|
||||||
steps: [],
|
steps: [],
|
||||||
|
count: 1,
|
||||||
}],
|
}],
|
||||||
}],
|
}],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,24 @@
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.test-result-counter {
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--color-canvas-default);
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(prefers-color-scheme: light) {
|
||||||
|
.test-result-counter {
|
||||||
|
background: var(--color-scale-gray-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(prefers-color-scheme: dark) {
|
||||||
|
.test-result-counter {
|
||||||
|
background: var(--color-scale-gray-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
@media only screen and (max-width: 600px) {
|
||||||
.test-result {
|
.test-result {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ const StepTreeItem: React.FC<{
|
||||||
<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.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) ? () => {
|
</span>} loadChildren={step.steps.length + (step.snippet ? 1 : 0) ? () => {
|
||||||
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1}></StepTreeItem>);
|
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1}></StepTreeItem>);
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ export type TestStep = {
|
||||||
snippet?: string;
|
snippet?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
steps: TestStep[];
|
steps: TestStep[];
|
||||||
|
count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TestEntry = {
|
type TestEntry = {
|
||||||
|
|
@ -478,7 +479,8 @@ class HtmlBuilder {
|
||||||
snippet: step.snippet,
|
snippet: step.snippet,
|
||||||
steps: step.steps.map(s => this._createTestStep(s)),
|
steps: step.steps.map(s => this._createTestStep(s)),
|
||||||
location: step.location,
|
location: step.location,
|
||||||
error: step.error
|
error: step.error,
|
||||||
|
count: step.count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ export type JsonTestStep = {
|
||||||
steps: JsonTestStep[];
|
steps: JsonTestStep[];
|
||||||
location?: Location;
|
location?: Location;
|
||||||
snippet?: string;
|
snippet?: string;
|
||||||
|
count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RawReporter {
|
class RawReporter {
|
||||||
|
|
@ -220,7 +221,7 @@ class RawReporter {
|
||||||
status: result.status,
|
status: result.status,
|
||||||
error: formatResultFailure(test, result, '', true).tokens.join('').trim(),
|
error: formatResultFailure(test, result, '', true).tokens.join('').trim(),
|
||||||
attachments: this._createAttachments(result),
|
attachments: this._createAttachments(result),
|
||||||
steps: result.steps.map(step => this._serializeStep(test, step))
|
steps: dedupeSteps(result.steps.map(step => this._serializeStep(test, step)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,7 +233,8 @@ class RawReporter {
|
||||||
duration: step.duration,
|
duration: step.duration,
|
||||||
error: step.error?.message,
|
error: step.error?.message,
|
||||||
location: this._relativeLocation(step.location),
|
location: this._relativeLocation(step.location),
|
||||||
steps: step.steps.map(step => this._serializeStep(test, step)),
|
steps: dedupeSteps(step.steps.map(step => this._serializeStep(test, step))),
|
||||||
|
count: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
if (step.location)
|
if (step.location)
|
||||||
|
|
@ -292,4 +294,20 @@ class RawReporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dedupeSteps(steps: JsonTestStep[]): JsonTestStep[] {
|
||||||
|
const result: JsonTestStep[] = [];
|
||||||
|
let lastStep: JsonTestStep | undefined;
|
||||||
|
for (const step of steps) {
|
||||||
|
const canDedupe = !step.error && step.duration >= 0 && step.location?.file && !step.steps.length;
|
||||||
|
if (canDedupe && lastStep && step.category === lastStep.category && step.title === lastStep.title && step.location?.file === lastStep.location?.file && step.location?.line === lastStep.location?.line && step.location?.column === lastStep.location?.column) {
|
||||||
|
++lastStep.count;
|
||||||
|
lastStep.duration += step.duration;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push(step);
|
||||||
|
lastStep = canDedupe ? step : undefined;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export default RawReporter;
|
export default RawReporter;
|
||||||
|
|
|
||||||
|
|
@ -447,3 +447,26 @@ test('should differentiate repeat-each test cases', async ({ runInlineTest, show
|
||||||
await expect(page.locator('text=Before Hooks')).toBeVisible();
|
await expect(page.locator('text=Before Hooks')).toBeVisible();
|
||||||
await expect(page.locator('text=ouch')).toBeHidden();
|
await expect(page.locator('text=ouch')).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should group similar / loop steps', async ({ runInlineTest, showReport, page }) => {
|
||||||
|
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/10098' });
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.js': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('sample', async ({}, testInfo) => {
|
||||||
|
for (let i = 0; i < 10; ++i)
|
||||||
|
expect(1).toBe(1);
|
||||||
|
for (let i = 0; i < 20; ++i)
|
||||||
|
expect(2).toEqual(2);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
}, { 'reporter': 'dot,html' });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
await showReport();
|
||||||
|
|
||||||
|
await page.locator('text=sample').first().click();
|
||||||
|
await expect(page.locator('.tree-item-title')).toContainText([
|
||||||
|
/expect\.toBe.*10/,
|
||||||
|
/expect\.toEqual.*20/,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue