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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1017 -0
  3. package/dist/evals/evals.d.ts +5 -0
  4. package/dist/evals/evals.js +41 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +62 -0
  7. package/dist/requestHandler.d.ts +3 -0
  8. package/dist/requestHandler.js +53 -0
  9. package/dist/toolHandler.d.ts +91 -0
  10. package/dist/toolHandler.js +725 -0
  11. package/dist/tools/api/base.d.ts +33 -0
  12. package/dist/tools/api/base.js +49 -0
  13. package/dist/tools/api/index.d.ts +2 -0
  14. package/dist/tools/api/index.js +3 -0
  15. package/dist/tools/api/requests.d.ts +47 -0
  16. package/dist/tools/api/requests.js +168 -0
  17. package/dist/tools/browser/base.d.ts +51 -0
  18. package/dist/tools/browser/base.js +111 -0
  19. package/dist/tools/browser/cleanSession.d.ts +10 -0
  20. package/dist/tools/browser/cleanSession.js +42 -0
  21. package/dist/tools/browser/comparePositions.d.ts +11 -0
  22. package/dist/tools/browser/comparePositions.js +149 -0
  23. package/dist/tools/browser/computedStyles.d.ts +11 -0
  24. package/dist/tools/browser/computedStyles.js +128 -0
  25. package/dist/tools/browser/console.d.ts +37 -0
  26. package/dist/tools/browser/console.js +106 -0
  27. package/dist/tools/browser/elementExists.d.ts +9 -0
  28. package/dist/tools/browser/elementExists.js +57 -0
  29. package/dist/tools/browser/elementInspection.d.ts +21 -0
  30. package/dist/tools/browser/elementInspection.js +151 -0
  31. package/dist/tools/browser/elementPosition.d.ts +11 -0
  32. package/dist/tools/browser/elementPosition.js +107 -0
  33. package/dist/tools/browser/elementVisibility.d.ts +12 -0
  34. package/dist/tools/browser/elementVisibility.js +224 -0
  35. package/dist/tools/browser/findByText.d.ts +13 -0
  36. package/dist/tools/browser/findByText.js +207 -0
  37. package/dist/tools/browser/getRequestDetails.d.ts +9 -0
  38. package/dist/tools/browser/getRequestDetails.js +137 -0
  39. package/dist/tools/browser/getTestIds.d.ts +12 -0
  40. package/dist/tools/browser/getTestIds.js +148 -0
  41. package/dist/tools/browser/index.d.ts +7 -0
  42. package/dist/tools/browser/index.js +7 -0
  43. package/dist/tools/browser/inspectDom.d.ts +12 -0
  44. package/dist/tools/browser/inspectDom.js +447 -0
  45. package/dist/tools/browser/interaction.d.ts +104 -0
  46. package/dist/tools/browser/interaction.js +259 -0
  47. package/dist/tools/browser/listNetworkRequests.d.ts +10 -0
  48. package/dist/tools/browser/listNetworkRequests.js +74 -0
  49. package/dist/tools/browser/measureElement.d.ts +9 -0
  50. package/dist/tools/browser/measureElement.js +139 -0
  51. package/dist/tools/browser/navigation.d.ts +38 -0
  52. package/dist/tools/browser/navigation.js +109 -0
  53. package/dist/tools/browser/output.d.ts +11 -0
  54. package/dist/tools/browser/output.js +29 -0
  55. package/dist/tools/browser/querySelectorAll.d.ts +12 -0
  56. package/dist/tools/browser/querySelectorAll.js +201 -0
  57. package/dist/tools/browser/response.d.ts +29 -0
  58. package/dist/tools/browser/response.js +67 -0
  59. package/dist/tools/browser/screenshot.d.ts +16 -0
  60. package/dist/tools/browser/screenshot.js +70 -0
  61. package/dist/tools/browser/useragent.d.ts +15 -0
  62. package/dist/tools/browser/useragent.js +32 -0
  63. package/dist/tools/browser/visiblePage.d.ts +20 -0
  64. package/dist/tools/browser/visiblePage.js +170 -0
  65. package/dist/tools/browser/waitForElement.d.ts +10 -0
  66. package/dist/tools/browser/waitForElement.js +38 -0
  67. package/dist/tools/browser/waitForNetworkIdle.d.ts +8 -0
  68. package/dist/tools/browser/waitForNetworkIdle.js +32 -0
  69. package/dist/tools/codegen/generator.d.ts +21 -0
  70. package/dist/tools/codegen/generator.js +158 -0
  71. package/dist/tools/codegen/index.d.ts +11 -0
  72. package/dist/tools/codegen/index.js +187 -0
  73. package/dist/tools/codegen/recorder.d.ts +14 -0
  74. package/dist/tools/codegen/recorder.js +62 -0
  75. package/dist/tools/codegen/types.d.ts +28 -0
  76. package/dist/tools/codegen/types.js +1 -0
  77. package/dist/tools/common/types.d.ts +17 -0
  78. package/dist/tools/common/types.js +20 -0
  79. package/dist/tools/index.d.ts +2 -0
  80. package/dist/tools/index.js +2 -0
  81. package/dist/tools.d.ts +557 -0
  82. package/dist/tools.js +554 -0
  83. package/dist/types.d.ts +16 -0
  84. package/dist/types.js +1 -0
  85. package/package.json +60 -0
@@ -0,0 +1,12 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { ToolContext, ToolResponse } from '../common/types.js';
3
+ /**
4
+ * Tool for testing a selector and returning information about all matched elements
5
+ * Useful for selector debugging and finding the right element to interact with
6
+ */
7
+ export declare class QuerySelectorAllTool extends BrowserToolBase {
8
+ /**
9
+ * Execute the query selector all tool
10
+ */
11
+ execute(args: any, context: ToolContext): Promise<ToolResponse>;
12
+ }
@@ -0,0 +1,201 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { createSuccessResponse, createErrorResponse } from '../common/types.js';
3
+ /**
4
+ * Tool for testing a selector and returning information about all matched elements
5
+ * Useful for selector debugging and finding the right element to interact with
6
+ */
7
+ export class QuerySelectorAllTool extends BrowserToolBase {
8
+ /**
9
+ * Execute the query selector all tool
10
+ */
11
+ async execute(args, context) {
12
+ return this.safeExecute(context, async (page) => {
13
+ const selector = this.normalizeSelector(args.selector);
14
+ const limit = args.limit ?? 10;
15
+ const onlyVisible = args.onlyVisible; // true = visible only, false = hidden only, undefined = all
16
+ const showAttributes = args.showAttributes
17
+ ? args.showAttributes.split(',').map((a) => a.trim())
18
+ : undefined;
19
+ try {
20
+ // Query all elements matching the selector
21
+ const elements = await page.locator(selector).all();
22
+ const totalMatches = elements.length;
23
+ if (totalMatches === 0) {
24
+ return createSuccessResponse(`No elements found matching "${args.selector}"\n\nTip: Try using inspect_dom to explore the page structure.`);
25
+ }
26
+ // Get detailed information for each element (all elements to allow filtering)
27
+ const matchData = [];
28
+ for (let i = 0; i < elements.length; i++) {
29
+ const element = elements[i];
30
+ try {
31
+ const elementInfo = await element.evaluate((el, attrList) => {
32
+ const rect = el.getBoundingClientRect();
33
+ const styles = window.getComputedStyle(el);
34
+ const isVisible = styles.display !== 'none' &&
35
+ styles.visibility !== 'hidden' &&
36
+ parseFloat(styles.opacity) > 0 &&
37
+ rect.width > 0 &&
38
+ rect.height > 0;
39
+ // Get test ID
40
+ const testId = el.getAttribute('data-testid') ||
41
+ el.getAttribute('data-test') ||
42
+ el.getAttribute('data-cy') ||
43
+ undefined;
44
+ // Get selector representation
45
+ let selectorRepr = '';
46
+ if (testId) {
47
+ const attrName = el.hasAttribute('data-testid')
48
+ ? 'data-testid'
49
+ : el.hasAttribute('data-test')
50
+ ? 'data-test'
51
+ : 'data-cy';
52
+ selectorRepr = `${attrName}="${testId}"`;
53
+ }
54
+ else if (el.id) {
55
+ selectorRepr = `#${el.id}`;
56
+ }
57
+ else if (el.className && typeof el.className === 'string') {
58
+ const classes = el.className.trim().split(/\s+/).slice(0, 3).join('.');
59
+ if (classes) {
60
+ selectorRepr = `class="${classes}"`;
61
+ }
62
+ }
63
+ // Check if interactive
64
+ const tag = el.tagName.toLowerCase();
65
+ const interactiveTags = new Set(['button', 'a', 'input', 'select', 'textarea']);
66
+ const isInteractive = interactiveTags.has(tag) ||
67
+ el.hasAttribute('onclick') ||
68
+ el.hasAttribute('contenteditable') ||
69
+ el.getAttribute('role') === 'button';
70
+ // Get text content (trimmed and limited)
71
+ const text = el.textContent?.trim().slice(0, 100) || '';
72
+ // Get requested attributes if specified
73
+ let attributes;
74
+ if (attrList && attrList.length > 0) {
75
+ attributes = {};
76
+ attrList.forEach((attr) => {
77
+ const value = el.getAttribute(attr);
78
+ if (value !== null) {
79
+ attributes[attr] = value;
80
+ }
81
+ });
82
+ }
83
+ return {
84
+ tag: tag,
85
+ selector: selectorRepr,
86
+ testId,
87
+ classes: Array.from(el.classList).join('.'),
88
+ text,
89
+ position: {
90
+ x: Math.round(rect.x),
91
+ y: Math.round(rect.y),
92
+ width: Math.round(rect.width),
93
+ height: Math.round(rect.height),
94
+ },
95
+ isVisible,
96
+ isInteractive,
97
+ opacity: parseFloat(styles.opacity),
98
+ display: styles.display,
99
+ attributes,
100
+ };
101
+ }, showAttributes);
102
+ matchData.push(elementInfo);
103
+ }
104
+ catch (error) {
105
+ // Skip elements that fail evaluation (e.g., detached from DOM)
106
+ continue;
107
+ }
108
+ }
109
+ // Filter by visibility if requested
110
+ let filteredMatches = matchData;
111
+ if (onlyVisible !== undefined) {
112
+ filteredMatches = matchData.filter((match) => onlyVisible ? match.isVisible : !match.isVisible);
113
+ }
114
+ // Apply limit after filtering
115
+ const displayMatches = filteredMatches.slice(0, limit);
116
+ // Format compact text output
117
+ const lines = [];
118
+ // Header with counts
119
+ if (onlyVisible === true) {
120
+ const visibleCount = filteredMatches.length;
121
+ lines.push(`Found ${totalMatches} element${totalMatches > 1 ? 's' : ''} matching "${args.selector}" (${visibleCount} visible):`);
122
+ }
123
+ else if (onlyVisible === false) {
124
+ const hiddenCount = filteredMatches.length;
125
+ lines.push(`Found ${totalMatches} element${totalMatches > 1 ? 's' : ''} matching "${args.selector}" (${hiddenCount} hidden):`);
126
+ }
127
+ else {
128
+ lines.push(`Found ${totalMatches} element${totalMatches > 1 ? 's' : ''} matching "${args.selector}":`);
129
+ }
130
+ lines.push('');
131
+ displayMatches.forEach((match, index) => {
132
+ const prefix = `[${index}]`;
133
+ // Element tag with selector info
134
+ let tagInfo = `<${match.tag}`;
135
+ if (match.selector) {
136
+ tagInfo += ` ${match.selector}`;
137
+ }
138
+ tagInfo += '>';
139
+ lines.push(`${prefix} ${tagInfo}`);
140
+ // Position
141
+ lines.push(` @ (${match.position.x},${match.position.y}) ${match.position.width}x${match.position.height}px`);
142
+ // Text content
143
+ if (match.text) {
144
+ const displayText = match.text.length > 50 ? match.text.slice(0, 47) + '...' : match.text;
145
+ lines.push(` "${displayText}"`);
146
+ }
147
+ // Attributes (if requested)
148
+ if (match.attributes && Object.keys(match.attributes).length > 0) {
149
+ Object.entries(match.attributes).forEach(([attr, value]) => {
150
+ const displayValue = value.length > 50 ? value.slice(0, 47) + '...' : value;
151
+ lines.push(` ${attr}: "${displayValue}"`);
152
+ });
153
+ }
154
+ // Status symbols
155
+ const statusParts = [];
156
+ statusParts.push(match.isVisible ? '✓ visible' : '✗ hidden');
157
+ if (!match.isVisible) {
158
+ // Add reason for being hidden
159
+ if (match.display === 'none') {
160
+ statusParts.push('display: none');
161
+ }
162
+ else if (match.opacity === 0) {
163
+ statusParts.push('opacity: 0');
164
+ }
165
+ else if (match.position.width === 0 || match.position.height === 0) {
166
+ statusParts.push('zero size');
167
+ }
168
+ }
169
+ if (match.isInteractive) {
170
+ statusParts.push('⚡ interactive');
171
+ }
172
+ lines.push(` ${statusParts.join(', ')}`);
173
+ lines.push('');
174
+ });
175
+ // Show omitted count and summary
176
+ if (filteredMatches.length > limit) {
177
+ const omitted = filteredMatches.length - limit;
178
+ const matchType = onlyVisible === true ? 'visible ' : onlyVisible === false ? 'hidden ' : '';
179
+ lines.push(`Showing ${limit} of ${filteredMatches.length} ${matchType}matches (${omitted} omitted)`);
180
+ lines.push(`Use limit parameter to show more: { selector: "${args.selector}", limit: ${Math.min(filteredMatches.length, 50)} }`);
181
+ }
182
+ else {
183
+ const matchWord = filteredMatches.length === 1 ? 'match' : 'matches';
184
+ if (onlyVisible === true) {
185
+ lines.push(`Showing ${filteredMatches.length} visible ${matchWord}`);
186
+ }
187
+ else if (onlyVisible === false) {
188
+ lines.push(`Showing ${filteredMatches.length} hidden ${matchWord}`);
189
+ }
190
+ else {
191
+ lines.push(`Showing all ${filteredMatches.length} ${matchWord}`);
192
+ }
193
+ }
194
+ return createSuccessResponse(lines.join('\n'));
195
+ }
196
+ catch (error) {
197
+ return createErrorResponse(`Failed to query selector: ${error.message}`);
198
+ }
199
+ });
200
+ }
201
+ }
@@ -0,0 +1,29 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import type { ToolContext, ToolResponse } from '../common/types.js';
3
+ interface ExpectResponseArgs {
4
+ id: string;
5
+ url: string;
6
+ }
7
+ interface AssertResponseArgs {
8
+ id: string;
9
+ value?: string;
10
+ }
11
+ /**
12
+ * Tool for setting up response wait operations
13
+ */
14
+ export declare class ExpectResponseTool extends BrowserToolBase {
15
+ /**
16
+ * Execute the expect response tool
17
+ */
18
+ execute(args: ExpectResponseArgs, context: ToolContext): Promise<ToolResponse>;
19
+ }
20
+ /**
21
+ * Tool for asserting and validating responses
22
+ */
23
+ export declare class AssertResponseTool extends BrowserToolBase {
24
+ /**
25
+ * Execute the assert response tool
26
+ */
27
+ execute(args: AssertResponseArgs, context: ToolContext): Promise<ToolResponse>;
28
+ }
29
+ export {};
@@ -0,0 +1,67 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { createSuccessResponse, createErrorResponse } from '../common/types.js';
3
+ const responsePromises = new Map();
4
+ /**
5
+ * Tool for setting up response wait operations
6
+ */
7
+ export class ExpectResponseTool extends BrowserToolBase {
8
+ /**
9
+ * Execute the expect response tool
10
+ */
11
+ async execute(args, context) {
12
+ return this.safeExecute(context, async (page) => {
13
+ if (!args.id || !args.url) {
14
+ return createErrorResponse("Missing required parameters: id and url must be provided");
15
+ }
16
+ const responsePromise = page.waitForResponse(args.url);
17
+ responsePromises.set(args.id, responsePromise);
18
+ return createSuccessResponse(`Started waiting for response with ID ${args.id}`);
19
+ });
20
+ }
21
+ }
22
+ /**
23
+ * Tool for asserting and validating responses
24
+ */
25
+ export class AssertResponseTool extends BrowserToolBase {
26
+ /**
27
+ * Execute the assert response tool
28
+ */
29
+ async execute(args, context) {
30
+ return this.safeExecute(context, async () => {
31
+ if (!args.id) {
32
+ return createErrorResponse("Missing required parameter: id must be provided");
33
+ }
34
+ const responsePromise = responsePromises.get(args.id);
35
+ if (!responsePromise) {
36
+ return createErrorResponse(`No response wait operation found with ID: ${args.id}`);
37
+ }
38
+ try {
39
+ const response = await responsePromise;
40
+ const body = await response.json();
41
+ if (args.value) {
42
+ const bodyStr = JSON.stringify(body);
43
+ if (!bodyStr.includes(args.value)) {
44
+ const messages = [
45
+ `Response body does not contain expected value: ${args.value}`,
46
+ `Actual body: ${bodyStr}`
47
+ ];
48
+ return createErrorResponse(messages.join('\n'));
49
+ }
50
+ }
51
+ const messages = [
52
+ `Response assertion for ID ${args.id} successful`,
53
+ `URL: ${response.url()}`,
54
+ `Status: ${response.status()}`,
55
+ `Body: ${JSON.stringify(body, null, 2)}`
56
+ ];
57
+ return createSuccessResponse(messages.join('\n'));
58
+ }
59
+ catch (error) {
60
+ return createErrorResponse(`Failed to assert response: ${error.message}`);
61
+ }
62
+ finally {
63
+ responsePromises.delete(args.id);
64
+ }
65
+ });
66
+ }
67
+ }
@@ -0,0 +1,16 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { ToolContext, ToolResponse } from '../common/types.js';
3
+ /**
4
+ * Tool for taking screenshots of pages or elements
5
+ */
6
+ export declare class ScreenshotTool extends BrowserToolBase {
7
+ private screenshots;
8
+ /**
9
+ * Execute the screenshot tool
10
+ */
11
+ execute(args: any, context: ToolContext): Promise<ToolResponse>;
12
+ /**
13
+ * Get all stored screenshots
14
+ */
15
+ getScreenshots(): Map<string, string>;
16
+ }
@@ -0,0 +1,70 @@
1
+ import fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import { BrowserToolBase } from './base.js';
5
+ import { createSuccessResponse } from '../common/types.js';
6
+ import { getScreenshotsDir } from '../../toolHandler.js';
7
+ const defaultDownloadsPath = path.join(os.homedir(), 'Downloads');
8
+ /**
9
+ * Tool for taking screenshots of pages or elements
10
+ */
11
+ export class ScreenshotTool extends BrowserToolBase {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.screenshots = new Map();
15
+ }
16
+ /**
17
+ * Execute the screenshot tool
18
+ */
19
+ async execute(args, context) {
20
+ return this.safeExecute(context, async (page) => {
21
+ const screenshotOptions = {
22
+ type: args.type || "png",
23
+ fullPage: !!args.fullPage
24
+ };
25
+ if (args.selector) {
26
+ // Normalize selector (support testid: shorthand)
27
+ const selector = this.normalizeSelector(args.selector);
28
+ const element = await page.$(selector);
29
+ if (!element) {
30
+ return {
31
+ content: [{
32
+ type: "text",
33
+ text: `Element not found: ${selector}`,
34
+ }],
35
+ isError: true
36
+ };
37
+ }
38
+ screenshotOptions.element = element;
39
+ }
40
+ // Generate output path
41
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
42
+ const filename = `${args.name || 'screenshot'}-${timestamp}.png`;
43
+ // Use screenshots directory from config, fall back to downloadsDir arg, then default Downloads
44
+ const downloadsDir = args.downloadsDir || getScreenshotsDir();
45
+ if (!fs.existsSync(downloadsDir)) {
46
+ fs.mkdirSync(downloadsDir, { recursive: true });
47
+ }
48
+ const outputPath = path.join(downloadsDir, filename);
49
+ screenshotOptions.path = outputPath;
50
+ const screenshot = await page.screenshot(screenshotOptions);
51
+ const base64Screenshot = screenshot.toString('base64');
52
+ const messages = [`Screenshot saved to: ${path.relative(process.cwd(), outputPath)}`];
53
+ // Handle base64 storage
54
+ if (args.storeBase64 !== false) {
55
+ this.screenshots.set(args.name || 'screenshot', base64Screenshot);
56
+ this.server.notification({
57
+ method: "notifications/resources/list_changed",
58
+ });
59
+ messages.push(`Screenshot also stored in memory with name: '${args.name || 'screenshot'}'`);
60
+ }
61
+ return createSuccessResponse(messages);
62
+ });
63
+ }
64
+ /**
65
+ * Get all stored screenshots
66
+ */
67
+ getScreenshots() {
68
+ return this.screenshots;
69
+ }
70
+ }
@@ -0,0 +1,15 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import type { ToolContext, ToolResponse } from '../common/types.js';
3
+ interface CustomUserAgentArgs {
4
+ userAgent: string;
5
+ }
6
+ /**
7
+ * Tool for validating custom User Agent settings
8
+ */
9
+ export declare class CustomUserAgentTool extends BrowserToolBase {
10
+ /**
11
+ * Execute the custom user agent tool
12
+ */
13
+ execute(args: CustomUserAgentArgs, context: ToolContext): Promise<ToolResponse>;
14
+ }
15
+ export {};
@@ -0,0 +1,32 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { createSuccessResponse, createErrorResponse } from '../common/types.js';
3
+ /**
4
+ * Tool for validating custom User Agent settings
5
+ */
6
+ export class CustomUserAgentTool extends BrowserToolBase {
7
+ /**
8
+ * Execute the custom user agent tool
9
+ */
10
+ async execute(args, context) {
11
+ return this.safeExecute(context, async (page) => {
12
+ if (!args.userAgent) {
13
+ return createErrorResponse("Missing required parameter: userAgent must be provided");
14
+ }
15
+ try {
16
+ const currentUserAgent = await page.evaluate(() => navigator.userAgent);
17
+ if (currentUserAgent !== args.userAgent) {
18
+ const messages = [
19
+ "Page was already initialized with a different User Agent.",
20
+ `Requested: ${args.userAgent}`,
21
+ `Current: ${currentUserAgent}`
22
+ ];
23
+ return createErrorResponse(messages.join('\n'));
24
+ }
25
+ return createSuccessResponse("User Agent validation successful");
26
+ }
27
+ catch (error) {
28
+ return createErrorResponse(`Failed to validate User Agent: ${error.message}`);
29
+ }
30
+ });
31
+ }
32
+ }
@@ -0,0 +1,20 @@
1
+ import { ToolContext, ToolResponse } from "../common/types.js";
2
+ import { BrowserToolBase } from "./base.js";
3
+ /**
4
+ * Tool for getting the visible text content of the current page
5
+ */
6
+ export declare class VisibleTextTool extends BrowserToolBase {
7
+ /**
8
+ * Execute the visible text page tool
9
+ */
10
+ execute(args: any, context: ToolContext): Promise<ToolResponse>;
11
+ }
12
+ /**
13
+ * Tool for getting the visible HTML content of the current page
14
+ */
15
+ export declare class VisibleHtmlTool extends BrowserToolBase {
16
+ /**
17
+ * Execute the visible HTML page tool
18
+ */
19
+ execute(args: any, context: ToolContext): Promise<ToolResponse>;
20
+ }
@@ -0,0 +1,170 @@
1
+ import { resetBrowserState } from "../../toolHandler.js";
2
+ import { createErrorResponse, createSuccessResponse } from "../common/types.js";
3
+ import { BrowserToolBase } from "./base.js";
4
+ /**
5
+ * Tool for getting the visible text content of the current page
6
+ */
7
+ export class VisibleTextTool extends BrowserToolBase {
8
+ /**
9
+ * Execute the visible text page 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
+ return this.safeExecute(context, async (page) => {
23
+ try {
24
+ const visibleText = await page.evaluate(() => {
25
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
26
+ acceptNode: (node) => {
27
+ const style = window.getComputedStyle(node.parentElement);
28
+ return (style.display !== "none" && style.visibility !== "hidden")
29
+ ? NodeFilter.FILTER_ACCEPT
30
+ : NodeFilter.FILTER_REJECT;
31
+ },
32
+ });
33
+ let text = "";
34
+ let node;
35
+ while ((node = walker.nextNode())) {
36
+ const trimmedText = node.textContent?.trim();
37
+ if (trimmedText) {
38
+ text += trimmedText + "\n";
39
+ }
40
+ }
41
+ return text.trim();
42
+ });
43
+ // Truncate logic
44
+ const maxLength = typeof args.maxLength === 'number' ? args.maxLength : 20000;
45
+ let output = visibleText;
46
+ let truncated = false;
47
+ if (output.length > maxLength) {
48
+ output = output.slice(0, maxLength) + '\n[Output truncated due to size limits]';
49
+ truncated = true;
50
+ }
51
+ return createSuccessResponse(`Visible text content:\n${output}`);
52
+ }
53
+ catch (error) {
54
+ return createErrorResponse(`Failed to get visible text content: ${error.message}`);
55
+ }
56
+ });
57
+ }
58
+ }
59
+ /**
60
+ * Tool for getting the visible HTML content of the current page
61
+ */
62
+ export class VisibleHtmlTool extends BrowserToolBase {
63
+ /**
64
+ * Execute the visible HTML page tool
65
+ */
66
+ async execute(args, context) {
67
+ // Check if browser is available
68
+ if (!context.browser || !context.browser.isConnected()) {
69
+ // If browser is not connected, we need to reset the state to force recreation
70
+ resetBrowserState();
71
+ return createErrorResponse("Browser is not connected. The connection has been reset - please retry your navigation.");
72
+ }
73
+ // Check if page is available and not closed
74
+ if (!context.page || context.page.isClosed()) {
75
+ return createErrorResponse("Page is not available or has been closed. Please retry your navigation.");
76
+ }
77
+ return this.safeExecute(context, async (page) => {
78
+ try {
79
+ const { removeComments, removeStyles, removeMeta, minify, cleanHtml } = args;
80
+ // Default removeScripts to true unless explicitly set to false
81
+ const removeScripts = args.removeScripts === false ? false : true;
82
+ // Normalize selector (support testid: shorthand)
83
+ const selector = args.selector ? this.normalizeSelector(args.selector) : undefined;
84
+ // Get the HTML content
85
+ let htmlContent;
86
+ if (selector) {
87
+ // If a selector is provided, get only the HTML for that element
88
+ const element = await page.$(selector);
89
+ if (!element) {
90
+ return createErrorResponse(`Element with selector "${selector}" not found`);
91
+ }
92
+ htmlContent = await page.evaluate((el) => el.outerHTML, element);
93
+ }
94
+ else {
95
+ // Otherwise get the full page HTML
96
+ htmlContent = await page.content();
97
+ }
98
+ // Determine if we need to apply filters
99
+ const shouldRemoveScripts = removeScripts || cleanHtml;
100
+ const shouldRemoveComments = removeComments || cleanHtml;
101
+ const shouldRemoveStyles = removeStyles || cleanHtml;
102
+ const shouldRemoveMeta = removeMeta || cleanHtml;
103
+ // Apply filters in the browser context
104
+ if (shouldRemoveScripts || shouldRemoveComments || shouldRemoveStyles || shouldRemoveMeta || minify) {
105
+ htmlContent = await page.evaluate(({ html, removeScripts, removeComments, removeStyles, removeMeta, minify }) => {
106
+ // Create a DOM parser to work with the HTML
107
+ const parser = new DOMParser();
108
+ const doc = parser.parseFromString(html, 'text/html');
109
+ // Remove script tags if requested
110
+ if (removeScripts) {
111
+ const scripts = doc.querySelectorAll('script');
112
+ scripts.forEach(script => script.remove());
113
+ }
114
+ // Remove style tags if requested
115
+ if (removeStyles) {
116
+ const styles = doc.querySelectorAll('style');
117
+ styles.forEach(style => style.remove());
118
+ }
119
+ // Remove meta tags if requested
120
+ if (removeMeta) {
121
+ const metaTags = doc.querySelectorAll('meta');
122
+ metaTags.forEach(meta => meta.remove());
123
+ }
124
+ // Remove HTML comments if requested
125
+ if (removeComments) {
126
+ const removeComments = (node) => {
127
+ const childNodes = node.childNodes;
128
+ for (let i = childNodes.length - 1; i >= 0; i--) {
129
+ const child = childNodes[i];
130
+ if (child.nodeType === 8) { // 8 is for comment nodes
131
+ node.removeChild(child);
132
+ }
133
+ else if (child.nodeType === 1) { // 1 is for element nodes
134
+ removeComments(child);
135
+ }
136
+ }
137
+ };
138
+ removeComments(doc.documentElement);
139
+ }
140
+ // Get the processed HTML
141
+ let result = doc.documentElement.outerHTML;
142
+ // Minify if requested
143
+ if (minify) {
144
+ // Simple minification: remove extra whitespace
145
+ result = result.replace(/>\s+</g, '><').trim();
146
+ }
147
+ return result;
148
+ }, {
149
+ html: htmlContent,
150
+ removeScripts: shouldRemoveScripts,
151
+ removeComments: shouldRemoveComments,
152
+ removeStyles: shouldRemoveStyles,
153
+ removeMeta: shouldRemoveMeta,
154
+ minify
155
+ });
156
+ }
157
+ // Truncate logic
158
+ const maxLength = typeof args.maxLength === 'number' ? args.maxLength : 20000;
159
+ let output = htmlContent;
160
+ if (output.length > maxLength) {
161
+ output = output.slice(0, maxLength) + '\n<!-- Output truncated due to size limits -->';
162
+ }
163
+ return createSuccessResponse(`HTML content:\n${output}`);
164
+ }
165
+ catch (error) {
166
+ return createErrorResponse(`Failed to get visible HTML content: ${error.message}`);
167
+ }
168
+ });
169
+ }
170
+ }
@@ -0,0 +1,10 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { ToolContext, ToolResponse } from '../common/types.js';
3
+ export interface WaitForElementArgs {
4
+ selector: string;
5
+ state?: 'visible' | 'hidden' | 'attached' | 'detached';
6
+ timeout?: number;
7
+ }
8
+ export declare class WaitForElementTool extends BrowserToolBase {
9
+ execute(args: WaitForElementArgs, context: ToolContext): Promise<ToolResponse>;
10
+ }