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';