real-browser-mcp-server 1.2.0 → 1.2.2
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/README.md +96 -9
- package/dist/lib/cjs/index.d.ts +2 -0
- package/dist/lib/cjs/index.d.ts.map +1 -0
- package/dist/lib/cjs/index.js +385 -0
- package/dist/lib/cjs/index.js.map +1 -0
- package/dist/lib/cjs/module/pageController.d.ts +2 -0
- package/dist/lib/cjs/module/pageController.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/pageController.js +28 -29
- package/dist/lib/cjs/module/pageController.js.map +1 -0
- package/dist/lib/cjs/module/turnstile.d.ts +2 -0
- package/dist/lib/cjs/module/turnstile.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/turnstile.js +24 -12
- package/dist/lib/cjs/module/turnstile.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +118 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/handlers/browser.d.ts +30 -0
- package/dist/src/mcp/handlers/browser.d.ts.map +1 -0
- package/dist/src/mcp/handlers/browser.js +231 -0
- package/dist/src/mcp/handlers/browser.js.map +1 -0
- package/dist/src/mcp/handlers/dom.d.ts +134 -0
- package/dist/src/mcp/handlers/dom.d.ts.map +1 -0
- package/dist/src/mcp/handlers/dom.js +551 -0
- package/dist/src/mcp/handlers/dom.js.map +1 -0
- package/dist/src/mcp/handlers/extract.d.ts +59 -0
- package/dist/src/mcp/handlers/extract.d.ts.map +1 -0
- package/dist/src/mcp/handlers/extract.js +455 -0
- package/dist/src/mcp/handlers/extract.js.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts +9 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.js +56 -0
- package/dist/src/mcp/handlers/form-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/helpers.d.ts +47 -0
- package/dist/src/mcp/handlers/helpers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/helpers.js +515 -0
- package/dist/src/mcp/handlers/helpers.js.map +1 -0
- package/dist/src/mcp/handlers/index.d.ts +6 -0
- package/dist/src/mcp/handlers/index.d.ts.map +1 -0
- package/dist/src/mcp/handlers/index.js +61 -0
- package/dist/src/mcp/handlers/index.js.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts +10 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.js +535 -0
- package/dist/src/mcp/handlers/media-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/network.d.ts +147 -0
- package/dist/src/mcp/handlers/network.d.ts.map +1 -0
- package/dist/src/mcp/handlers/network.js +1135 -0
- package/dist/src/mcp/handlers/network.js.map +1 -0
- package/dist/src/mcp/handlers/state.d.ts +34 -0
- package/dist/src/mcp/handlers/state.d.ts.map +1 -0
- package/dist/src/mcp/handlers/state.js +225 -0
- package/dist/src/mcp/handlers/state.js.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts +167 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.js +280 -0
- package/dist/src/mcp/handlers/utility-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/vision.d.ts +127 -0
- package/dist/src/mcp/handlers/vision.d.ts.map +1 -0
- package/dist/src/mcp/handlers/vision.js +483 -0
- package/dist/src/mcp/handlers/vision.js.map +1 -0
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +166 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +117 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools.d.ts +8 -0
- package/dist/src/mcp/tools.d.ts.map +1 -0
- package/{src → dist/src}/mcp/tools.js +12 -11
- package/dist/src/mcp/tools.js.map +1 -0
- package/dist/src/shared/cache-manager.d.ts +80 -0
- package/dist/src/shared/cache-manager.d.ts.map +1 -0
- package/dist/src/shared/cache-manager.js +221 -0
- package/dist/src/shared/cache-manager.js.map +1 -0
- package/dist/src/shared/tools.d.ts +2 -0
- package/dist/src/shared/tools.d.ts.map +1 -0
- package/dist/src/shared/tools.js +599 -0
- package/dist/src/shared/tools.js.map +1 -0
- package/dist/src/types.d.ts +365 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/test/cjs/test.d.ts +11 -0
- package/dist/test/cjs/test.d.ts.map +1 -0
- package/dist/test/cjs/test.js +289 -0
- package/dist/test/cjs/test.js.map +1 -0
- package/dist/test/mcp/smoke-test.d.ts +29 -0
- package/dist/test/mcp/smoke-test.d.ts.map +1 -0
- package/dist/test/mcp/smoke-test.js +132 -0
- package/dist/test/mcp/smoke-test.js.map +1 -0
- package/lib/esm/index.mjs +232 -79
- package/lib/esm/module/pageController.mjs +21 -18
- package/lib/esm/module/turnstile.mjs +7 -0
- package/package.json +25 -15
- package/typings.d.ts +12 -6
- package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
- package/.github/SETUP.md +0 -111
- package/.github/workflows/publish.yml +0 -135
- package/Dockerfile +0 -79
- package/lib/cjs/adblocker.bin +0 -0
- package/lib/cjs/index.js +0 -249
- package/src/ai/action-parser.js +0 -274
- package/src/ai/core.js +0 -378
- package/src/ai/element-finder.js +0 -466
- package/src/ai/index.js +0 -82
- package/src/ai/page-analyzer.js +0 -304
- package/src/ai/selector-healer.js +0 -236
- package/src/index.js +0 -121
- package/src/mcp/handlers.js +0 -5071
- package/src/mcp/index.js +0 -190
- package/src/mcp/server.js +0 -144
- package/src/shared/tools.js +0 -618
- package/test/cjs/test.js +0 -259
- package/test/esm/package.json +0 -13
- package/test/esm/test.js +0 -226
- package/test/esm/test_option2.js +0 -46
- package/test/esm/test_playwright_ghost.js +0 -30
package/src/ai/element-finder.js
DELETED
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Element Finder - Smart element finding with multiple strategies
|
|
3
|
-
*
|
|
4
|
-
* Strategies:
|
|
5
|
-
* - text: Find by visible text content
|
|
6
|
-
* - aria: Find by accessibility attributes (aria-label, role, etc.)
|
|
7
|
-
* - visual: Find by position, size, color context
|
|
8
|
-
* - semantic: Find by HTML structure and semantic meaning
|
|
9
|
-
* - auto: Try all strategies and return best matches
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
class ElementFinder {
|
|
13
|
-
constructor() {
|
|
14
|
-
// Common element patterns for different types
|
|
15
|
-
this.patterns = {
|
|
16
|
-
button: {
|
|
17
|
-
tags: ['button', 'input[type="button"]', 'input[type="submit"]', 'a.btn', '[role="button"]'],
|
|
18
|
-
keywords: ['click', 'submit', 'button', 'btn', 'press']
|
|
19
|
-
},
|
|
20
|
-
input: {
|
|
21
|
-
tags: ['input', 'textarea', '[contenteditable="true"]'],
|
|
22
|
-
keywords: ['input', 'field', 'textbox', 'enter', 'type', 'write']
|
|
23
|
-
},
|
|
24
|
-
link: {
|
|
25
|
-
tags: ['a[href]', '[role="link"]'],
|
|
26
|
-
keywords: ['link', 'go to', 'navigate', 'open']
|
|
27
|
-
},
|
|
28
|
-
search: {
|
|
29
|
-
tags: ['input[type="search"]', 'input[name*="search"]', 'input[placeholder*="search"]', '[role="searchbox"]'],
|
|
30
|
-
keywords: ['search', 'find', 'lookup', 'query']
|
|
31
|
-
},
|
|
32
|
-
login: {
|
|
33
|
-
tags: ['input[type="password"]', 'input[name*="password"]', 'input[name*="login"]', 'input[name*="user"]'],
|
|
34
|
-
keywords: ['login', 'password', 'username', 'email', 'signin', 'sign in']
|
|
35
|
-
},
|
|
36
|
-
form: {
|
|
37
|
-
tags: ['form', '[role="form"]'],
|
|
38
|
-
keywords: ['form', 'submit']
|
|
39
|
-
},
|
|
40
|
-
menu: {
|
|
41
|
-
tags: ['nav', '[role="navigation"]', '[role="menu"]', 'ul.menu', '.nav'],
|
|
42
|
-
keywords: ['menu', 'navigation', 'nav']
|
|
43
|
-
},
|
|
44
|
-
image: {
|
|
45
|
-
tags: ['img', 'picture', '[role="img"]', 'svg'],
|
|
46
|
-
keywords: ['image', 'picture', 'photo', 'icon', 'logo']
|
|
47
|
-
},
|
|
48
|
-
video: {
|
|
49
|
-
tags: ['video', 'iframe[src*="youtube"]', 'iframe[src*="vimeo"]', '[role="video"]'],
|
|
50
|
-
keywords: ['video', 'player', 'watch']
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Common action keywords
|
|
55
|
-
this.actionKeywords = {
|
|
56
|
-
click: ['click', 'press', 'tap', 'hit', 'select', 'choose'],
|
|
57
|
-
type: ['type', 'enter', 'write', 'input', 'fill'],
|
|
58
|
-
scroll: ['scroll', 'move', 'go down', 'go up'],
|
|
59
|
-
hover: ['hover', 'mouse over', 'point'],
|
|
60
|
-
wait: ['wait', 'pause', 'delay']
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Find elements matching the query
|
|
66
|
-
*/
|
|
67
|
-
async find(page, query, options = {}) {
|
|
68
|
-
const {
|
|
69
|
-
strategy = 'auto',
|
|
70
|
-
context = null,
|
|
71
|
-
confidence = 0.7,
|
|
72
|
-
returnMultiple = false
|
|
73
|
-
} = options;
|
|
74
|
-
|
|
75
|
-
let results = [];
|
|
76
|
-
|
|
77
|
-
// Parse query to understand what user is looking for
|
|
78
|
-
const parsedQuery = this.parseQuery(query);
|
|
79
|
-
|
|
80
|
-
// Apply different strategies
|
|
81
|
-
if (strategy === 'auto' || strategy === 'text') {
|
|
82
|
-
const textResults = await this.findByText(page, parsedQuery, context);
|
|
83
|
-
results = [...results, ...textResults];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (strategy === 'auto' || strategy === 'aria') {
|
|
87
|
-
const ariaResults = await this.findByAria(page, parsedQuery, context);
|
|
88
|
-
results = [...results, ...ariaResults];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (strategy === 'auto' || strategy === 'semantic') {
|
|
92
|
-
const semanticResults = await this.findBySemantic(page, parsedQuery, context);
|
|
93
|
-
results = [...results, ...semanticResults];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (strategy === 'auto' || strategy === 'visual') {
|
|
97
|
-
const visualResults = await this.findByVisual(page, parsedQuery, context);
|
|
98
|
-
results = [...results, ...visualResults];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Deduplicate and sort by confidence
|
|
102
|
-
results = this.deduplicateResults(results);
|
|
103
|
-
results.sort((a, b) => b.confidence - a.confidence);
|
|
104
|
-
|
|
105
|
-
// Filter by confidence threshold
|
|
106
|
-
results = results.filter(r => r.confidence >= confidence);
|
|
107
|
-
|
|
108
|
-
return returnMultiple ? results : results.slice(0, 1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Parse natural language query
|
|
113
|
-
*/
|
|
114
|
-
parseQuery(query) {
|
|
115
|
-
const lowerQuery = query.toLowerCase();
|
|
116
|
-
|
|
117
|
-
// Detect element type
|
|
118
|
-
let elementType = 'any';
|
|
119
|
-
for (const [type, pattern] of Object.entries(this.patterns)) {
|
|
120
|
-
if (pattern.keywords.some(kw => lowerQuery.includes(kw))) {
|
|
121
|
-
elementType = type;
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Extract key terms
|
|
127
|
-
const terms = query.split(/\s+/).filter(t => t.length > 2);
|
|
128
|
-
|
|
129
|
-
// Detect if looking for specific text
|
|
130
|
-
const textMatch = query.match(/["']([^"']+)["']|containing\s+(.+)|with text\s+(.+)|labeled\s+(.+)/i);
|
|
131
|
-
const targetText = textMatch ? (textMatch[1] || textMatch[2] || textMatch[3] || textMatch[4]) : null;
|
|
132
|
-
|
|
133
|
-
// Detect color mentions
|
|
134
|
-
const colorMatch = query.match(/\b(red|blue|green|yellow|orange|purple|black|white|gray|grey)\b/i);
|
|
135
|
-
const color = colorMatch ? colorMatch[1].toLowerCase() : null;
|
|
136
|
-
|
|
137
|
-
// Detect position mentions
|
|
138
|
-
const positionMatch = query.match(/\b(top|bottom|left|right|center|first|last|header|footer)\b/i);
|
|
139
|
-
const position = positionMatch ? positionMatch[1].toLowerCase() : null;
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
original: query,
|
|
143
|
-
elementType,
|
|
144
|
-
terms,
|
|
145
|
-
targetText,
|
|
146
|
-
color,
|
|
147
|
-
position
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Find by text content
|
|
153
|
-
*/
|
|
154
|
-
async findByText(page, parsedQuery, context) {
|
|
155
|
-
const contextSelector = context || 'body';
|
|
156
|
-
|
|
157
|
-
return await page.evaluate(({ parsedQuery, contextSelector }) => {
|
|
158
|
-
const results = [];
|
|
159
|
-
const container = document.querySelector(contextSelector) || document.body;
|
|
160
|
-
|
|
161
|
-
// Get all interactive and text-containing elements
|
|
162
|
-
const elements = container.querySelectorAll('button, a, input, label, span, div, p, h1, h2, h3, h4, h5, h6, [role]');
|
|
163
|
-
|
|
164
|
-
for (const el of elements) {
|
|
165
|
-
const text = el.textContent?.trim().toLowerCase() || '';
|
|
166
|
-
const placeholder = el.placeholder?.toLowerCase() || '';
|
|
167
|
-
const value = el.value?.toLowerCase() || '';
|
|
168
|
-
const ariaLabel = el.getAttribute('aria-label')?.toLowerCase() || '';
|
|
169
|
-
|
|
170
|
-
let confidence = 0;
|
|
171
|
-
const matchedTerms = [];
|
|
172
|
-
|
|
173
|
-
// Check each search term
|
|
174
|
-
for (const term of parsedQuery.terms) {
|
|
175
|
-
const lowerTerm = term.toLowerCase();
|
|
176
|
-
|
|
177
|
-
if (text.includes(lowerTerm)) {
|
|
178
|
-
confidence += 0.3;
|
|
179
|
-
matchedTerms.push({ term, in: 'text' });
|
|
180
|
-
}
|
|
181
|
-
if (placeholder.includes(lowerTerm)) {
|
|
182
|
-
confidence += 0.25;
|
|
183
|
-
matchedTerms.push({ term, in: 'placeholder' });
|
|
184
|
-
}
|
|
185
|
-
if (ariaLabel.includes(lowerTerm)) {
|
|
186
|
-
confidence += 0.25;
|
|
187
|
-
matchedTerms.push({ term, in: 'aria-label' });
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Exact text match bonus
|
|
192
|
-
if (parsedQuery.targetText && text.includes(parsedQuery.targetText.toLowerCase())) {
|
|
193
|
-
confidence += 0.4;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Visibility bonus
|
|
197
|
-
const rect = el.getBoundingClientRect();
|
|
198
|
-
if (rect.width > 0 && rect.height > 0) {
|
|
199
|
-
confidence += 0.1;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (confidence > 0) {
|
|
203
|
-
// Generate selector
|
|
204
|
-
let selector = '';
|
|
205
|
-
if (el.id) {
|
|
206
|
-
selector = `#${el.id}`;
|
|
207
|
-
} else if (el.className && typeof el.className === 'string') {
|
|
208
|
-
const classes = el.className.split(' ').filter(c => c).slice(0, 2).join('.');
|
|
209
|
-
selector = `${el.tagName.toLowerCase()}.${classes}`;
|
|
210
|
-
} else {
|
|
211
|
-
selector = el.tagName.toLowerCase();
|
|
212
|
-
if (el.getAttribute('type')) {
|
|
213
|
-
selector += `[type="${el.getAttribute('type')}"]`;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
results.push({
|
|
218
|
-
selector,
|
|
219
|
-
confidence: Math.min(confidence, 1),
|
|
220
|
-
text: text.substring(0, 100),
|
|
221
|
-
strategy: 'text',
|
|
222
|
-
matchedTerms,
|
|
223
|
-
tag: el.tagName.toLowerCase(),
|
|
224
|
-
visible: rect.width > 0 && rect.height > 0
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return results;
|
|
230
|
-
}, { parsedQuery, contextSelector });
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Find by ARIA attributes
|
|
235
|
-
*/
|
|
236
|
-
async findByAria(page, parsedQuery, context) {
|
|
237
|
-
const contextSelector = context || 'body';
|
|
238
|
-
|
|
239
|
-
return await page.evaluate(({ parsedQuery, contextSelector }) => {
|
|
240
|
-
const results = [];
|
|
241
|
-
const container = document.querySelector(contextSelector) || document.body;
|
|
242
|
-
|
|
243
|
-
// Find elements with ARIA attributes
|
|
244
|
-
const ariaElements = container.querySelectorAll('[aria-label], [aria-describedby], [role], [aria-placeholder]');
|
|
245
|
-
|
|
246
|
-
for (const el of ariaElements) {
|
|
247
|
-
const ariaLabel = el.getAttribute('aria-label')?.toLowerCase() || '';
|
|
248
|
-
const role = el.getAttribute('role')?.toLowerCase() || '';
|
|
249
|
-
const ariaPlaceholder = el.getAttribute('aria-placeholder')?.toLowerCase() || '';
|
|
250
|
-
|
|
251
|
-
let confidence = 0;
|
|
252
|
-
|
|
253
|
-
for (const term of parsedQuery.terms) {
|
|
254
|
-
const lowerTerm = term.toLowerCase();
|
|
255
|
-
|
|
256
|
-
if (ariaLabel.includes(lowerTerm)) {
|
|
257
|
-
confidence += 0.35;
|
|
258
|
-
}
|
|
259
|
-
if (role.includes(lowerTerm)) {
|
|
260
|
-
confidence += 0.2;
|
|
261
|
-
}
|
|
262
|
-
if (ariaPlaceholder.includes(lowerTerm)) {
|
|
263
|
-
confidence += 0.25;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Role matching for element type
|
|
268
|
-
if (parsedQuery.elementType !== 'any') {
|
|
269
|
-
if (role === parsedQuery.elementType ||
|
|
270
|
-
role === 'button' && parsedQuery.elementType === 'button') {
|
|
271
|
-
confidence += 0.2;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (confidence > 0) {
|
|
276
|
-
let selector = '';
|
|
277
|
-
if (el.getAttribute('aria-label')) {
|
|
278
|
-
selector = `[aria-label="${el.getAttribute('aria-label')}"]`;
|
|
279
|
-
} else if (el.id) {
|
|
280
|
-
selector = `#${el.id}`;
|
|
281
|
-
} else {
|
|
282
|
-
selector = `[role="${role}"]`;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const rect = el.getBoundingClientRect();
|
|
286
|
-
|
|
287
|
-
results.push({
|
|
288
|
-
selector,
|
|
289
|
-
confidence: Math.min(confidence, 1),
|
|
290
|
-
ariaLabel,
|
|
291
|
-
role,
|
|
292
|
-
strategy: 'aria',
|
|
293
|
-
tag: el.tagName.toLowerCase(),
|
|
294
|
-
visible: rect.width > 0 && rect.height > 0
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return results;
|
|
300
|
-
}, { parsedQuery, contextSelector });
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Find by semantic HTML structure
|
|
305
|
-
*/
|
|
306
|
-
async findBySemantic(page, parsedQuery, context) {
|
|
307
|
-
const contextSelector = context || 'body';
|
|
308
|
-
const patterns = this.patterns;
|
|
309
|
-
|
|
310
|
-
return await page.evaluate(({ parsedQuery, contextSelector, patterns }) => {
|
|
311
|
-
const results = [];
|
|
312
|
-
const container = document.querySelector(contextSelector) || document.body;
|
|
313
|
-
|
|
314
|
-
// Get pattern for element type
|
|
315
|
-
const pattern = patterns[parsedQuery.elementType];
|
|
316
|
-
|
|
317
|
-
if (pattern) {
|
|
318
|
-
for (const tagSelector of pattern.tags) {
|
|
319
|
-
const elements = container.querySelectorAll(tagSelector);
|
|
320
|
-
|
|
321
|
-
for (const el of elements) {
|
|
322
|
-
const rect = el.getBoundingClientRect();
|
|
323
|
-
if (rect.width === 0 || rect.height === 0) continue;
|
|
324
|
-
|
|
325
|
-
let confidence = 0.5; // Base confidence for matching pattern
|
|
326
|
-
|
|
327
|
-
// Check text content matches
|
|
328
|
-
const text = el.textContent?.toLowerCase() || '';
|
|
329
|
-
for (const term of parsedQuery.terms) {
|
|
330
|
-
if (text.includes(term.toLowerCase())) {
|
|
331
|
-
confidence += 0.2;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Generate unique selector
|
|
336
|
-
let selector = '';
|
|
337
|
-
if (el.id) {
|
|
338
|
-
selector = `#${el.id}`;
|
|
339
|
-
confidence += 0.1;
|
|
340
|
-
} else if (el.name) {
|
|
341
|
-
selector = `[name="${el.name}"]`;
|
|
342
|
-
confidence += 0.1;
|
|
343
|
-
} else {
|
|
344
|
-
selector = tagSelector;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
results.push({
|
|
348
|
-
selector,
|
|
349
|
-
confidence: Math.min(confidence, 1),
|
|
350
|
-
text: text.substring(0, 50),
|
|
351
|
-
strategy: 'semantic',
|
|
352
|
-
elementType: parsedQuery.elementType,
|
|
353
|
-
tag: el.tagName.toLowerCase(),
|
|
354
|
-
visible: true
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return results;
|
|
361
|
-
}, { parsedQuery, contextSelector, patterns });
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Find by visual properties (position, approximate area)
|
|
366
|
-
*/
|
|
367
|
-
async findByVisual(page, parsedQuery, context) {
|
|
368
|
-
const contextSelector = context || 'body';
|
|
369
|
-
|
|
370
|
-
return await page.evaluate(({ parsedQuery, contextSelector }) => {
|
|
371
|
-
const results = [];
|
|
372
|
-
const container = document.querySelector(contextSelector) || document.body;
|
|
373
|
-
|
|
374
|
-
// Get viewport dimensions
|
|
375
|
-
const viewportHeight = window.innerHeight;
|
|
376
|
-
const viewportWidth = window.innerWidth;
|
|
377
|
-
|
|
378
|
-
const elements = container.querySelectorAll('button, a, input, [role="button"], [role="link"]');
|
|
379
|
-
|
|
380
|
-
for (const el of elements) {
|
|
381
|
-
const rect = el.getBoundingClientRect();
|
|
382
|
-
if (rect.width === 0 || rect.height === 0) continue;
|
|
383
|
-
|
|
384
|
-
let confidence = 0.3; // Base confidence
|
|
385
|
-
|
|
386
|
-
// Position matching
|
|
387
|
-
if (parsedQuery.position) {
|
|
388
|
-
const pos = parsedQuery.position;
|
|
389
|
-
|
|
390
|
-
if (pos === 'top' && rect.top < viewportHeight * 0.3) {
|
|
391
|
-
confidence += 0.3;
|
|
392
|
-
} else if (pos === 'bottom' && rect.bottom > viewportHeight * 0.7) {
|
|
393
|
-
confidence += 0.3;
|
|
394
|
-
} else if (pos === 'left' && rect.left < viewportWidth * 0.3) {
|
|
395
|
-
confidence += 0.3;
|
|
396
|
-
} else if (pos === 'right' && rect.right > viewportWidth * 0.7) {
|
|
397
|
-
confidence += 0.3;
|
|
398
|
-
} else if (pos === 'center' &&
|
|
399
|
-
rect.left > viewportWidth * 0.3 &&
|
|
400
|
-
rect.right < viewportWidth * 0.7) {
|
|
401
|
-
confidence += 0.3;
|
|
402
|
-
} else if (pos === 'header' && rect.top < 100) {
|
|
403
|
-
confidence += 0.3;
|
|
404
|
-
} else if (pos === 'footer' && rect.bottom > viewportHeight - 100) {
|
|
405
|
-
confidence += 0.3;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Check text content
|
|
410
|
-
const text = el.textContent?.toLowerCase() || '';
|
|
411
|
-
for (const term of parsedQuery.terms) {
|
|
412
|
-
if (text.includes(term.toLowerCase())) {
|
|
413
|
-
confidence += 0.2;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (confidence > 0.3) {
|
|
418
|
-
let selector = '';
|
|
419
|
-
if (el.id) {
|
|
420
|
-
selector = `#${el.id}`;
|
|
421
|
-
} else {
|
|
422
|
-
selector = el.tagName.toLowerCase();
|
|
423
|
-
if (el.className && typeof el.className === 'string') {
|
|
424
|
-
selector += '.' + el.className.split(' ')[0];
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
results.push({
|
|
429
|
-
selector,
|
|
430
|
-
confidence: Math.min(confidence, 1),
|
|
431
|
-
text: text.substring(0, 50),
|
|
432
|
-
strategy: 'visual',
|
|
433
|
-
position: {
|
|
434
|
-
top: Math.round(rect.top),
|
|
435
|
-
left: Math.round(rect.left),
|
|
436
|
-
width: Math.round(rect.width),
|
|
437
|
-
height: Math.round(rect.height)
|
|
438
|
-
},
|
|
439
|
-
tag: el.tagName.toLowerCase(),
|
|
440
|
-
visible: true
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
return results;
|
|
446
|
-
}, { parsedQuery, contextSelector });
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Deduplicate results based on selector
|
|
451
|
-
*/
|
|
452
|
-
deduplicateResults(results) {
|
|
453
|
-
const seen = new Map();
|
|
454
|
-
|
|
455
|
-
for (const result of results) {
|
|
456
|
-
const existing = seen.get(result.selector);
|
|
457
|
-
if (!existing || existing.confidence < result.confidence) {
|
|
458
|
-
seen.set(result.selector, result);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return Array.from(seen.values());
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
module.exports = ElementFinder;
|
package/src/ai/index.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Module - Main Entry Point
|
|
3
|
-
*
|
|
4
|
-
* This module provides AI-powered features that automatically enhance
|
|
5
|
-
* ALL tools in the browser automation project.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* 1. Smart Element Finding - Find elements using natural language
|
|
9
|
-
* 2. Selector Auto-Healing - Automatically fix broken selectors
|
|
10
|
-
* 3. Page Understanding - Analyze and understand page structure
|
|
11
|
-
* 4. Natural Language Commands - Execute actions from text commands
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* ```javascript
|
|
15
|
-
* const { aiCore, smartFind, smartClick, executeCommand } = require('./ai');
|
|
16
|
-
*
|
|
17
|
-
* // Smart find
|
|
18
|
-
* const elements = await smartFind(page, 'login button');
|
|
19
|
-
*
|
|
20
|
-
* // Smart click with auto-healing
|
|
21
|
-
* await smartClick(page, '#old-selector');
|
|
22
|
-
*
|
|
23
|
-
* // Execute natural language command
|
|
24
|
-
* await executeCommand(page, 'click the submit button');
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* Integration with existing tools:
|
|
28
|
-
* All existing handlers can be wrapped with AI capabilities using:
|
|
29
|
-
* ```javascript
|
|
30
|
-
* const enhancedHandler = wrapHandler(originalHandler, 'handlerName');
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
const {
|
|
35
|
-
AICore,
|
|
36
|
-
aiCore,
|
|
37
|
-
smartFind,
|
|
38
|
-
smartClick,
|
|
39
|
-
smartType,
|
|
40
|
-
understandPage,
|
|
41
|
-
executeCommand,
|
|
42
|
-
wrapHandler,
|
|
43
|
-
configure
|
|
44
|
-
} = require('./core');
|
|
45
|
-
|
|
46
|
-
const ElementFinder = require('./element-finder');
|
|
47
|
-
const SelectorHealer = require('./selector-healer');
|
|
48
|
-
const PageAnalyzer = require('./page-analyzer');
|
|
49
|
-
const ActionParser = require('./action-parser');
|
|
50
|
-
|
|
51
|
-
// Export everything
|
|
52
|
-
module.exports = {
|
|
53
|
-
// Main AI Core
|
|
54
|
-
AICore,
|
|
55
|
-
aiCore,
|
|
56
|
-
|
|
57
|
-
// Quick access functions
|
|
58
|
-
smartFind,
|
|
59
|
-
smartClick,
|
|
60
|
-
smartType,
|
|
61
|
-
understandPage,
|
|
62
|
-
executeCommand,
|
|
63
|
-
wrapHandler,
|
|
64
|
-
configure,
|
|
65
|
-
|
|
66
|
-
// Individual modules (for advanced usage)
|
|
67
|
-
ElementFinder,
|
|
68
|
-
SelectorHealer,
|
|
69
|
-
PageAnalyzer,
|
|
70
|
-
ActionParser,
|
|
71
|
-
|
|
72
|
-
// Version info
|
|
73
|
-
version: '1.0.0',
|
|
74
|
-
|
|
75
|
-
// Feature flags
|
|
76
|
-
features: {
|
|
77
|
-
smartFind: true,
|
|
78
|
-
autoHealing: true,
|
|
79
|
-
pageAnalysis: true,
|
|
80
|
-
naturalLanguage: true
|
|
81
|
-
}
|
|
82
|
-
};
|