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.
*/
import fs from 'fs';
import path from 'path';
import readline from 'readline';
import type { Reporter, TestError } from '../../types/testReporter';
import { InProcessLoaderHost, OutOfProcessLoaderHost } from './loaderHost';
import { Suite } from '../common/test';
@ -29,6 +27,8 @@ import { requireOrImport } from '../common/transform';
import { buildFileSuiteForProject, filterByFocusedLine, filterByTestIds, filterOnly, filterTestsRemoveEmptySuites } from '../common/suiteUtils';
import { createTestGroups, filterForShard, type TestGroup } from './testGroups';
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) {
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.
const filesToRunByProject = new Map<FullProjectInternal, string[]>();
for (const [project, files] of allFilesForProject) {
const matchedFiles = await Promise.all(files.map(async file => {
if (additionalFileMatcher && !additionalFileMatcher(file))
return;
if (cliFileMatcher) {
if (!cliFileMatcher(file) && !await isPotentiallyJavaScriptFileWithSourceMap(file, sourceMapCache))
return;
}
return file;
}));
const matchedFiles = files.filter(file => {
const hasMatchingSources = sourceMapSources(file, sourceMapCache).some(source => {
if (additionalFileMatcher && !additionalFileMatcher(source))
return false;
if (cliFileMatcher && !cliFileMatcher(source))
return false;
return true;
});
return hasMatchingSources;
});
const filteredFiles = matchedFiles.filter(Boolean) as string[];
if (filteredFiles.length)
filesToRunByProject.set(project, filteredFiles);
@ -263,29 +264,20 @@ export function loadReporter(config: FullConfigInternal, file: string): Promise<
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'))
return false;
return sources;
if (cache.has(file))
return cache.get(file)!;
try {
const stream = fs.createReadStream(file);
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
let lastLine: string | undefined;
rl.on('line', line => {
lastLine = line;
});
await new Promise((fulfill, reject) => {
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;
const sourceMap = sourceMapSupport.retrieveSourceMap(file);
const sourceMapData: RawSourceMap | undefined = typeof sourceMap?.map === 'string' ? JSON.parse(sourceMap.map) : sourceMap?.map;
if (sourceMapData?.sources)
sources = sourceMapData.sources.map(source => path.resolve(path.dirname(file), source));
} finally {
cache.set(file, sources);
return sources;
}
}

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 }) => {
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({
'playwright.config.js': `export default { projects: [{}, {}] }`,
'a.spec.js': fileWithSourceMap,
'gherkin.spec.js': `
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'] });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(2);
expect(result.output).not.toContain('a.spec.js');
expect(result.passed).toBe(1);
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');
});