diff --git a/packages/playwright-test/src/third_party/tsconfig-loader.ts b/packages/playwright-test/src/third_party/tsconfig-loader.ts index d1cf7bda47..c6a0ae2c3e 100644 --- a/packages/playwright-test/src/third_party/tsconfig-loader.ts +++ b/packages/playwright-test/src/third_party/tsconfig-loader.ts @@ -37,6 +37,7 @@ interface Tsconfig { baseUrl?: string; paths?: { [key: string]: Array }; strict?: boolean; + allowJs?: boolean; }; } @@ -45,6 +46,7 @@ export interface TsConfigLoaderResult { baseUrl: string | undefined; paths: { [key: string]: Array } | undefined; serialized: string | undefined; + allowJs: boolean; } export interface TsConfigLoaderParams { @@ -72,6 +74,7 @@ function loadSyncDefault( baseUrl: undefined, paths: undefined, serialized: undefined, + allowJs: false, }; } const config = loadTsconfig(configPath); @@ -82,6 +85,7 @@ function loadSyncDefault( (config && config.compilerOptions && config.compilerOptions.baseUrl), paths: config && config.compilerOptions && config.compilerOptions.paths, serialized: undefined, + allowJs: !!config?.compilerOptions?.allowJs, }; } diff --git a/packages/playwright-test/src/transform.ts b/packages/playwright-test/src/transform.ts index f830e5017e..5add8c15c1 100644 --- a/packages/playwright-test/src/transform.ts +++ b/packages/playwright-test/src/transform.ts @@ -33,6 +33,7 @@ const sourceMaps: Map = new Map(); type ParsedTsConfigData = { absoluteBaseUrl: string; paths: { key: string, values: string[] }[]; + allowJs: boolean; }; const cachedTSConfigs = new Map(); @@ -73,7 +74,11 @@ function validateTsConfig(tsconfig: TsConfigLoaderResult): ParsedTsConfigData | // Make 'baseUrl' absolute, because it is relative to the tsconfig.json, not to cwd. const absoluteBaseUrl = path.resolve(path.dirname(tsconfig.tsConfigPath), tsconfig.baseUrl); const paths = tsconfig.paths || { '*': ['*'] }; - return { absoluteBaseUrl, paths: Object.entries(paths).map(([key, values]) => ({ key, values })) }; + return { + allowJs: tsconfig.allowJs, + absoluteBaseUrl, + paths: Object.entries(paths).map(([key, values]) => ({ key, values })) + }; } function loadAndValidateTsconfigForFile(file: string): ParsedTsConfigData | undefined { @@ -95,9 +100,16 @@ const builtins = new Set(Module.builtinModules); export function resolveHook(isModule: boolean, filename: string, specifier: string): string | undefined { if (builtins.has(specifier)) return; + // In real life, playwright-test is under node_modules, but in the tests it isn't. + if (filename.startsWith(kPlaywrightInternalPrefix)) + return; + + if (isRelativeSpecifier(specifier)) + return isModule ? js2ts(path.resolve(path.dirname(filename), specifier)) : undefined; + const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx'); - const tsconfig = isTypeScript ? loadAndValidateTsconfigForFile(filename) : undefined; - if (tsconfig && !isRelativeSpecifier(specifier)) { + const tsconfig = loadAndValidateTsconfigForFile(filename); + if (tsconfig && (isTypeScript || tsconfig.allowJs)) { let longestPrefixLength = -1; let pathMatchedByLongestPrefix: string | undefined; diff --git a/tests/playwright-test/resolver.spec.ts b/tests/playwright-test/resolver.spec.ts index 313139fdf1..1ac8cc2ec3 100644 --- a/tests/playwright-test/resolver.spec.ts +++ b/tests/playwright-test/resolver.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { test, expect } from './playwright-test-fixtures'; +import { test, expect, stripAnsi } from './playwright-test-fixtures'; test('should respect path resolver', async ({ runInlineTest }) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11656' }); @@ -343,3 +343,58 @@ test('should not use baseurl for relative imports when dir with same name exists expect(result.output).not.toContain(`Could not`); expect(result.output).not.toContain(`Cannot`); }); + +test('should respect path resolver for JS files when allowJs', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': `export default { projects: [{name: 'foo'}], };`, + 'tsconfig.json': `{ + "compilerOptions": { + "allowJs": true, + "baseUrl": ".", + "paths": { + "util/*": ["./foo/bar/util/*"], + }, + }, + }`, + 'a.test.js': ` + const { foo } = require('util/b'); + const { test } = pwt; + test('test', ({}, testInfo) => { + expect(testInfo.project.name).toBe(foo); + }); + `, + 'foo/bar/util/b.ts': ` + module.exports = { foo: 'foo' }; + `, + }); + + expect(result.passed).toBe(1); + expect(result.exitCode).toBe(0); +}); + +test('should not respect path resolver for JS files w/o allowJS', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': `export default { projects: [{name: 'foo'}], };`, + 'tsconfig.json': `{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "util/*": ["./foo/bar/util/*"], + }, + }, + }`, + 'a.test.js': ` + const { foo } = require('util/b'); + const { test } = pwt; + test('test', ({}, testInfo) => { + expect(testInfo.project.name).toBe(foo); + }); + `, + 'foo/bar/util/b.ts': ` + module.exports = { foo: 'foo' }; + `, + }); + + expect(stripAnsi(result.output)).toContain('Cannot find module \'util/b\''); + expect(result.exitCode).toBe(1); +});