@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.
- package/LICENSE +202 -0
- package/README.md +860 -0
- package/cli.js +19 -0
- package/index.d.ts +23 -0
- package/index.js +1061 -0
- package/lib/auth.js +82 -0
- package/lib/browserContextFactory.js +205 -0
- package/lib/browserServerBackend.js +125 -0
- package/lib/config.js +266 -0
- package/lib/context.js +232 -0
- package/lib/databaseLogger.js +264 -0
- package/lib/extension/cdpRelay.js +346 -0
- package/lib/extension/extensionContextFactory.js +56 -0
- package/lib/extension/main.js +26 -0
- package/lib/fileUtils.js +32 -0
- package/lib/httpServer.js +39 -0
- package/lib/index.js +39 -0
- package/lib/javascript.js +49 -0
- package/lib/log.js +21 -0
- package/lib/loop/loop.js +69 -0
- package/lib/loop/loopClaude.js +152 -0
- package/lib/loop/loopOpenAI.js +143 -0
- package/lib/loop/main.js +60 -0
- package/lib/loopTools/context.js +66 -0
- package/lib/loopTools/main.js +49 -0
- package/lib/loopTools/perform.js +32 -0
- package/lib/loopTools/snapshot.js +29 -0
- package/lib/loopTools/tool.js +18 -0
- package/lib/manualPromise.js +111 -0
- package/lib/mcp/inProcessTransport.js +72 -0
- package/lib/mcp/server.js +93 -0
- package/lib/mcp/transport.js +217 -0
- package/lib/mongoDBLogger.js +252 -0
- package/lib/package.js +20 -0
- package/lib/program.js +113 -0
- package/lib/response.js +172 -0
- package/lib/sessionLog.js +156 -0
- package/lib/tab.js +266 -0
- package/lib/tools/cdp.js +169 -0
- package/lib/tools/common.js +55 -0
- package/lib/tools/console.js +33 -0
- package/lib/tools/dialogs.js +47 -0
- package/lib/tools/evaluate.js +53 -0
- package/lib/tools/extraction.js +217 -0
- package/lib/tools/files.js +44 -0
- package/lib/tools/forms.js +180 -0
- package/lib/tools/getext.js +99 -0
- package/lib/tools/install.js +53 -0
- package/lib/tools/interactions.js +191 -0
- package/lib/tools/keyboard.js +86 -0
- package/lib/tools/mouse.js +99 -0
- package/lib/tools/navigate.js +70 -0
- package/lib/tools/network.js +41 -0
- package/lib/tools/pdf.js +40 -0
- package/lib/tools/screenshot.js +75 -0
- package/lib/tools/selectors.js +233 -0
- package/lib/tools/snapshot.js +169 -0
- package/lib/tools/states.js +147 -0
- package/lib/tools/tabs.js +87 -0
- package/lib/tools/tool.js +33 -0
- package/lib/tools/utils.js +74 -0
- package/lib/tools/wait.js +56 -0
- package/lib/tools.js +64 -0
- package/lib/utils.js +26 -0
- package/openapi.json +683 -0
- package/package.json +92 -0
|
@@ -0,0 +1,217 @@
|
|
|
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
|
+
// Get all links from page
|
|
19
|
+
const getAllLinks = defineTabTool({
|
|
20
|
+
capability: 'core',
|
|
21
|
+
schema: {
|
|
22
|
+
name: 'browser_get_all_links',
|
|
23
|
+
title: 'Get all links',
|
|
24
|
+
description: 'Extract all links from the current page',
|
|
25
|
+
inputSchema: z.object({
|
|
26
|
+
includeText: z.boolean().optional().default(true).describe('Include link text'),
|
|
27
|
+
includeHref: z.boolean().optional().default(true).describe('Include href attribute'),
|
|
28
|
+
limit: z.number().optional().default(20).describe('Maximum number of links to return'),
|
|
29
|
+
}),
|
|
30
|
+
type: 'readOnly',
|
|
31
|
+
},
|
|
32
|
+
handle: async (tab, params, response) => {
|
|
33
|
+
try {
|
|
34
|
+
const links = await tab.page.evaluate(() => {
|
|
35
|
+
const linkElements = Array.from(document.querySelectorAll('a[href]'));
|
|
36
|
+
return linkElements.map((link, index) => ({
|
|
37
|
+
index,
|
|
38
|
+
text: link.textContent?.trim() || '',
|
|
39
|
+
href: link.getAttribute('href') || '',
|
|
40
|
+
title: link.getAttribute('title') || '',
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
const limitedLinks = links.slice(0, params.limit);
|
|
44
|
+
const results = [];
|
|
45
|
+
limitedLinks.forEach((link) => {
|
|
46
|
+
const parts = [`[${link.index}]`];
|
|
47
|
+
if (params.includeText && link.text) {
|
|
48
|
+
parts.push(`"${link.text}"`);
|
|
49
|
+
}
|
|
50
|
+
if (params.includeHref) {
|
|
51
|
+
parts.push(`→ ${link.href}`);
|
|
52
|
+
}
|
|
53
|
+
if (link.title) {
|
|
54
|
+
parts.push(`(${link.title})`);
|
|
55
|
+
}
|
|
56
|
+
results.push(parts.join(' '));
|
|
57
|
+
});
|
|
58
|
+
response.addCode(`const links = await page.evaluate(() => Array.from(document.querySelectorAll('a[href]')).map(link => ({ text: link.textContent, href: link.href })));`);
|
|
59
|
+
response.addResult(`Found ${links.length} links (showing first ${limitedLinks.length}):\n${results.join('\n')}`);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
response.addError(`Failed to get links: ${error}`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
// Get all images from page
|
|
67
|
+
const getAllImages = defineTabTool({
|
|
68
|
+
capability: 'core',
|
|
69
|
+
schema: {
|
|
70
|
+
name: 'browser_get_all_images',
|
|
71
|
+
title: 'Get all images',
|
|
72
|
+
description: 'Extract all images and their sources from the current page',
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
includeAlt: z.boolean().optional().default(true).describe('Include alt text'),
|
|
75
|
+
includeSrc: z.boolean().optional().default(true).describe('Include src attribute'),
|
|
76
|
+
limit: z.number().optional().default(20).describe('Maximum number of images to return'),
|
|
77
|
+
}),
|
|
78
|
+
type: 'readOnly',
|
|
79
|
+
},
|
|
80
|
+
handle: async (tab, params, response) => {
|
|
81
|
+
try {
|
|
82
|
+
const images = await tab.page.evaluate(() => {
|
|
83
|
+
const imgElements = Array.from(document.querySelectorAll('img'));
|
|
84
|
+
return imgElements.map((img, index) => ({
|
|
85
|
+
index,
|
|
86
|
+
src: img.src || '',
|
|
87
|
+
alt: img.alt || '',
|
|
88
|
+
title: img.title || '',
|
|
89
|
+
width: img.naturalWidth || img.width,
|
|
90
|
+
height: img.naturalHeight || img.height,
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
93
|
+
const limitedImages = images.slice(0, params.limit);
|
|
94
|
+
const results = [];
|
|
95
|
+
limitedImages.forEach((img) => {
|
|
96
|
+
const parts = [`[${img.index}]`];
|
|
97
|
+
if (params.includeAlt && img.alt) {
|
|
98
|
+
parts.push(`"${img.alt}"`);
|
|
99
|
+
}
|
|
100
|
+
if (params.includeSrc) {
|
|
101
|
+
parts.push(`→ ${img.src}`);
|
|
102
|
+
}
|
|
103
|
+
if (img.width && img.height) {
|
|
104
|
+
parts.push(`(${img.width}x${img.height})`);
|
|
105
|
+
}
|
|
106
|
+
results.push(parts.join(' '));
|
|
107
|
+
});
|
|
108
|
+
response.addCode(`const images = await page.evaluate(() => Array.from(document.querySelectorAll('img')).map(img => ({ src: img.src, alt: img.alt })));`);
|
|
109
|
+
response.addResult(`Found ${images.length} images (showing first ${limitedImages.length}):\n${results.join('\n')}`);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
response.addError(`Failed to get images: ${error}`);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
// Get table data
|
|
117
|
+
const getTableData = defineTabTool({
|
|
118
|
+
capability: 'core',
|
|
119
|
+
schema: {
|
|
120
|
+
name: 'browser_get_table_data',
|
|
121
|
+
title: 'Extract table data',
|
|
122
|
+
description: 'Extract data from HTML tables',
|
|
123
|
+
inputSchema: z.object({
|
|
124
|
+
selector: z.string().optional().default('table').describe('CSS selector for the table'),
|
|
125
|
+
includeHeaders: z.boolean().optional().default(true).describe('Include table headers'),
|
|
126
|
+
maxRows: z.number().optional().default(10).describe('Maximum number of rows to extract'),
|
|
127
|
+
}),
|
|
128
|
+
type: 'readOnly',
|
|
129
|
+
},
|
|
130
|
+
handle: async (tab, params, response) => {
|
|
131
|
+
try {
|
|
132
|
+
const tableData = await tab.page.evaluate(({ selector, includeHeaders, maxRows }) => {
|
|
133
|
+
const table = document.querySelector(selector);
|
|
134
|
+
if (!table)
|
|
135
|
+
return null;
|
|
136
|
+
const rows = Array.from(table.querySelectorAll('tr'));
|
|
137
|
+
const data = [];
|
|
138
|
+
rows.slice(0, maxRows).forEach((row) => {
|
|
139
|
+
const cells = Array.from(row.querySelectorAll('td, th'));
|
|
140
|
+
const rowData = cells.map(cell => cell.textContent?.trim() || '');
|
|
141
|
+
if (rowData.some(cell => cell)) { // Only include non-empty rows
|
|
142
|
+
data.push(rowData);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return data;
|
|
146
|
+
}, { selector: params.selector, includeHeaders: params.includeHeaders, maxRows: params.maxRows });
|
|
147
|
+
if (!tableData) {
|
|
148
|
+
response.addError(`No table found with selector "${params.selector}"`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const results = [];
|
|
152
|
+
tableData.forEach((row, index) => {
|
|
153
|
+
const isHeader = index === 0 && params.includeHeaders;
|
|
154
|
+
const prefix = isHeader ? 'HEADER' : `ROW ${index}`;
|
|
155
|
+
results.push(`${prefix}: ${row.join(' | ')}`);
|
|
156
|
+
});
|
|
157
|
+
response.addCode(`const tableData = await page.evaluate(() => Array.from(document.querySelector('${params.selector}').querySelectorAll('tr')).map(row => Array.from(row.cells).map(cell => cell.textContent)));`);
|
|
158
|
+
response.addResult(`Table data (${tableData.length} rows):\n${results.join('\n')}`);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
response.addError(`Failed to extract table data: ${error}`);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// Get computed styles
|
|
166
|
+
const getElementStyle = defineTabTool({
|
|
167
|
+
capability: 'core',
|
|
168
|
+
schema: {
|
|
169
|
+
name: 'browser_get_element_style',
|
|
170
|
+
title: 'Get element styles',
|
|
171
|
+
description: 'Get computed CSS styles for an element',
|
|
172
|
+
inputSchema: z.object({
|
|
173
|
+
selector: z.string().describe('CSS selector for the element'),
|
|
174
|
+
properties: z.array(z.string()).optional().describe('Specific CSS properties to get (if not provided, gets common ones)'),
|
|
175
|
+
}),
|
|
176
|
+
type: 'readOnly',
|
|
177
|
+
},
|
|
178
|
+
handle: async (tab, params, response) => {
|
|
179
|
+
try {
|
|
180
|
+
const defaultProperties = [
|
|
181
|
+
'display', 'visibility', 'opacity', 'position', 'top', 'left', 'width', 'height',
|
|
182
|
+
'margin', 'padding', 'border', 'background-color', 'color', 'font-size', 'font-family'
|
|
183
|
+
];
|
|
184
|
+
const propertiesToGet = params.properties || defaultProperties;
|
|
185
|
+
const styles = await tab.page.evaluate(({ selector, properties }) => {
|
|
186
|
+
const element = document.querySelector(selector);
|
|
187
|
+
if (!element)
|
|
188
|
+
return null;
|
|
189
|
+
const computedStyle = window.getComputedStyle(element);
|
|
190
|
+
const result = {};
|
|
191
|
+
properties.forEach((prop) => {
|
|
192
|
+
result[prop] = computedStyle.getPropertyValue(prop);
|
|
193
|
+
});
|
|
194
|
+
return result;
|
|
195
|
+
}, { selector: params.selector, properties: propertiesToGet });
|
|
196
|
+
if (!styles) {
|
|
197
|
+
response.addError(`No element found with selector "${params.selector}"`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const results = Object.entries(styles)
|
|
201
|
+
.filter(([_, value]) => value && value !== 'auto' && value !== 'none')
|
|
202
|
+
.map(([prop, value]) => `${prop}: ${value}`)
|
|
203
|
+
.join('\n');
|
|
204
|
+
response.addCode(`const styles = await page.evaluate(() => { const el = document.querySelector('${params.selector}'); return window.getComputedStyle(el); });`);
|
|
205
|
+
response.addResult(`Element styles:\n${results}`);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
response.addError(`Failed to get element styles: ${error}`);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
export default [
|
|
213
|
+
getAllLinks,
|
|
214
|
+
getAllImages,
|
|
215
|
+
getTableData,
|
|
216
|
+
getElementStyle,
|
|
217
|
+
];
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
const uploadFile = defineTabTool({
|
|
19
|
+
capability: 'core',
|
|
20
|
+
schema: {
|
|
21
|
+
name: 'browser_file_upload',
|
|
22
|
+
title: 'Upload files',
|
|
23
|
+
description: 'Upload one or multiple files',
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
paths: z.array(z.string()).describe('The absolute paths to the files to upload. Can be a single file or multiple files.'),
|
|
26
|
+
}),
|
|
27
|
+
type: 'destructive',
|
|
28
|
+
},
|
|
29
|
+
handle: async (tab, params, response) => {
|
|
30
|
+
response.setIncludeSnapshot();
|
|
31
|
+
const modalState = tab.modalStates().find(state => state.type === 'fileChooser');
|
|
32
|
+
if (!modalState)
|
|
33
|
+
throw new Error('No file chooser visible');
|
|
34
|
+
response.addCode(`await fileChooser.setFiles(${JSON.stringify(params.paths)})`);
|
|
35
|
+
tab.clearModalState(modalState);
|
|
36
|
+
await tab.waitForCompletion(async () => {
|
|
37
|
+
await modalState.fileChooser.setFiles(params.paths);
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
clearsModalState: 'fileChooser',
|
|
41
|
+
});
|
|
42
|
+
export default [
|
|
43
|
+
uploadFile,
|
|
44
|
+
];
|
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
// Schema for CSS selector operations
|
|
21
|
+
const selectorSchema = z.object({
|
|
22
|
+
selector: z.string().describe('CSS selector to target the element'),
|
|
23
|
+
index: z.number().optional().describe('Index of element if multiple elements match (0-based)'),
|
|
24
|
+
});
|
|
25
|
+
// Check/uncheck checkbox
|
|
26
|
+
const checkCheckbox = defineTabTool({
|
|
27
|
+
capability: 'core',
|
|
28
|
+
schema: {
|
|
29
|
+
name: 'browser_check_checkbox',
|
|
30
|
+
title: 'Check or uncheck checkbox',
|
|
31
|
+
description: 'Check or uncheck a checkbox element',
|
|
32
|
+
inputSchema: elementSchema.extend({
|
|
33
|
+
checked: z.boolean().describe('Whether to check (true) or uncheck (false) the checkbox'),
|
|
34
|
+
}),
|
|
35
|
+
type: 'destructive',
|
|
36
|
+
},
|
|
37
|
+
handle: async (tab, params, response) => {
|
|
38
|
+
try {
|
|
39
|
+
// Get locator with selector information for database logging
|
|
40
|
+
const { locator, selector, resolvedSelector } = await tab.refLocatorWithSelector(params);
|
|
41
|
+
// Store element interaction data for database logging
|
|
42
|
+
response.setElementInteraction({
|
|
43
|
+
elementRef: params.ref,
|
|
44
|
+
elementDescription: params.element,
|
|
45
|
+
playwrightSelector: selector,
|
|
46
|
+
resolvedSelector: resolvedSelector,
|
|
47
|
+
});
|
|
48
|
+
if (params.checked) {
|
|
49
|
+
await locator.check();
|
|
50
|
+
response.addCode(`await page.${await generateLocator(locator)}.check();`);
|
|
51
|
+
response.addResult('Checkbox checked successfully');
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
await locator.uncheck();
|
|
55
|
+
response.addCode(`await page.${await generateLocator(locator)}.uncheck();`);
|
|
56
|
+
response.addResult('Checkbox unchecked successfully');
|
|
57
|
+
}
|
|
58
|
+
response.setIncludeSnapshot();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
response.addError(`Failed to ${params.checked ? 'check' : 'uncheck'} checkbox: ${error}`);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
// Select radio button
|
|
66
|
+
const selectRadio = defineTabTool({
|
|
67
|
+
capability: 'core',
|
|
68
|
+
schema: {
|
|
69
|
+
name: 'browser_select_radio',
|
|
70
|
+
title: 'Select radio button',
|
|
71
|
+
description: 'Select a radio button',
|
|
72
|
+
inputSchema: elementSchema,
|
|
73
|
+
type: 'destructive',
|
|
74
|
+
},
|
|
75
|
+
handle: async (tab, params, response) => {
|
|
76
|
+
try {
|
|
77
|
+
// Get locator with selector information for database logging
|
|
78
|
+
const { locator, selector, resolvedSelector } = await tab.refLocatorWithSelector(params);
|
|
79
|
+
// Store element interaction data for database logging
|
|
80
|
+
response.setElementInteraction({
|
|
81
|
+
elementRef: params.ref,
|
|
82
|
+
elementDescription: params.element,
|
|
83
|
+
playwrightSelector: selector,
|
|
84
|
+
resolvedSelector: resolvedSelector,
|
|
85
|
+
});
|
|
86
|
+
await locator.check();
|
|
87
|
+
response.addCode(`await page.${await generateLocator(locator)}.check();`);
|
|
88
|
+
response.addResult('Radio button selected successfully');
|
|
89
|
+
response.setIncludeSnapshot();
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
response.addError(`Failed to select radio button: ${error}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
// Clear input field
|
|
97
|
+
const clearInput = defineTabTool({
|
|
98
|
+
capability: 'core',
|
|
99
|
+
schema: {
|
|
100
|
+
name: 'browser_clear_input',
|
|
101
|
+
title: 'Clear input field',
|
|
102
|
+
description: 'Clear the content of an input field',
|
|
103
|
+
inputSchema: elementSchema,
|
|
104
|
+
type: 'destructive',
|
|
105
|
+
},
|
|
106
|
+
handle: async (tab, params, response) => {
|
|
107
|
+
try {
|
|
108
|
+
// Get locator with selector information for database logging
|
|
109
|
+
const { locator, selector, resolvedSelector } = await tab.refLocatorWithSelector(params);
|
|
110
|
+
// Store element interaction data for database logging
|
|
111
|
+
response.setElementInteraction({
|
|
112
|
+
elementRef: params.ref,
|
|
113
|
+
elementDescription: params.element,
|
|
114
|
+
playwrightSelector: selector,
|
|
115
|
+
resolvedSelector: resolvedSelector,
|
|
116
|
+
});
|
|
117
|
+
await locator.clear();
|
|
118
|
+
response.addCode(`await page.${await generateLocator(locator)}.clear();`);
|
|
119
|
+
response.addResult('Input field cleared successfully');
|
|
120
|
+
response.setIncludeSnapshot();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
response.addError(`Failed to clear input field: ${error}`);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
// Get input value
|
|
128
|
+
const getInputValue = defineTabTool({
|
|
129
|
+
capability: 'core',
|
|
130
|
+
schema: {
|
|
131
|
+
name: 'browser_get_input_value',
|
|
132
|
+
title: 'Get input field value',
|
|
133
|
+
description: 'Get the current value of an input field',
|
|
134
|
+
inputSchema: elementSchema,
|
|
135
|
+
type: 'readOnly',
|
|
136
|
+
},
|
|
137
|
+
handle: async (tab, params, response) => {
|
|
138
|
+
try {
|
|
139
|
+
const locator = await tab.refLocator(params);
|
|
140
|
+
const value = await locator.inputValue();
|
|
141
|
+
response.addCode(`const value = await page.${await generateLocator(locator)}.inputValue();`);
|
|
142
|
+
response.addResult(`Input value: ${value}`);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
response.addError(`Failed to get input value: ${error}`);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
// Submit form
|
|
150
|
+
const submitForm = defineTabTool({
|
|
151
|
+
capability: 'core',
|
|
152
|
+
schema: {
|
|
153
|
+
name: 'browser_submit_form',
|
|
154
|
+
title: 'Submit form',
|
|
155
|
+
description: 'Submit a form element',
|
|
156
|
+
inputSchema: elementSchema,
|
|
157
|
+
type: 'destructive',
|
|
158
|
+
},
|
|
159
|
+
handle: async (tab, params, response) => {
|
|
160
|
+
try {
|
|
161
|
+
const locator = await tab.refLocator(params);
|
|
162
|
+
await tab.waitForCompletion(async () => {
|
|
163
|
+
await locator.evaluate((form) => form.submit());
|
|
164
|
+
});
|
|
165
|
+
response.addCode(`await page.${await generateLocator(locator)}.evaluate(form => form.submit());`);
|
|
166
|
+
response.addResult('Form submitted successfully');
|
|
167
|
+
response.setIncludeSnapshot();
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
response.addError(`Failed to submit form: ${error}`);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
export default [
|
|
175
|
+
checkCheckbox,
|
|
176
|
+
selectRadio,
|
|
177
|
+
clearInput,
|
|
178
|
+
getInputValue,
|
|
179
|
+
submitForm,
|
|
180
|
+
];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corpo handle: async (tab, params, response) => {
|
|
3
|
+
const { ref, element, attribute } = params;
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const locator = await tab.refLocator(params);
|
|
7
|
+
|
|
8
|
+
let result: string;
|
|
9
|
+
if (attribute) {
|
|
10
|
+
result = await locator.getAttribute(attribute) || '';
|
|
11
|
+
response.addCode(`const attributeValue = await page.${await generateLocator(locator)}.getAttribute('${attribute}');`);
|
|
12
|
+
} else {
|
|
13
|
+
result = await locator.textContent() || '';
|
|
14
|
+
response.addCode(`const textContent = await page.${await generateLocator(locator)}.textContent();`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
response.addResult(`${attribute ? `Attribute "${attribute}"` : 'Text content'}: ${result}`);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
response.addError(`Failed to get ${attribute ? `attribute "${attribute}"` : 'text content'} from element: ${error}`);
|
|
20
|
+
}
|
|
21
|
+
},ed under the Apache License, Version 2.0 (the "License");
|
|
22
|
+
* you may not use this file except in compliance with the License.
|
|
23
|
+
* You may obtain a copy of the License at
|
|
24
|
+
*
|
|
25
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
26
|
+
*
|
|
27
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
28
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
29
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
30
|
+
* See the License for the specific language governing permissions and
|
|
31
|
+
* limitations under the License.
|
|
32
|
+
*/
|
|
33
|
+
import { z } from 'zod';
|
|
34
|
+
import { defineTabTool } from './tool.js';
|
|
35
|
+
import { elementSchema } from './snapshot.js';
|
|
36
|
+
import { generateLocator } from './utils.js';
|
|
37
|
+
const getTextSchema = elementSchema.extend({
|
|
38
|
+
attribute: z.string().optional().describe('Optional attribute to get instead of text content (e.g., "href", "src", "value")'),
|
|
39
|
+
});
|
|
40
|
+
const getText = defineTabTool({
|
|
41
|
+
capability: 'core',
|
|
42
|
+
schema: {
|
|
43
|
+
name: 'browser_get_text',
|
|
44
|
+
title: 'Get text content or attribute from element',
|
|
45
|
+
description: 'Get text content or attribute value from a specific element on the page',
|
|
46
|
+
inputSchema: getTextSchema,
|
|
47
|
+
type: 'readOnly',
|
|
48
|
+
},
|
|
49
|
+
handle: async (tab, params, response) => {
|
|
50
|
+
const { ref, element, attribute } = params;
|
|
51
|
+
try {
|
|
52
|
+
const locator = await tab.refLocator(params);
|
|
53
|
+
let result;
|
|
54
|
+
if (attribute) {
|
|
55
|
+
result = await locator.getAttribute(attribute) || '';
|
|
56
|
+
response.addCode(`const attributeValue = await page.${await generateLocator(locator)}.getAttribute('${attribute}');`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
result = await locator.textContent() || '';
|
|
60
|
+
response.addCode(`const textContent = await page.${await generateLocator(locator)}.textContent();`);
|
|
61
|
+
}
|
|
62
|
+
response.addResult(`${attribute ? `Attribute "${attribute}"` : 'Text content'}: ${result}`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
response.addError(`Failed to get ${attribute ? `attribute "${attribute}"` : 'text content'} from element: ${error}`);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
const getPageInfo = defineTabTool({
|
|
70
|
+
capability: 'core',
|
|
71
|
+
schema: {
|
|
72
|
+
name: 'browser_get_page_info',
|
|
73
|
+
title: 'Get page information',
|
|
74
|
+
description: 'Get basic information about the current page (title, URL, etc.)',
|
|
75
|
+
inputSchema: z.object({}),
|
|
76
|
+
type: 'readOnly',
|
|
77
|
+
},
|
|
78
|
+
handle: async (tab, params, response) => {
|
|
79
|
+
try {
|
|
80
|
+
const title = await tab.page.title();
|
|
81
|
+
const url = tab.page.url();
|
|
82
|
+
const viewport = tab.page.viewportSize();
|
|
83
|
+
response.addCode(`const title = await page.title();
|
|
84
|
+
const url = page.url();
|
|
85
|
+
const viewport = page.viewportSize();`);
|
|
86
|
+
response.addResult(`Page Information:
|
|
87
|
+
- Title: ${title}
|
|
88
|
+
- URL: ${url}
|
|
89
|
+
- Viewport: ${viewport ? `${viewport.width}x${viewport.height}` : 'Not set'}`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
response.addError(`Failed to get page information: ${error}`);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
export default [
|
|
97
|
+
getText,
|
|
98
|
+
getPageInfo
|
|
99
|
+
];
|
|
@@ -0,0 +1,53 @@
|
|
|
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 { fork } from 'child_process';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { defineTool } from './tool.js';
|
|
21
|
+
const install = defineTool({
|
|
22
|
+
capability: 'core-install',
|
|
23
|
+
schema: {
|
|
24
|
+
name: 'browser_install',
|
|
25
|
+
title: 'Install the browser specified in the config',
|
|
26
|
+
description: 'Install the browser specified in the config. Call this if you get an error about the browser not being installed.',
|
|
27
|
+
inputSchema: z.object({}),
|
|
28
|
+
type: 'destructive',
|
|
29
|
+
},
|
|
30
|
+
handle: async (context, params, response) => {
|
|
31
|
+
const channel = context.config.browser?.launchOptions?.channel ?? context.config.browser?.browserName ?? 'chrome';
|
|
32
|
+
const cliUrl = import.meta.resolve('playwright/package.json');
|
|
33
|
+
const cliPath = path.join(fileURLToPath(cliUrl), '..', 'cli.js');
|
|
34
|
+
const child = fork(cliPath, ['install', channel], {
|
|
35
|
+
stdio: 'pipe',
|
|
36
|
+
});
|
|
37
|
+
const output = [];
|
|
38
|
+
child.stdout?.on('data', data => output.push(data.toString()));
|
|
39
|
+
child.stderr?.on('data', data => output.push(data.toString()));
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
child.on('close', code => {
|
|
42
|
+
if (code === 0)
|
|
43
|
+
resolve();
|
|
44
|
+
else
|
|
45
|
+
reject(new Error(`Failed to install browser: ${output.join('')}`));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
response.setIncludeTabs();
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
export default [
|
|
52
|
+
install,
|
|
53
|
+
];
|