mcp-web-inspector 0.1.0
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 +21 -0
- package/README.md +1017 -0
- package/dist/evals/evals.d.ts +5 -0
- package/dist/evals/evals.js +41 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +62 -0
- package/dist/requestHandler.d.ts +3 -0
- package/dist/requestHandler.js +53 -0
- package/dist/toolHandler.d.ts +91 -0
- package/dist/toolHandler.js +725 -0
- package/dist/tools/api/base.d.ts +33 -0
- package/dist/tools/api/base.js +49 -0
- package/dist/tools/api/index.d.ts +2 -0
- package/dist/tools/api/index.js +3 -0
- package/dist/tools/api/requests.d.ts +47 -0
- package/dist/tools/api/requests.js +168 -0
- package/dist/tools/browser/base.d.ts +51 -0
- package/dist/tools/browser/base.js +111 -0
- package/dist/tools/browser/cleanSession.d.ts +10 -0
- package/dist/tools/browser/cleanSession.js +42 -0
- package/dist/tools/browser/comparePositions.d.ts +11 -0
- package/dist/tools/browser/comparePositions.js +149 -0
- package/dist/tools/browser/computedStyles.d.ts +11 -0
- package/dist/tools/browser/computedStyles.js +128 -0
- package/dist/tools/browser/console.d.ts +37 -0
- package/dist/tools/browser/console.js +106 -0
- package/dist/tools/browser/elementExists.d.ts +9 -0
- package/dist/tools/browser/elementExists.js +57 -0
- package/dist/tools/browser/elementInspection.d.ts +21 -0
- package/dist/tools/browser/elementInspection.js +151 -0
- package/dist/tools/browser/elementPosition.d.ts +11 -0
- package/dist/tools/browser/elementPosition.js +107 -0
- package/dist/tools/browser/elementVisibility.d.ts +12 -0
- package/dist/tools/browser/elementVisibility.js +224 -0
- package/dist/tools/browser/findByText.d.ts +13 -0
- package/dist/tools/browser/findByText.js +207 -0
- package/dist/tools/browser/getRequestDetails.d.ts +9 -0
- package/dist/tools/browser/getRequestDetails.js +137 -0
- package/dist/tools/browser/getTestIds.d.ts +12 -0
- package/dist/tools/browser/getTestIds.js +148 -0
- package/dist/tools/browser/index.d.ts +7 -0
- package/dist/tools/browser/index.js +7 -0
- package/dist/tools/browser/inspectDom.d.ts +12 -0
- package/dist/tools/browser/inspectDom.js +447 -0
- package/dist/tools/browser/interaction.d.ts +104 -0
- package/dist/tools/browser/interaction.js +259 -0
- package/dist/tools/browser/listNetworkRequests.d.ts +10 -0
- package/dist/tools/browser/listNetworkRequests.js +74 -0
- package/dist/tools/browser/measureElement.d.ts +9 -0
- package/dist/tools/browser/measureElement.js +139 -0
- package/dist/tools/browser/navigation.d.ts +38 -0
- package/dist/tools/browser/navigation.js +109 -0
- package/dist/tools/browser/output.d.ts +11 -0
- package/dist/tools/browser/output.js +29 -0
- package/dist/tools/browser/querySelectorAll.d.ts +12 -0
- package/dist/tools/browser/querySelectorAll.js +201 -0
- package/dist/tools/browser/response.d.ts +29 -0
- package/dist/tools/browser/response.js +67 -0
- package/dist/tools/browser/screenshot.d.ts +16 -0
- package/dist/tools/browser/screenshot.js +70 -0
- package/dist/tools/browser/useragent.d.ts +15 -0
- package/dist/tools/browser/useragent.js +32 -0
- package/dist/tools/browser/visiblePage.d.ts +20 -0
- package/dist/tools/browser/visiblePage.js +170 -0
- package/dist/tools/browser/waitForElement.d.ts +10 -0
- package/dist/tools/browser/waitForElement.js +38 -0
- package/dist/tools/browser/waitForNetworkIdle.d.ts +8 -0
- package/dist/tools/browser/waitForNetworkIdle.js +32 -0
- package/dist/tools/codegen/generator.d.ts +21 -0
- package/dist/tools/codegen/generator.js +158 -0
- package/dist/tools/codegen/index.d.ts +11 -0
- package/dist/tools/codegen/index.js +187 -0
- package/dist/tools/codegen/recorder.d.ts +14 -0
- package/dist/tools/codegen/recorder.js +62 -0
- package/dist/tools/codegen/types.d.ts +28 -0
- package/dist/tools/codegen/types.js +1 -0
- package/dist/tools/common/types.d.ts +17 -0
- package/dist/tools/common/types.js +20 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools.d.ts +557 -0
- package/dist/tools.js +554 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { createSuccessResponse, createErrorResponse } from '../common/types.js';
|
|
3
|
+
import { setGlobalPage } from '../../toolHandler.js';
|
|
4
|
+
/**
|
|
5
|
+
* Tool for clicking elements on the page
|
|
6
|
+
*/
|
|
7
|
+
export class ClickTool extends BrowserToolBase {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the click tool
|
|
10
|
+
*/
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
this.recordInteraction();
|
|
13
|
+
return this.safeExecute(context, async (page) => {
|
|
14
|
+
const selector = this.normalizeSelector(args.selector);
|
|
15
|
+
await page.click(selector);
|
|
16
|
+
return createSuccessResponse(`Clicked element: ${args.selector}`);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tool for clicking a link and switching to the new tab
|
|
22
|
+
*/
|
|
23
|
+
export class ClickAndSwitchTabTool extends BrowserToolBase {
|
|
24
|
+
/**
|
|
25
|
+
* Execute the click and switch tab tool
|
|
26
|
+
*/
|
|
27
|
+
async execute(args, context) {
|
|
28
|
+
this.recordInteraction();
|
|
29
|
+
return this.safeExecute(context, async (page) => {
|
|
30
|
+
const selector = this.normalizeSelector(args.selector);
|
|
31
|
+
// Listen for a new tab to open
|
|
32
|
+
const [newPage] = await Promise.all([
|
|
33
|
+
//context.browser.waitForEvent('page'), // Wait for a new page (tab) to open
|
|
34
|
+
page.context().waitForEvent('page'), // Wait for a new page (tab) to open
|
|
35
|
+
page.click(selector), // Click the link that opens the new tab
|
|
36
|
+
]);
|
|
37
|
+
// Wait for the new page to load
|
|
38
|
+
await newPage.waitForLoadState('domcontentloaded');
|
|
39
|
+
// Switch control to the new tab
|
|
40
|
+
setGlobalPage(newPage);
|
|
41
|
+
//page= newPage; // Update the current page to the new tab
|
|
42
|
+
//context.page = newPage;
|
|
43
|
+
//context.page.bringToFront(); // Bring the new tab to the front
|
|
44
|
+
return createSuccessResponse(`Clicked link and switched to new tab: ${newPage.url()}`);
|
|
45
|
+
//return createSuccessResponse(`Clicked link and switched to new tab: ${context.page.url()}`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Tool for clicking elements inside iframes
|
|
51
|
+
*/
|
|
52
|
+
export class IframeClickTool extends BrowserToolBase {
|
|
53
|
+
/**
|
|
54
|
+
* Execute the iframe click tool
|
|
55
|
+
*/
|
|
56
|
+
async execute(args, context) {
|
|
57
|
+
this.recordInteraction();
|
|
58
|
+
return this.safeExecute(context, async (page) => {
|
|
59
|
+
const iframeSelector = this.normalizeSelector(args.iframeSelector);
|
|
60
|
+
const selector = this.normalizeSelector(args.selector);
|
|
61
|
+
const frame = page.frameLocator(iframeSelector);
|
|
62
|
+
if (!frame) {
|
|
63
|
+
return createErrorResponse(`Iframe not found: ${args.iframeSelector}`);
|
|
64
|
+
}
|
|
65
|
+
await frame.locator(selector).click();
|
|
66
|
+
return createSuccessResponse(`Clicked element ${args.selector} inside iframe ${args.iframeSelector}`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Tool for filling elements inside iframes
|
|
72
|
+
*/
|
|
73
|
+
export class IframeFillTool extends BrowserToolBase {
|
|
74
|
+
/**
|
|
75
|
+
* Execute the iframe fill tool
|
|
76
|
+
*/
|
|
77
|
+
async execute(args, context) {
|
|
78
|
+
this.recordInteraction();
|
|
79
|
+
return this.safeExecute(context, async (page) => {
|
|
80
|
+
const iframeSelector = this.normalizeSelector(args.iframeSelector);
|
|
81
|
+
const selector = this.normalizeSelector(args.selector);
|
|
82
|
+
const frame = page.frameLocator(iframeSelector);
|
|
83
|
+
if (!frame) {
|
|
84
|
+
return createErrorResponse(`Iframe not found: ${args.iframeSelector}`);
|
|
85
|
+
}
|
|
86
|
+
await frame.locator(selector).fill(args.value);
|
|
87
|
+
return createSuccessResponse(`Filled element ${args.selector} inside iframe ${args.iframeSelector} with: ${args.value}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Tool for filling form fields
|
|
93
|
+
*/
|
|
94
|
+
export class FillTool extends BrowserToolBase {
|
|
95
|
+
/**
|
|
96
|
+
* Execute the fill tool
|
|
97
|
+
*/
|
|
98
|
+
async execute(args, context) {
|
|
99
|
+
this.recordInteraction();
|
|
100
|
+
return this.safeExecute(context, async (page) => {
|
|
101
|
+
const selector = this.normalizeSelector(args.selector);
|
|
102
|
+
await page.waitForSelector(selector);
|
|
103
|
+
await page.fill(selector, args.value);
|
|
104
|
+
return createSuccessResponse(`Filled ${args.selector} with: ${args.value}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Tool for selecting options from dropdown menus
|
|
110
|
+
*/
|
|
111
|
+
export class SelectTool extends BrowserToolBase {
|
|
112
|
+
/**
|
|
113
|
+
* Execute the select tool
|
|
114
|
+
*/
|
|
115
|
+
async execute(args, context) {
|
|
116
|
+
this.recordInteraction();
|
|
117
|
+
return this.safeExecute(context, async (page) => {
|
|
118
|
+
const selector = this.normalizeSelector(args.selector);
|
|
119
|
+
await page.waitForSelector(selector);
|
|
120
|
+
await page.selectOption(selector, args.value);
|
|
121
|
+
return createSuccessResponse(`Selected ${args.selector} with: ${args.value}`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Tool for hovering over elements
|
|
127
|
+
*/
|
|
128
|
+
export class HoverTool extends BrowserToolBase {
|
|
129
|
+
/**
|
|
130
|
+
* Execute the hover tool
|
|
131
|
+
*/
|
|
132
|
+
async execute(args, context) {
|
|
133
|
+
this.recordInteraction();
|
|
134
|
+
return this.safeExecute(context, async (page) => {
|
|
135
|
+
const selector = this.normalizeSelector(args.selector);
|
|
136
|
+
await page.waitForSelector(selector);
|
|
137
|
+
await page.hover(selector);
|
|
138
|
+
return createSuccessResponse(`Hovered ${args.selector}`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Tool for uploading files
|
|
144
|
+
*/
|
|
145
|
+
export class UploadFileTool extends BrowserToolBase {
|
|
146
|
+
/**
|
|
147
|
+
* Execute the upload file tool
|
|
148
|
+
*/
|
|
149
|
+
async execute(args, context) {
|
|
150
|
+
this.recordInteraction();
|
|
151
|
+
return this.safeExecute(context, async (page) => {
|
|
152
|
+
const selector = this.normalizeSelector(args.selector);
|
|
153
|
+
await page.waitForSelector(selector);
|
|
154
|
+
await page.setInputFiles(selector, args.filePath);
|
|
155
|
+
return createSuccessResponse(`Uploaded file '${args.filePath}' to '${args.selector}'`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Tool for executing JavaScript in the browser
|
|
161
|
+
*/
|
|
162
|
+
export class EvaluateTool extends BrowserToolBase {
|
|
163
|
+
/**
|
|
164
|
+
* Execute the evaluate tool
|
|
165
|
+
*/
|
|
166
|
+
async execute(args, context) {
|
|
167
|
+
this.recordInteraction();
|
|
168
|
+
return this.safeExecute(context, async (page) => {
|
|
169
|
+
const result = await page.evaluate(args.script);
|
|
170
|
+
// Convert result to string for display
|
|
171
|
+
let resultStr;
|
|
172
|
+
try {
|
|
173
|
+
resultStr = JSON.stringify(result, null, 2);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
resultStr = String(result);
|
|
177
|
+
}
|
|
178
|
+
return createSuccessResponse([
|
|
179
|
+
`Executed JavaScript:`,
|
|
180
|
+
`${args.script}`,
|
|
181
|
+
`Result:`,
|
|
182
|
+
`${resultStr}`
|
|
183
|
+
]);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Tool for dragging elements on the page
|
|
189
|
+
*/
|
|
190
|
+
export class DragTool extends BrowserToolBase {
|
|
191
|
+
/**
|
|
192
|
+
* Execute the drag tool
|
|
193
|
+
*/
|
|
194
|
+
async execute(args, context) {
|
|
195
|
+
this.recordInteraction();
|
|
196
|
+
return this.safeExecute(context, async (page) => {
|
|
197
|
+
const sourceSelector = this.normalizeSelector(args.sourceSelector);
|
|
198
|
+
const targetSelector = this.normalizeSelector(args.targetSelector);
|
|
199
|
+
const sourceElement = await page.waitForSelector(sourceSelector);
|
|
200
|
+
const targetElement = await page.waitForSelector(targetSelector);
|
|
201
|
+
const sourceBound = await sourceElement.boundingBox();
|
|
202
|
+
const targetBound = await targetElement.boundingBox();
|
|
203
|
+
if (!sourceBound || !targetBound) {
|
|
204
|
+
return createErrorResponse("Could not get element positions for drag operation");
|
|
205
|
+
}
|
|
206
|
+
await page.mouse.move(sourceBound.x + sourceBound.width / 2, sourceBound.y + sourceBound.height / 2);
|
|
207
|
+
await page.mouse.down();
|
|
208
|
+
await page.mouse.move(targetBound.x + targetBound.width / 2, targetBound.y + targetBound.height / 2);
|
|
209
|
+
await page.mouse.up();
|
|
210
|
+
return createSuccessResponse(`Dragged element from ${args.sourceSelector} to ${args.targetSelector}`);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Tool for pressing keyboard keys
|
|
216
|
+
*/
|
|
217
|
+
export class PressKeyTool extends BrowserToolBase {
|
|
218
|
+
/**
|
|
219
|
+
* Execute the key press tool
|
|
220
|
+
*/
|
|
221
|
+
async execute(args, context) {
|
|
222
|
+
this.recordInteraction();
|
|
223
|
+
return this.safeExecute(context, async (page) => {
|
|
224
|
+
if (args.selector) {
|
|
225
|
+
const selector = this.normalizeSelector(args.selector);
|
|
226
|
+
await page.waitForSelector(selector);
|
|
227
|
+
await page.focus(selector);
|
|
228
|
+
}
|
|
229
|
+
await page.keyboard.press(args.key);
|
|
230
|
+
return createSuccessResponse(`Pressed key: ${args.key}`);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Tool for switching browser tabs
|
|
236
|
+
*/
|
|
237
|
+
// export class SwitchTabTool extends BrowserToolBase {
|
|
238
|
+
// /**
|
|
239
|
+
// * Switch the tab to the specified index
|
|
240
|
+
// */
|
|
241
|
+
// async execute(args: any, context: ToolContext): Promise<ToolResponse> {
|
|
242
|
+
// return this.safeExecute(context, async (page) => {
|
|
243
|
+
// const tabs = await browser.page;
|
|
244
|
+
// // Validate the tab index
|
|
245
|
+
// const tabIndex = Number(args.index);
|
|
246
|
+
// if (isNaN(tabIndex)) {
|
|
247
|
+
// return createErrorResponse(`Invalid tab index: ${args.index}. It must be a number.`);
|
|
248
|
+
// }
|
|
249
|
+
// if (tabIndex >= 0 && tabIndex < tabs.length) {
|
|
250
|
+
// await tabs[tabIndex].bringToFront();
|
|
251
|
+
// return createSuccessResponse(`Switched to tab with index ${tabIndex}`);
|
|
252
|
+
// } else {
|
|
253
|
+
// return createErrorResponse(
|
|
254
|
+
// `Tab index out of range: ${tabIndex}. Available tabs: 0 to ${tabs.length - 1}.`
|
|
255
|
+
// );
|
|
256
|
+
// }
|
|
257
|
+
// });
|
|
258
|
+
// }
|
|
259
|
+
// }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ToolContext, ToolResponse } from '../common/types.js';
|
|
2
|
+
import { BrowserToolBase } from './base.js';
|
|
3
|
+
interface ListNetworkRequestsArgs {
|
|
4
|
+
type?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class ListNetworkRequestsTool extends BrowserToolBase {
|
|
8
|
+
execute(args: ListNetworkRequestsArgs, context: ToolContext): Promise<ToolResponse>;
|
|
9
|
+
}
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { getNetworkLog } from '../../toolHandler.js';
|
|
3
|
+
export class ListNetworkRequestsTool extends BrowserToolBase {
|
|
4
|
+
async execute(args, context) {
|
|
5
|
+
return this.safeExecute(context, async () => {
|
|
6
|
+
const { type, limit = 50 } = args;
|
|
7
|
+
const networkLog = getNetworkLog();
|
|
8
|
+
// Filter by resource type if specified
|
|
9
|
+
let filtered = type
|
|
10
|
+
? networkLog.filter(req => req.resourceType === type)
|
|
11
|
+
: networkLog;
|
|
12
|
+
// Get most recent requests (reverse chronological)
|
|
13
|
+
filtered = filtered.slice(-limit).reverse();
|
|
14
|
+
if (filtered.length === 0) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: type
|
|
19
|
+
? `No network requests found for type: ${type}`
|
|
20
|
+
: "No network requests captured yet"
|
|
21
|
+
}],
|
|
22
|
+
isError: false
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// Format output in compact text format
|
|
26
|
+
const lines = [`Network Requests (${filtered.length} of ${networkLog.length}, recent first):\n`];
|
|
27
|
+
filtered.forEach(req => {
|
|
28
|
+
const statusInfo = req.status
|
|
29
|
+
? `${req.status} ${req.statusText || 'OK'}`
|
|
30
|
+
: 'pending';
|
|
31
|
+
const timing = req.timing ? `${req.timing}ms` : '...';
|
|
32
|
+
// Check if cached
|
|
33
|
+
const cached = req.responseData?.headers['cache-control']?.includes('max-age') ||
|
|
34
|
+
req.responseData?.headers['x-cache'] === 'HIT'
|
|
35
|
+
? 'cached'
|
|
36
|
+
: '';
|
|
37
|
+
// Get response size
|
|
38
|
+
let sizeInfo = '';
|
|
39
|
+
if (req.responseData?.body) {
|
|
40
|
+
const bytes = req.responseData.body.length;
|
|
41
|
+
if (bytes < 1024) {
|
|
42
|
+
sizeInfo = `${bytes}B`;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
sizeInfo = `${(bytes / 1024).toFixed(1)}KB`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const parts = [
|
|
49
|
+
`[${req.index}]`,
|
|
50
|
+
req.method,
|
|
51
|
+
req.url.length > 80 ? req.url.substring(0, 77) + '...' : req.url,
|
|
52
|
+
statusInfo,
|
|
53
|
+
'|',
|
|
54
|
+
req.resourceType,
|
|
55
|
+
'|',
|
|
56
|
+
timing
|
|
57
|
+
];
|
|
58
|
+
if (sizeInfo)
|
|
59
|
+
parts.push('|', sizeInfo);
|
|
60
|
+
if (cached)
|
|
61
|
+
parts.push('|', cached);
|
|
62
|
+
lines.push(parts.join(' '));
|
|
63
|
+
});
|
|
64
|
+
lines.push('\nUse get_request_details(index) for full info');
|
|
65
|
+
return {
|
|
66
|
+
content: [{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: lines.join('\n')
|
|
69
|
+
}],
|
|
70
|
+
isError: false
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ToolHandler } from '../common/types.js';
|
|
2
|
+
import { BrowserToolBase } from './base.js';
|
|
3
|
+
import type { ToolContext, ToolResponse } from '../common/types.js';
|
|
4
|
+
export interface MeasureElementArgs {
|
|
5
|
+
selector: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class MeasureElementTool extends BrowserToolBase implements ToolHandler {
|
|
8
|
+
execute(args: MeasureElementArgs, context: ToolContext): Promise<ToolResponse>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
export class MeasureElementTool extends BrowserToolBase {
|
|
3
|
+
async execute(args, context) {
|
|
4
|
+
return this.safeExecute(context, async (page) => {
|
|
5
|
+
const normalizedSelector = this.normalizeSelector(args.selector);
|
|
6
|
+
// Find the element
|
|
7
|
+
const locator = page.locator(normalizedSelector);
|
|
8
|
+
const count = await locator.count();
|
|
9
|
+
if (count === 0) {
|
|
10
|
+
return {
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: 'text',
|
|
14
|
+
text: `✗ Element not found: ${args.selector}`
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
isError: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Handle multiple matches by using first() - show warning (consistent with other tools)
|
|
21
|
+
const targetLocator = count > 1 ? locator.first() : locator;
|
|
22
|
+
let warning = '';
|
|
23
|
+
if (count > 1) {
|
|
24
|
+
warning = `⚠ Warning: Selector matched ${count} elements, using first\n\n`;
|
|
25
|
+
}
|
|
26
|
+
// Get element descriptor
|
|
27
|
+
const elementInfo = await targetLocator.evaluate((el) => {
|
|
28
|
+
const tag = el.tagName.toLowerCase();
|
|
29
|
+
const testId = el.getAttribute('data-testid') || el.getAttribute('data-test') || el.getAttribute('data-cy');
|
|
30
|
+
const id = el.id ? `#${el.id}` : '';
|
|
31
|
+
const classes = el.className && typeof el.className === 'string'
|
|
32
|
+
? `.${el.className.split(' ').filter(c => c).slice(0, 2).join('.')}`
|
|
33
|
+
: '';
|
|
34
|
+
let descriptor = `<${tag}`;
|
|
35
|
+
if (testId)
|
|
36
|
+
descriptor += ` data-testid="${testId}"`;
|
|
37
|
+
else if (id)
|
|
38
|
+
descriptor += id;
|
|
39
|
+
else if (classes)
|
|
40
|
+
descriptor += classes;
|
|
41
|
+
descriptor += '>';
|
|
42
|
+
return { descriptor };
|
|
43
|
+
});
|
|
44
|
+
// Get box model measurements
|
|
45
|
+
const measurements = await targetLocator.evaluate((el) => {
|
|
46
|
+
const computed = window.getComputedStyle(el);
|
|
47
|
+
const rect = el.getBoundingClientRect();
|
|
48
|
+
const parseValue = (val) => parseFloat(val) || 0;
|
|
49
|
+
return {
|
|
50
|
+
x: Math.round(rect.x),
|
|
51
|
+
y: Math.round(rect.y),
|
|
52
|
+
width: parseValue(computed.width),
|
|
53
|
+
height: parseValue(computed.height),
|
|
54
|
+
marginTop: parseValue(computed.marginTop),
|
|
55
|
+
marginRight: parseValue(computed.marginRight),
|
|
56
|
+
marginBottom: parseValue(computed.marginBottom),
|
|
57
|
+
marginLeft: parseValue(computed.marginLeft),
|
|
58
|
+
paddingTop: parseValue(computed.paddingTop),
|
|
59
|
+
paddingRight: parseValue(computed.paddingRight),
|
|
60
|
+
paddingBottom: parseValue(computed.paddingBottom),
|
|
61
|
+
paddingLeft: parseValue(computed.paddingLeft),
|
|
62
|
+
borderTopWidth: parseValue(computed.borderTopWidth),
|
|
63
|
+
borderRightWidth: parseValue(computed.borderRightWidth),
|
|
64
|
+
borderBottomWidth: parseValue(computed.borderBottomWidth),
|
|
65
|
+
borderLeftWidth: parseValue(computed.borderLeftWidth),
|
|
66
|
+
borderStyle: computed.borderStyle,
|
|
67
|
+
borderColor: computed.borderColor
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
// Calculate dimensions
|
|
71
|
+
const contentWidth = Math.round(measurements.width - measurements.paddingLeft - measurements.paddingRight);
|
|
72
|
+
const contentHeight = Math.round(measurements.height - measurements.paddingTop - measurements.paddingBottom);
|
|
73
|
+
const boxWidth = Math.round(measurements.width);
|
|
74
|
+
const boxHeight = Math.round(measurements.height);
|
|
75
|
+
const totalWidth = Math.round(boxWidth + measurements.marginLeft + measurements.marginRight);
|
|
76
|
+
const totalHeight = Math.round(boxHeight + measurements.marginTop + measurements.marginBottom);
|
|
77
|
+
// Format border info
|
|
78
|
+
const formatBorder = () => {
|
|
79
|
+
const { borderTopWidth: top, borderRightWidth: right, borderBottomWidth: bottom, borderLeftWidth: left, borderStyle: style } = measurements;
|
|
80
|
+
// Check if all sides are the same
|
|
81
|
+
if (top === right && right === bottom && bottom === left) {
|
|
82
|
+
if (top === 0)
|
|
83
|
+
return ' Border: none';
|
|
84
|
+
return ` Border: ${top}px ${style}`;
|
|
85
|
+
}
|
|
86
|
+
// Different sides
|
|
87
|
+
const lines = [];
|
|
88
|
+
if (top > 0)
|
|
89
|
+
lines.push(`↑${top}px`);
|
|
90
|
+
if (right > 0)
|
|
91
|
+
lines.push(`→${right}px`);
|
|
92
|
+
if (bottom > 0)
|
|
93
|
+
lines.push(`↓${bottom}px`);
|
|
94
|
+
if (left > 0)
|
|
95
|
+
lines.push(`←${left}px`);
|
|
96
|
+
return lines.length > 0
|
|
97
|
+
? ` Border: ${lines.join(' ')} ${style}`
|
|
98
|
+
: ' Border: none';
|
|
99
|
+
};
|
|
100
|
+
// Format spacing (margin/padding) with directional arrows
|
|
101
|
+
const formatSpacing = (top, right, bottom, left) => {
|
|
102
|
+
const parts = [];
|
|
103
|
+
if (top > 0)
|
|
104
|
+
parts.push(`↑${Math.round(top)}px`);
|
|
105
|
+
if (bottom > 0)
|
|
106
|
+
parts.push(`↓${Math.round(bottom)}px`);
|
|
107
|
+
if (left > 0)
|
|
108
|
+
parts.push(`←${Math.round(left)}px`);
|
|
109
|
+
if (right > 0)
|
|
110
|
+
parts.push(`→${Math.round(right)}px`);
|
|
111
|
+
return parts.length > 0 ? parts.join(' ') : '0px';
|
|
112
|
+
};
|
|
113
|
+
// Build output in compact text format
|
|
114
|
+
const sections = [];
|
|
115
|
+
if (warning) {
|
|
116
|
+
sections.push(warning.trim());
|
|
117
|
+
}
|
|
118
|
+
sections.push(`Element: ${elementInfo.descriptor}`);
|
|
119
|
+
sections.push(`@ (${measurements.x},${measurements.y}) ${boxWidth}x${boxHeight}px`);
|
|
120
|
+
sections.push('');
|
|
121
|
+
sections.push('Box Model:');
|
|
122
|
+
sections.push(` Content: ${contentWidth}x${contentHeight}px`);
|
|
123
|
+
sections.push(` Padding: ${formatSpacing(measurements.paddingTop, measurements.paddingRight, measurements.paddingBottom, measurements.paddingLeft)}`);
|
|
124
|
+
sections.push(formatBorder());
|
|
125
|
+
sections.push(` Margin: ${formatSpacing(measurements.marginTop, measurements.marginRight, measurements.marginBottom, measurements.marginLeft)}`);
|
|
126
|
+
sections.push('');
|
|
127
|
+
sections.push(`Total Space: ${totalWidth}x${totalHeight}px (with margin)`);
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: sections.join('\n')
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
isError: false
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { ToolContext, ToolResponse } from '../common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tool for navigating to URLs
|
|
5
|
+
*/
|
|
6
|
+
export declare class NavigationTool extends BrowserToolBase {
|
|
7
|
+
/**
|
|
8
|
+
* Execute the navigation tool
|
|
9
|
+
*/
|
|
10
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Tool for closing the browser
|
|
14
|
+
*/
|
|
15
|
+
export declare class CloseBrowserTool extends BrowserToolBase {
|
|
16
|
+
/**
|
|
17
|
+
* Execute the close browser tool
|
|
18
|
+
*/
|
|
19
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Tool for navigating back in browser history
|
|
23
|
+
*/
|
|
24
|
+
export declare class GoBackTool extends BrowserToolBase {
|
|
25
|
+
/**
|
|
26
|
+
* Execute the go back tool
|
|
27
|
+
*/
|
|
28
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Tool for navigating forward in browser history
|
|
32
|
+
*/
|
|
33
|
+
export declare class GoForwardTool extends BrowserToolBase {
|
|
34
|
+
/**
|
|
35
|
+
* Execute the go forward tool
|
|
36
|
+
*/
|
|
37
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { createSuccessResponse, createErrorResponse } from '../common/types.js';
|
|
3
|
+
import { resetBrowserState } from '../../toolHandler.js';
|
|
4
|
+
/**
|
|
5
|
+
* Tool for navigating to URLs
|
|
6
|
+
*/
|
|
7
|
+
export class NavigationTool extends BrowserToolBase {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the navigation tool
|
|
10
|
+
*/
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
// Check if browser is available
|
|
13
|
+
if (!context.browser || !context.browser.isConnected()) {
|
|
14
|
+
// If browser is not connected, we need to reset the state to force recreation
|
|
15
|
+
resetBrowserState();
|
|
16
|
+
return createErrorResponse("Browser is not connected. The connection has been reset - please retry your navigation.");
|
|
17
|
+
}
|
|
18
|
+
// Check if page is available and not closed
|
|
19
|
+
if (!context.page || context.page.isClosed()) {
|
|
20
|
+
return createErrorResponse("Page is not available or has been closed. Please retry your navigation.");
|
|
21
|
+
}
|
|
22
|
+
this.recordNavigation();
|
|
23
|
+
return this.safeExecute(context, async (page) => {
|
|
24
|
+
try {
|
|
25
|
+
await page.goto(args.url, {
|
|
26
|
+
timeout: args.timeout || 30000,
|
|
27
|
+
waitUntil: args.waitUntil || "load"
|
|
28
|
+
});
|
|
29
|
+
return createSuccessResponse(`Navigated to ${args.url}`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const errorMessage = error.message;
|
|
33
|
+
// Check for common disconnection errors
|
|
34
|
+
if (errorMessage.includes("Target page, context or browser has been closed") ||
|
|
35
|
+
errorMessage.includes("Target closed") ||
|
|
36
|
+
errorMessage.includes("Browser has been disconnected")) {
|
|
37
|
+
// Reset browser state to force recreation on next attempt
|
|
38
|
+
resetBrowserState();
|
|
39
|
+
return createErrorResponse(`Browser connection issue: ${errorMessage}. Connection has been reset - please retry your navigation.`);
|
|
40
|
+
}
|
|
41
|
+
// For other errors, return the standard error
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Tool for closing the browser
|
|
49
|
+
*/
|
|
50
|
+
export class CloseBrowserTool extends BrowserToolBase {
|
|
51
|
+
/**
|
|
52
|
+
* Execute the close browser tool
|
|
53
|
+
*/
|
|
54
|
+
async execute(args, context) {
|
|
55
|
+
if (context.browser) {
|
|
56
|
+
try {
|
|
57
|
+
// Check if browser is still connected
|
|
58
|
+
if (context.browser.isConnected()) {
|
|
59
|
+
await context.browser.close().catch(error => {
|
|
60
|
+
console.error("Error while closing browser:", error);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.error("Browser already disconnected, cleaning up state");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error("Error during browser close operation:", error);
|
|
69
|
+
// Continue with resetting state even if close fails
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
// Always reset the global browser and page references
|
|
73
|
+
resetBrowserState();
|
|
74
|
+
}
|
|
75
|
+
return createSuccessResponse("Browser closed successfully");
|
|
76
|
+
}
|
|
77
|
+
return createSuccessResponse("No browser instance to close");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Tool for navigating back in browser history
|
|
82
|
+
*/
|
|
83
|
+
export class GoBackTool extends BrowserToolBase {
|
|
84
|
+
/**
|
|
85
|
+
* Execute the go back tool
|
|
86
|
+
*/
|
|
87
|
+
async execute(args, context) {
|
|
88
|
+
this.recordNavigation();
|
|
89
|
+
return this.safeExecute(context, async (page) => {
|
|
90
|
+
await page.goBack();
|
|
91
|
+
return createSuccessResponse("Navigated back in browser history");
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Tool for navigating forward in browser history
|
|
97
|
+
*/
|
|
98
|
+
export class GoForwardTool extends BrowserToolBase {
|
|
99
|
+
/**
|
|
100
|
+
* Execute the go forward tool
|
|
101
|
+
*/
|
|
102
|
+
async execute(args, context) {
|
|
103
|
+
this.recordNavigation();
|
|
104
|
+
return this.safeExecute(context, async (page) => {
|
|
105
|
+
await page.goForward();
|
|
106
|
+
return createSuccessResponse("Navigated forward in browser history");
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { ToolContext, ToolResponse } from '../common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tool for saving page as PDF
|
|
5
|
+
*/
|
|
6
|
+
export declare class SaveAsPdfTool extends BrowserToolBase {
|
|
7
|
+
/**
|
|
8
|
+
* Execute the save as PDF tool
|
|
9
|
+
*/
|
|
10
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { createSuccessResponse } from '../common/types.js';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Tool for saving page as PDF
|
|
6
|
+
*/
|
|
7
|
+
export class SaveAsPdfTool extends BrowserToolBase {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the save as PDF tool
|
|
10
|
+
*/
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
return this.safeExecute(context, async (page) => {
|
|
13
|
+
const filename = args.filename || 'page.pdf';
|
|
14
|
+
const options = {
|
|
15
|
+
path: path.resolve(args.outputPath || '.', filename),
|
|
16
|
+
format: args.format || 'A4',
|
|
17
|
+
printBackground: args.printBackground !== false,
|
|
18
|
+
margin: args.margin || {
|
|
19
|
+
top: '1cm',
|
|
20
|
+
right: '1cm',
|
|
21
|
+
bottom: '1cm',
|
|
22
|
+
left: '1cm'
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
await page.pdf(options);
|
|
26
|
+
return createSuccessResponse(`Saved page as PDF: ${options.path}`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|