diff --git a/docs/src/test-annotations-js.md b/docs/src/test-annotations-js.md
index 68bdd79848bd5..8ed468af75538 100644
--- a/docs/src/test-annotations-js.md
+++ b/docs/src/test-annotations-js.md
@@ -192,6 +192,7 @@ test.describe('report tests', {
annotation: [
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23180' },
{ type: 'performance', description: 'very slow test!' },
+ { type: 'performance', url: 'https://github.com/microsoft/playwright/issues/23180' },
],
}, async ({ page }) => {
// ...
diff --git a/packages/html-reporter/src/filter.ts b/packages/html-reporter/src/filter.ts
index 9ec4ea7e59691..effea7b9b1340 100644
--- a/packages/html-reporter/src/filter.ts
+++ b/packages/html-reporter/src/filter.ts
@@ -176,7 +176,10 @@ function cacheSearchValues(test: TestCaseSummary): SearchValues {
line: String(test.location.line),
column: String(test.location.column),
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;
return searchValues;
diff --git a/packages/html-reporter/src/testCaseView.tsx b/packages/html-reporter/src/testCaseView.tsx
index eecbdac9f5753..26cc4d1b73bda 100644
--- a/packages/html-reporter/src/testCaseView.tsx
+++ b/packages/html-reporter/src/testCaseView.tsx
@@ -72,11 +72,20 @@ function renderAnnotationDescription(description: string) {
return description;
}
-function TestCaseAnnotationView({ annotation: { type, description } }: { annotation: TestCaseAnnotation }) {
+function renderAnnotationLink(url: string) {
+ try {
+ if (['http:', 'https:'].includes(new URL(url).protocol))
+ return {url};
+ } catch {}
+ return url;
+}
+
+function TestCaseAnnotationView({ annotation: { type, description, url } }: { annotation: TestCaseAnnotation }) {
return (
{type}
{description && : {renderAnnotationDescription(description)}}
+ {url && : {renderAnnotationLink(url)}}
);
}
diff --git a/packages/playwright/src/common/ipc.ts b/packages/playwright/src/common/ipc.ts
index f5e3ec0858874..437909569e5f8 100644
--- a/packages/playwright/src/common/ipc.ts
+++ b/packages/playwright/src/common/ipc.ts
@@ -80,7 +80,7 @@ export type TestEndPayload = {
errors: TestInfoError[];
hasNonRetriableError: boolean;
expectedStatus: TestStatus;
- annotations: { type: string, description?: string }[];
+ annotations: { type: string, description?: string, url?: string }[];
timeout: number;
};
diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts
index da377b099fc17..fa308b3405d61 100644
--- a/packages/playwright/src/isomorphic/teleReceiver.ts
+++ b/packages/playwright/src/isomorphic/teleReceiver.ts
@@ -69,14 +69,14 @@ export type JsonTestCase = {
retries: number;
tags?: string[];
repeatEachIndex: number;
- annotations?: { type: string, description?: string }[];
+ annotations?: { type: string, description?: string, url?: string }[];
};
export type JsonTestEnd = {
testId: string;
expectedStatus: reporterTypes.TestStatus;
timeout: number;
- annotations: { type: string, description?: string }[];
+ annotations: { type: string, description?: string, url?: string }[];
};
export type JsonTestResultStart = {
diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts
index 2a5bbc94d4272..bb0acf19aa153 100644
--- a/packages/playwright/src/reporters/html.ts
+++ b/packages/playwright/src/reporters/html.ts
@@ -380,7 +380,10 @@ class HtmlBuilder {
location,
duration,
// 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
+ })),
tags: test.tags,
outcome: test.outcome(),
path,
@@ -394,7 +397,10 @@ class HtmlBuilder {
location,
duration,
// 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
+ })),
tags: test.tags,
outcome: test.outcome(),
path,
diff --git a/packages/playwright/src/reporters/versions/blobV1.ts b/packages/playwright/src/reporters/versions/blobV1.ts
index 5ea9350285fcc..4bc3591f5f451 100644
--- a/packages/playwright/src/reporters/versions/blobV1.ts
+++ b/packages/playwright/src/reporters/versions/blobV1.ts
@@ -71,7 +71,7 @@ export type JsonTestEnd = {
testId: string;
expectedStatus: reporterTypes.TestStatus;
timeout: number;
- annotations: { type: string, description?: string }[];
+ annotations: { type: string, description?: string, url?: string }[];
};
export type JsonTestResultStart = {
diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts
index 1e9e1f9c46ee1..fb10a8fe5fa22 100644
--- a/packages/playwright/src/worker/testInfo.ts
+++ b/packages/playwright/src/worker/testInfo.ts
@@ -198,7 +198,7 @@ export class TestInfoImpl implements TestInfo {
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') {
throw new Error([
'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));
}
- skip(...args: [arg?: any, description?: string]) {
+ skip(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('skip', args);
}
- fixme(...args: [arg?: any, description?: string]) {
+ fixme(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('fixme', args);
}
- fail(...args: [arg?: any, description?: string]) {
+ fail(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('fail', args);
}
- slow(...args: [arg?: any, description?: string]) {
+ slow(...args: [arg?: any, description?: string, url?: string]) {
this._modifier('slow', args);
}
diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts
index e1299272a82a2..9a5ea6ec38217 100644
--- a/packages/playwright/types/test.d.ts
+++ b/packages/playwright/types/test.d.ts
@@ -1845,6 +1845,7 @@ export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interru
type TestDetailsAnnotation = {
type: string;
description?: string;
+ url?: string;
};
export type TestDetails = {
diff --git a/packages/playwright/types/testReporter.d.ts b/packages/playwright/types/testReporter.d.ts
index 52073066f0efb..913892747653b 100644
--- a/packages/playwright/types/testReporter.d.ts
+++ b/packages/playwright/types/testReporter.d.ts
@@ -273,7 +273,7 @@ export interface JSONReportSpec {
export interface JSONReportTest {
timeout: number;
- annotations: { type: string, description?: string }[],
+ annotations: { type: string, description?: string, url?: string }[],
expectedStatus: TestStatus;
projectName: string;
projectId: string;
diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts
index 83992c0bad55b..e31921be894ad 100644
--- a/tests/playwright-test/reporter-html.spec.ts
+++ b/tests/playwright-test/reporter-html.spec.ts
@@ -724,7 +724,7 @@ for (const useIntermediateMergeReport of [false] as const) {
'a.test.js': `
import { test, expect } from '@playwright/test';
test('annotated test', async ({ page }) => {
- test.info().annotations.push({ type: 'issue', description: 'I am not interested in this test' });
+test.info().annotations.push({ type: 'issue', description: 'I am not interested in this test' });
});
`,
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
@@ -736,6 +736,25 @@ for (const useIntermediateMergeReport of [false] as const) {
await expect(page.locator('.test-case-annotation')).toHaveText('issue: I am not interested in this test');
});
+ test('should render annotations with url', async ({ runInlineTest, page, showReport }) => {
+ const result = await runInlineTest({
+ 'playwright.config.js': `
+ module.exports = { timeout: 1500 };
+ `,
+ 'a.test.js': `
+ import { test, expect } from '@playwright/test';
+ test('annotated test', async ({ page }) => {
+ test.info().annotations.push({ type: 'url', url: 'https://github.com/microsoft/playwright/pull/31014' });
+ });
+ `,
+ }, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
+ expect(result.exitCode).toBe(0);
+ expect(result.passed).toBe(1);
+ await showReport();
+ await page.click('text=annotated 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 }) => {
const result = await runInlineTest({
'playwright.config.js': `
diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts
index 0367f3259ca29..77ac80e6ec0d4 100644
--- a/utils/generate_types/overrides-test.d.ts
+++ b/utils/generate_types/overrides-test.d.ts
@@ -68,6 +68,7 @@ export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interru
type TestDetailsAnnotation = {
type: string;
description?: string;
+ url?: string;
};
export type TestDetails = {
diff --git a/utils/generate_types/overrides-testReporter.d.ts b/utils/generate_types/overrides-testReporter.d.ts
index 51eab7e370508..a69d27651bde7 100644
--- a/utils/generate_types/overrides-testReporter.d.ts
+++ b/utils/generate_types/overrides-testReporter.d.ts
@@ -94,7 +94,7 @@ export interface JSONReportSpec {
export interface JSONReportTest {
timeout: number;
- annotations: { type: string, description?: string }[],
+ annotations: { type: string, description?: string, url?: string }[],
expectedStatus: TestStatus;
projectName: string;
projectId: string;