diff --git a/packages/html-reporter/src/reportView.tsx b/packages/html-reporter/src/reportView.tsx
index 390c12c1dd..796c03d6a1 100644
--- a/packages/html-reporter/src/reportView.tsx
+++ b/packages/html-reporter/src/reportView.tsx
@@ -78,11 +78,21 @@ const TestCaseViewLoader: React.FC<{
const testId = searchParams.get('testId');
const anchor = (searchParams.get('anchor') || '') as 'video' | 'diff' | '';
const run = +(searchParams.get('run') || '0');
+
+ const testIdToFileIdMap = React.useMemo(() => {
+ const map = new Map();
+ for (const file of report.json().files) {
+ for (const test of file.tests)
+ map.set(test.testId, file.fileId);
+ }
+ return map;
+ }, [report]);
+
React.useEffect(() => {
(async () => {
if (!testId || testId === test?.testId)
return;
- const fileId = testId.split('-')[0];
+ const fileId = testIdToFileIdMap.get(testId);
if (!fileId)
return;
const file = await report.entry(`${fileId}.json`) as TestFile;
@@ -93,7 +103,7 @@ const TestCaseViewLoader: React.FC<{
}
}
})();
- }, [test, report, testId]);
+ }, [test, report, testId, testIdToFileIdMap]);
return ;
};
diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts
index 9b67ea69b4..8cad4e0eb2 100644
--- a/packages/playwright/src/reporters/html.ts
+++ b/packages/playwright/src/reporters/html.ts
@@ -246,7 +246,7 @@ class HtmlBuilder {
}
const { testFile, testFileSummary } = fileEntry;
const testEntries: TestEntry[] = [];
- this._processSuite(fileSuite, fileId, projectSuite.project()!.name, [], testEntries);
+ this._processSuite(fileSuite, projectSuite.project()!.name, [], testEntries);
for (const test of testEntries) {
testFile.tests.push(test.testCase);
testFileSummary.tests.push(test.testCaseSummary);
@@ -346,30 +346,25 @@ class HtmlBuilder {
this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName);
}
- private _processSuite(suite: Suite, fileId: string, projectName: string, path: string[], outTests: TestEntry[]) {
+ private _processSuite(suite: Suite, projectName: string, path: string[], outTests: TestEntry[]) {
const newPath = [...path, suite.title];
suite.entries().forEach(e => {
if (e.type === 'test')
- outTests.push(this._createTestEntry(fileId, e, projectName, newPath));
+ outTests.push(this._createTestEntry(e, projectName, newPath));
else
- this._processSuite(e, fileId, projectName, newPath, outTests);
+ this._processSuite(e, projectName, newPath, outTests);
});
}
- private _createTestEntry(fileId: string, test: TestCasePublic, projectName: string, path: string[]): TestEntry {
+ private _createTestEntry(test: TestCasePublic, projectName: string, path: string[]): TestEntry {
const duration = test.results.reduce((a, r) => a + r.duration, 0);
const location = this._relativeLocation(test.location)!;
path = path.slice(1);
-
- const [file, ...titles] = test.titlePath();
- const testIdExpression = `[project=${this._projectId(test.parent)}]${toPosixPath(file)}\x1e${titles.join('\x1e')} (repeat:${test.repeatEachIndex})`;
- const testId = fileId + '-' + calculateSha1(testIdExpression).slice(0, 20);
-
const results = test.results.map(r => this._createTestResult(test, r));
return {
testCase: {
- testId,
+ testId: test.id,
title: test.title,
projectName,
location,
@@ -383,7 +378,7 @@ class HtmlBuilder {
ok: test.outcome() === 'expected' || test.outcome() === 'flaky',
},
testCaseSummary: {
- testId,
+ testId: test.id,
title: test.title,
projectName,
location,
diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts
index 7ea9cbfb80..76aa6a3e8a 100644
--- a/tests/playwright-test/reporter-html.spec.ts
+++ b/tests/playwright-test/reporter-html.spec.ts
@@ -86,6 +86,32 @@ for (const useIntermediateMergeReport of [false, true] as const) {
await expect(page.locator('.metadata-view')).not.toBeVisible();
});
+ test('should allow navigating to testId=test.id', async ({ runInlineTest, page, showReport }) => {
+ const result = await runInlineTest({
+ 'a.test.js': `
+ import { test, expect } from '@playwright/test';
+ test('passes', async ({ page }) => {
+ console.log('TESTID=' + test.info().testId);
+ await expect(1).toBe(1);
+ });
+ `,
+ }, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
+ expect(result.exitCode).toBe(0);
+ expect(result.passed).toBe(1);
+
+ await showReport();
+ await page.click('text=passes');
+ await page.locator('text=stdout').click();
+ await expect(page.locator('.attachment-body')).toHaveText(/TESTID=.*/);
+ const idString = await page.locator('.attachment-body').textContent();
+ const testId = idString.match(/TESTID=(.*)/)[1];
+ expect(page.url()).toContain('testId=' + testId);
+
+ // Expect test to be opened.
+ await page.reload();
+ await page.locator('text=stdout').click();
+ await expect(page.locator('.attachment-body')).toHaveText(/TESTID=.*/);
+ });
test('should not throw when PW_TEST_HTML_REPORT_OPEN value is invalid', async ({ runInlineTest, page, showReport }, testInfo) => {
const invalidOption = 'invalid-option';