diff --git a/README.md b/README.md
index 6fb96a3..6bcc17e 100644
--- a/README.md
+++ b/README.md
@@ -598,6 +598,22 @@ X Y coordinate space, based on the provided screenshot.
+
+Evaluation
+
+
+
+- **browser_evaluate**
+ - Title: Evaluate JavaScript
+ - Description: Evaluate JavaScript expression on page or element
+ - Parameters:
+ - `function` (string): () => { /* code */ } or (element) => { /* code */ } when element is provided
+ - `element` (string, optional): Human-readable element description used to obtain permission to interact with the element
+ - `ref` (string, optional): Exact target element reference from the page snapshot
+ - Read-only: **false**
+
+
+
Resources
diff --git a/package-lock.json b/package-lock.json
index a24e9ac..20dc676 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,8 +13,8 @@
"commander": "^13.1.0",
"debug": "^4.4.1",
"mime": "^4.0.7",
- "playwright": "1.55.0-alpha-1752540053000",
- "playwright-core": "1.55.0-alpha-1752540053000",
+ "playwright": "1.55.0-alpha-1752701791000",
+ "playwright-core": "1.55.0-alpha-1752701791000",
"ws": "^8.18.1",
"zod-to-json-schema": "^3.24.4"
},
@@ -24,7 +24,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
- "@playwright/test": "1.55.0-alpha-1752540053000",
+ "@playwright/test": "1.55.0-alpha-1752701791000",
"@stylistic/eslint-plugin": "^3.0.1",
"@types/chrome": "^0.0.315",
"@types/debug": "^4.1.12",
@@ -293,13 +293,13 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.55.0-alpha-1752540053000",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0-alpha-1752540053000.tgz",
- "integrity": "sha512-lpiGWId9fRQMn8IXR3Bimbpn/6PLlM2rwn3eWg58BO4X+JjAWXHVmBHvbIkAqfR4v5rxzAJIMQi2XHvPsur3YA==",
+ "version": "1.55.0-alpha-1752701791000",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0-alpha-1752701791000.tgz",
+ "integrity": "sha512-mnitdsjXKPyKTjQQDJ78Or1xZSGcaoDzZVD/0BWFCvygn3nyNmGmiias/Mlfvzvgz9UWBbPeZYxU/bd2Lu+OrQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright": "1.55.0-alpha-1752540053000"
+ "playwright": "1.55.0-alpha-1752701791000"
},
"bin": {
"playwright": "cli.js"
@@ -3301,12 +3301,12 @@
}
},
"node_modules/playwright": {
- "version": "1.55.0-alpha-1752540053000",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0-alpha-1752540053000.tgz",
- "integrity": "sha512-Nk6feSnITP79R936KspfwS5MrhbMTK+oK8en2O/mI3lHyAiiBOh8JfOwdvRLna33E7p9KVRWXM7DbdGmFXovAQ==",
+ "version": "1.55.0-alpha-1752701791000",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0-alpha-1752701791000.tgz",
+ "integrity": "sha512-PA3TvDz7uQ+Pde0uaii5/WpU5vntRJsYFsaSPoBzywIqzYFO1ugk1ZZ0q6z4/xHq0ha1UClvsv3P77B+u1fi+w==",
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.55.0-alpha-1752540053000"
+ "playwright-core": "1.55.0-alpha-1752701791000"
},
"bin": {
"playwright": "cli.js"
@@ -3319,9 +3319,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.55.0-alpha-1752540053000",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0-alpha-1752540053000.tgz",
- "integrity": "sha512-ZQaYt7sduxL1NaVfTg8oJdYGzt5XbFVVhdkuaofjwmsr8Yf245rZybb5YuhAwRT2h0MNJUH4UOdboQNDb6mqmw==",
+ "version": "1.55.0-alpha-1752701791000",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0-alpha-1752701791000.tgz",
+ "integrity": "sha512-mQhzhjJMiqnGNnYZv7M4yk1OcNTt1E72jrTLO7EqZuoeat4+qpcU0/mbK+RcTEass5a9YheoVFh6OIhruFMGVg==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
diff --git a/package.json b/package.json
index 11a2fb7..757a52c 100644
--- a/package.json
+++ b/package.json
@@ -40,15 +40,15 @@
"commander": "^13.1.0",
"debug": "^4.4.1",
"mime": "^4.0.7",
- "playwright": "1.55.0-alpha-1752540053000",
- "playwright-core": "1.55.0-alpha-1752540053000",
+ "playwright": "1.55.0-alpha-1752701791000",
+ "playwright-core": "1.55.0-alpha-1752701791000",
"ws": "^8.18.1",
"zod-to-json-schema": "^3.24.4"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
- "@playwright/test": "1.55.0-alpha-1752540053000",
+ "@playwright/test": "1.55.0-alpha-1752701791000",
"@stylistic/eslint-plugin": "^3.0.1",
"@types/chrome": "^0.0.315",
"@types/debug": "^4.1.12",
diff --git a/src/context.ts b/src/context.ts
index c7a0717..f63a250 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -190,19 +190,19 @@ ${code.join('\n')}
result.push('');
}
- if (this.tabs().length > 1)
- result.push(await this.listTabsMarkdown(), '');
+ if (captureSnapshot && tab.hasSnapshot()) {
+ if (this.tabs().length > 1)
+ result.push(await this.listTabsMarkdown(), '');
- if (this.tabs().length > 1)
- result.push('### Current tab');
+ if (this.tabs().length > 1)
+ result.push('### Current tab');
- result.push(
- `- Page URL: ${tab.page.url()}`,
- `- Page Title: ${await tab.title()}`
- );
-
- if (captureSnapshot && tab.hasSnapshot())
+ result.push(
+ `- Page URL: ${tab.page.url()}`,
+ `- Page Title: ${await tab.title()}`
+ );
result.push(tab.snapshotOrDie().text());
+ }
const content = actionResult?.content ?? [];
diff --git a/src/tools.ts b/src/tools.ts
index 50a8756..2f20713 100644
--- a/src/tools.ts
+++ b/src/tools.ts
@@ -17,6 +17,7 @@
import common from './tools/common.js';
import console from './tools/console.js';
import dialogs from './tools/dialogs.js';
+import evaluate from './tools/evaluate.js';
import files from './tools/files.js';
import install from './tools/install.js';
import keyboard from './tools/keyboard.js';
@@ -35,6 +36,7 @@ export const snapshotTools: Tool[] = [
...common(true),
...console,
...dialogs(true),
+ ...evaluate,
...files(true),
...install,
...keyboard(true),
diff --git a/src/tools/evaluate.ts b/src/tools/evaluate.ts
new file mode 100644
index 0000000..73820e5
--- /dev/null
+++ b/src/tools/evaluate.ts
@@ -0,0 +1,71 @@
+/**
+ * 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.
+ */
+
+import { z } from 'zod';
+
+import { defineTool } from './tool.js';
+import * as javascript from '../javascript.js';
+import { generateLocator } from './utils.js';
+
+import type * as playwright from 'playwright';
+
+const evaluateSchema = z.object({
+ function: z.string().describe('() => { /* code */ } or (element) => { /* code */ } when element is provided'),
+ element: z.string().optional().describe('Human-readable element description used to obtain permission to interact with the element'),
+ ref: z.string().optional().describe('Exact target element reference from the page snapshot'),
+});
+
+const evaluate = defineTool({
+ capability: 'core',
+ schema: {
+ name: 'browser_evaluate',
+ title: 'Evaluate JavaScript',
+ description: 'Evaluate JavaScript expression on page or element',
+ inputSchema: evaluateSchema,
+ type: 'destructive',
+ },
+
+ handle: async (context, params) => {
+ const tab = context.currentTabOrDie();
+ const code: string[] = [];
+
+ let locator: playwright.Locator | undefined;
+ if (params.ref && params.element) {
+ const snapshot = tab.snapshotOrDie();
+ locator = snapshot.refLocator({ ref: params.ref, element: params.element });
+ code.push(`await page.${await generateLocator(locator)}.evaluate(${javascript.quote(params.function)});`);
+ } else {
+ code.push(`await page.evaluate(${javascript.quote(params.function)});`);
+ }
+
+ return {
+ code,
+ action: async () => {
+ const receiver = locator ?? tab.page as any;
+ const result = await receiver._evaluateFunction(params.function);
+ return {
+ content: [{ type: 'text', text: '- Result: ' + (JSON.stringify(result, null, 2) || 'undefined') }],
+ };
+ },
+ captureSnapshot: false,
+ waitForNetwork: false,
+ };
+ },
+});
+
+export default [
+ evaluate,
+];
diff --git a/tests/capabilities.spec.ts b/tests/capabilities.spec.ts
index 3d00859..cd7defd 100644
--- a/tests/capabilities.spec.ts
+++ b/tests/capabilities.spec.ts
@@ -22,6 +22,7 @@ test('test snapshot tool list', async ({ client }) => {
'browser_click',
'browser_console_messages',
'browser_drag',
+ 'browser_evaluate',
'browser_file_upload',
'browser_handle_dialog',
'browser_hover',
diff --git a/tests/fixtures.ts b/tests/fixtures.ts
index 3c5fbad..3668a64 100644
--- a/tests/fixtures.ts
+++ b/tests/fixtures.ts
@@ -232,17 +232,14 @@ export const expect = baseExpect.extend({
};
},
- toContainTextContent(response: Response, content: string | string[]) {
+ toContainTextContent(response: Response, content: string) {
const isNot = this.isNot;
try {
- content = Array.isArray(content) ? content : [content];
- const texts = (response.content as any).map(c => c.text);
- for (let i = 0; i < texts.length; i++) {
- if (isNot)
- expect(texts[i]).not.toContain(content[i]);
- else
- expect(texts[i]).toContain(content[i]);
- }
+ const texts = (response.content as any).map(c => c.text).join('\n');
+ if (isNot)
+ expect(texts).not.toContain(content);
+ else
+ expect(texts).toContain(content);
} catch (e) {
return {
pass: isNot,
diff --git a/utils/update-readme.js b/utils/update-readme.js
index 5e2ded1..144838d 100644
--- a/utils/update-readme.js
+++ b/utils/update-readme.js
@@ -24,6 +24,7 @@ import zodToJsonSchema from 'zod-to-json-schema'
import commonTools from '../lib/tools/common.js';
import consoleTools from '../lib/tools/console.js';
import dialogsTools from '../lib/tools/dialogs.js';
+import evaluateTools from '../lib/tools/evaluate.js';
import filesTools from '../lib/tools/files.js';
import installTools from '../lib/tools/install.js';
import keyboardTools from '../lib/tools/keyboard.js';
@@ -48,6 +49,9 @@ const categories = {
'Navigation': [
...navigateTools(true),
],
+ 'Evaluation': [
+ ...evaluateTools,
+ ],
'Resources': [
...screenshotTools,
...pdfTools,