feat(html reporter): render multiple annotations of the same type together (#21580)

Multiple annotations are rendered as comma-separated lists. Fixes
#21253.

![IMAGE 2023-03-10
14:24:10](https://user-images.githubusercontent.com/9881434/224439435-3bf8e4f4-d874-4595-931d-ea5ff33ea374.jpg)
This commit is contained in:
Dmitry Gozman 2023-03-10 15:26:02 -08:00 committed by GitHub
parent 46f9fa005e
commit 21950bc8ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 28 additions and 6 deletions

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { TestCase, TestCaseAnnotation } from './types';
import type { TestCase } from './types';
import * as React from 'react';
import { TabbedPane } from './tabbedPane';
import { AutoChip } from './chip';
@ -32,13 +32,20 @@ export const TestCaseView: React.FC<{
}> = ({ projectNames, test, run, anchor }) => {
const [selectedResultIndex, setSelectedResultIndex] = React.useState(run);
const annotations = new Map<string, (string | undefined)[]>();
test?.annotations.forEach(annotation => {
const list = annotations.get(annotation.type) || [];
list.push(annotation.description);
annotations.set(annotation.type, list);
});
return <div className='test-case-column vbox'>
{test && <div className='test-case-path'>{test.path.join(' ')}</div>}
{test && <div className='test-case-title'>{test?.title}</div>}
{test && <div className='test-case-location'>{test.location.file}:{test.location.line}</div>}
{test && !!test.projectName && <ProjectLink projectNames={projectNames} projectName={test.projectName}></ProjectLink>}
{test && !!test.annotations.length && <AutoChip header='Annotations'>
{test.annotations.map(annotation => <TestCaseAnnotationView annotation={annotation} />)}
{annotations.size && <AutoChip header='Annotations'>
{[...annotations].map(annotation => <TestCaseAnnotationView type={annotation[0]} descriptions={annotation[1]} />)}
</AutoChip>}
{test && <TabbedPane tabs={
test.results.map((result, index) => ({
@ -57,11 +64,17 @@ function renderAnnotationDescription(description: string) {
return description;
}
function TestCaseAnnotationView({ annotation: { type, description } }: { annotation: TestCaseAnnotation }) {
function TestCaseAnnotationView({ type, descriptions }: { type: string, descriptions: (string | undefined)[] }) {
const filteredDescriptions = descriptions.filter(Boolean) as string[];
return (
<div className='test-case-annotation'>
<span style={{ fontWeight: 'bold' }}>{type}</span>
{description && <span>: {renderAnnotationDescription(description)}</span>}
{!!filteredDescriptions.length && <span>: {filteredDescriptions.map((d, i) => {
const rendered = renderAnnotationDescription(d);
if (i < filteredDescriptions.length - 1)
return <>{rendered}, </>;
return rendered;
})}</span>}
</div>
);
}

View File

@ -565,6 +565,11 @@ test('should render annotations', async ({ runInlineTest, page, showReport }) =>
'a.test.js': `
import { test, expect } from '@playwright/test';
test('skipped test', async ({ page }) => {
test.info().annotations.push({ type: 'issue', description: '#123'});
test.info().annotations.push({ type: 'issue', description: '#456'});
test.info().annotations.push({ type: 'issue', description: 'https://playwright.dev'});
test.info().annotations.push({ type: 'issue' });
test.info().annotations.push({ type: 'empty' });
test.skip(true, 'I am not interested in this test');
});
`,
@ -574,7 +579,11 @@ test('should render annotations', async ({ runInlineTest, page, showReport }) =>
await showReport();
await page.click('text=skipped test');
await expect(page.locator('.test-case-annotation')).toHaveText('skip: I am not interested in this test');
await expect(page.locator('.test-case-annotation')).toHaveText([
'issue: #123, #456, https://playwright.dev',
'empty',
'skip: I am not interested in this test',
]);
});
test('should render annotations as link if needed', async ({ runInlineTest, page, showReport, server }) => {