diff --git a/docs/src/test-typescript-js.md b/docs/src/test-typescript-js.md index 12a1173ea4..09614f0306 100644 --- a/docs/src/test-typescript-js.md +++ b/docs/src/test-typescript-js.md @@ -108,16 +108,3 @@ In `package.json`, add two scripts: The `pretest` script runs typescript on the tests. `test` will run the tests that have been generated to the `tests-out` directory. The `-c` argument configures the test runner to look for tests inside the `tests-out` directory. Then `npm run test` will build the tests and run them. - -## Using `import` inside `evaluate()` - -Using dynamic imports inside a function passed to various `evaluate()` methods is not supported. This is because Playwright uses `Function.prototype.toString()` to serialize functions, and transpiler will sometimes replace dynamic imports with `require()` calls, which are not valid inside the web page. - -To work around this issue, use a string template instead of a function: - -```js -await page.evaluate(`(async () => { - const { value } = await import('some-module'); - console.log(value); -})()`); -``` diff --git a/packages/playwright/ThirdPartyNotices.txt b/packages/playwright/ThirdPartyNotices.txt index d682f43304..d77271ad73 100644 --- a/packages/playwright/ThirdPartyNotices.txt +++ b/packages/playwright/ThirdPartyNotices.txt @@ -38,7 +38,6 @@ This project incorporates components from the projects listed below. The origina - @babel/plugin-syntax-async-generators@7.8.4 (https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-async-generators) - @babel/plugin-syntax-class-static-block@7.14.5 (https://github.com/babel/babel) - @babel/plugin-syntax-decorators@7.24.1 (https://github.com/babel/babel) -- @babel/plugin-syntax-dynamic-import@7.8.3 (https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-dynamic-import) - @babel/plugin-syntax-explicit-resource-management@7.24.1 (https://github.com/babel/babel) - @babel/plugin-syntax-export-namespace-from@7.8.3 (https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-export-namespace-from) - @babel/plugin-syntax-import-attributes@7.24.1 (https://github.com/babel/babel) @@ -54,7 +53,6 @@ This project incorporates components from the projects listed below. The origina - @babel/plugin-syntax-typescript@7.24.1 (https://github.com/babel/babel) - @babel/plugin-transform-class-properties@7.24.1 (https://github.com/babel/babel) - @babel/plugin-transform-class-static-block@7.24.4 (https://github.com/babel/babel) -- @babel/plugin-transform-dynamic-import@7.24.1 (https://github.com/babel/babel) - @babel/plugin-transform-export-namespace-from@7.24.1 (https://github.com/babel/babel) - @babel/plugin-transform-logical-assignment-operators@7.24.1 (https://github.com/babel/babel) - @babel/plugin-transform-modules-commonjs@7.24.1 (https://github.com/babel/babel) @@ -1252,33 +1250,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF @babel/plugin-syntax-decorators@7.24.1 AND INFORMATION -%% @babel/plugin-syntax-dynamic-import@7.8.3 NOTICES AND INFORMATION BEGIN HERE -========================================= -MIT License - -Copyright (c) 2014-present Sebastian McKenzie and other contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -========================================= -END OF @babel/plugin-syntax-dynamic-import@7.8.3 AND INFORMATION - %% @babel/plugin-syntax-explicit-resource-management@7.24.1 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -1684,33 +1655,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF @babel/plugin-transform-class-static-block@7.24.4 AND INFORMATION -%% @babel/plugin-transform-dynamic-import@7.24.1 NOTICES AND INFORMATION BEGIN HERE -========================================= -MIT License - -Copyright (c) 2014-present Sebastian McKenzie and other contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -========================================= -END OF @babel/plugin-transform-dynamic-import@7.24.1 AND INFORMATION - %% @babel/plugin-transform-export-namespace-from@7.24.1 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -4461,6 +4405,6 @@ END OF yallist@3.1.1 AND INFORMATION SUMMARY BEGIN HERE ========================================= -Total Packages: 153 +Total Packages: 151 ========================================= END OF SUMMARY \ No newline at end of file diff --git a/packages/playwright/bundles/babel/package-lock.json b/packages/playwright/bundles/babel/package-lock.json index c045128866..0902f0601e 100644 --- a/packages/playwright/bundles/babel/package-lock.json +++ b/packages/playwright/bundles/babel/package-lock.json @@ -21,7 +21,6 @@ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-transform-class-properties": "^7.24.1", "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-dynamic-import": "^7.24.1", "@babel/plugin-transform-export-namespace-from": "^7.24.1", "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", "@babel/plugin-transform-modules-commonjs": "^7.24.1", @@ -434,17 +433,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-explicit-resource-management": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-explicit-resource-management/-/plugin-syntax-explicit-resource-management-7.24.1.tgz", @@ -634,21 +622,6 @@ "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-export-namespace-from": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", diff --git a/packages/playwright/bundles/babel/package.json b/packages/playwright/bundles/babel/package.json index ff4eaa62ae..223f0310c7 100644 --- a/packages/playwright/bundles/babel/package.json +++ b/packages/playwright/bundles/babel/package.json @@ -22,7 +22,6 @@ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-transform-class-properties": "^7.24.1", "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-dynamic-import": "^7.24.1", "@babel/plugin-transform-export-namespace-from": "^7.24.1", "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", "@babel/plugin-transform-modules-commonjs": "^7.24.1", diff --git a/packages/playwright/bundles/babel/src/babelBundleImpl.ts b/packages/playwright/bundles/babel/src/babelBundleImpl.ts index 929b4a551a..f4e44fcea3 100644 --- a/packages/playwright/bundles/babel/src/babelBundleImpl.ts +++ b/packages/playwright/bundles/babel/src/babelBundleImpl.ts @@ -73,8 +73,9 @@ function babelTransformOptions(isTypeScript: boolean, isModule: boolean, plugins if (!isModule) { plugins.push([require('@babel/plugin-transform-modules-commonjs')]); - // This converts async imports to require() calls so that we can intercept them with pirates. - plugins.push([require('@babel/plugin-transform-dynamic-import')]); + // Note: we used to include '@babel/plugin-transform-dynamic-import' to convert async imports + // into require(), so that pirates can intercept them. With the ESM loader enabled by default, + // there is no need for this. plugins.push([ (): PluginObj => ({ name: 'css-to-identity-obj-proxy', diff --git a/packages/playwright/src/transform/esmLoader.ts b/packages/playwright/src/transform/esmLoader.ts index 13c99f1f7f..dfe6539942 100644 --- a/packages/playwright/src/transform/esmLoader.ts +++ b/packages/playwright/src/transform/esmLoader.ts @@ -19,6 +19,7 @@ import url from 'url'; import { addToCompilationCache, currentFileDepsCollector, serializeCompilationCache, startCollectingFileDeps, stopCollectingFileDeps } from './compilationCache'; import { transformHook, resolveHook, setTransformConfig, shouldTransform } from './transform'; import { PortTransport } from './portTransport'; +import { fileIsModule } from '../util'; // Node < 18.6: defaultResolve takes 3 arguments. // Node >= 18.6: nextResolve from the chain takes 2 arguments. @@ -62,9 +63,13 @@ async function load(moduleUrl: string, context: { format?: string }, defaultLoad if (transformed.serializedCache) await transport?.send('pushToCompilationCache', { cache: transformed.serializedCache }); - // Output format is always the same as input format, if it was unknown, we always report modules. + // Output format is required, so we determine it manually when unknown. // shortCircuit is required by Node >= 18.6 to designate no more loaders should be called. - return { format: context.format || 'module', source: transformed.code, shortCircuit: true }; + return { + format: context.format || (fileIsModule(filename) ? 'module' : 'commonjs'), + source: transformed.code, + shortCircuit: true, + }; } let transport: PortTransport | undefined; diff --git a/tests/playwright-test/loader.spec.ts b/tests/playwright-test/loader.spec.ts index 98796644b5..491fa3d2bf 100644 --- a/tests/playwright-test/loader.spec.ts +++ b/tests/playwright-test/loader.spec.ts @@ -961,21 +961,45 @@ test('should complain when one test file imports another', async ({ runInlineTes expect(result.output).toContain(`test file "a.test.ts" should not import test file "b.test.ts"`); }); -test('should support dynamic import', async ({ runInlineTest }) => { +test('should support dynamic imports of js, ts from js, ts and cjs', async ({ runInlineTest }) => { const result = await runInlineTest({ 'helper.ts': ` module.exports.foo = 'foo'; `, + 'helper2.ts': ` + module.exports.bar = 'bar'; + `, + 'helper3.js': ` + module.exports.baz = 'baz'; + `, + 'passthrough.cjs': ` + module.exports.load = () => import('./helper2'); + `, 'a.test.ts': ` import { test, expect } from '@playwright/test'; test('pass', async () => { const { foo } = await import('./helper'); expect(foo).toBe('foo'); + + const { baz } = await import('./helper3'); + expect(baz).toBe('baz'); }); `, 'b.test.ts': ` import { test, expect } from '@playwright/test'; + import { load } from './passthrough.cjs'; + + test('pass', async () => { + const { foo } = await import('./helper'); + expect(foo).toBe('foo'); + + const { bar } = await load(); + expect(bar).toBe('bar'); + }); + `, + 'c.test.js': ` + import { test, expect } from '@playwright/test'; test('pass', async () => { const { foo } = await import('./helper'); @@ -983,7 +1007,33 @@ test('should support dynamic import', async ({ runInlineTest }) => { }); `, }, { workers: 1 }); - expect(result.passed).toBe(2); + expect(result.passed).toBe(3); + expect(result.exitCode).toBe(0); +}); + +test('should support dynamic imports of esm-only packages', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'node_modules/foo-pkg/package.json': ` + { + "name": "foo-pkg", + "type": "module", + "exports": { "default": "./index.js" } + } + `, + 'node_modules/foo-pkg/index.js': ` + export const foo = 'bar'; + `, + 'package.json': `{ "name": "test-project" }`, + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + + test('pass', async () => { + const { foo } = await import('foo-pkg'); + expect(foo).toBe('bar'); + }); + `, + }, { workers: 1 }); + expect(result.passed).toBe(1); expect(result.exitCode).toBe(0); }); diff --git a/tests/playwright-test/playwright.spec.ts b/tests/playwright-test/playwright.spec.ts index ab33f2e6f6..faaa069950 100644 --- a/tests/playwright-test/playwright.spec.ts +++ b/tests/playwright-test/playwright.spec.ts @@ -848,3 +848,27 @@ test('should explain a failure when using a dispose APIRequestContext', async ({ expect(result.passed).toBe(0); expect(result.output).toContain(`Recommended fix: use a separate { request } in the test`); }); + +test('should allow dynamic import in evaluate', async ({ runInlineTest, server }) => { + server.setRoute('/foo.js', (req, res) => { + res.writeHead(200, { 'Content-Type': 'application/javascript' }).end(` + export const foo = 'bar'; + `); + }); + const result = await runInlineTest({ + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + + test('test', async ({ page }) => { + await page.goto("${server.EMPTY_PAGE}"); + const result = await page.evaluate(async () => { + const { foo } = await import("${server.PREFIX + '/foo.js'}"); + return foo; + }); + expect(result).toBe('bar'); + }); + `, + }, { workers: 1 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); +});