mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: respect deps when watching files (#20695)
This commit is contained in:
parent
430d08f4fb
commit
361ea949aa
@ -32,7 +32,7 @@ const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwri
|
|||||||
|
|
||||||
const sourceMaps: Map<string, string> = new Map();
|
const sourceMaps: Map<string, string> = new Map();
|
||||||
const memoryCache = new Map<string, MemoryCache>();
|
const memoryCache = new Map<string, MemoryCache>();
|
||||||
const fileDependencies = new Map<string, string[]>();
|
const fileDependencies = new Map<string, Set<string>>();
|
||||||
|
|
||||||
Error.stackTraceLimit = 200;
|
Error.stackTraceLimit = 200;
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ export function serializeCompilationCache(): any {
|
|||||||
return {
|
return {
|
||||||
sourceMaps: [...sourceMaps.entries()],
|
sourceMaps: [...sourceMaps.entries()],
|
||||||
memoryCache: [...memoryCache.entries()],
|
memoryCache: [...memoryCache.entries()],
|
||||||
fileDependencies: [...fileDependencies.entries()],
|
fileDependencies: [...fileDependencies.entries()].map(([filename, deps]) => ([filename, [...deps]])),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ export function addToCompilationCache(payload: any) {
|
|||||||
for (const entry of payload.memoryCache)
|
for (const entry of payload.memoryCache)
|
||||||
memoryCache.set(entry[0], entry[1]);
|
memoryCache.set(entry[0], entry[1]);
|
||||||
for (const entry of payload.fileDependencies)
|
for (const entry of payload.fileDependencies)
|
||||||
fileDependencies.set(entry[0], entry[1]);
|
fileDependencies.set(entry[0], new Set(entry[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateCachePath(content: string, filePath: string, isModule: boolean): string {
|
function calculateCachePath(content: string, filePath: string, isModule: boolean): string {
|
||||||
@ -134,8 +134,11 @@ export function stopCollectingFileDeps(filename: string) {
|
|||||||
if (!depsCollector)
|
if (!depsCollector)
|
||||||
return;
|
return;
|
||||||
depsCollector.delete(filename);
|
depsCollector.delete(filename);
|
||||||
const deps = [...depsCollector!].filter(f => !belongsToNodeModules(f));
|
for (const dep of depsCollector) {
|
||||||
fileDependencies.set(filename, deps);
|
if (belongsToNodeModules(dep))
|
||||||
|
depsCollector.delete(dep);
|
||||||
|
}
|
||||||
|
fileDependencies.set(filename, depsCollector);
|
||||||
depsCollector = undefined;
|
depsCollector = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +150,14 @@ export function fileDependenciesForTest() {
|
|||||||
return fileDependencies;
|
return fileDependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function collectAffectedTestFiles(dependency: string, testFileCollector: Set<string>) {
|
||||||
|
testFileCollector.add(dependency);
|
||||||
|
for (const [testFile, deps] of fileDependencies) {
|
||||||
|
if (deps.has(dependency))
|
||||||
|
testFileCollector.add(testFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// These two are only used in the dev mode, they are specifically excluding
|
// These two are only used in the dev mode, they are specifically excluding
|
||||||
// files from packages/playwright*. In production mode, node_modules covers
|
// files from packages/playwright*. In production mode, node_modules covers
|
||||||
// that.
|
// that.
|
||||||
|
|||||||
@ -221,10 +221,8 @@ function installTransform(): () => void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const collectCJSDependencies = (module: Module, dependencies: Set<string>) => {
|
const collectCJSDependencies = (module: Module, dependencies: Set<string>) => {
|
||||||
if (dependencies.has(module.filename))
|
|
||||||
return;
|
|
||||||
module.children.forEach(child => {
|
module.children.forEach(child => {
|
||||||
if (!belongsToNodeModules(child.filename)) {
|
if (!belongsToNodeModules(child.filename) && !dependencies.has(child.filename)) {
|
||||||
dependencies.add(child.filename);
|
dependencies.add(child.filename);
|
||||||
collectCJSDependencies(child, dependencies);
|
collectCJSDependencies(child, dependencies);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,6 @@ import { fileDependenciesForTest } from './common/compilationCache';
|
|||||||
|
|
||||||
export function fileDependencies() {
|
export function fileDependencies() {
|
||||||
return Object.fromEntries([...fileDependenciesForTest().entries()].map(entry => (
|
return Object.fromEntries([...fileDependenciesForTest().entries()].map(entry => (
|
||||||
[path.basename(entry[0]), entry[1].map(f => path.basename(f))]
|
[path.basename(entry[0]), [...entry[1]].map(f => path.basename(f)).sort()]
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import type { Matcher } from '../util';
|
|||||||
import { createTaskRunnerForWatch } from './tasks';
|
import { createTaskRunnerForWatch } from './tasks';
|
||||||
import type { TaskRunnerState } from './tasks';
|
import type { TaskRunnerState } from './tasks';
|
||||||
import { buildProjectsClosure, filterProjects } from './projectUtils';
|
import { buildProjectsClosure, filterProjects } from './projectUtils';
|
||||||
import { clearCompilationCache } from '../common/compilationCache';
|
import { clearCompilationCache, collectAffectedTestFiles } from '../common/compilationCache';
|
||||||
import type { FullResult, TestCase } from 'packages/playwright-test/reporter';
|
import type { FullResult, TestCase } from 'packages/playwright-test/reporter';
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
import { WatchModeReporter } from './reporters';
|
import { WatchModeReporter } from './reporters';
|
||||||
@ -149,14 +149,19 @@ ${colors.dim('press')} ${colors.bold('h')} ${colors.dim('to show help, press')}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runChangedTests(config: FullConfigInternal, failedTestIds: Set<string>, projectClosure: FullProjectInternal[], files: Set<string>) {
|
async function runChangedTests(config: FullConfigInternal, failedTestIdCollector: Set<string>, projectClosure: FullProjectInternal[], changedFiles: Set<string>) {
|
||||||
const commandLineFileMatcher = config._internal.cliFileFilters.length ? createFileMatcherFromFilters(config._internal.cliFileFilters) : () => true;
|
const commandLineFileMatcher = config._internal.cliFileFilters.length ? createFileMatcherFromFilters(config._internal.cliFileFilters) : () => true;
|
||||||
|
|
||||||
|
// Resolve files that depend on the changed files.
|
||||||
|
const testFiles = new Set<string>();
|
||||||
|
for (const file of changedFiles)
|
||||||
|
collectAffectedTestFiles(file, testFiles);
|
||||||
|
|
||||||
// Collect projects with changes.
|
// Collect projects with changes.
|
||||||
const filesByProject = new Map<FullProjectInternal, string[]>();
|
const filesByProject = new Map<FullProjectInternal, string[]>();
|
||||||
for (const project of projectClosure) {
|
for (const project of projectClosure) {
|
||||||
const projectFiles: string[] = [];
|
const projectFiles: string[] = [];
|
||||||
for (const file of files) {
|
for (const file of testFiles) {
|
||||||
if (!file.startsWith(project.testDir))
|
if (!file.startsWith(project.testDir))
|
||||||
continue;
|
continue;
|
||||||
if (project._internal.type === 'dependency' || commandLineFileMatcher(file))
|
if (project._internal.type === 'dependency' || commandLineFileMatcher(file))
|
||||||
@ -174,11 +179,11 @@ async function runChangedTests(config: FullConfigInternal, failedTestIds: Set<st
|
|||||||
|
|
||||||
// If there are affected dependency projects, do the full run, respect the original CLI.
|
// If there are affected dependency projects, do the full run, respect the original CLI.
|
||||||
// if there are no affected dependency projects, intersect CLI with dirty files
|
// if there are no affected dependency projects, intersect CLI with dirty files
|
||||||
const additionalFileMatcher = affectsAnyDependency ? () => true : (file: string) => files.has(file);
|
const additionalFileMatcher = affectsAnyDependency ? () => true : (file: string) => testFiles.has(file);
|
||||||
return await runTests(config, failedTestIds, projectsToIgnore, additionalFileMatcher);
|
return await runTests(config, failedTestIdCollector, projectsToIgnore, additionalFileMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runTests(config: FullConfigInternal, failedTestIds: Set<string>, projectsToIgnore?: Set<FullProjectInternal>, additionalFileMatcher?: Matcher) {
|
async function runTests(config: FullConfigInternal, failedTestIdCollector: Set<string>, projectsToIgnore?: Set<FullProjectInternal>, additionalFileMatcher?: Matcher) {
|
||||||
const reporter = new Multiplexer([new WatchModeReporter()]);
|
const reporter = new Multiplexer([new WatchModeReporter()]);
|
||||||
const taskRunner = createTaskRunnerForWatch(config, reporter, projectsToIgnore, additionalFileMatcher);
|
const taskRunner = createTaskRunnerForWatch(config, reporter, projectsToIgnore, additionalFileMatcher);
|
||||||
const context: TaskRunnerState = {
|
const context: TaskRunnerState = {
|
||||||
@ -194,9 +199,9 @@ async function runTests(config: FullConfigInternal, failedTestIds: Set<string>,
|
|||||||
let hasFailedTests = false;
|
let hasFailedTests = false;
|
||||||
for (const test of context.rootSuite?.allTests() || []) {
|
for (const test of context.rootSuite?.allTests() || []) {
|
||||||
if (test.outcome() === 'expected') {
|
if (test.outcome() === 'expected') {
|
||||||
failedTestIds.delete(test.id);
|
failedTestIdCollector.delete(test.id);
|
||||||
} else {
|
} else {
|
||||||
failedTestIds.add(test.id);
|
failedTestIdCollector.add(test.id);
|
||||||
hasFailedTests = true;
|
hasFailedTests = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,13 +24,14 @@ test('should print dependencies in CJS mode', async ({ runInlineTest }) => {
|
|||||||
globalTeardown: './globalTeardown.ts',
|
globalTeardown: './globalTeardown.ts',
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
'helper.ts': `export function foo() {}`,
|
'helperA.ts': `export function foo() {}`,
|
||||||
|
'helperB.ts': `import './helperA';`,
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
import './helper';
|
import './helperA';
|
||||||
pwt.test('passes', () => {});
|
pwt.test('passes', () => {});
|
||||||
`,
|
`,
|
||||||
'b.test.ts': `
|
'b.test.ts': `
|
||||||
import './helper';
|
import './helperB';
|
||||||
pwt.test('passes', () => {});
|
pwt.test('passes', () => {});
|
||||||
`,
|
`,
|
||||||
'globalTeardown.ts': `
|
'globalTeardown.ts': `
|
||||||
@ -46,8 +47,8 @@ test('should print dependencies in CJS mode', async ({ runInlineTest }) => {
|
|||||||
const output = stripAnsi(result.output);
|
const output = stripAnsi(result.output);
|
||||||
const deps = JSON.parse(output.match(/###(.*)###/)![1]);
|
const deps = JSON.parse(output.match(/###(.*)###/)![1]);
|
||||||
expect(deps).toEqual({
|
expect(deps).toEqual({
|
||||||
'a.test.ts': ['helper.ts'],
|
'a.test.ts': ['helperA.ts'],
|
||||||
'b.test.ts': ['helper.ts'],
|
'b.test.ts': ['helperA.ts', 'helperB.ts'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -61,13 +62,14 @@ test('should print dependencies in ESM mode', async ({ runInlineTest, nodeVersio
|
|||||||
globalTeardown: './globalTeardown.ts',
|
globalTeardown: './globalTeardown.ts',
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
'helper.ts': `export function foo() {}`,
|
'helperA.ts': `export function foo() {}`,
|
||||||
|
'helperB.ts': `import './helperA.js';`,
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
import './helper.js';
|
import './helperA.js';
|
||||||
pwt.test('passes', () => {});
|
pwt.test('passes', () => {});
|
||||||
`,
|
`,
|
||||||
'b.test.ts': `
|
'b.test.ts': `
|
||||||
import './helper.js';
|
import './helperB.js';
|
||||||
pwt.test('passes', () => {});
|
pwt.test('passes', () => {});
|
||||||
`,
|
`,
|
||||||
'globalTeardown.ts': `
|
'globalTeardown.ts': `
|
||||||
@ -83,7 +85,7 @@ test('should print dependencies in ESM mode', async ({ runInlineTest, nodeVersio
|
|||||||
const output = stripAnsi(result.output);
|
const output = stripAnsi(result.output);
|
||||||
const deps = JSON.parse(output.match(/###(.*)###/)![1]);
|
const deps = JSON.parse(output.match(/###(.*)###/)![1]);
|
||||||
expect(deps).toEqual({
|
expect(deps).toEqual({
|
||||||
'a.test.ts': ['helper.ts'],
|
'a.test.ts': ['helperA.ts'],
|
||||||
'b.test.ts': ['helper.ts'],
|
'b.test.ts': ['helperA.ts', 'helperB.ts'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user