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

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.
This commit is contained in:
Dmitry Gozman 2024-03-18 16:05:09 -07:00
parent 0db1d40abc
commit 1dcc04bb6a
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'],
});
});