fix(test runner): avoid dependency tracking colliding between esm and cjs (#29994)

When collecting dependencies both from CJS loader and from ESM loader,
the latter would overwrite the dependencies set instead of appending.

Also make sure cts/cjs/mts/mjs are all supported equally.

References #29747.
This commit is contained in:
Dmitry Gozman 2024-03-18 17:17:58 -07:00 committed by GitHub
parent c7b074d39e
commit b41b802662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 95 additions and 14 deletions

View file

@ -15,7 +15,7 @@
*/
import util from 'util';
import { serializeCompilationCache } from '../transform/compilationCache';
import { type SerializedCompilationCache, serializeCompilationCache } from '../transform/compilationCache';
import type { ConfigLocation, FullConfigInternal } from './config';
import type { ReporterDescription, TestInfoError, TestStatus } from '../../types/test';
@ -43,7 +43,7 @@ export type ConfigCLIOverrides = {
export type SerializedConfig = {
location: ConfigLocation;
configCLIOverrides: ConfigCLIOverrides;
compilationCache: any;
compilationCache?: SerializedCompilationCache;
};
export type TtyParams = {

View file

@ -27,7 +27,7 @@ export type MemoryCache = {
moduleUrl?: string;
};
type SerializedCompilationCache = {
export type SerializedCompilationCache = {
sourceMaps: [string, string][],
memoryCache: [string, MemoryCache][],
fileDependencies: [string, string[]][],
@ -158,15 +158,19 @@ export function serializeCompilationCache(): SerializedCompilationCache {
};
}
export function addToCompilationCache(payload: any) {
export function addToCompilationCache(payload: SerializedCompilationCache) {
for (const entry of payload.sourceMaps)
sourceMaps.set(entry[0], entry[1]);
for (const entry of payload.memoryCache)
memoryCache.set(entry[0], entry[1]);
for (const entry of payload.fileDependencies)
fileDependencies.set(entry[0], new Set(entry[1]));
for (const entry of payload.externalDependencies)
externalDependencies.set(entry[0], new Set(entry[1]));
for (const entry of payload.fileDependencies) {
const existing = fileDependencies.get(entry[0]) || [];
fileDependencies.set(entry[0], new Set([...entry[1], ...existing]));
}
for (const entry of payload.externalDependencies) {
const existing = externalDependencies.get(entry[0]) || [];
externalDependencies.set(entry[0], new Set([...entry[1], ...existing]));
}
}
function calculateCachePath(filePath: string, hash: string): string {
@ -249,9 +253,9 @@ const kPlaywrightCoveragePrefix = path.resolve(__dirname, '../../../../tests/con
export function belongsToNodeModules(file: string) {
if (file.includes(`${path.sep}node_modules${path.sep}`))
return true;
if (file.startsWith(kPlaywrightInternalPrefix) && file.endsWith('.js'))
if (file.startsWith(kPlaywrightInternalPrefix) && (file.endsWith('.js') || file.endsWith('.mjs')))
return true;
if (file.startsWith(kPlaywrightCoveragePrefix) && file.endsWith('.js'))
if (file.startsWith(kPlaywrightCoveragePrefix) && (file.endsWith('.js') || file.endsWith('.mjs')))
return true;
return false;
}

View file

@ -240,7 +240,7 @@ function installTransform(): () => void {
if (!shouldTransform(filename))
return code;
return transformHook(code, filename).code;
}, { exts: ['.ts', '.tsx', '.js', '.jsx', '.mjs'] });
}, { exts: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.mts', '.cjs', '.cts'] });
return () => {
reverted = true;

View file

@ -171,7 +171,7 @@ export class TestInfoImpl implements TestInfo {
this._timeoutManager = new TimeoutManager(this.project.timeout);
this.outputDir = (() => {
const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile.replace(/\.(spec|test)\.(js|ts|mjs)$/, ''));
const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile.replace(/\.(spec|test)\.(js|ts|jsx|tsx|mjs|mts|cjs|cts)$/, ''));
const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-');
const fullTitleWithoutSpec = this.titlePath.slice(1).join(' ');

View file

@ -91,8 +91,85 @@ test('should print dependencies in ESM mode', async ({ runInlineTest }) => {
const output = result.output;
const deps = JSON.parse(output.match(/###(.*)###/)![1]);
expect(deps).toEqual({
'a.test.ts': ['helperA.ts', 'index.mjs'],
'b.test.ts': ['helperA.ts', 'helperB.ts', 'index.mjs'],
'a.test.ts': ['helperA.ts'],
'b.test.ts': ['helperA.ts', 'helperB.ts'],
});
});
test('should print dependencies in mixed CJS/ESM mode 1', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalTeardown: './globalTeardown.ts',
});
`,
'helperA.cjs': `exports.foo = () => {}`,
'helperB.cjs': `require('./helperA');`,
'a.test.ts': `
import './helperA';
import { test, expect } from '@playwright/test';
test('passes', () => {});
`,
'b.test.cjs': `
require('./helperB');
const { test, expect } = require('@playwright/test');
test('passes', () => {});
`,
'globalTeardown.ts': `
import { fileDependencies } from 'playwright/lib/internalsForTest';
export default () => {
console.log('###' + JSON.stringify(fileDependencies()) + '###');
};
`
}, {});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(2);
const output = result.output;
const deps = JSON.parse(output.match(/###(.*)###/)![1]);
expect(deps).toEqual({
'a.test.ts': ['helperA.cjs'],
'b.test.cjs': ['helperA.cjs', 'helperB.cjs'],
});
});
test('should print dependencies in mixed CJS/ESM mode 2', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.mts': `
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalTeardown: './globalTeardown.ts',
});
`,
'helperA.cjs': `exports.foo = () => {}`,
'helperB.cts': `import './helperA';`,
'a.test.mts': `
import './helperA';
import { test, expect } from '@playwright/test';
test('passes', () => {});
`,
'b.test.ts': `
import './helperB';
const { test, expect } = require('@playwright/test');
test('passes', () => {});
`,
'globalTeardown.ts': `
import { fileDependencies } from 'playwright/lib/internalsForTest';
export default () => {
console.log('###' + JSON.stringify(fileDependencies()) + '###');
};
`
}, {});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(2);
const output = result.output;
const deps = JSON.parse(output.match(/###(.*)###/)![1]);
expect(deps).toEqual({
'a.test.mts': ['helperA.cjs'],
'b.test.ts': ['helperA.cjs', 'helperB.cts'],
});
});