mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-03 08:54:05 +03:00
feat(test-runner): render step titles (#8270)
This commit is contained in:
parent
52d63aa376
commit
710cec80a0
@ -10,6 +10,7 @@ Step category to differentiate steps with different origin and verbosity. Built-
|
||||
* `hook` for fixtures and hooks initialization and teardown
|
||||
* `expect` for expect calls
|
||||
* `pw:api` for Playwright API calls.
|
||||
* `test.step` for test.step API calls.
|
||||
|
||||
## property: TestStep.duration
|
||||
- type: <[float]>
|
||||
@ -21,6 +22,11 @@ Running time in milliseconds.
|
||||
|
||||
An error thrown during the step execution, if any.
|
||||
|
||||
## property: TestStep.parent
|
||||
- type: <[void]|[TestStep]>
|
||||
|
||||
Parent step, if any.
|
||||
|
||||
## property: TestStep.startTime
|
||||
- type: <[Date]>
|
||||
|
||||
@ -35,3 +41,8 @@ List of steps inside this step.
|
||||
- type: <[string]>
|
||||
|
||||
User-friendly test step title.
|
||||
|
||||
## method: TestStep.titlePath
|
||||
- returns: <[Array]<[string]>>
|
||||
|
||||
Returns a list of step titles from the root step down to this step.
|
||||
|
@ -282,16 +282,21 @@ export class Dispatcher {
|
||||
});
|
||||
worker.on('stepBegin', (params: StepBeginPayload) => {
|
||||
const { test, result, steps, stepStack } = this._testById.get(params.testId)!;
|
||||
const parentStep = [...stepStack].pop();
|
||||
const step: TestStep = {
|
||||
title: params.title,
|
||||
titlePath: () => {
|
||||
const parentPath = parentStep?.titlePath() || [];
|
||||
return [...parentPath, params.title];
|
||||
},
|
||||
parent: parentStep,
|
||||
category: params.category,
|
||||
startTime: new Date(params.wallTime),
|
||||
duration: 0,
|
||||
steps: [],
|
||||
};
|
||||
steps.set(params.stepId, step);
|
||||
const parentStep = [...stepStack].pop() || result;
|
||||
parentStep.steps.push(step);
|
||||
(parentStep || result).steps.push(step);
|
||||
stepStack.add(step);
|
||||
this._reporter.onStepBegin?.(test, result, step);
|
||||
});
|
||||
|
@ -21,7 +21,7 @@ import fs from 'fs';
|
||||
import milliseconds from 'ms';
|
||||
import path from 'path';
|
||||
import StackUtils from 'stack-utils';
|
||||
import { FullConfig, TestCase, Suite, TestResult, TestError, Reporter, FullResult } from '../../../types/testReporter';
|
||||
import { FullConfig, TestCase, Suite, TestResult, TestError, Reporter, FullResult, TestStep } from '../../../types/testReporter';
|
||||
|
||||
const stackUtils = new StackUtils();
|
||||
|
||||
@ -195,12 +195,17 @@ function relativeTestPath(config: FullConfig, test: TestCase): string {
|
||||
return path.relative(config.rootDir, test.location.file) || path.basename(test.location.file);
|
||||
}
|
||||
|
||||
export function formatTestTitle(config: FullConfig, test: TestCase): string {
|
||||
function stepSuffix(step: TestStep | undefined) {
|
||||
const stepTitles = step ? step.titlePath() : [];
|
||||
return stepTitles.map(t => ' › ' + t).join('');
|
||||
}
|
||||
|
||||
export function formatTestTitle(config: FullConfig, test: TestCase, step?: TestStep): string {
|
||||
// root, project, file, ...describes, test
|
||||
const [, projectName, , ...titles] = test.titlePath();
|
||||
const location = `${relativeTestPath(config, test)}:${test.location.line}:${test.location.column}`;
|
||||
const projectTitle = projectName ? `[${projectName}] › ` : '';
|
||||
return `${projectTitle}${location} › ${titles.join(' ')}`;
|
||||
return `${projectTitle}${location} › ${titles.join(' ')}${stepSuffix(step)}`;
|
||||
}
|
||||
|
||||
function formatTestHeader(config: FullConfig, test: TestCase, indent: string, index?: number): string {
|
||||
|
@ -19,7 +19,7 @@ import colors from 'colors/safe';
|
||||
// @ts-ignore
|
||||
import milliseconds from 'ms';
|
||||
import { BaseReporter, formatTestTitle } from './base';
|
||||
import { FullConfig, FullResult, Suite, TestCase, TestResult } from '../../../types/testReporter';
|
||||
import { FullConfig, FullResult, Suite, TestCase, TestResult, TestStep } from '../../../types/testReporter';
|
||||
|
||||
// Allow it in the Visual Studio Code Terminal and the new Windows Terminal
|
||||
const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION;
|
||||
@ -30,6 +30,12 @@ class ListReporter extends BaseReporter {
|
||||
private _lastRow = 0;
|
||||
private _testRows = new Map<TestCase, number>();
|
||||
private _needNewLine = false;
|
||||
private _liveTerminal: string | boolean | undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._liveTerminal = process.stdout.isTTY || process.env.PWTEST_SKIP_TEST_OUTPUT;
|
||||
}
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
super.onBegin(config, suite);
|
||||
@ -37,7 +43,7 @@ class ListReporter extends BaseReporter {
|
||||
}
|
||||
|
||||
onTestBegin(test: TestCase) {
|
||||
if (process.stdout.isTTY) {
|
||||
if (this._liveTerminal) {
|
||||
if (this._needNewLine) {
|
||||
this._needNewLine = false;
|
||||
process.stdout.write('\n');
|
||||
@ -58,12 +64,28 @@ class ListReporter extends BaseReporter {
|
||||
this._dumpToStdio(test, chunk, process.stdout);
|
||||
}
|
||||
|
||||
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||
if (!this._liveTerminal)
|
||||
return;
|
||||
if (step.category !== 'test.step')
|
||||
return;
|
||||
this._updateTestLine(test, ' ' + colors.gray(formatTestTitle(this.config, test, step)));
|
||||
}
|
||||
|
||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||
if (!this._liveTerminal)
|
||||
return;
|
||||
if (step.category !== 'test.step')
|
||||
return;
|
||||
this._updateTestLine(test, ' ' + colors.gray(formatTestTitle(this.config, test, step.parent)));
|
||||
}
|
||||
|
||||
private _dumpToStdio(test: TestCase | undefined, chunk: string | Buffer, stream: NodeJS.WriteStream) {
|
||||
if (this.config.quiet)
|
||||
return;
|
||||
const text = chunk.toString('utf-8');
|
||||
this._needNewLine = text[text.length - 1] !== '\n';
|
||||
if (process.stdout.isTTY) {
|
||||
if (this._liveTerminal) {
|
||||
const newLineCount = text.split('\n').length - 1;
|
||||
this._lastRow += newLineCount;
|
||||
}
|
||||
@ -86,25 +108,41 @@ class ListReporter extends BaseReporter {
|
||||
text = '\u001b[2K\u001b[0G' + colors.red(statusMark + title) + duration;
|
||||
}
|
||||
|
||||
const testRow = this._testRows.get(test)!;
|
||||
// Go up if needed
|
||||
if (process.stdout.isTTY && testRow !== this._lastRow)
|
||||
process.stdout.write(`\u001B[${this._lastRow - testRow}A`);
|
||||
// Erase line
|
||||
if (process.stdout.isTTY)
|
||||
process.stdout.write('\u001B[2K');
|
||||
if (!process.stdout.isTTY && this._needNewLine) {
|
||||
this._needNewLine = false;
|
||||
if (this._liveTerminal) {
|
||||
this._updateTestLine(test, text);
|
||||
} else {
|
||||
if (this._needNewLine) {
|
||||
this._needNewLine = false;
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
process.stdout.write(text);
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
process.stdout.write(text);
|
||||
}
|
||||
|
||||
private _updateTestLine(test: TestCase, line: string) {
|
||||
if (process.env.PWTEST_SKIP_TEST_OUTPUT)
|
||||
this._updateTestLineForTest(test,line);
|
||||
else
|
||||
this._updateTestLineForTTY(test,line);
|
||||
}
|
||||
|
||||
private _updateTestLineForTTY(test: TestCase, line: string) {
|
||||
const testRow = this._testRows.get(test)!;
|
||||
// Go up if needed
|
||||
if (testRow !== this._lastRow)
|
||||
process.stdout.write(`\u001B[${this._lastRow - testRow}A`);
|
||||
// Erase line
|
||||
process.stdout.write('\u001B[2K');
|
||||
process.stdout.write(line);
|
||||
// Go down if needed.
|
||||
if (testRow !== this._lastRow) {
|
||||
if (process.stdout.isTTY)
|
||||
process.stdout.write(`\u001B[${this._lastRow - testRow}E`);
|
||||
else
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
if (testRow !== this._lastRow)
|
||||
process.stdout.write(`\u001B[${this._lastRow - testRow}E`);
|
||||
}
|
||||
|
||||
private _updateTestLineForTest(test: TestCase, line: string) {
|
||||
const testRow = this._testRows.get(test)!;
|
||||
process.stdout.write(testRow + ' : ' + line + '\n');
|
||||
}
|
||||
|
||||
async onEnd(result: FullResult) {
|
||||
|
@ -47,3 +47,38 @@ test('render each test with project name', async ({ runInlineTest }) => {
|
||||
expect(text).toContain(`- [bar] › a.test.ts:12:12 › skipped`);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test('render steps', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('passes', async ({}) => {
|
||||
await test.step('outer 1.0', async () => {
|
||||
await test.step('inner 1.1', async () => {});
|
||||
await test.step('inner 1.1', async () => {});
|
||||
});
|
||||
await test.step('outer 2.0', async () => {
|
||||
await test.step('inner 2.1', async () => {});
|
||||
await test.step('inner 2.1', async () => {});
|
||||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'list' });
|
||||
const text = stripAscii(result.output);
|
||||
const lines = text.split('\n').filter(l => l.startsWith('0 :'));
|
||||
lines.pop(); // Remove last item that contains [v] and time in ms.
|
||||
expect(lines).toEqual([
|
||||
'0 : a.test.ts:6:7 › passes › outer 1.0',
|
||||
'0 : a.test.ts:6:7 › passes › outer 1.0 › inner 1.1',
|
||||
'0 : a.test.ts:6:7 › passes › outer 1.0',
|
||||
'0 : a.test.ts:6:7 › passes › outer 1.0 › inner 1.1',
|
||||
'0 : a.test.ts:6:7 › passes › outer 1.0',
|
||||
'0 : a.test.ts:6:7 › passes',
|
||||
'0 : a.test.ts:6:7 › passes › outer 2.0',
|
||||
'0 : a.test.ts:6:7 › passes › outer 2.0 › inner 2.1',
|
||||
'0 : a.test.ts:6:7 › passes › outer 2.0',
|
||||
'0 : a.test.ts:6:7 › passes › outer 2.0 › inner 2.1',
|
||||
'0 : a.test.ts:6:7 › passes › outer 2.0',
|
||||
'0 : a.test.ts:6:7 › passes',
|
||||
]);
|
||||
});
|
||||
|
@ -34,6 +34,32 @@ class Reporter {
|
||||
module.exports = Reporter;
|
||||
`;
|
||||
|
||||
const stepsReporterJS = `
|
||||
class Reporter {
|
||||
onStdOut(chunk) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
distillStep(step) {
|
||||
return {
|
||||
...step,
|
||||
startTime: undefined,
|
||||
duration: undefined,
|
||||
parent: undefined,
|
||||
steps: step.steps.length ? step.steps.map(s => this.distillStep(s)) : undefined,
|
||||
};
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
console.log('%%%% begin', JSON.stringify(this.distillStep(step)));
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
if (step.error?.stack)
|
||||
step.error.stack = '<stack>';
|
||||
console.log('%%%% end', JSON.stringify(this.distillStep(step)));
|
||||
}
|
||||
}
|
||||
module.exports = Reporter;
|
||||
`;
|
||||
|
||||
test('should work with custom reporter', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'reporter.ts': `
|
||||
@ -159,27 +185,8 @@ test('should load reporter from node_modules', async ({ runInlineTest }) => {
|
||||
});
|
||||
|
||||
test('should report expect steps', async ({ runInlineTest }) => {
|
||||
const expectReporterJS = `
|
||||
class Reporter {
|
||||
onStdOut(chunk) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
console.log('%%%% begin', JSON.stringify(copy));
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
if (copy.error?.stack)
|
||||
copy.error.stack = '<stack>';
|
||||
console.log('%%%% end', JSON.stringify(copy));
|
||||
}
|
||||
}
|
||||
module.exports = Reporter;
|
||||
`;
|
||||
|
||||
const result = await runInlineTest({
|
||||
'reporter.ts': expectReporterJS,
|
||||
'reporter.ts': stepsReporterJS,
|
||||
'playwright.config.ts': `
|
||||
module.exports = {
|
||||
reporter: './reporter',
|
||||
@ -219,46 +226,21 @@ test('should report expect steps', async ({ runInlineTest }) => {
|
||||
`%% begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\"}`,
|
||||
`%% begin {\"title\":\"page.title\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"page.title\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\"}`,
|
||||
`%% end {\"title\":\"expect.not.toHaveTitle\",\"category\":\"expect\",\"steps\":[{\"title\":\"page.title\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`,
|
||||
]);
|
||||
});
|
||||
|
||||
test('should report api steps', async ({ runInlineTest }) => {
|
||||
const expectReporterJS = `
|
||||
class Reporter {
|
||||
onStdOut(chunk) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
onTestBegin(test) {
|
||||
console.log('%%%% test begin ' + test.title);
|
||||
}
|
||||
onTestEnd(test) {
|
||||
console.log('%%%% test end ' + test.title);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
console.log('%%%% begin', JSON.stringify(copy));
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
if (copy.error?.stack)
|
||||
copy.error.stack = '<stack>';
|
||||
console.log('%%%% end', JSON.stringify(copy));
|
||||
}
|
||||
}
|
||||
module.exports = Reporter;
|
||||
`;
|
||||
|
||||
const result = await runInlineTest({
|
||||
'reporter.ts': expectReporterJS,
|
||||
'reporter.ts': stepsReporterJS,
|
||||
'playwright.config.ts': `
|
||||
module.exports = {
|
||||
reporter: './reporter',
|
||||
@ -294,11 +276,10 @@ test('should report api steps', async ({ runInlineTest }) => {
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.output.split('\n').filter(line => line.startsWith('%%')).map(stripEscapedAscii)).toEqual([
|
||||
`%%%% test begin pass`,
|
||||
`%% begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
|
||||
`%% begin {\"title\":\"page.click\",\"category\":\"pw:api\"}`,
|
||||
@ -306,50 +287,26 @@ test('should report api steps', async ({ runInlineTest }) => {
|
||||
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%%%% test end pass`,
|
||||
`%%%% test begin pass1`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"page.click\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"page.click\",\"category\":\"pw:api\"}`,
|
||||
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%%%% test end pass1`,
|
||||
`%%%% test begin pass2`,
|
||||
`%% begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"page.click\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"page.click\",\"category\":\"pw:api\"}`,
|
||||
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%%%% test end pass2`,
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
test('should report api step failure', async ({ runInlineTest }) => {
|
||||
const expectReporterJS = `
|
||||
class Reporter {
|
||||
onStdOut(chunk) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
console.log('%%%% begin', JSON.stringify(copy));
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
if (copy.error?.stack)
|
||||
copy.error.stack = '<stack>';
|
||||
console.log('%%%% end', JSON.stringify(copy));
|
||||
}
|
||||
}
|
||||
module.exports = Reporter;
|
||||
`;
|
||||
|
||||
const result = await runInlineTest({
|
||||
'reporter.ts': expectReporterJS,
|
||||
'reporter.ts': stepsReporterJS,
|
||||
'playwright.config.ts': `
|
||||
module.exports = {
|
||||
reporter: './reporter',
|
||||
@ -369,7 +326,7 @@ test('should report api step failure', async ({ runInlineTest }) => {
|
||||
`%% begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`,
|
||||
`%% begin {\"title\":\"page.click\",\"category\":\"pw:api\"}`,
|
||||
@ -377,32 +334,13 @@ test('should report api step failure', async ({ runInlineTest }) => {
|
||||
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`,
|
||||
]);
|
||||
});
|
||||
|
||||
test('should report test.step', async ({ runInlineTest }) => {
|
||||
const expectReporterJS = `
|
||||
class Reporter {
|
||||
onStdOut(chunk) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
console.log('%%%% begin', JSON.stringify(copy));
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
const copy = { ...step, startTime: undefined, duration: undefined, steps: undefined };
|
||||
if (copy.error?.stack)
|
||||
copy.error.stack = '<stack>';
|
||||
console.log('%%%% end', JSON.stringify(copy));
|
||||
}
|
||||
}
|
||||
module.exports = Reporter;
|
||||
`;
|
||||
|
||||
const result = await runInlineTest({
|
||||
'reporter.ts': expectReporterJS,
|
||||
'reporter.ts': stepsReporterJS,
|
||||
'playwright.config.ts': `
|
||||
module.exports = {
|
||||
reporter: './reporter',
|
||||
@ -423,15 +361,15 @@ test('should report test.step', async ({ runInlineTest }) => {
|
||||
`%% begin {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"Before Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.newPage\",\"category\":\"pw:api\"}]}`,
|
||||
`%% begin {\"title\":\"First step\",\"category\":\"test.step\"}`,
|
||||
`%% begin {\"title\":\"expect.toBe\",\"category\":\"expect\"}`,
|
||||
`%% end {\"title\":\"expect.toBe\",\"category\":\"expect\",\"error\":{\"message\":\"expect(received).toBe(expected) // Object.is equality\\n\\nExpected: 2\\nReceived: 1\",\"stack\":\"<stack>\"}}`,
|
||||
`%% end {\"title\":\"First step\",\"category\":\"test.step\",\"error\":{\"message\":\"expect(received).toBe(expected) // Object.is equality\\n\\nExpected: 2\\nReceived: 1\",\"stack\":\"<stack>\"}}`,
|
||||
`%% end {\"title\":\"First step\",\"category\":\"test.step\",\"steps\":[{\"title\":\"expect.toBe\",\"category\":\"expect\",\"error\":{\"message\":\"expect(received).toBe(expected) // Object.is equality\\n\\nExpected: 2\\nReceived: 1\",\"stack\":\"<stack>\"}}],\"error\":{\"message\":\"expect(received).toBe(expected) // Object.is equality\\n\\nExpected: 2\\nReceived: 1\",\"stack\":\"<stack>\"}}`,
|
||||
`%% begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% begin {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"browserContext.close\",\"category\":\"pw:api\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\"}`,
|
||||
`%% end {\"title\":\"After Hooks\",\"category\":\"hook\",\"steps\":[{\"title\":\"browserContext.close\",\"category\":\"pw:api\"}]}`,
|
||||
]);
|
||||
});
|
||||
|
||||
@ -442,6 +380,16 @@ test('should report api step hierarchy', async ({ runInlineTest }) => {
|
||||
this.suite = suite;
|
||||
}
|
||||
|
||||
distillStep(step) {
|
||||
return {
|
||||
...step,
|
||||
startTime: undefined,
|
||||
duration: undefined,
|
||||
parent: undefined,
|
||||
steps: step.steps.length ? step.steps.map(s => this.distillStep(s)) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async onEnd() {
|
||||
const processSuite = (suite: Suite) => {
|
||||
for (const child of suite.suites)
|
||||
@ -449,7 +397,7 @@ test('should report api step hierarchy', async ({ runInlineTest }) => {
|
||||
for (const test of suite.tests) {
|
||||
for (const result of test.results) {
|
||||
for (const step of result.steps) {
|
||||
console.log('%% ' + JSON.stringify(step));
|
||||
console.log('%% ' + JSON.stringify(this.distillStep(step)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -484,84 +432,52 @@ test('should report api step hierarchy', async ({ runInlineTest }) => {
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
const objects = result.output.split('\n').filter(line => line.startsWith('%% ')).map(line => line.substring(3).trim()).filter(Boolean).map(line => JSON.parse(line));
|
||||
const distill = step => {
|
||||
step.duration = 1;
|
||||
step.startTime = 'time';
|
||||
step.steps.forEach(distill);
|
||||
};
|
||||
objects.forEach(distill);
|
||||
expect(objects).toEqual([
|
||||
{
|
||||
category: 'hook',
|
||||
title: 'Before Hooks',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [
|
||||
{
|
||||
category: 'pw:api',
|
||||
title: 'browserContext.newPage',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'test.step',
|
||||
title: 'outer step 1',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [
|
||||
{
|
||||
category: 'test.step',
|
||||
title: 'inner step 1.1',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
category: 'test.step',
|
||||
title: 'inner step 1.2',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'test.step',
|
||||
title: 'outer step 2',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [
|
||||
{
|
||||
category: 'test.step',
|
||||
title: 'inner step 2.1',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
category: 'test.step',
|
||||
title: 'inner step 2.2',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'hook',
|
||||
title: 'After Hooks',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [
|
||||
{
|
||||
category: 'pw:api',
|
||||
title: 'browserContext.close',
|
||||
duration: 1,
|
||||
startTime: 'time',
|
||||
steps: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
9
types/testReporter.d.ts
vendored
9
types/testReporter.d.ts
vendored
@ -227,11 +227,20 @@ export interface TestStep {
|
||||
* User-friendly test step title.
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Returns a list of step titles from the root step down to this step.
|
||||
*/
|
||||
titlePath(): string[];
|
||||
/**
|
||||
* Parent step, if any.
|
||||
*/
|
||||
parent?: TestStep;
|
||||
/**
|
||||
* Step category to differentiate steps with different origin and verbosity. Built-in categories are:
|
||||
* - `hook` for fixtures and hooks initialization and teardown
|
||||
* - `expect` for expect calls
|
||||
* - `pw:api` for Playwright API calls.
|
||||
* - `test.step` for test.step API calls.
|
||||
*/
|
||||
category: string,
|
||||
/**
|
||||
|
@ -60,6 +60,8 @@ export interface TestResult {
|
||||
|
||||
export interface TestStep {
|
||||
title: string;
|
||||
titlePath(): string[];
|
||||
parent?: TestStep;
|
||||
category: string,
|
||||
startTime: Date;
|
||||
duration: number;
|
||||
|
Loading…
Reference in New Issue
Block a user