diff --git a/package-lock.json b/package-lock.json index 828a03cdba7e9..6824d966f2696 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@stylistic/eslint-plugin": "^5.2.3", "@types/codemirror": "^5.60.7", "@types/formidable": "^2.0.4", - "@types/node": "18.19.76", + "@types/node": "25.0.3", "@types/react": "^19.2.1", "@types/react-dom": "^19.2.1", "@types/ws": "^8.5.3", @@ -1811,12 +1811,13 @@ } }, "node_modules/@types/node": { - "version": "18.19.76", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", - "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "devOptional": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~7.16.0" } }, "node_modules/@types/react": { @@ -7667,10 +7668,11 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" }, "node_modules/universal-user-agent": { "version": "6.0.1", diff --git a/package.json b/package.json index f4fa02b71f5e5..80ca729fb86d7 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@stylistic/eslint-plugin": "^5.2.3", "@types/codemirror": "^5.60.7", "@types/formidable": "^2.0.4", - "@types/node": "18.19.76", + "@types/node": "25.0.3", "@types/react": "^19.2.1", "@types/react-dom": "^19.2.1", "@types/ws": "^8.5.3", diff --git a/packages/playwright/src/mcp/browser/tools/runCode.ts b/packages/playwright/src/mcp/browser/tools/runCode.ts index 537ffdb06444c..58fa46cbb9cc5 100644 --- a/packages/playwright/src/mcp/browser/tools/runCode.ts +++ b/packages/playwright/src/mcp/browser/tools/runCode.ts @@ -53,7 +53,9 @@ const runCode = defineTabTool({ __end__.reject(e); } })()`; - await vm.runInContext(snippet, context); + await vm.runInContext(snippet, context, { + importModuleDynamically: vm.constants?.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); const result = await __end__; if (typeof result === 'string') response.addResult(result); diff --git a/tests/mcp/run-code.spec.ts b/tests/mcp/run-code.spec.ts index a8e3149714198..3768568106f2c 100644 --- a/tests/mcp/run-code.spec.ts +++ b/tests/mcp/run-code.spec.ts @@ -166,3 +166,66 @@ test('browser_run_code return value', async ({ client, server }) => { result: '{"message":"Hello, world!"}', }); }); + +test('browser_run_code dynamic import', async ({ client, server }) => { + test.skip(process.platform === 'win32', 'windows only works with file:// imports'); + server.setContent('/', ` + + `, 'text/html'); + await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.PREFIX }, + }); + + const scriptPath = test.info().outputPath('helper.mjs'); + await fs.writeFile(scriptPath, ` +export async function clickSubmit(page) { + await page.getByRole("button", { name: "Submit" }).click(); +} +`); + + const code = `async (page) => { + const { clickSubmit } = await import(${JSON.stringify(scriptPath)}); + await clickSubmit(page); +}`; + expect(await client.callTool({ + name: 'browser_run_code', + arguments: { + code, + }, + })).toHaveResponse({ + code: `await (${code})(page);`, + consoleMessages: expect.stringContaining('- [LOG] Submit'), + }); +}); + +test('browser_run_code dynamic import (file: proto)', async ({ client, server }) => { + server.setContent('/', ` + + `, 'text/html'); + await client.callTool({ + name: 'browser_navigate', + arguments: { url: server.PREFIX }, + }); + + const scriptPath = test.info().outputPath('helper.mjs'); + await fs.writeFile(scriptPath, ` +export async function clickSubmit(page) { + await page.getByRole("button", { name: "Submit" }).click(); +} +`); + + const code = `async (page) => { + const { clickSubmit } = await import(${JSON.stringify('file://' + scriptPath)}); + await clickSubmit(page); +}`; + expect(await client.callTool({ + name: 'browser_run_code', + arguments: { + code, + }, + })).toHaveResponse({ + code: `await (${code})(page);`, + consoleMessages: expect.stringContaining('- [LOG] Submit'), + }); +});