diff --git a/src/test/reporters/json.ts b/src/test/reporters/json.ts index 8cb60b387b..71fc7e018e 100644 --- a/src/test/reporters/json.ts +++ b/src/test/reporters/json.ts @@ -16,7 +16,7 @@ import fs from 'fs'; import path from 'path'; -import { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStatus, Location, Reporter } from '../../../types/testReporter'; +import { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, FullResult, TestStatus, Location, Reporter } from '../../../types/testReporter'; export interface JSONReport { config: Omit & { @@ -67,8 +67,15 @@ export interface JSONReportTestResult { stdout: JSONReportSTDIOEntry[], stderr: JSONReportSTDIOEntry[], retry: number; + steps?: JSONReportTestStep[]; attachments: { name: string, path?: string, body?: string, contentType: string }[]; } +export interface JSONReportTestStep { + title: string; + duration: number; + error: TestError | undefined; + steps?: JSONReportTestStep[]; +} export type JSONReportSTDIOEntry = { text: string } | { buffer: string }; export function toPosixPath(aPath: string): string { @@ -213,6 +220,7 @@ class JSONReporter implements Reporter { } private _serializeTestResult(result: TestResult): JSONReportTestResult { + const steps = result.steps.filter(s => s.category === 'test.step'); return { workerIndex: result.workerIndex, status: result.status, @@ -221,6 +229,7 @@ class JSONReporter implements Reporter { stdout: result.stdout.map(s => stdioEntry(s)), stderr: result.stderr.map(s => stdioEntry(s)), retry: result.retry, + steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined, attachments: result.attachments.map(a => ({ name: a.name, contentType: a.contentType, @@ -229,6 +238,16 @@ class JSONReporter implements Reporter { })), }; } + + private _serializeTestStep(step: TestStep): JSONReportTestStep { + const steps = step.steps.filter(s => s.category === 'test.step'); + return { + title: step.title, + duration: step.duration, + error: step.error, + steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined, + }; + } } function outputReport(report: JSONReport, outputFile: string | undefined) { diff --git a/tests/playwright-test/json-reporter.spec.ts b/tests/playwright-test/json-reporter.spec.ts index 15c59afebd..62220ee1cb 100644 --- a/tests/playwright-test/json-reporter.spec.ts +++ b/tests/playwright-test/json-reporter.spec.ts @@ -108,6 +108,37 @@ test('should report projects', async ({ runInlineTest }, testInfo) => { expect(result.report.suites[0].specs[0].tests[1].projectName).toBe('p2'); }); +test('should show steps', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.test.js': ` + const { test } = pwt; + test('math works!', async ({}) => { + expect(1 + 1).toBe(2); + await test.step('math works in a step', async () => { + expect(2 + 2).toBe(4); + await test.step('nested step', async () => { + expect(2 + 2).toBe(4); + await test.step('deeply nested step', async () => { + expect(2 + 2).toBe(4); + }); + }) + }) + await test.step('failing step', async () => { + expect(2 + 2).toBe(5); + }); + }); + ` + }); + expect(result.exitCode).toBe(1); + expect(result.report.suites.length).toBe(1); + expect(result.report.suites[0].specs.length).toBe(1); + expect(result.report.suites[0].specs[0].tests[0].results[0].steps[0].title).toBe('math works in a step'); + expect(result.report.suites[0].specs[0].tests[0].results[0].steps[0].steps[0].title).toBe('nested step'); + expect(result.report.suites[0].specs[0].tests[0].results[0].steps[0].steps[0].steps[0].title).toBe('deeply nested step'); + expect(result.report.suites[0].specs[0].tests[0].results[0].steps[0].steps[0].steps[0].steps).toBeUndefined(); + expect(result.report.suites[0].specs[0].tests[0].results[0].steps[1].error).not.toBeUndefined(); +}); + test('should have relative always-posix paths', async ({ runInlineTest }) => { const result = await runInlineTest({ 'a.test.js': `