diff --git a/packages/playwright-test/src/experimentalLoader.ts b/packages/playwright-test/src/experimentalLoader.ts index 67c18f0631..35a5adfca7 100644 --- a/packages/playwright-test/src/experimentalLoader.ts +++ b/packages/playwright-test/src/experimentalLoader.ts @@ -23,7 +23,7 @@ import { transformHook, resolveHook, belongsToNodeModules } from './transform'; async function resolve(specifier: string, context: { parentURL?: string }, defaultResolve: Function) { if (context.parentURL && context.parentURL.startsWith('file://')) { const filename = url.fileURLToPath(context.parentURL); - const resolved = resolveHook(filename, specifier); + const resolved = resolveHook(true, filename, specifier); if (resolved !== undefined) specifier = url.pathToFileURL(resolved).toString(); } diff --git a/packages/playwright-test/src/transform.ts b/packages/playwright-test/src/transform.ts index a9c3925f8f..f830e5017e 100644 --- a/packages/playwright-test/src/transform.ts +++ b/packages/playwright-test/src/transform.ts @@ -92,7 +92,7 @@ const scriptPreprocessor = process.env.PW_TEST_SOURCE_TRANSFORM ? require(process.env.PW_TEST_SOURCE_TRANSFORM) : undefined; const builtins = new Set(Module.builtinModules); -export function resolveHook(filename: string, specifier: string): string | undefined { +export function resolveHook(isModule: boolean, filename: string, specifier: string): string | undefined { if (builtins.has(specifier)) return; const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx'); @@ -132,11 +132,21 @@ export function resolveHook(filename: string, specifier: string): string | undef if (value.includes('*')) candidate = candidate.replace('*', matchedPartOfSpecifier); candidate = path.resolve(tsconfig.absoluteBaseUrl, candidate.replace(/\//g, path.sep)); - for (const ext of ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.cjs', '.mts', '.cts']) { - if (fs.existsSync(candidate + ext)) { + if (isModule) { + const transformed = js2ts(candidate); + if (transformed || fs.existsSync(candidate)) { if (keyPrefix.length > longestPrefixLength) { longestPrefixLength = keyPrefix.length; - pathMatchedByLongestPrefix = candidate; + pathMatchedByLongestPrefix = transformed || candidate; + } + } + } else { + for (const ext of ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.cjs', '.mts', '.cts']) { + if (fs.existsSync(candidate + ext)) { + if (keyPrefix.length > longestPrefixLength) { + longestPrefixLength = keyPrefix.length; + pathMatchedByLongestPrefix = candidate; + } } } } @@ -145,13 +155,17 @@ export function resolveHook(filename: string, specifier: string): string | undef if (pathMatchedByLongestPrefix) return pathMatchedByLongestPrefix; } - if (specifier.endsWith('.js')) { - const resolved = path.resolve(path.dirname(filename), specifier); - if (resolved.endsWith('.js')) { - const tsResolved = resolved.substring(0, resolved.length - 3) + '.ts'; - if (!fs.existsSync(resolved) && fs.existsSync(tsResolved)) - return tsResolved; - } + + if (isModule) + return js2ts(path.resolve(path.dirname(filename), specifier)); +} + +export function js2ts(resolved: string): string | undefined { + const match = resolved.match(/(.*)(\.js|\.jsx|\.mjs)$/); + if (match) { + const tsResolved = match[1] + match[2].replace('j', 't'); + if (!fs.existsSync(resolved) && fs.existsSync(tsResolved)) + return tsResolved; } } @@ -197,7 +211,7 @@ export function installTransform(): () => void { const originalResolveFilename = (Module as any)._resolveFilename; function resolveFilename(this: any, specifier: string, parent: Module, ...rest: any[]) { if (!reverted && parent) { - const resolved = resolveHook(parent.filename, specifier); + const resolved = resolveHook(false, parent.filename, specifier); if (resolved !== undefined) specifier = resolved; } diff --git a/tests/playwright-test/esm.spec.ts b/tests/playwright-test/esm.spec.ts index b3a7bd5ed4..cecdc81eb5 100644 --- a/tests/playwright-test/esm.spec.ts +++ b/tests/playwright-test/esm.spec.ts @@ -109,7 +109,7 @@ test('should respect path resolver in experimental mode', async ({ runInlineTest }, }`, 'a.test.ts': ` - import { foo } from 'util/b.ts'; + import { foo } from 'util/b.js'; const { test } = pwt; test('check project name', ({}, testInfo) => { expect(testInfo.project.name).toBe(foo);