feat(html-report): add url fields for rendered.

This commit is contained in:
osohyun0224 2024-06-09 16:56:48 +09:00
parent e8fc8af22e
commit 68c6defbc3
13 changed files with 44 additions and 20 deletions

View file

@ -182,7 +182,7 @@ You can also annotate all tests in a group or provide multiple annotations:
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test.describe('report tests', { test.describe('report tests', {
annotation: { type: 'category', description: 'report' }, annotation: { type: 'category', description: 'report', url: 'https://github.com/microsoft/playwright/issues/23180'},
}, () => { }, () => {
test('test report header', async ({ page }) => { test('test report header', async ({ page }) => {
// ... // ...
@ -192,6 +192,7 @@ test.describe('report tests', {
annotation: [ annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23180' }, { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23180' },
{ type: 'performance', description: 'very slow test!' }, { type: 'performance', description: 'very slow test!' },
{ type: 'performance', description: 'very slow test!', url: 'https://github.com/microsoft/playwright/issues/23180'},
], ],
}, async ({ page }) => { }, async ({ page }) => {
// ... // ...

View file

@ -176,7 +176,10 @@ function cacheSearchValues(test: TestCaseSummary): SearchValues {
line: String(test.location.line), line: String(test.location.line),
column: String(test.location.column), column: String(test.location.column),
labels: test.tags.map(tag => tag.toLowerCase()), labels: test.tags.map(tag => tag.toLowerCase()),
annotations: test.annotations.map(a => a.type.toLowerCase() + '=' + a.description?.toLocaleLowerCase()) annotations: test.annotations.map(a => {
const value = a.description?.toLocaleLowerCase() || a.url?.toLocaleLowerCase() || '';
return a.type.toLowerCase() + '=' + value;
}),
}; };
(test as any)[searchValuesSymbol] = searchValues; (test as any)[searchValuesSymbol] = searchValues;
return searchValues; return searchValues;

View file

@ -80,7 +80,7 @@ export type TestEndPayload = {
errors: TestInfoError[]; errors: TestInfoError[];
hasNonRetriableError: boolean; hasNonRetriableError: boolean;
expectedStatus: TestStatus; expectedStatus: TestStatus;
annotations: { type: string, description?: string }[]; annotations: { type: string, description?: string, url?: string }[];
timeout: number; timeout: number;
}; };

View file

@ -221,12 +221,13 @@ export class TestTypeImpl {
} }
if (typeof modifierArgs[0] === 'function') { if (typeof modifierArgs[0] === 'function') {
suite._modifiers.push({ type, fn: modifierArgs[0], location, description: modifierArgs[1] }); suite._modifiers.push({ type, fn: modifierArgs[0], location, description: modifierArgs[1], url: modifierArgs[1] });
} else { } else {
if (modifierArgs.length >= 1 && !modifierArgs[0]) if (modifierArgs.length >= 1 && !modifierArgs[0])
return; return;
const description = modifierArgs[1]; const description = modifierArgs[1];
suite._staticAnnotations.push({ type, description }); const url = modifierArgs[1];
suite._staticAnnotations.push({ type, description, url });
} }
return; return;
} }

View file

@ -69,14 +69,14 @@ export type JsonTestCase = {
retries: number; retries: number;
tags?: string[]; tags?: string[];
repeatEachIndex: number; repeatEachIndex: number;
annotations?: { type: string, description?: string }[]; annotations?: { type: string, description?: string, url?: string }[];
}; };
export type JsonTestEnd = { export type JsonTestEnd = {
testId: string; testId: string;
expectedStatus: reporterTypes.TestStatus; expectedStatus: reporterTypes.TestStatus;
timeout: number; timeout: number;
annotations: { type: string, description?: string }[]; annotations: { type: string, description?: string, url?: string }[];
}; };
export type JsonTestResultStart = { export type JsonTestResultStart = {

View file

@ -380,7 +380,11 @@ class HtmlBuilder {
location, location,
duration, duration,
// Annotations can be pushed directly, with a wrong type. // Annotations can be pushed directly, with a wrong type.
annotations: test.annotations.map(a => ({ type: a.type, description: a.description ? String(a.description) : a.description })), annotations: test.annotations.map(a => ({
type: a.type,
description: a.description ? String(a.description) : a.description,
url: a.url ? String(a.url) : a.url
})),
tags: test.tags, tags: test.tags,
outcome: test.outcome(), outcome: test.outcome(),
path, path,
@ -394,7 +398,11 @@ class HtmlBuilder {
location, location,
duration, duration,
// Annotations can be pushed directly, with a wrong type. // Annotations can be pushed directly, with a wrong type.
annotations: test.annotations.map(a => ({ type: a.type, description: a.description ? String(a.description) : a.description })), annotations: test.annotations.map(a => ({
type: a.type,
description: a.description ? String(a.description) : a.description,
url: a.url ? String(a.url) : a.url
})),
tags: test.tags, tags: test.tags,
outcome: test.outcome(), outcome: test.outcome(),
path, path,

View file

@ -71,7 +71,7 @@ export type JsonTestEnd = {
testId: string; testId: string;
expectedStatus: reporterTypes.TestStatus; expectedStatus: reporterTypes.TestStatus;
timeout: number; timeout: number;
annotations: { type: string, description?: string }[]; annotations: { type: string, description?: string, url?: string }[];
}; };
export type JsonTestResultStart = { export type JsonTestResultStart = {

View file

@ -198,7 +198,7 @@ export class TestInfoImpl implements TestInfo {
this._tracing = new TestTracing(this, workerParams.artifactsDir); this._tracing = new TestTracing(this, workerParams.artifactsDir);
} }
private _modifier(type: 'skip' | 'fail' | 'fixme' | 'slow', modifierArgs: [arg?: any, description?: string]) { private _modifier(type: 'skip' | 'fail' | 'fixme' | 'slow', modifierArgs: [arg?: any, description?: string, url?: string]) {
if (typeof modifierArgs[1] === 'function') { if (typeof modifierArgs[1] === 'function') {
throw new Error([ throw new Error([
'It looks like you are calling test.skip() inside the test and pass a callback.', 'It looks like you are calling test.skip() inside the test and pass a callback.',
@ -473,19 +473,19 @@ export class TestInfoImpl implements TestInfo {
return path.normalize(path.resolve(this._configInternal.configDir, snapshotPath)); return path.normalize(path.resolve(this._configInternal.configDir, snapshotPath));
} }
skip(...args: [arg?: any, description?: string]) { skip(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('skip', args); this._modifier('skip', args);
} }
fixme(...args: [arg?: any, description?: string]) { fixme(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('fixme', args); this._modifier('fixme', args);
} }
fail(...args: [arg?: any, description?: string]) { fail(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('fail', args); this._modifier('fail', args);
} }
slow(...args: [arg?: any, description?: string]) { slow(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('slow', args); this._modifier('slow', args);
} }

View file

@ -1845,6 +1845,7 @@ export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interru
type TestDetailsAnnotation = { type TestDetailsAnnotation = {
type: string; type: string;
description?: string; description?: string;
url?: string;
}; };
export type TestDetails = { export type TestDetails = {
@ -8175,6 +8176,11 @@ export interface TestInfo {
* Optional description. * Optional description.
*/ */
description?: string; description?: string;
/**
* Optional url.
*/
url?: string;
}>; }>;
/** /**

View file

@ -273,7 +273,7 @@ export interface JSONReportSpec {
export interface JSONReportTest { export interface JSONReportTest {
timeout: number; timeout: number;
annotations: { type: string, description?: string }[], annotations: { type: string, description?: string, url?: string }[],
expectedStatus: TestStatus; expectedStatus: TestStatus;
projectName: string; projectName: string;
projectId: string; projectId: string;
@ -481,6 +481,11 @@ export interface TestCase {
* Optional description. * Optional description.
*/ */
description?: string; description?: string;
/**
* Optional url.
*/
url?: string;
}>; }>;
/** /**

View file

@ -744,16 +744,15 @@ test.info().annotations.push({ type: 'issue', description: 'I am not interested
'a.test.js': ` 'a.test.js': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test('annotated test', async ({ page }) => { test('annotated test', async ({ page }) => {
test.info().annotations.push({ type: 'issue', description: 'I add URL field to annotations for hyperlink display', url: 'https://github.com/microsoft/playwright/pull/31014' }); test.info().annotations.push({ type: 'url', url: 'https://github.com/microsoft/playwright/pull/31014' });
}); });
`, `,
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }); }, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
await showReport(); await showReport();
await page.click('text=annotated test'); await page.click('text=annotated test');
await expect(page.locator('.test-case-annotation')).toHaveText('issue: I am not interested in this test'); await expect(page.locator('.test-case-annotation')).toHaveText(`url: https://github.com/microsoft/playwright/pull/31014`);
}); });
test('should render annotations as link if needed', async ({ runInlineTest, page, showReport, server }) => { test('should render annotations as link if needed', async ({ runInlineTest, page, showReport, server }) => {

View file

@ -68,6 +68,7 @@ export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interru
type TestDetailsAnnotation = { type TestDetailsAnnotation = {
type: string; type: string;
description?: string; description?: string;
url?: string;
}; };
export type TestDetails = { export type TestDetails = {

View file

@ -94,7 +94,7 @@ export interface JSONReportSpec {
export interface JSONReportTest { export interface JSONReportTest {
timeout: number; timeout: number;
annotations: { type: string, description?: string }[], annotations: { type: string, description?: string, url?: string }[],
expectedStatus: TestStatus; expectedStatus: TestStatus;
projectName: string; projectName: string;
projectId: string; projectId: string;