diff --git a/packages/playwright-test/jsx-runtime.js b/packages/playwright-test/jsx-runtime.js new file mode 100644 index 0000000000000..220f4578c2b4f --- /dev/null +++ b/packages/playwright-test/jsx-runtime.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = require('playwright/jsx-runtime'); diff --git a/packages/playwright-test/jsx-runtime.mjs b/packages/playwright-test/jsx-runtime.mjs new file mode 100644 index 0000000000000..ddd37fdd2a8b0 --- /dev/null +++ b/packages/playwright-test/jsx-runtime.mjs @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from 'playwright/jsx-runtime'; diff --git a/packages/playwright-test/package.json b/packages/playwright-test/package.json index bca8706e1d1e8..98bf8209a6535 100644 --- a/packages/playwright-test/package.json +++ b/packages/playwright-test/package.json @@ -22,6 +22,11 @@ "default": "./index.js" }, "./cli": "./cli.js", + "./jsx-runtime": { + "import": "./jsx-runtime.mjs", + "require": "./jsx-runtime.js", + "default": "./jsx-runtime.js" + }, "./package.json": "./package.json", "./reporter": "./reporter.js" }, diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts index 6e2fa63afb98d..fae219e96b3c0 100644 --- a/packages/playwright/src/common/configLoader.ts +++ b/packages/playwright/src/common/configLoader.ts @@ -117,8 +117,7 @@ export async function loadConfig(location: ConfigLocation, overrides?: ConfigCLI // 3. Load transform options from the playwright config. const external = userConfig.build?.external || []; - const jsxImportSource = path.dirname(require.resolve('playwright')); - await setTransformConfig({ external, jsxImportSource }); + await setTransformConfig({ external, jsxImportSource: '@playwright/test' }); if (!overrides?.tsconfig) await setSingleTSConfig(fullConfig?.singleTSConfigPath); diff --git a/tests/playwright-test/cache.spec.ts b/tests/playwright-test/cache.spec.ts index 132cf8ca6ee68..307ab16c9dc31 100644 --- a/tests/playwright-test/cache.spec.ts +++ b/tests/playwright-test/cache.spec.ts @@ -101,3 +101,40 @@ test('should automatically clean cached versions of a changed file', async ({ ru expect(finalPathHash).toBe(firstPathHash); expect(finalTestHash).not.toBe(firstTestHash); }); + +test('should not reuse cached jsx transform after playwright moves', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42934' }, +}, async ({ runInlineTest, writeFiles, childProcess }) => { + const cacheDir = test.info().outputPath('playwright-test-cache'); + const files = { + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/test'; + export default defineConfig({}); + `, + 'a.spec.tsx': ` + import { test, expect } from '@playwright/test'; + const element =
; + test('jsx', () => { expect(element).toBeTruthy(); }); + `, + }; + const baseDir = await writeFiles(files); + + // Resolve playwright through symlinks so that the cached transform references them. + const packagesDir = path.join(__dirname, '../../packages'); + const nodeModules = path.join(baseDir, 'node_modules'); + await fs.promises.mkdir(path.join(nodeModules, '@playwright'), { recursive: true }); + await fs.promises.symlink(path.join(packagesDir, 'playwright'), path.join(nodeModules, 'playwright'), 'dir'); + await fs.promises.symlink(path.join(packagesDir, 'playwright-test'), path.join(nodeModules, '@playwright', 'test'), 'dir'); + const first = childProcess({ + command: ['node', '--preserve-symlinks', '--preserve-symlinks-main', path.join(nodeModules, '@playwright', 'test', 'cli.js'), 'test', '--list'], + env: { PWTEST_CACHE_DIR: cacheDir, NODE_PRESERVE_SYMLINKS: '1' }, + cwd: baseDir, + }); + expect((await first.exited).exitCode).toBe(0); + expect(first.output).toContain('Total: 1 test in 1 file'); + + await fs.promises.rm(nodeModules, { recursive: true }); + const result = await runInlineTest(files, undefined, { PWTEST_CACHE_DIR: cacheDir }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); +}); diff --git a/tests/playwright-test/loader.spec.ts b/tests/playwright-test/loader.spec.ts index cca5bbf037d20..f4c4cf5e05271 100644 --- a/tests/playwright-test/loader.spec.ts +++ b/tests/playwright-test/loader.spec.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { test, expect } from './playwright-test-fixtures'; +import { test, expect, cliEntrypoint } from './playwright-test-fixtures'; import fs from 'fs'; +import os from 'os'; import path from 'path'; import url from 'url'; @@ -547,6 +548,35 @@ test('should load jsx with top-level component', async ({ runInlineTest }) => { expect(exitCode).toBe(0); }); +test('should load jsx when playwright is not resolvable from the project', async ({ childProcess }) => { + // Mimic pnpm isolated layout: only @playwright/test is linked into the project's node_modules. + // The project must live outside of the repo, so that the repo's node_modules is not visible. + const baseDir = await fs.promises.realpath(await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-jsx-'))); + const symlinkType = process.platform === 'win32' ? 'junction' : 'dir'; + await fs.promises.mkdir(path.join(baseDir, 'node_modules', '@playwright'), { recursive: true }); + await fs.promises.symlink(path.join(__dirname, '../../packages/playwright-test'), path.join(baseDir, 'node_modules', '@playwright', 'test'), symlinkType); + await fs.promises.writeFile(path.join(baseDir, 'playwright.config.ts'), `export default {};`); + await fs.promises.writeFile(path.join(baseDir, 'a.spec.tsx'), ` + import { test, expect } from '@playwright/test'; + const component =
; + test('succeeds', () => { + expect(component).toEqual({ __pw_type: 'jsx', type: 'div', props: {} }); + }); + `); + try { + const testProcess = childProcess({ + command: ['node', cliEntrypoint, 'test', '--reporter=line'], + env: { PWTEST_CACHE_DIR: path.join(baseDir, 'cache') }, + cwd: baseDir, + }); + const { exitCode } = await testProcess.exited; + expect(testProcess.output).toContain('1 passed'); + expect(exitCode).toBe(0); + } finally { + await fs.promises.rm(baseDir, { recursive: true, force: true }); + } +}); + test('should load a jsx/tsx files with fragments', async ({ runInlineTest }) => { const { exitCode, passed } = await runInlineTest({ 'helper.tsx': `