diff --git a/packages/playwright-test/src/isomorphic/teleReceiver.ts b/packages/playwright-test/src/isomorphic/teleReceiver.ts index e7b77a7d31..b84416be0d 100644 --- a/packages/playwright-test/src/isomorphic/teleReceiver.ts +++ b/packages/playwright-test/src/isomorphic/teleReceiver.ts @@ -28,7 +28,7 @@ export type JsonConfig = Pick; +export type MergeReporterConfig = Pick; export type JsonPattern = { s?: string; @@ -171,7 +171,7 @@ export class TeleReporterReceiver { } private _onBegin(config: JsonConfig, projects: JsonProject[]) { - this._rootDir = config.rootDir; + this._rootDir = this._reportConfig?.rootDir || config.rootDir; for (const project of projects) { let projectSuite = this._rootSuite.suites.find(suite => suite.project()!.id === project.id); if (!projectSuite) { @@ -247,6 +247,8 @@ export class TeleReporterReceiver { }; if (parentStep) parentStep.steps.push(step); + else + result.steps.push(step); result.stepMap.set(payload.id, step); this._reporter.onStepBegin?.(test, result, step); } @@ -286,6 +288,7 @@ export class TeleReporterReceiver { const result = { ...baseFullConfig, ...config }; if (this._reportConfig) { result.configFile = this._reportConfig.configFile; + result.rootDir = this._reportConfig.rootDir; result.reportSlowTests = this._reportConfig.reportSlowTests; result.quiet = this._reportConfig.quiet; } diff --git a/packages/playwright-test/src/reporters/merge.ts b/packages/playwright-test/src/reporters/merge.ts index 61534e4b25..db58ea3d62 100644 --- a/packages/playwright-test/src/reporters/merge.ts +++ b/packages/playwright-test/src/reporters/merge.ts @@ -16,7 +16,7 @@ import fs from 'fs'; import path from 'path'; -import type { FullConfig, ReporterDescription } from '../../types/test'; +import type { ReporterDescription } from '../../types/test'; import type { FullResult } from '../../types/testReporter'; import type { FullConfigInternal } from '../common/config'; import { TeleReporterReceiver, type JsonEvent, type JsonProject, type JsonSuite, type JsonTestResultEnd, type JsonConfig } from '../isomorphic/teleReceiver'; @@ -25,7 +25,7 @@ import { Multiplexer } from './multiplexer'; export async function createMergedReport(config: FullConfigInternal, dir: string, reporterDescriptions: ReporterDescription[], resolvePaths: boolean) { const shardFiles = await sortedShardFiles(dir); - const events = await mergeEvents(dir, shardFiles, config.config); + const events = await mergeEvents(dir, shardFiles); if (resolvePaths) patchAttachmentPaths(events, dir); @@ -53,7 +53,7 @@ function parseEvents(reportJsonl: string): JsonEvent[] { return reportJsonl.toString().split('\n').filter(line => line.length).map(line => JSON.parse(line)) as JsonEvent[]; } -async function mergeEvents(dir: string, shardReportFiles: string[], reportConfig: FullConfig) { +async function mergeEvents(dir: string, shardReportFiles: string[]) { const events: JsonEvent[] = []; const beginEvents: JsonEvent[] = []; const endEvents: JsonEvent[] = []; @@ -69,10 +69,10 @@ async function mergeEvents(dir: string, shardReportFiles: string[], reportConfig events.push(event); } } - return [mergeBeginEvents(beginEvents, reportConfig), ...events, mergeEndEvents(endEvents), { method: 'onExit', params: undefined }]; + return [mergeBeginEvents(beginEvents), ...events, mergeEndEvents(endEvents), { method: 'onExit', params: undefined }]; } -function mergeBeginEvents(beginEvents: JsonEvent[], reportConfig: FullConfig): JsonEvent { +function mergeBeginEvents(beginEvents: JsonEvent[]): JsonEvent { if (!beginEvents.length) throw new Error('No begin events found'); const projects: JsonProject[] = []; diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index f6d46fbc79..11224f32eb 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -47,10 +47,11 @@ const test = baseTest.extend<{ if (options.additionalArgs) command.push(...options.additionalArgs); + const cwd = options.cwd ? path.resolve(test.info().outputDir, options.cwd) : test.info().outputDir; const testProcess = childProcess({ command, env: cleanEnv(env), - cwd: test.info().outputDir, + cwd, }); const { exitCode } = await testProcess.exited; return { exitCode, output: testProcess.output.toString() }; @@ -409,14 +410,12 @@ test('preserve attachments', async ({ runInlineTest, mergeReports, showReport, p await showReport(); - // Check file attachment. await page.getByText('first').click(); - await expect(page.getByText('file-attachment')).toBeVisible(); - - // Check file attachment content. const popupPromise = page.waitForEvent('popup'); - await page.getByText('file-attachment').click(); + // Check file attachment. + await page.getByRole('link', { name: 'file-attachment' }).click(); const popup = await popupPromise; + // Check file attachment content. await expect(popup.locator('body')).toHaveText('hello!'); await popup.close(); await page.goBack(); @@ -484,15 +483,14 @@ test('generate html with attachment urls', async ({ runInlineTest, mergeReports, return oldSeveFile.call(server, req, res, filePath); }; - // Check file attachment. await page.goto(`${server.PREFIX}/index.html`); await page.getByText('first').click(); - await expect(page.getByText('file-attachment')).toBeVisible(); - // Check file attachment content. const popupPromise = page.waitForEvent('popup'); - await page.getByText('file-attachment').click(); + // Check file attachment. + await page.getByRole('link', { name: 'file-attachment' }).click(); const popup = await popupPromise; + // Check file attachment content. await expect(popup.locator('body')).toHaveText('hello!'); await popup.close(); await page.goBack(); @@ -846,4 +844,58 @@ test('preserve config fields', async ({ runInlineTest, mergeReports }) => { expect(json.reportSlowTests).toEqual(mergeConfig.reportSlowTests); expect(json.configFile).toEqual(test.info().outputPath('merge.config.ts')); expect(json.quiet).toEqual(mergeConfig.quiet); +}); + +test('preserve steps in html report', async ({ runInlineTest, mergeReports, showReport, page }) => { + test.slow(); + const reportDir = test.info().outputPath('blob-report'); + const files = { + 'playwright.config.ts': ` + module.exports = { + reporter: [['blob']] + }; + `, + 'tests/a.test.js': ` + import { test, expect } from '@playwright/test'; + test.beforeAll(() => { + expect(1).toBe(1); + }) + test('test 1', async ({}) => { + await test.step('my step', async () => { + expect(2).toBe(2); + }); + }); + `, + }; + await runInlineTest(files); + const reportFiles = await fs.promises.readdir(reportDir); + reportFiles.sort(); + expect(reportFiles).toEqual([expect.stringMatching(/report-.*.jsonl/), 'resources']); + // Run merger in a different directory to make sure relative paths will not be resolved + // relative to the current directory. + const mergeCwd = test.info().outputPath('foo'); + await fs.promises.mkdir(mergeCwd, { recursive: true }); + const { exitCode, output } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'], cwd: mergeCwd }); + expect(exitCode).toBe(0); + + expect(output).toContain('To open last HTML report run:'); + + await showReport(); + + await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('1'); + await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('1'); + + await page.getByRole('link', { name: 'test 1' }).click(); + + await page.getByText('Before Hooks').click(); + await page.getByText('beforeAll hook').click(); + await expect(page.getByText('expect.toBe')).toBeVisible(); + // Collapse hooks. + await page.getByText('Before Hooks').click(); + await expect(page.getByText('expect.toBe')).not.toBeVisible(); + + // Check that 'my step' location is relative. + await expect(page.getByText('— tests/a.test.js:7')).toBeVisible(); + await page.getByText('my step').click(); + await expect(page.getByText('expect.toBe')).toBeVisible(); }); \ No newline at end of file