From 25dd9b5cd49470215c71d77ef46f81a66960ec3d Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 10 Oct 2024 01:37:46 -0700 Subject: [PATCH] feat: config.build.tsconfig (#33026) Allows to specify `tsconfig` in the configuration file, which applies to test files but not the config file itself. Fixes #32808. --- docs/src/test-api/class-testconfig.md | 16 +++++ docs/src/test-typescript-js.md | 10 ++++ packages/playwright/src/common/config.ts | 2 + .../playwright/src/common/configLoader.ts | 2 + .../playwright/src/common/esmLoaderHost.ts | 1 + packages/playwright/types/test.d.ts | 19 ++++++ tests/playwright-test/resolver.spec.ts | 59 +++++++++++++++++++ 7 files changed, 109 insertions(+) diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index d013f5e4ea..0d1f4c1538 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -552,6 +552,22 @@ export default defineConfig({ }); ``` +## property: TestConfig.tsconfig +* since: v1.49 +- type: ?<[string]> + +Path to a single `tsconfig` applicable to all imported files. By default, `tsconfig` for each imported file is looked up separately. Note that `tsconfig` property has no effect while the configuration file or any of its dependencies are loaded. Ignored when `--tsconfig` command line option is specified. + +**Usage** + +```js title="playwright.config.ts" +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + tsconfig: './tsconfig.test.json', +}); +``` + ## property: TestConfig.updateSnapshots * since: v1.10 - type: ?<[UpdateSnapshots]<"all"|"none"|"missing">> diff --git a/docs/src/test-typescript-js.md b/docs/src/test-typescript-js.md index 6e18b3c615..538f5a137e 100644 --- a/docs/src/test-typescript-js.md +++ b/docs/src/test-typescript-js.md @@ -90,6 +90,16 @@ Alternatively, you can specify a single tsconfig file to use in the command line npx playwright test --tsconfig=tsconfig.test.json ``` +You can specify a single tsconfig file in the config file, that will be used for loading test files, reporters, etc. However, it will not be used while loading the playwright config itself or any files imported from it. + +```js title="playwright.config.ts" +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + tsconfig: './tsconfig.test.json', +}); +``` + ## Manually compile tests with TypeScript Sometimes, Playwright Test will not be able to transform your TypeScript code correctly, for example when you are using experimental or very recent features of TypeScript, usually configured in `tsconfig.json`. diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index d7fb499645..a694839f81 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -45,6 +45,7 @@ export class FullConfigInternal { readonly webServers: NonNullable[]; readonly plugins: TestRunnerPluginRegistration[]; readonly projects: FullProjectInternal[] = []; + readonly singleTSConfigPath?: string; cliArgs: string[] = []; cliGrep: string | undefined; cliGrepInvert: string | undefined; @@ -69,6 +70,7 @@ export class FullConfigInternal { this.configCLIOverrides = configCLIOverrides; const privateConfiguration = (userConfig as any)['@playwright/test']; this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p })); + this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig); this.config = { configFile: resolvedConfigFile, diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts index 5ed1c68ea7..eef56c4458 100644 --- a/packages/playwright/src/common/configLoader.ts +++ b/packages/playwright/src/common/configLoader.ts @@ -118,6 +118,8 @@ export async function loadConfig(location: ConfigLocation, overrides?: ConfigCLI const babelPlugins = (userConfig as any)['@playwright/test']?.babelPlugins || []; const external = userConfig.build?.external || []; setTransformConfig({ babelPlugins, external }); + if (!overrides?.tsconfig) + setSingleTSConfig(fullConfig?.singleTSConfigPath); // 4. Send transform options to ESM loader. await configureESMLoaderTransformConfig(); diff --git a/packages/playwright/src/common/esmLoaderHost.ts b/packages/playwright/src/common/esmLoaderHost.ts index 1611b0f91d..9b5b4f9b8c 100644 --- a/packages/playwright/src/common/esmLoaderHost.ts +++ b/packages/playwright/src/common/esmLoaderHost.ts @@ -77,5 +77,6 @@ export async function configureESMLoader() { export async function configureESMLoaderTransformConfig() { if (!loaderChannel) return; + await loaderChannel.send('setSingleTSConfig', { tsconfig: singleTSConfig() }); await loaderChannel.send('setTransformConfig', { config: transformConfig() }); } diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 91fe2f2b5c..a1128c519e 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1642,6 +1642,25 @@ interface TestConfig { */ timeout?: number; + /** + * Path to a single `tsconfig` applicable to all imported files. By default, `tsconfig` for each imported file is + * looked up separately. Note that `tsconfig` property has no effect while the configuration file or any of its + * dependencies are loaded. Ignored when `--tsconfig` command line option is specified. + * + * **Usage** + * + * ```js + * // playwright.config.ts + * import { defineConfig } from '@playwright/test'; + * + * export default defineConfig({ + * tsconfig: './tsconfig.test.json', + * }); + * ``` + * + */ + tsconfig?: string; + /** * Whether to update expected snapshots with the actual results produced by the test run. Defaults to `'missing'`. * - `'all'` - All tests that are executed will update snapshots that did not match. Matching snapshots will not be diff --git a/tests/playwright-test/resolver.spec.ts b/tests/playwright-test/resolver.spec.ts index 037ba14f22..d4dec96495 100644 --- a/tests/playwright-test/resolver.spec.ts +++ b/tests/playwright-test/resolver.spec.ts @@ -675,6 +675,65 @@ test('should respect --tsconfig option', async ({ runInlineTest }) => { expect(result.output).not.toContain(`Could not`); }); +test('should respect config.tsconfig option', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export { configFoo } from '~/foo'; + export default { + testDir: './tests', + tsconfig: './tsconfig.tests.json', + }; + `, + 'tsconfig.json': `{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["./mapped-from-config/*"], + }, + }, + }`, + 'mapped-from-config/foo.ts': ` + export const configFoo = 17; + `, + 'tsconfig.tests.json': `{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["./mapped-from-tests/*"], + }, + }, + }`, + 'mapped-from-tests/foo.ts': ` + export const testFoo = 42; + `, + 'tests/tsconfig.json': `{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "~/*": ["../should-be-ignored/*"], + }, + }, + }`, + 'tests/a.test.ts': ` + import { testFoo } from '~/foo'; + import { configFoo } from '../playwright.config'; + import { test, expect } from '@playwright/test'; + test('test', ({}) => { + expect(testFoo).toBe(42); + expect(configFoo).toBe(17); + }); + `, + 'should-be-ignored/foo.ts': ` + export const testFoo = 43; + export const configFoo = 18; + `, + }); + + expect(result.passed).toBe(1); + expect(result.exitCode).toBe(0); + expect(result.output).not.toContain(`Could not`); +}); + test.describe('directory imports', () => { test('should resolve index.js without path mapping in CJS', async ({ runInlineTest, runTSC }) => { const files = {