feat(test-runner): suite per project (#7688)

This makes our suite structure the following:
```
Root(title='') > Project(title=projectName) > File(title=relativeFilePath) > ...suites > test
```

Removed `fullTitle()` because it is not used directly by anyone.
Default reporters now report each test as
```
[project-name] › relative/file/path.spec.ts:42:42 › suite subsuite test title
```
This commit is contained in:
Dmitry Gozman 2021-07-16 15:23:50 -07:00 committed by GitHub
parent bde764085c
commit 18be5f5319
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 91 additions and 105 deletions

View file

@ -79,14 +79,14 @@ export class Dispatcher {
_filesSortedByWorkerHash(): DispatcherEntry[] { _filesSortedByWorkerHash(): DispatcherEntry[] {
const entriesByWorkerHashAndFile = new Map<string, Map<string, DispatcherEntry>>(); const entriesByWorkerHashAndFile = new Map<string, Map<string, DispatcherEntry>>();
for (const fileSuite of this._suite.suites) { for (const projectSuite of this._suite.suites) {
const file = fileSuite._requireFile; for (const test of projectSuite.allTests()) {
for (const test of fileSuite.allTests()) {
let entriesByFile = entriesByWorkerHashAndFile.get(test._workerHash); let entriesByFile = entriesByWorkerHashAndFile.get(test._workerHash);
if (!entriesByFile) { if (!entriesByFile) {
entriesByFile = new Map(); entriesByFile = new Map();
entriesByWorkerHashAndFile.set(test._workerHash, entriesByFile); entriesByWorkerHashAndFile.set(test._workerHash, entriesByFile);
} }
const file = test._requireFile;
let entry = entriesByFile.get(file); let entry = entriesByFile.get(file);
if (!entry) { if (!entry) {
entry = { entry = {
@ -94,8 +94,8 @@ export class Dispatcher {
entries: [], entries: [],
file, file,
}, },
repeatEachIndex: fileSuite._repeatEachIndex, repeatEachIndex: test._repeatEachIndex,
projectIndex: fileSuite._projectIndex, projectIndex: test._projectIndex,
hash: test._workerHash, hash: test._workerHash,
}; };
entriesByFile.set(file, entry); entriesByFile.set(file, entry);

View file

@ -110,7 +110,7 @@ export class Loader {
if (this._fileSuites.has(file)) if (this._fileSuites.has(file))
return this._fileSuites.get(file)!; return this._fileSuites.get(file)!;
try { try {
const suite = new Suite(''); const suite = new Suite(path.relative(this._fullConfig.rootDir, file) || path.basename(file));
suite._requireFile = file; suite._requireFile = file;
suite.location.file = file; suite.location.file = file;
setCurrentlyLoadingFileSuite(suite); setCurrentlyLoadingFileSuite(suite);

View file

@ -78,15 +78,15 @@ export class ProjectImpl {
return this.testPools.get(test)!; return this.testPools.get(test)!;
} }
cloneSuite(suite: Suite, repeatEachIndex: number, filter: (test: Test) => boolean): Suite | undefined { private _cloneEntries(from: Suite, to: Suite, repeatEachIndex: number, filter: (test: Test) => boolean): boolean {
const result = suite._clone(); for (const entry of from._entries) {
result._repeatEachIndex = repeatEachIndex;
result._projectIndex = this.index;
for (const entry of suite._entries) {
if (entry instanceof Suite) { if (entry instanceof Suite) {
const cloned = this.cloneSuite(entry, repeatEachIndex, filter); const suite = entry._clone();
if (cloned) to._addSuite(suite);
result._addSuite(cloned); if (!this._cloneEntries(entry, suite, repeatEachIndex, filter)) {
to._entries.pop();
to.suites.pop();
}
} else { } else {
const pool = this.buildPool(entry); const pool = this.buildPool(entry);
const test = entry._clone(); const test = entry._clone();
@ -95,14 +95,21 @@ export class ProjectImpl {
test._workerHash = `run${this.index}-${pool.digest}-repeat${repeatEachIndex}`; test._workerHash = `run${this.index}-${pool.digest}-repeat${repeatEachIndex}`;
test._id = `${entry._ordinalInFile}@${entry._requireFile}#run${this.index}-repeat${repeatEachIndex}`; test._id = `${entry._ordinalInFile}@${entry._requireFile}#run${this.index}-repeat${repeatEachIndex}`;
test._pool = pool; test._pool = pool;
test._buildTitlePath(suite._titlePath); test._repeatEachIndex = repeatEachIndex;
if (!filter(test)) test._projectIndex = this.index;
continue; to._addTest(test);
result._addTest(test); if (!filter(test)) {
to._entries.pop();
to.suites.pop();
} }
} }
if (result._entries.length) }
return result; return to._entries.length > 0;
}
cloneFileSuite(suite: Suite, repeatEachIndex: number, filter: (test: Test) => boolean): Suite | undefined {
const result = suite._clone();
return this._cloneEntries(suite, result, repeatEachIndex, filter) ? result : undefined;
} }
private resolveFixtures(testType: TestTypeImpl): FixturesWithLocation[] { private resolveFixtures(testType: TestTypeImpl): FixturesWithLocation[] {

View file

@ -28,7 +28,6 @@ export interface Suite {
suites: Suite[]; suites: Suite[];
tests: Test[]; tests: Test[];
titlePath(): string[]; titlePath(): string[];
fullTitle(): string;
allTests(): Test[]; allTests(): Test[];
} }
export interface Test { export interface Test {
@ -38,10 +37,8 @@ export interface Test {
expectedStatus: TestStatus; expectedStatus: TestStatus;
timeout: number; timeout: number;
annotations: { type: string, description?: string }[]; annotations: { type: string, description?: string }[];
projectName: string;
retries: number; retries: number;
titlePath(): string[]; titlePath(): string[];
fullTitle(): string;
status(): 'skipped' | 'expected' | 'unexpected' | 'flaky'; status(): 'skipped' | 'expected' | 'unexpected' | 'flaky';
ok(): boolean; ok(): boolean;
} }

View file

@ -50,8 +50,9 @@ export class BaseReporter implements Reporter {
} }
onTestEnd(test: Test, result: TestResult) { onTestEnd(test: Test, result: TestResult) {
const projectName = test.titlePath()[1];
const relativePath = relativeTestPath(this.config, test); const relativePath = relativeTestPath(this.config, test);
const fileAndProject = relativePath + (test.projectName ? ` [${test.projectName}]` : ''); const fileAndProject = (projectName ? `[${projectName}] ` : '') + relativePath;
const duration = this.fileDurations.get(fileAndProject) || 0; const duration = this.fileDurations.get(fileAndProject) || 0;
this.fileDurations.set(fileAndProject, duration + result.duration); this.fileDurations.set(fileAndProject, duration + result.duration);
} }
@ -156,10 +157,11 @@ function relativeTestPath(config: FullConfig, test: Test): string {
} }
export function formatTestTitle(config: FullConfig, test: Test): string { export function formatTestTitle(config: FullConfig, test: Test): string {
let relativePath = relativeTestPath(config, test); // root, project, file, ...describes, test
relativePath += ':' + test.location.line + ':' + test.location.column; const [, projectName, , ...titles] = test.titlePath();
const title = (test.projectName ? `[${test.projectName}] ` : '') + test.fullTitle(); const location = `${relativeTestPath(config, test)}:${test.location.line}:${test.location.column}`;
return `${relativePath} ${title}`; const projectTitle = projectName ? `[${projectName}] ` : '';
return `${projectTitle}${location} ${titles.join(' ')}`;
} }
function formatTestHeader(config: FullConfig, test: Test, indent: string, index?: number): string { function formatTestHeader(config: FullConfig, test: Test, indent: string, index?: number): string {

View file

@ -123,18 +123,19 @@ class JSONReporter implements Reporter {
} }
private _mergeSuites(suites: Suite[]): JSONReportSuite[] { private _mergeSuites(suites: Suite[]): JSONReportSuite[] {
debugger;
const fileSuites = new Map<string, JSONReportSuite>(); const fileSuites = new Map<string, JSONReportSuite>();
const result: JSONReportSuite[] = []; const result: JSONReportSuite[] = [];
for (const suite of suites) { for (const projectSuite of suites) {
if (!fileSuites.has(suite.location.file)) { for (const fileSuite of projectSuite.suites) {
const serialized = this._serializeSuite(suite); if (!fileSuites.has(fileSuite.location.file)) {
const serialized = this._serializeSuite(fileSuite);
if (serialized) { if (serialized) {
fileSuites.set(suite.location.file, serialized); fileSuites.set(fileSuite.location.file, serialized);
result.push(serialized); result.push(serialized);
} }
} else { } else {
this._mergeTestsFromSuite(fileSuites.get(suite.location.file)!, suite); this._mergeTestsFromSuite(fileSuites.get(fileSuite.location.file)!, fileSuite);
}
} }
} }
return result; return result;
@ -202,7 +203,7 @@ class JSONReporter implements Reporter {
timeout: test.timeout, timeout: test.timeout,
annotations: test.annotations, annotations: test.annotations,
expectedStatus: test.expectedStatus, expectedStatus: test.expectedStatus,
projectName: test.projectName, projectName: test.titlePath()[1],
results: test.results.map(r => this._serializeTestResult(r)), results: test.results.map(r => this._serializeTestResult(r)),
status: test.status(), status: test.status(),
}; };

View file

@ -46,8 +46,10 @@ class JUnitReporter implements Reporter {
async onEnd(result: FullResult) { async onEnd(result: FullResult) {
const duration = monotonicTime() - this.startTime; const duration = monotonicTime() - this.startTime;
const children: XMLEntry[] = []; const children: XMLEntry[] = [];
for (const suite of this.suite.suites) for (const projectSuite of this.suite.suites) {
children.push(this._buildTestSuite(suite)); for (const fileSuite of projectSuite.suites)
children.push(this._buildTestSuite(fileSuite));
}
const tokens: string[] = []; const tokens: string[] = [];
const self = this; const self = this;
@ -119,7 +121,8 @@ class JUnitReporter implements Reporter {
const entry = { const entry = {
name: 'testcase', name: 'testcase',
attributes: { attributes: {
name: test.fullTitle(), // Skip root, project, file
name: test.titlePath().slice(3).join(' '),
classname: formatTestTitle(this.config, test), classname: formatTestTitle(this.config, test),
time: (test.results.reduce((acc, value) => acc + value.duration, 0)) / 1000 time: (test.results.reduce((acc, value) => acc + value.duration, 0)) / 1000
}, },

View file

@ -180,8 +180,14 @@ export class Runner {
preprocessRoot._addSuite(fileSuite); preprocessRoot._addSuite(fileSuite);
if (config.forbidOnly) { if (config.forbidOnly) {
const onlyTestsAndSuites = preprocessRoot._getOnlyItems(); const onlyTestsAndSuites = preprocessRoot._getOnlyItems();
if (onlyTestsAndSuites.length > 0) if (onlyTestsAndSuites.length > 0) {
return { status: 'forbid-only', locations: onlyTestsAndSuites.map(testOrSuite => `${buildItemLocation(config.rootDir, testOrSuite)} > ${testOrSuite.fullTitle()}`) }; const locations = onlyTestsAndSuites.map(testOrSuite => {
// Skip root and file.
const title = testOrSuite.titlePath().slice(2).join(' ');
return `${buildItemLocation(config.rootDir, testOrSuite)} > ${title}`;
});
return { status: 'forbid-only', locations };
}
} }
const clashingTests = getClashingTestsPerSuite(preprocessRoot); const clashingTests = getClashingTestsPerSuite(preprocessRoot);
if (clashingTests.size > 0) if (clashingTests.size > 0)
@ -198,20 +204,21 @@ export class Runner {
const grepInvertMatcher = config.grepInvert ? createMatcher(config.grepInvert) : null; const grepInvertMatcher = config.grepInvert ? createMatcher(config.grepInvert) : null;
const rootSuite = new Suite(''); const rootSuite = new Suite('');
for (const project of projects) { for (const project of projects) {
const projectSuite = new Suite(project.config.name);
rootSuite._addSuite(projectSuite);
for (const file of files.get(project)!) { for (const file of files.get(project)!) {
const fileSuite = fileSuites.get(file); const fileSuite = fileSuites.get(file);
if (!fileSuite) if (!fileSuite)
continue; continue;
for (let repeatEachIndex = 0; repeatEachIndex < project.config.repeatEach; repeatEachIndex++) { for (let repeatEachIndex = 0; repeatEachIndex < project.config.repeatEach; repeatEachIndex++) {
const cloned = project.cloneSuite(fileSuite, repeatEachIndex, test => { const cloned = project.cloneFileSuite(fileSuite, repeatEachIndex, test => {
const fullTitle = test.fullTitle(); const grepTitle = test.titlePath().join(' ');
const titleWithProject = (test.projectName ? `[${test.projectName}] ` : '') + fullTitle; if (grepInvertMatcher?.(grepTitle))
if (grepInvertMatcher?.(titleWithProject))
return false; return false;
return grepMatcher(titleWithProject); return grepMatcher(grepTitle);
}); });
if (cloned) if (cloned)
rootSuite._addSuite(cloned); projectSuite._addSuite(cloned);
} }
} }
outputDirs.add(project.config.outputDir); outputDirs.add(project.config.outputDir);
@ -380,7 +387,7 @@ function getClashingTestsPerSuite(rootSuite: Suite): Map<string, Test[]> {
for (const childSuite of suite.suites) for (const childSuite of suite.suites)
visit(childSuite, clashingTests); visit(childSuite, clashingTests);
for (const test of suite.tests) { for (const test of suite.tests) {
const fullTitle = test.fullTitle(); const fullTitle = test.titlePath().slice(2).join(' ');
if (!clashingTests.has(fullTitle)) if (!clashingTests.has(fullTitle))
clashingTests.set(fullTitle, []); clashingTests.set(fullTitle, []);
clashingTests.set(fullTitle, clashingTests.get(fullTitle)!.concat(test)); clashingTests.set(fullTitle, clashingTests.get(fullTitle)!.concat(test));

View file

@ -24,7 +24,6 @@ class Base {
location: Location = { file: '', line: 0, column: 0 }; location: Location = { file: '', line: 0, column: 0 };
parent?: Suite; parent?: Suite;
_titlePath: string[] = [];
_only = false; _only = false;
_requireFile: string = ''; _requireFile: string = '';
@ -32,18 +31,10 @@ class Base {
this.title = title; this.title = title;
} }
_buildTitlePath(parentTitlePath: string[]) {
this._titlePath = [...parentTitlePath];
if (this.title)
this._titlePath.push(this.title);
}
titlePath(): string[] { titlePath(): string[] {
return this._titlePath; const titlePath = this.parent ? this.parent.titlePath() : [];
} titlePath.push(this.title);
return titlePath;
fullTitle(): string {
return this._titlePath.join(' ');
} }
} }
@ -67,8 +58,6 @@ export class Suite extends Base implements reporterTypes.Suite {
_timeout: number | undefined; _timeout: number | undefined;
_annotations: Annotations = []; _annotations: Annotations = [];
_modifiers: Modifier[] = []; _modifiers: Modifier[] = [];
_repeatEachIndex = 0;
_projectIndex = 0;
_addTest(test: Test) { _addTest(test: Test) {
test.parent = this; test.parent = this;
@ -139,6 +128,8 @@ export class Test extends Base implements reporterTypes.Test {
_id = ''; _id = '';
_workerHash = ''; _workerHash = '';
_pool: FixturePool | undefined; _pool: FixturePool | undefined;
_repeatEachIndex = 0;
_projectIndex = 0;
constructor(title: string, fn: Function, ordinalInFile: number, testType: TestTypeImpl) { constructor(title: string, fn: Function, ordinalInFile: number, testType: TestTypeImpl) {
super(title); super(title);

View file

@ -66,7 +66,6 @@ export class TestTypeImpl {
test._requireFile = suite._requireFile; test._requireFile = suite._requireFile;
test.location = location; test.location = location;
suite._addTest(test); suite._addTest(test);
test._buildTitlePath(suite._titlePath);
if (type === 'only') if (type === 'only')
test._only = true; test._only = true;
@ -81,7 +80,6 @@ export class TestTypeImpl {
child._requireFile = suite._requireFile; child._requireFile = suite._requireFile;
child.location = location; child.location = location;
suite._addSuite(child); suite._addSuite(child);
child._buildTitlePath(suite._titlePath);
if (type === 'only') if (type === 'only')
child._only = true; child._only = true;

View file

@ -119,7 +119,7 @@ export class WorkerRunner extends EventEmitter {
const fileSuite = await this._loader.loadTestFile(runPayload.file); const fileSuite = await this._loader.loadTestFile(runPayload.file);
let anyPool: FixturePool | undefined; let anyPool: FixturePool | undefined;
const suite = this._project.cloneSuite(fileSuite, this._params.repeatEachIndex, test => { const suite = this._project.cloneFileSuite(fileSuite, this._params.repeatEachIndex, test => {
if (!this._entries.has(test._id)) if (!this._entries.has(test._id))
return false; return false;
anyPool = test._pool; anyPool = test._pool;

View file

@ -123,14 +123,14 @@ test('should print slow tests', async ({ runInlineTest }) => {
}); });
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(8); expect(result.passed).toBe(8);
expect(stripAscii(result.output)).toContain(`Slow test: dir${path.sep}a.test.js [foo] (`); expect(stripAscii(result.output)).toContain(`Slow test: [foo] dir${path.sep}a.test.js (`);
expect(stripAscii(result.output)).toContain(`Slow test: dir${path.sep}a.test.js [bar] (`); expect(stripAscii(result.output)).toContain(`Slow test: [bar] dir${path.sep}a.test.js (`);
expect(stripAscii(result.output)).toContain(`Slow test: dir${path.sep}a.test.js [baz] (`); expect(stripAscii(result.output)).toContain(`Slow test: [baz] dir${path.sep}a.test.js (`);
expect(stripAscii(result.output)).toContain(`Slow test: dir${path.sep}a.test.js [qux] (`); expect(stripAscii(result.output)).toContain(`Slow test: [qux] dir${path.sep}a.test.js (`);
expect(stripAscii(result.output)).not.toContain(`Slow test: dir${path.sep}b.test.js [foo] (`); expect(stripAscii(result.output)).not.toContain(`Slow test: [foo] dir${path.sep}b.test.js (`);
expect(stripAscii(result.output)).not.toContain(`Slow test: dir${path.sep}b.test.js [bar] (`); expect(stripAscii(result.output)).not.toContain(`Slow test: [bar] dir${path.sep}b.test.js (`);
expect(stripAscii(result.output)).not.toContain(`Slow test: dir${path.sep}b.test.js [baz] (`); expect(stripAscii(result.output)).not.toContain(`Slow test: [baz] dir${path.sep}b.test.js (`);
expect(stripAscii(result.output)).not.toContain(`Slow test: dir${path.sep}b.test.js [qux] (`); expect(stripAscii(result.output)).not.toContain(`Slow test: [qux] dir${path.sep}b.test.js (`);
}); });
test('should not print slow tests', async ({ runInlineTest }) => { test('should not print slow tests', async ({ runInlineTest }) => {

View file

@ -212,16 +212,14 @@ test('should render projects', async ({ runInlineTest }) => {
expect(xml['testsuites']['testsuite'][0]['$']['failures']).toBe('0'); expect(xml['testsuites']['testsuite'][0]['$']['failures']).toBe('0');
expect(xml['testsuites']['testsuite'][0]['$']['skipped']).toBe('0'); expect(xml['testsuites']['testsuite'][0]['$']['skipped']).toBe('0');
expect(xml['testsuites']['testsuite'][0]['testcase'][0]['$']['name']).toBe('one'); expect(xml['testsuites']['testsuite'][0]['testcase'][0]['$']['name']).toBe('one');
expect(xml['testsuites']['testsuite'][0]['testcase'][0]['$']['classname']).toContain('[project1] one'); expect(xml['testsuites']['testsuite'][0]['testcase'][0]['$']['classname']).toContain('[project1] a.test.js:6:7 one');
expect(xml['testsuites']['testsuite'][0]['testcase'][0]['$']['classname']).toContain('a.test.js:6:7');
expect(xml['testsuites']['testsuite'][1]['$']['name']).toBe('a.test.js'); expect(xml['testsuites']['testsuite'][1]['$']['name']).toBe('a.test.js');
expect(xml['testsuites']['testsuite'][1]['$']['tests']).toBe('1'); expect(xml['testsuites']['testsuite'][1]['$']['tests']).toBe('1');
expect(xml['testsuites']['testsuite'][1]['$']['failures']).toBe('0'); expect(xml['testsuites']['testsuite'][1]['$']['failures']).toBe('0');
expect(xml['testsuites']['testsuite'][1]['$']['skipped']).toBe('0'); expect(xml['testsuites']['testsuite'][1]['$']['skipped']).toBe('0');
expect(xml['testsuites']['testsuite'][1]['testcase'][0]['$']['name']).toBe('one'); expect(xml['testsuites']['testsuite'][1]['testcase'][0]['$']['name']).toBe('one');
expect(xml['testsuites']['testsuite'][1]['testcase'][0]['$']['classname']).toContain('[project2] one'); expect(xml['testsuites']['testsuite'][1]['testcase'][0]['$']['classname']).toContain('[project2] a.test.js:6:7 one');
expect(xml['testsuites']['testsuite'][1]['testcase'][0]['$']['classname']).toContain('a.test.js:6:7');
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
}); });

View file

@ -37,9 +37,9 @@ test('render each test with project name', async ({ runInlineTest }) => {
const text = stripAscii(result.output); const text = stripAscii(result.output);
const positiveStatusMarkPrefix = process.platform === 'win32' ? 'ok' : '✓ '; const positiveStatusMarkPrefix = process.platform === 'win32' ? 'ok' : '✓ ';
const negativateStatusMarkPrefix = process.platform === 'win32' ? 'x ' : '✘ '; const negativateStatusMarkPrefix = process.platform === 'win32' ? 'x ' : '✘ ';
expect(text).toContain(`${negativateStatusMarkPrefix} 1) a.test.ts:6:7 [foo] fails`); expect(text).toContain(`${negativateStatusMarkPrefix} 1) [foo] a.test.ts:6:7 fails`);
expect(text).toContain(`${negativateStatusMarkPrefix} 2) a.test.ts:6:7 [bar] fails`); expect(text).toContain(`${negativateStatusMarkPrefix} 2) [bar] a.test.ts:6:7 fails`);
expect(text).toContain(`${positiveStatusMarkPrefix} a.test.ts:9:7 [foo] passes`); expect(text).toContain(`${positiveStatusMarkPrefix} [foo] a.test.ts:9:7 passes`);
expect(text).toContain(`${positiveStatusMarkPrefix} a.test.ts:9:7 [bar] passes`); expect(text).toContain(`${positiveStatusMarkPrefix} [bar] a.test.ts:9:7 passes`);
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
}); });

View file

@ -81,24 +81,6 @@ test('should grep test name with regular expression and a space', async ({ runIn
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
}); });
test('should grep by project name', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { projects: [
{ name: 'foo' },
{ name: 'bar' },
]};
`,
'a.spec.ts': `
pwt.test('should work', () => {});
`,
}, { 'grep': 'foo]' });
expect(result.passed).toBe(1);
expect(result.skipped).toBe(0);
expect(result.failed).toBe(0);
expect(result.exitCode).toBe(0);
});
test('should grep invert test name', async ({ runInlineTest }) => { test('should grep invert test name', async ({ runInlineTest }) => {
const result = await runInlineTest(files, { 'grep-invert': 'BB' }); const result = await runInlineTest(files, { 'grep-invert': 'BB' });
expect(result.passed).toBe(6); expect(result.passed).toBe(6);

View file

@ -24,10 +24,10 @@ test('should work with custom reporter', async ({ runInlineTest }) => {
this.options = options; this.options = options;
} }
onBegin(config, suite) { onBegin(config, suite) {
console.log('\\n%%reporter-begin-' + this.options.begin + '-' + suite.suites.length + '%%'); console.log('\\n%%reporter-begin-' + this.options.begin + '%%');
} }
onTestBegin(test) { onTestBegin(test) {
console.log('\\n%%reporter-testbegin-' + test.title + '-' + test.projectName + '%%'); console.log('\\n%%reporter-testbegin-' + test.title + '-' + test.titlePath()[1] + '%%');
} }
onStdOut() { onStdOut() {
console.log('\\n%%reporter-stdout%%'); console.log('\\n%%reporter-stdout%%');
@ -36,7 +36,7 @@ test('should work with custom reporter', async ({ runInlineTest }) => {
console.log('\\n%%reporter-stderr%%'); console.log('\\n%%reporter-stderr%%');
} }
onTestEnd(test) { onTestEnd(test) {
console.log('\\n%%reporter-testend-' + test.title + '-' + test.projectName + '%%'); console.log('\\n%%reporter-testend-' + test.title + '-' + test.titlePath()[1] + '%%');
} }
onTimeout() { onTimeout() {
console.log('\\n%%reporter-timeout%%'); console.log('\\n%%reporter-timeout%%');
@ -73,7 +73,7 @@ test('should work with custom reporter', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.output.split('\n').filter(line => line.startsWith('%%'))).toEqual([ expect(result.output.split('\n').filter(line => line.startsWith('%%'))).toEqual([
'%%reporter-begin-begin-3%%', '%%reporter-begin-begin%%',
'%%reporter-testbegin-pass-foo%%', '%%reporter-testbegin-pass-foo%%',
'%%reporter-stdout%%', '%%reporter-stdout%%',
'%%reporter-stderr%%', '%%reporter-stderr%%',