@ejazullah/browser-mcp 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +860 -0
  3. package/cli.js +19 -0
  4. package/index.d.ts +23 -0
  5. package/index.js +1061 -0
  6. package/lib/auth.js +82 -0
  7. package/lib/browserContextFactory.js +205 -0
  8. package/lib/browserServerBackend.js +125 -0
  9. package/lib/config.js +266 -0
  10. package/lib/context.js +232 -0
  11. package/lib/databaseLogger.js +264 -0
  12. package/lib/extension/cdpRelay.js +346 -0
  13. package/lib/extension/extensionContextFactory.js +56 -0
  14. package/lib/extension/main.js +26 -0
  15. package/lib/fileUtils.js +32 -0
  16. package/lib/httpServer.js +39 -0
  17. package/lib/index.js +39 -0
  18. package/lib/javascript.js +49 -0
  19. package/lib/log.js +21 -0
  20. package/lib/loop/loop.js +69 -0
  21. package/lib/loop/loopClaude.js +152 -0
  22. package/lib/loop/loopOpenAI.js +143 -0
  23. package/lib/loop/main.js +60 -0
  24. package/lib/loopTools/context.js +66 -0
  25. package/lib/loopTools/main.js +49 -0
  26. package/lib/loopTools/perform.js +32 -0
  27. package/lib/loopTools/snapshot.js +29 -0
  28. package/lib/loopTools/tool.js +18 -0
  29. package/lib/manualPromise.js +111 -0
  30. package/lib/mcp/inProcessTransport.js +72 -0
  31. package/lib/mcp/server.js +93 -0
  32. package/lib/mcp/transport.js +217 -0
  33. package/lib/mongoDBLogger.js +252 -0
  34. package/lib/package.js +20 -0
  35. package/lib/program.js +113 -0
  36. package/lib/response.js +172 -0
  37. package/lib/sessionLog.js +156 -0
  38. package/lib/tab.js +266 -0
  39. package/lib/tools/cdp.js +169 -0
  40. package/lib/tools/common.js +55 -0
  41. package/lib/tools/console.js +33 -0
  42. package/lib/tools/dialogs.js +47 -0
  43. package/lib/tools/evaluate.js +53 -0
  44. package/lib/tools/extraction.js +217 -0
  45. package/lib/tools/files.js +44 -0
  46. package/lib/tools/forms.js +180 -0
  47. package/lib/tools/getext.js +99 -0
  48. package/lib/tools/install.js +53 -0
  49. package/lib/tools/interactions.js +191 -0
  50. package/lib/tools/keyboard.js +86 -0
  51. package/lib/tools/mouse.js +99 -0
  52. package/lib/tools/navigate.js +70 -0
  53. package/lib/tools/network.js +41 -0
  54. package/lib/tools/pdf.js +40 -0
  55. package/lib/tools/screenshot.js +75 -0
  56. package/lib/tools/selectors.js +233 -0
  57. package/lib/tools/snapshot.js +169 -0
  58. package/lib/tools/states.js +147 -0
  59. package/lib/tools/tabs.js +87 -0
  60. package/lib/tools/tool.js +33 -0
  61. package/lib/tools/utils.js +74 -0
  62. package/lib/tools/wait.js +56 -0
  63. package/lib/tools.js +64 -0
  64. package/lib/utils.js +26 -0
  65. package/openapi.json +683 -0
  66. package/package.json +92 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTabTool } from './tool.js';
18
+ // Schema for CSS selector based operations
19
+ const cssSelectorSchema = z.object({
20
+ selector: z.string().describe('CSS selector to target the element (e.g., "#id", ".class", "button", "[data-test=\'value\']")'),
21
+ attribute: z.string().optional().describe('Optional attribute to get instead of text content (e.g., "href", "src", "value")'),
22
+ index: z.number().optional().describe('Index of element if multiple elements match (0-based)'),
23
+ });
24
+ // Schema for XPath selector based operations
25
+ const xpathSelectorSchema = z.object({
26
+ xpath: z.string().describe('XPath expression to target the element (e.g., "//button[@id=\'submit\']", "//div[contains(@class, \'content\')]")'),
27
+ attribute: z.string().optional().describe('Optional attribute to get instead of text content (e.g., "href", "src", "value")'),
28
+ });
29
+ // Schema for text-based locators
30
+ const textSelectorSchema = z.object({
31
+ text: z.string().describe('Text content to search for in elements'),
32
+ elementType: z.string().optional().describe('Element type to filter by (e.g., "button", "a", "div")'),
33
+ exact: z.boolean().optional().default(false).describe('Whether to match text exactly or use contains'),
34
+ attribute: z.string().optional().describe('Optional attribute to get instead of text content'),
35
+ });
36
+ // Schema for role-based locators (accessibility)
37
+ const roleSelectorSchema = z.object({
38
+ role: z.string().describe('ARIA role of the element (e.g., "button", "link", "textbox", "heading")'),
39
+ name: z.string().optional().describe('Accessible name of the element'),
40
+ level: z.number().optional().describe('Level for headings (1-6)'),
41
+ attribute: z.string().optional().describe('Optional attribute to get instead of text content'),
42
+ });
43
+ const getTextByCSS = defineTabTool({
44
+ capability: 'core',
45
+ schema: {
46
+ name: 'browser_get_text_by_css',
47
+ title: 'Get text by CSS selector',
48
+ description: 'Get text content or attribute from element using CSS selector',
49
+ inputSchema: cssSelectorSchema,
50
+ type: 'readOnly',
51
+ },
52
+ handle: async (tab, params, response) => {
53
+ try {
54
+ let locator = tab.page.locator(params.selector);
55
+ // If index is specified and multiple elements might match
56
+ if (params.index !== undefined) {
57
+ locator = locator.nth(params.index);
58
+ }
59
+ let result;
60
+ if (params.attribute) {
61
+ result = await locator.getAttribute(params.attribute) || '';
62
+ response.addCode(`const attributeValue = await page.locator('${params.selector}')${params.index !== undefined ? `.nth(${params.index})` : ''}.getAttribute('${params.attribute}');`);
63
+ }
64
+ else {
65
+ result = await locator.textContent() || '';
66
+ response.addCode(`const textContent = await page.locator('${params.selector}')${params.index !== undefined ? `.nth(${params.index})` : ''}.textContent();`);
67
+ }
68
+ response.addResult(`${params.attribute ? `Attribute "${params.attribute}"` : 'Text content'}: ${result}`);
69
+ }
70
+ catch (error) {
71
+ response.addError(`Failed to get ${params.attribute ? `attribute "${params.attribute}"` : 'text content'} using CSS selector "${params.selector}": ${error}`);
72
+ }
73
+ },
74
+ });
75
+ const getTextByXPath = defineTabTool({
76
+ capability: 'core',
77
+ schema: {
78
+ name: 'browser_get_text_by_xpath',
79
+ title: 'Get text by XPath',
80
+ description: 'Get text content or attribute from element using XPath expression',
81
+ inputSchema: xpathSelectorSchema,
82
+ type: 'readOnly',
83
+ },
84
+ handle: async (tab, params, response) => {
85
+ try {
86
+ const locator = tab.page.locator(`xpath=${params.xpath}`);
87
+ let result;
88
+ if (params.attribute) {
89
+ result = await locator.getAttribute(params.attribute) || '';
90
+ response.addCode(`const attributeValue = await page.locator('xpath=${params.xpath}').getAttribute('${params.attribute}');`);
91
+ }
92
+ else {
93
+ result = await locator.textContent() || '';
94
+ response.addCode(`const textContent = await page.locator('xpath=${params.xpath}').textContent();`);
95
+ }
96
+ response.addResult(`${params.attribute ? `Attribute "${params.attribute}"` : 'Text content'}: ${result}`);
97
+ }
98
+ catch (error) {
99
+ response.addError(`Failed to get ${params.attribute ? `attribute "${params.attribute}"` : 'text content'} using XPath "${params.xpath}": ${error}`);
100
+ }
101
+ },
102
+ });
103
+ const getTextByText = defineTabTool({
104
+ capability: 'core',
105
+ schema: {
106
+ name: 'browser_get_text_by_text',
107
+ title: 'Get text by text content',
108
+ description: 'Find element by its text content and get text or attribute',
109
+ inputSchema: textSelectorSchema,
110
+ type: 'readOnly',
111
+ },
112
+ handle: async (tab, params, response) => {
113
+ try {
114
+ let locator;
115
+ if (params.elementType) {
116
+ // Filter by element type first
117
+ if (params.exact) {
118
+ locator = tab.page.locator(params.elementType).filter({ hasText: new RegExp(`^${params.text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`) });
119
+ }
120
+ else {
121
+ locator = tab.page.locator(params.elementType).filter({ hasText: params.text });
122
+ }
123
+ }
124
+ else {
125
+ // Search in all elements
126
+ if (params.exact) {
127
+ locator = tab.page.getByText(params.text, { exact: true });
128
+ }
129
+ else {
130
+ locator = tab.page.getByText(params.text);
131
+ }
132
+ }
133
+ let result;
134
+ if (params.attribute) {
135
+ result = await locator.getAttribute(params.attribute) || '';
136
+ response.addCode(`const attributeValue = await page.${params.elementType ? `locator('${params.elementType}').filter({ hasText: '${params.text}' })` : `getByText('${params.text}'${params.exact ? ', { exact: true }' : ''})`}.getAttribute('${params.attribute}');`);
137
+ }
138
+ else {
139
+ result = await locator.textContent() || '';
140
+ response.addCode(`const textContent = await page.${params.elementType ? `locator('${params.elementType}').filter({ hasText: '${params.text}' })` : `getByText('${params.text}'${params.exact ? ', { exact: true }' : ''})`}.textContent();`);
141
+ }
142
+ response.addResult(`${params.attribute ? `Attribute "${params.attribute}"` : 'Text content'}: ${result}`);
143
+ }
144
+ catch (error) {
145
+ response.addError(`Failed to get ${params.attribute ? `attribute "${params.attribute}"` : 'text content'} for element with text "${params.text}": ${error}`);
146
+ }
147
+ },
148
+ });
149
+ const getTextByRole = defineTabTool({
150
+ capability: 'core',
151
+ schema: {
152
+ name: 'browser_get_text_by_role',
153
+ title: 'Get text by ARIA role',
154
+ description: 'Get text content or attribute from element using ARIA role',
155
+ inputSchema: roleSelectorSchema,
156
+ type: 'readOnly',
157
+ },
158
+ handle: async (tab, params, response) => {
159
+ try {
160
+ let locator;
161
+ if (params.role === 'heading' && params.level) {
162
+ locator = tab.page.getByRole('heading', { level: params.level, name: params.name });
163
+ }
164
+ else if (params.name) {
165
+ locator = tab.page.getByRole(params.role, { name: params.name });
166
+ }
167
+ else {
168
+ locator = tab.page.getByRole(params.role);
169
+ }
170
+ let result;
171
+ if (params.attribute) {
172
+ result = await locator.getAttribute(params.attribute) || '';
173
+ response.addCode(`const attributeValue = await page.getByRole('${params.role}'${params.name ? `, { name: '${params.name}' }` : ''}${params.level ? `, { level: ${params.level} }` : ''}).getAttribute('${params.attribute}');`);
174
+ }
175
+ else {
176
+ result = await locator.textContent() || '';
177
+ response.addCode(`const textContent = await page.getByRole('${params.role}'${params.name ? `, { name: '${params.name}' }` : ''}${params.level ? `, { level: ${params.level} }` : ''}).textContent();`);
178
+ }
179
+ response.addResult(`${params.attribute ? `Attribute "${params.attribute}"` : 'Text content'}: ${result}`);
180
+ }
181
+ catch (error) {
182
+ response.addError(`Failed to get ${params.attribute ? `attribute "${params.attribute}"` : 'text content'} for ${params.role} element: ${error}`);
183
+ }
184
+ },
185
+ });
186
+ // Tool to get all text from multiple elements
187
+ const getAllText = defineTabTool({
188
+ capability: 'core',
189
+ schema: {
190
+ name: 'browser_get_all_text',
191
+ title: 'Get all text from multiple elements',
192
+ description: 'Get text content from all elements matching a CSS selector',
193
+ inputSchema: z.object({
194
+ selector: z.string().describe('CSS selector to target multiple elements'),
195
+ attribute: z.string().optional().describe('Optional attribute to get instead of text content'),
196
+ limit: z.number().optional().default(10).describe('Maximum number of elements to process'),
197
+ }),
198
+ type: 'readOnly',
199
+ },
200
+ handle: async (tab, params, response) => {
201
+ try {
202
+ const locator = tab.page.locator(params.selector);
203
+ const count = await locator.count();
204
+ const limit = Math.min(count, params.limit || 10);
205
+ const results = [];
206
+ for (let i = 0; i < limit; i++) {
207
+ const elementLocator = locator.nth(i);
208
+ let result;
209
+ if (params.attribute) {
210
+ result = await elementLocator.getAttribute(params.attribute) || '';
211
+ }
212
+ else {
213
+ result = await elementLocator.textContent() || '';
214
+ }
215
+ if (result.trim()) {
216
+ results.push(`[${i}] ${result}`);
217
+ }
218
+ }
219
+ response.addCode(`const elements = await page.locator('${params.selector}').all();\nconst results = await Promise.all(elements.slice(0, ${limit}).map(el => el.${params.attribute ? `getAttribute('${params.attribute}')` : 'textContent()'}));`);
220
+ response.addResult(`Found ${count} elements, showing first ${limit}:\n${results.join('\n')}`);
221
+ }
222
+ catch (error) {
223
+ response.addError(`Failed to get ${params.attribute ? `attribute "${params.attribute}"` : 'text content'} from elements matching "${params.selector}": ${error}`);
224
+ }
225
+ },
226
+ });
227
+ export default [
228
+ getTextByCSS,
229
+ getTextByXPath,
230
+ getTextByText,
231
+ getTextByRole,
232
+ getAllText,
233
+ ];
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTabTool, defineTool } from './tool.js';
18
+ import * as javascript from '../javascript.js';
19
+ import { generateLocator } from './utils.js';
20
+ const snapshot = defineTool({
21
+ capability: 'core',
22
+ schema: {
23
+ name: 'browser_snapshot',
24
+ title: 'Page snapshot',
25
+ description: 'Capture accessibility snapshot of the current page, this is better than screenshot',
26
+ inputSchema: z.object({}),
27
+ type: 'readOnly',
28
+ },
29
+ handle: async (context, params, response) => {
30
+ await context.ensureTab();
31
+ response.setIncludeSnapshot();
32
+ },
33
+ });
34
+ export const elementSchema = z.object({
35
+ element: z.string().describe('Human-readable element description used to obtain permission to interact with the element'),
36
+ ref: z.string().describe('Exact target element reference from the page snapshot'),
37
+ });
38
+ const clickSchema = elementSchema.extend({
39
+ doubleClick: z.boolean().optional().describe('Whether to perform a double click instead of a single click'),
40
+ button: z.enum(['left', 'right', 'middle']).optional().describe('Button to click, defaults to left'),
41
+ });
42
+ const click = defineTabTool({
43
+ capability: 'core',
44
+ schema: {
45
+ name: 'browser_click',
46
+ title: 'Click',
47
+ description: 'Perform click on a web page',
48
+ inputSchema: clickSchema,
49
+ type: 'destructive',
50
+ },
51
+ handle: async (tab, params, response) => {
52
+ response.setIncludeSnapshot();
53
+ // Get locator with selector information for database logging
54
+ const { locator, selector, resolvedSelector } = await tab.refLocatorWithSelector(params);
55
+ // Store element interaction data for database logging
56
+ response.setElementInteraction({
57
+ elementRef: params.ref,
58
+ elementDescription: params.element,
59
+ playwrightSelector: selector,
60
+ resolvedSelector: resolvedSelector,
61
+ });
62
+ const button = params.button;
63
+ const buttonAttr = button ? `{ button: '${button}' }` : '';
64
+ if (params.doubleClick)
65
+ response.addCode(`await page.${await generateLocator(locator)}.dblclick(${buttonAttr});`);
66
+ else
67
+ response.addCode(`await page.${await generateLocator(locator)}.click(${buttonAttr});`);
68
+ await tab.waitForCompletion(async () => {
69
+ if (params.doubleClick)
70
+ await locator.dblclick({ button });
71
+ else
72
+ await locator.click({ button });
73
+ });
74
+ },
75
+ });
76
+ const drag = defineTabTool({
77
+ capability: 'core',
78
+ schema: {
79
+ name: 'browser_drag',
80
+ title: 'Drag mouse',
81
+ description: 'Perform drag and drop between two elements',
82
+ inputSchema: z.object({
83
+ startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'),
84
+ startRef: z.string().describe('Exact source element reference from the page snapshot'),
85
+ endElement: z.string().describe('Human-readable target element description used to obtain the permission to interact with the element'),
86
+ endRef: z.string().describe('Exact target element reference from the page snapshot'),
87
+ }),
88
+ type: 'destructive',
89
+ },
90
+ handle: async (tab, params, response) => {
91
+ response.setIncludeSnapshot();
92
+ // Get locators with selector information for database logging
93
+ const startInfo = await tab.refLocatorWithSelector({ ref: params.startRef, element: params.startElement });
94
+ const endInfo = await tab.refLocatorWithSelector({ ref: params.endRef, element: params.endElement });
95
+ // Store element interaction data for database logging (log start element primarily)
96
+ response.setElementInteraction({
97
+ elementRef: params.startRef,
98
+ elementDescription: `${params.startElement} -> ${params.endElement}`,
99
+ playwrightSelector: startInfo.selector,
100
+ resolvedSelector: startInfo.resolvedSelector,
101
+ });
102
+ await tab.waitForCompletion(async () => {
103
+ await startInfo.locator.dragTo(endInfo.locator);
104
+ });
105
+ response.addCode(`await page.${await generateLocator(startInfo.locator)}.dragTo(page.${await generateLocator(endInfo.locator)});`);
106
+ },
107
+ });
108
+ const hover = defineTabTool({
109
+ capability: 'core',
110
+ schema: {
111
+ name: 'browser_hover',
112
+ title: 'Hover mouse',
113
+ description: 'Hover over element on page',
114
+ inputSchema: elementSchema,
115
+ type: 'readOnly',
116
+ },
117
+ handle: async (tab, params, response) => {
118
+ response.setIncludeSnapshot();
119
+ // Get locator with selector information for database logging
120
+ const { locator, selector, resolvedSelector } = await tab.refLocatorWithSelector(params);
121
+ // Store element interaction data for database logging
122
+ response.setElementInteraction({
123
+ elementRef: params.ref,
124
+ elementDescription: params.element,
125
+ playwrightSelector: selector,
126
+ resolvedSelector: resolvedSelector,
127
+ });
128
+ response.addCode(`await page.${await generateLocator(locator)}.hover();`);
129
+ await tab.waitForCompletion(async () => {
130
+ await locator.hover();
131
+ });
132
+ },
133
+ });
134
+ const selectOptionSchema = elementSchema.extend({
135
+ values: z.array(z.string()).describe('Array of values to select in the dropdown. This can be a single value or multiple values.'),
136
+ });
137
+ const selectOption = defineTabTool({
138
+ capability: 'core',
139
+ schema: {
140
+ name: 'browser_select_option',
141
+ title: 'Select option',
142
+ description: 'Select an option in a dropdown',
143
+ inputSchema: selectOptionSchema,
144
+ type: 'destructive',
145
+ },
146
+ handle: async (tab, params, response) => {
147
+ response.setIncludeSnapshot();
148
+ // Get locator with selector information for database logging
149
+ const { locator, selector, resolvedSelector } = await tab.refLocatorWithSelector(params);
150
+ // Store element interaction data for database logging
151
+ response.setElementInteraction({
152
+ elementRef: params.ref,
153
+ elementDescription: params.element,
154
+ playwrightSelector: selector,
155
+ resolvedSelector: resolvedSelector,
156
+ });
157
+ response.addCode(`await page.${await generateLocator(locator)}.selectOption(${javascript.formatObject(params.values)});`);
158
+ await tab.waitForCompletion(async () => {
159
+ await locator.selectOption(params.values);
160
+ });
161
+ },
162
+ });
163
+ export default [
164
+ snapshot,
165
+ click,
166
+ drag,
167
+ hover,
168
+ selectOption,
169
+ ];
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTabTool } from './tool.js';
18
+ import { elementSchema } from './snapshot.js';
19
+ import { generateLocator } from './utils.js';
20
+ // Check if element is visible
21
+ const isVisible = defineTabTool({
22
+ capability: 'core',
23
+ schema: {
24
+ name: 'browser_is_visible',
25
+ title: 'Check if element is visible',
26
+ description: 'Check if an element is visible on the page',
27
+ inputSchema: elementSchema,
28
+ type: 'readOnly',
29
+ },
30
+ handle: async (tab, params, response) => {
31
+ try {
32
+ const locator = await tab.refLocator(params);
33
+ const visible = await locator.isVisible();
34
+ response.addCode(`const isVisible = await page.${await generateLocator(locator)}.isVisible();`);
35
+ response.addResult(`Element is ${visible ? 'visible' : 'not visible'}`);
36
+ }
37
+ catch (error) {
38
+ response.addError(`Failed to check visibility: ${error}`);
39
+ }
40
+ },
41
+ });
42
+ // Check if element is enabled
43
+ const isEnabled = defineTabTool({
44
+ capability: 'core',
45
+ schema: {
46
+ name: 'browser_is_enabled',
47
+ title: 'Check if element is enabled',
48
+ description: 'Check if an element is enabled (not disabled)',
49
+ inputSchema: elementSchema,
50
+ type: 'readOnly',
51
+ },
52
+ handle: async (tab, params, response) => {
53
+ try {
54
+ const locator = await tab.refLocator(params);
55
+ const enabled = await locator.isEnabled();
56
+ response.addCode(`const isEnabled = await page.${await generateLocator(locator)}.isEnabled();`);
57
+ response.addResult(`Element is ${enabled ? 'enabled' : 'disabled'}`);
58
+ }
59
+ catch (error) {
60
+ response.addError(`Failed to check if element is enabled: ${error}`);
61
+ }
62
+ },
63
+ });
64
+ // Check if checkbox/radio is checked
65
+ const isChecked = defineTabTool({
66
+ capability: 'core',
67
+ schema: {
68
+ name: 'browser_is_checked',
69
+ title: 'Check if checkbox/radio is selected',
70
+ description: 'Check if a checkbox or radio button is checked/selected',
71
+ inputSchema: elementSchema,
72
+ type: 'readOnly',
73
+ },
74
+ handle: async (tab, params, response) => {
75
+ try {
76
+ const locator = await tab.refLocator(params);
77
+ const checked = await locator.isChecked();
78
+ response.addCode(`const isChecked = await page.${await generateLocator(locator)}.isChecked();`);
79
+ response.addResult(`Element is ${checked ? 'checked' : 'not checked'}`);
80
+ }
81
+ catch (error) {
82
+ response.addError(`Failed to check if element is checked: ${error}`);
83
+ }
84
+ },
85
+ });
86
+ // Wait for element to appear/disappear
87
+ const waitForElement = defineTabTool({
88
+ capability: 'core',
89
+ schema: {
90
+ name: 'browser_wait_for_element',
91
+ title: 'Wait for element state',
92
+ description: 'Wait for an element to appear, disappear, or change state',
93
+ inputSchema: elementSchema.extend({
94
+ state: z.enum(['visible', 'hidden', 'attached', 'detached']).default('visible').describe('State to wait for'),
95
+ timeout: z.number().optional().default(5000).describe('Timeout in milliseconds'),
96
+ }),
97
+ type: 'readOnly',
98
+ },
99
+ handle: async (tab, params, response) => {
100
+ try {
101
+ const locator = await tab.refLocator(params);
102
+ await locator.waitFor({
103
+ state: params.state,
104
+ timeout: params.timeout
105
+ });
106
+ response.addCode(`await page.${await generateLocator(locator)}.waitFor({ state: '${params.state}', timeout: ${params.timeout} });`);
107
+ response.addResult(`Element is now ${params.state}`);
108
+ if (params.state === 'visible' || params.state === 'attached') {
109
+ response.setIncludeSnapshot();
110
+ }
111
+ }
112
+ catch (error) {
113
+ response.addError(`Failed to wait for element to be ${params.state}: ${error}`);
114
+ }
115
+ },
116
+ });
117
+ // Count matching elements
118
+ const getElementCount = defineTabTool({
119
+ capability: 'core',
120
+ schema: {
121
+ name: 'browser_get_element_count',
122
+ title: 'Count elements',
123
+ description: 'Count the number of elements matching a CSS selector',
124
+ inputSchema: z.object({
125
+ selector: z.string().describe('CSS selector to count elements'),
126
+ }),
127
+ type: 'readOnly',
128
+ },
129
+ handle: async (tab, params, response) => {
130
+ try {
131
+ const locator = tab.page.locator(params.selector);
132
+ const count = await locator.count();
133
+ response.addCode(`const count = await page.locator('${params.selector}').count();`);
134
+ response.addResult(`Found ${count} elements matching "${params.selector}"`);
135
+ }
136
+ catch (error) {
137
+ response.addError(`Failed to count elements: ${error}`);
138
+ }
139
+ },
140
+ });
141
+ export default [
142
+ isVisible,
143
+ isEnabled,
144
+ isChecked,
145
+ waitForElement,
146
+ getElementCount,
147
+ ];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTool } from './tool.js';
18
+ const listTabs = defineTool({
19
+ capability: 'core-tabs',
20
+ schema: {
21
+ name: 'browser_tab_list',
22
+ title: 'List tabs',
23
+ description: 'List browser tabs',
24
+ inputSchema: z.object({}),
25
+ type: 'readOnly',
26
+ },
27
+ handle: async (context, params, response) => {
28
+ await context.ensureTab();
29
+ response.setIncludeTabs();
30
+ },
31
+ });
32
+ const selectTab = defineTool({
33
+ capability: 'core-tabs',
34
+ schema: {
35
+ name: 'browser_tab_select',
36
+ title: 'Select a tab',
37
+ description: 'Select a tab by index',
38
+ inputSchema: z.object({
39
+ index: z.number().describe('The index of the tab to select'),
40
+ }),
41
+ type: 'readOnly',
42
+ },
43
+ handle: async (context, params, response) => {
44
+ await context.selectTab(params.index);
45
+ response.setIncludeSnapshot();
46
+ },
47
+ });
48
+ const newTab = defineTool({
49
+ capability: 'core-tabs',
50
+ schema: {
51
+ name: 'browser_tab_new',
52
+ title: 'Open a new tab',
53
+ description: 'Open a new tab',
54
+ inputSchema: z.object({
55
+ url: z.string().optional().describe('The URL to navigate to in the new tab. If not provided, the new tab will be blank.'),
56
+ }),
57
+ type: 'readOnly',
58
+ },
59
+ handle: async (context, params, response) => {
60
+ const tab = await context.newTab();
61
+ if (params.url)
62
+ await tab.navigate(params.url);
63
+ response.setIncludeSnapshot();
64
+ },
65
+ });
66
+ const closeTab = defineTool({
67
+ capability: 'core-tabs',
68
+ schema: {
69
+ name: 'browser_tab_close',
70
+ title: 'Close a tab',
71
+ description: 'Close a tab',
72
+ inputSchema: z.object({
73
+ index: z.number().optional().describe('The index of the tab to close. Closes current tab if not provided.'),
74
+ }),
75
+ type: 'destructive',
76
+ },
77
+ handle: async (context, params, response) => {
78
+ await context.closeTab(params.index);
79
+ response.setIncludeSnapshot();
80
+ },
81
+ });
82
+ export default [
83
+ listTabs,
84
+ newTab,
85
+ selectTab,
86
+ closeTab,
87
+ ];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export function defineTool(tool) {
17
+ return tool;
18
+ }
19
+ export function defineTabTool(tool) {
20
+ return {
21
+ ...tool,
22
+ handle: async (context, params, response) => {
23
+ const tab = context.currentTabOrDie();
24
+ const modalStates = tab.modalStates().map(state => state.type);
25
+ if (tool.clearsModalState && !modalStates.includes(tool.clearsModalState))
26
+ response.addError(`Error: The tool "${tool.schema.name}" can only be used when there is related modal state present.\n` + tab.modalStatesMarkdown().join('\n'));
27
+ else if (!tool.clearsModalState && modalStates.length)
28
+ response.addError(`Error: Tool "${tool.schema.name}" does not handle the modal state.\n` + tab.modalStatesMarkdown().join('\n'));
29
+ else
30
+ return tool.handle(tab, params, response);
31
+ },
32
+ };
33
+ }