chore: respect source map sources when filtering in CLI (#22180)

Fixes #22123
This commit is contained in:
Pavel Feldman 2023-04-03 19:49:01 -07:00 committed by GitHub
parent 8bc7ed0469
commit 87acda74ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 43 deletions

View File

@ -14,9 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import fs from 'fs';
import path from 'path'; import path from 'path';
import readline from 'readline';
import type { Reporter, TestError } from '../../types/testReporter'; import type { Reporter, TestError } from '../../types/testReporter';
import { InProcessLoaderHost, OutOfProcessLoaderHost } from './loaderHost'; import { InProcessLoaderHost, OutOfProcessLoaderHost } from './loaderHost';
import { Suite } from '../common/test'; import { Suite } from '../common/test';
@ -29,6 +27,8 @@ import { requireOrImport } from '../common/transform';
import { buildFileSuiteForProject, filterByFocusedLine, filterByTestIds, filterOnly, filterTestsRemoveEmptySuites } from '../common/suiteUtils'; import { buildFileSuiteForProject, filterByFocusedLine, filterByTestIds, filterOnly, filterTestsRemoveEmptySuites } from '../common/suiteUtils';
import { createTestGroups, filterForShard, type TestGroup } from './testGroups'; import { createTestGroups, filterForShard, type TestGroup } from './testGroups';
import { dependenciesForTestFile } from '../common/compilationCache'; import { dependenciesForTestFile } from '../common/compilationCache';
import { sourceMapSupport } from '../utilsBundle';
import type { RawSourceMap } from 'source-map';
export async function collectProjectsAndTestFiles(config: FullConfigInternal, projectsToIgnore: Set<FullProjectInternal>, additionalFileMatcher: Matcher | undefined) { export async function collectProjectsAndTestFiles(config: FullConfigInternal, projectsToIgnore: Set<FullProjectInternal>, additionalFileMatcher: Matcher | undefined) {
const fsCache = new Map(); const fsCache = new Map();
@ -47,15 +47,16 @@ export async function collectProjectsAndTestFiles(config: FullConfigInternal, pr
// Filter files based on the file filters, eliminate the empty projects. // Filter files based on the file filters, eliminate the empty projects.
const filesToRunByProject = new Map<FullProjectInternal, string[]>(); const filesToRunByProject = new Map<FullProjectInternal, string[]>();
for (const [project, files] of allFilesForProject) { for (const [project, files] of allFilesForProject) {
const matchedFiles = await Promise.all(files.map(async file => { const matchedFiles = files.filter(file => {
if (additionalFileMatcher && !additionalFileMatcher(file)) const hasMatchingSources = sourceMapSources(file, sourceMapCache).some(source => {
return; if (additionalFileMatcher && !additionalFileMatcher(source))
if (cliFileMatcher) { return false;
if (!cliFileMatcher(file) && !await isPotentiallyJavaScriptFileWithSourceMap(file, sourceMapCache)) if (cliFileMatcher && !cliFileMatcher(source))
return; return false;
} return true;
return file; });
})); return hasMatchingSources;
});
const filteredFiles = matchedFiles.filter(Boolean) as string[]; const filteredFiles = matchedFiles.filter(Boolean) as string[];
if (filteredFiles.length) if (filteredFiles.length)
filesToRunByProject.set(project, filteredFiles); filesToRunByProject.set(project, filteredFiles);
@ -263,29 +264,20 @@ export function loadReporter(config: FullConfigInternal, file: string): Promise<
return requireOrImportDefaultFunction(path.resolve(config.rootDir, file), true); return requireOrImportDefaultFunction(path.resolve(config.rootDir, file), true);
} }
async function isPotentiallyJavaScriptFileWithSourceMap(file: string, cache: Map<string, boolean>): Promise<boolean> { function sourceMapSources(file: string, cache: Map<string, string[]>): string[] {
let sources = [file];
if (!file.endsWith('.js')) if (!file.endsWith('.js'))
return false; return sources;
if (cache.has(file)) if (cache.has(file))
return cache.get(file)!; return cache.get(file)!;
try { try {
const stream = fs.createReadStream(file); const sourceMap = sourceMapSupport.retrieveSourceMap(file);
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity }); const sourceMapData: RawSourceMap | undefined = typeof sourceMap?.map === 'string' ? JSON.parse(sourceMap.map) : sourceMap?.map;
let lastLine: string | undefined; if (sourceMapData?.sources)
rl.on('line', line => { sources = sourceMapData.sources.map(source => path.resolve(path.dirname(file), source));
lastLine = line; } finally {
}); cache.set(file, sources);
await new Promise((fulfill, reject) => { return sources;
rl.on('close', fulfill);
rl.on('error', reject);
stream.on('error', reject);
});
const hasSourceMap = !!lastLine && lastLine.startsWith('//# sourceMappingURL=');
cache.set(file, hasSourceMap);
return hasSourceMap;
} catch (e) {
cache.set(file, true);
return true;
} }
} }

View File

@ -503,21 +503,26 @@ test('should not load tests not matching filter', async ({ runInlineTest }) => {
}); });
test('should filter by sourcemapped file names', async ({ runInlineTest }) => { test('should filter by sourcemapped file names', async ({ runInlineTest }) => {
const fileWithSourceMap = `` +
`import {test} from '@playwright/test';
test.describe('Some describe', ()=>{
test('Some test', async ()=>{
console.log('test')
})
})
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImdoZXJraW4uZmVhdHVyZSJdLCJuYW1lcyI6WyJOb25lIl0sIm1hcHBpbmdzIjoiQUFBQUE7QUFBQUE7QUFBQUE7QUFBQUE7QUFBQUE7QUFBQUEiLCJmaWxlIjoiZ2hlcmtpbi5mZWF0dXJlIiwic291cmNlc0NvbnRlbnQiOlsiVGVzdCJdfQ==`;
const result = await runInlineTest({ const result = await runInlineTest({
'playwright.config.js': `export default { projects: [{}, {}] }`, 'gherkin.spec.js': `
'a.spec.js': fileWithSourceMap,
import { test } from '@playwright/test';
test('should run', () => {});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImdoZXJraW4uZmVhdHVyZSJdLCJuYW1lcyI6WyJOb25lIl0sIm1hcHBpbmdzIjoiQUFBQUE7QUFBQUE7QUFBQUE7QUFBQUE7QUFBQUE7QUFBQUEiLCJmaWxlIjoiZ2hlcmtpbi5mZWF0dXJlIiwic291cmNlc0NvbnRlbnQiOlsiVGVzdCJdfQ==`,
'another.spec.js': `
throw new Error('should not load another.spec.js');
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFub3RoZXIuZmVhdHVyZSJdLCJuYW1lcyI6WyJOb25lIl0sIm1hcHBpbmdzIjoiQUFBQUE7QUFBQUE7QUFBQUE7QUFBQUE7QUFBQUE7QUFBQUEiLCJmaWxlIjoiZ2hlcmtpbi5mZWF0dXJlIiwic291cmNlc0NvbnRlbnQiOlsiVGVzdCJdfQ==`,
'nomap.spec.js': `
throw new Error('should not load nomap.spec.js');`,
}, {}, {}, { additionalArgs: ['gherkin.feature'] }); }, {}, {}, { additionalArgs: ['gherkin.feature'] });
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(2); expect(result.passed).toBe(1);
expect(result.output).not.toContain('a.spec.js'); expect(result.output).not.toContain('spec.js');
expect(result.output).not.toContain('another.feature.js');
expect(result.output).not.toContain('should not load');
expect(result.output).toContain('gherkin.feature:1'); expect(result.output).toContain('gherkin.feature:1');
}); });