fix(test runner): properly keep track of requireFile to support helpers/wrappers (#7243)

This fixes an issue where we incorrectly labeled and assigned ids for tests
that declared tests in require'd files or used test wrappers.
See new tests for examples.
This commit is contained in:
Dmitry Gozman 2021-06-21 11:25:15 -07:00 committed by GitHub
parent b9c619206f
commit 6118d16edd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 11 deletions

View File

@ -82,7 +82,7 @@ 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 fileSuite of this._suite.suites) {
const file = fileSuite.file; const file = fileSuite._requireFile;
for (const spec of fileSuite._allSpecs()) { for (const spec of fileSuite._allSpecs()) {
for (const test of spec.tests) { for (const test of spec.tests) {
let entriesByFile = entriesByWorkerHashAndFile.get(test._workerHash); let entriesByFile = entriesByWorkerHashAndFile.get(test._workerHash);

View File

@ -117,6 +117,7 @@ export class Loader {
const revertBabelRequire = installTransform(); const revertBabelRequire = installTransform();
try { try {
const suite = new Suite(''); const suite = new Suite('');
suite._requireFile = file;
suite.file = file; suite.file = file;
setCurrentlyLoadingFileSuite(suite); setCurrentlyLoadingFileSuite(suite);
require(file); require(file);

View File

@ -90,7 +90,7 @@ export class ProjectImpl {
test._repeatEachIndex = i; test._repeatEachIndex = i;
test._projectIndex = this.index; test._projectIndex = this.index;
test._workerHash = `run${this.index}-${digest}-repeat${i}`; test._workerHash = `run${this.index}-${digest}-repeat${i}`;
test._id = `${spec._ordinalInFile}@${spec.file}#run${this.index}-repeat${i}`; test._id = `${spec._ordinalInFile}@${spec._requireFile}#run${this.index}-repeat${i}`;
spec.tests.push(test); spec.tests.push(test);
tests.push(test); tests.push(test);
} }

View File

@ -165,7 +165,7 @@ export class Runner {
const fileSuites = new Map<string, Suite>(); const fileSuites = new Map<string, Suite>();
for (const fileSuite of rootSuite.suites) for (const fileSuite of rootSuite.suites)
fileSuites.set(fileSuite.file, fileSuite); fileSuites.set(fileSuite._requireFile, fileSuite);
const outputDirs = new Set<string>(); const outputDirs = new Set<string>();
const grepMatcher = createMatcher(config.grep); const grepMatcher = createMatcher(config.grep);
@ -209,7 +209,7 @@ export class Runner {
if (process.stdout.isTTY) { if (process.stdout.isTTY) {
const workers = new Set(); const workers = new Set();
rootSuite.findTest(test => { rootSuite.findTest(test => {
workers.add(test.spec.file + test._workerHash); workers.add(test.spec._requireFile + test._workerHash);
}); });
console.log(); console.log();
const jobs = Math.min(config.workers, workers.size); const jobs = Math.min(config.workers, workers.size);

View File

@ -26,6 +26,7 @@ class Base {
parent?: Suite; parent?: Suite;
_only = false; _only = false;
_requireFile: string = '';
constructor(title: string) { constructor(title: string) {
this.title = title; this.title = title;

View File

@ -63,10 +63,11 @@ export class TestTypeImpl {
throw errorWithCallLocation(`test() can only be called in a test file`); throw errorWithCallLocation(`test() can only be called in a test file`);
const location = callLocation(suite.file); const location = callLocation(suite.file);
const ordinalInFile = countByFile.get(suite.file) || 0; const ordinalInFile = countByFile.get(suite._requireFile) || 0;
countByFile.set(location.file, ordinalInFile + 1); countByFile.set(suite._requireFile, ordinalInFile + 1);
const spec = new Spec(title, fn, ordinalInFile, this); const spec = new Spec(title, fn, ordinalInFile, this);
spec._requireFile = suite._requireFile;
spec.file = location.file; spec.file = location.file;
spec.line = location.line; spec.line = location.line;
spec.column = location.column; spec.column = location.column;
@ -83,7 +84,8 @@ export class TestTypeImpl {
const location = callLocation(suite.file); const location = callLocation(suite.file);
const child = new Suite(title); const child = new Suite(title);
child.file = suite.file; child._requireFile = suite._requireFile;
child.file = location.file;
child.line = location.line; child.line = location.line;
child.column = location.column; child.column = location.column;
suite._addSuite(child); suite._addSuite(child);

View File

@ -184,7 +184,7 @@ export class WorkerRunner extends EventEmitter {
const testId = test._id; const testId = test._id;
const baseOutputDir = (() => { const baseOutputDir = (() => {
const relativeTestFilePath = path.relative(this._project.config.testDir, spec.file.replace(/\.(spec|test)\.(js|ts)/, '')); const relativeTestFilePath = path.relative(this._project.config.testDir, spec._requireFile.replace(/\.(spec|test)\.(js|ts)/, ''));
const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-'); const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-');
let testOutputDir = sanitizedRelativePath + '-' + sanitizeForFilePath(spec.title); let testOutputDir = sanitizedRelativePath + '-' + sanitizeForFilePath(spec.title);
if (this._uniqueProjectNamePathSegment) if (this._uniqueProjectNamePathSegment)
@ -231,7 +231,7 @@ export class WorkerRunner extends EventEmitter {
else else
snapshotName += suffix; snapshotName += suffix;
} }
return path.join(spec.file + '-snapshots', snapshotName); return path.join(spec._requireFile + '-snapshots', snapshotName);
}, },
skip: (...args: [arg?: any, description?: string]) => modifier(testInfo, 'skip', args), skip: (...args: [arg?: any, description?: string]) => modifier(testInfo, 'skip', args),
fixme: (...args: [arg?: any, description?: string]) => modifier(testInfo, 'fixme', args), fixme: (...args: [arg?: any, description?: string]) => modifier(testInfo, 'fixme', args),

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { test, expect } from './playwright-test-fixtures'; import { test, expect, stripAscii } from './playwright-test-fixtures';
import * as path from 'path'; import * as path from 'path';
test('should fail', async ({ runInlineTest }) => { test('should fail', async ({ runInlineTest }) => {
@ -210,7 +210,7 @@ test('skip should take priority over fail', async ({ runInlineTest }) => {
expect(failed).toBe(1); expect(failed).toBe(1);
}); });
test('should focus test from one runTests', async ({ runInlineTest }) => { test('should focus test from one project', async ({ runInlineTest }) => {
const { exitCode, passed, skipped, failed } = await runInlineTest({ const { exitCode, passed, skipped, failed } = await runInlineTest({
'playwright.config.ts': ` 'playwright.config.ts': `
import * as path from 'path'; import * as path from 'path';
@ -251,3 +251,103 @@ test('should work with default export', async ({ runInlineTest }) => {
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
expect(result.failed).toBe(0); expect(result.failed).toBe(0);
}); });
test('should work with test wrapper', async ({ runInlineTest }) => {
const result = await runInlineTest({
'helper.js': `
console.log('%%helper');
exports.wrap = (title, fn) => {
pwt.test(title, fn);
};
`,
'a.spec.js': `
console.log('%%a.spec');
const { wrap } = require('./helper');
wrap('test1', () => {
console.log('%%test1');
});
pwt.test.describe('suite1', () => {
wrap('suite1.test1', () => {
console.log('%%suite1.test1');
});
});
`,
'b.spec.js': `
console.log('%%b.spec');
const { wrap } = require('./helper');
wrap('test2', () => {
console.log('%%test2');
});
pwt.test.describe('suite2', () => {
wrap('suite2.test2', () => {
console.log('%%suite2.test2');
});
});
`,
}, { workers: 1, reporter: 'line' });
expect(result.passed).toBe(4);
expect(result.exitCode).toBe(0);
expect(stripAscii(result.output).split('\n').filter(line => line.startsWith('%%'))).toEqual([
'%%a.spec',
'%%helper',
'%%b.spec',
'%%a.spec',
'%%helper',
'%%test1',
'%%suite1.test1',
'%%b.spec',
'%%test2',
'%%suite2.test2',
]);
});
test('should work with test helper', async ({ runInlineTest }) => {
const result = await runInlineTest({
'helper-a.js': `
console.log('%%helper-a');
pwt.test('test1', () => {
console.log('%%test1');
});
pwt.test.describe('suite1', () => {
pwt.test('suite1.test1', () => {
console.log('%%suite1.test1');
});
});
`,
'a.spec.js': `
console.log('%%a.spec');
require('./helper-a');
`,
'helper-b.js': `
console.log('%%helper-b');
pwt.test('test1', () => {
console.log('%%test2');
});
pwt.test.describe('suite2', () => {
pwt.test('suite2.test2', () => {
console.log('%%suite2.test2');
});
});
`,
'b.spec.js': `
console.log('%%b.spec');
require('./helper-b');
`,
}, { workers: 1, reporter: 'line' });
expect(result.passed).toBe(4);
expect(result.exitCode).toBe(0);
expect(stripAscii(result.output).split('\n').filter(line => line.startsWith('%%'))).toEqual([
'%%a.spec',
'%%helper-a',
'%%b.spec',
'%%helper-b',
'%%a.spec',
'%%helper-a',
'%%test1',
'%%suite1.test1',
'%%b.spec',
'%%helper-b',
'%%test2',
'%%suite2.test2',
]);
});