chrometools-mcp 2.5.0 → 3.1.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/CHANGELOG.md +420 -0
- package/COMPONENT_MAPPING_SPEC.md +1217 -0
- package/README.md +406 -38
- package/bridge/bridge-client.js +472 -0
- package/bridge/bridge-service.js +399 -0
- package/bridge/install.js +241 -0
- package/browser/browser-manager.js +107 -2
- package/browser/page-manager.js +226 -69
- package/docs/CHROME_EXTENSION.md +219 -0
- package/docs/PAGE_OBJECT_MODEL_CONCEPT.md +1756 -0
- package/extension/background.js +643 -0
- package/extension/content.js +715 -0
- package/extension/icons/create-icons.js +164 -0
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +58 -0
- package/extension/popup/popup.css +437 -0
- package/extension/popup/popup.html +102 -0
- package/extension/popup/popup.js +415 -0
- package/extension/recorder-overlay.css +93 -0
- package/index.js +3347 -2901
- package/models/BaseInputModel.js +93 -0
- package/models/CheckboxGroupModel.js +199 -0
- package/models/CheckboxModel.js +103 -0
- package/models/ColorInputModel.js +53 -0
- package/models/DateInputModel.js +67 -0
- package/models/RadioGroupModel.js +126 -0
- package/models/RangeInputModel.js +60 -0
- package/models/SelectModel.js +97 -0
- package/models/TextInputModel.js +34 -0
- package/models/TextareaModel.js +59 -0
- package/models/TimeInputModel.js +49 -0
- package/models/index.js +122 -0
- package/package.json +3 -2
- package/pom/apom-converter.js +267 -0
- package/pom/apom-tree-converter.js +515 -0
- package/pom/element-id-generator.js +175 -0
- package/recorder/page-object-generator.js +16 -0
- package/recorder/scenario-executor.js +80 -2
- package/server/tool-definitions.js +839 -713
- package/server/tool-groups.js +1 -1
- package/server/tool-schemas.js +367 -326
- package/server/websocket-bridge.js +447 -0
- package/utils/selector-resolver.js +186 -0
- package/utils/ui-framework-detector.js +392 -0
- package/RELEASE_NOTES_v2.5.0.md +0 -109
- package/npm_publish_output.txt +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Framework Detector
|
|
3
|
+
* Detects and extracts information from various UI component libraries
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect UI framework used by an element
|
|
8
|
+
* @param {HTMLElement} element - DOM element to analyze
|
|
9
|
+
* @returns {Object} Framework information
|
|
10
|
+
*/
|
|
11
|
+
function detectUIFramework(element) {
|
|
12
|
+
const classes = Array.from(element.classList || []);
|
|
13
|
+
const dataAttrs = Array.from(element.attributes || [])
|
|
14
|
+
.filter(attr => attr.name.startsWith('data-'))
|
|
15
|
+
.map(attr => attr.name);
|
|
16
|
+
|
|
17
|
+
// Material-UI (MUI) detection
|
|
18
|
+
if (classes.some(c => c.startsWith('Mui')) ||
|
|
19
|
+
classes.some(c => c.startsWith('MuiInput')) ||
|
|
20
|
+
classes.some(c => c.startsWith('MuiSelect')) ||
|
|
21
|
+
classes.some(c => c.startsWith('MuiButton'))) {
|
|
22
|
+
return {
|
|
23
|
+
name: 'mui',
|
|
24
|
+
version: detectMUIVersion(element),
|
|
25
|
+
component: detectMUIComponent(element, classes)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Ant Design detection
|
|
30
|
+
if (classes.some(c => c.startsWith('ant-')) ||
|
|
31
|
+
dataAttrs.includes('data-antd')) {
|
|
32
|
+
return {
|
|
33
|
+
name: 'antd',
|
|
34
|
+
version: null, // Can be enhanced
|
|
35
|
+
component: detectAntDComponent(element, classes)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Chakra UI detection
|
|
40
|
+
if (classes.some(c => c.startsWith('chakra-')) ||
|
|
41
|
+
dataAttrs.includes('data-chakra-component')) {
|
|
42
|
+
return {
|
|
43
|
+
name: 'chakra',
|
|
44
|
+
version: null,
|
|
45
|
+
component: element.getAttribute('data-chakra-component') || 'unknown'
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Bootstrap detection
|
|
50
|
+
if (classes.some(c => c.startsWith('form-')) ||
|
|
51
|
+
classes.some(c => c.startsWith('btn-')) ||
|
|
52
|
+
classes.includes('form-control') ||
|
|
53
|
+
classes.includes('form-select')) {
|
|
54
|
+
return {
|
|
55
|
+
name: 'bootstrap',
|
|
56
|
+
version: detectBootstrapVersion(element),
|
|
57
|
+
component: detectBootstrapComponent(element, classes)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Vuetify detection
|
|
62
|
+
if (classes.some(c => c.startsWith('v-')) ||
|
|
63
|
+
dataAttrs.includes('data-v-')) {
|
|
64
|
+
return {
|
|
65
|
+
name: 'vuetify',
|
|
66
|
+
version: null,
|
|
67
|
+
component: detectVuetifyComponent(element, classes)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Semantic UI detection
|
|
72
|
+
if (classes.includes('ui') &&
|
|
73
|
+
(classes.includes('input') || classes.includes('dropdown') || classes.includes('button'))) {
|
|
74
|
+
return {
|
|
75
|
+
name: 'semantic-ui',
|
|
76
|
+
version: null,
|
|
77
|
+
component: detectSemanticUIComponent(element, classes)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Default: vanilla HTML
|
|
82
|
+
return {
|
|
83
|
+
name: 'vanilla',
|
|
84
|
+
version: null,
|
|
85
|
+
component: element.tagName.toLowerCase()
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Detect MUI version
|
|
91
|
+
*/
|
|
92
|
+
function detectMUIVersion(element) {
|
|
93
|
+
// MUI v5 uses different class naming than v4
|
|
94
|
+
const classes = Array.from(element.classList || []);
|
|
95
|
+
|
|
96
|
+
// v5: MuiInputBase-root, MuiOutlinedInput-root
|
|
97
|
+
if (classes.some(c => c.match(/^Mui[A-Z][a-z]+Base-/))) {
|
|
98
|
+
return '5.x';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// v4: MuiInput-root, MuiSelect-root
|
|
102
|
+
if (classes.some(c => c.match(/^Mui[A-Z][a-z]+-root$/))) {
|
|
103
|
+
return '4.x';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Detect MUI component type
|
|
111
|
+
*/
|
|
112
|
+
function detectMUIComponent(element, classes) {
|
|
113
|
+
if (classes.some(c => c.includes('TextField'))) return 'TextField';
|
|
114
|
+
if (classes.some(c => c.includes('Select'))) return 'Select';
|
|
115
|
+
if (classes.some(c => c.includes('Autocomplete'))) return 'Autocomplete';
|
|
116
|
+
if (classes.some(c => c.includes('Button'))) return 'Button';
|
|
117
|
+
if (classes.some(c => c.includes('Checkbox'))) return 'Checkbox';
|
|
118
|
+
if (classes.some(c => c.includes('Radio'))) return 'Radio';
|
|
119
|
+
if (classes.some(c => c.includes('Switch'))) return 'Switch';
|
|
120
|
+
return 'unknown';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Detect Ant Design component type
|
|
125
|
+
*/
|
|
126
|
+
function detectAntDComponent(element, classes) {
|
|
127
|
+
if (classes.includes('ant-input')) return 'Input';
|
|
128
|
+
if (classes.includes('ant-select')) return 'Select';
|
|
129
|
+
if (classes.includes('ant-picker')) return 'DatePicker';
|
|
130
|
+
if (classes.includes('ant-btn')) return 'Button';
|
|
131
|
+
if (classes.includes('ant-checkbox')) return 'Checkbox';
|
|
132
|
+
if (classes.includes('ant-radio')) return 'Radio';
|
|
133
|
+
if (classes.includes('ant-switch')) return 'Switch';
|
|
134
|
+
return 'unknown';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Detect Bootstrap version
|
|
139
|
+
*/
|
|
140
|
+
function detectBootstrapVersion(element) {
|
|
141
|
+
// Check for Bootstrap 5 specific classes
|
|
142
|
+
const classes = Array.from(element.classList || []);
|
|
143
|
+
if (classes.includes('form-select')) return '5.x';
|
|
144
|
+
|
|
145
|
+
// Bootstrap 4 and earlier use 'custom-select'
|
|
146
|
+
if (classes.includes('custom-select')) return '4.x';
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Detect Bootstrap component type
|
|
153
|
+
*/
|
|
154
|
+
function detectBootstrapComponent(element, classes) {
|
|
155
|
+
if (classes.includes('form-control')) {
|
|
156
|
+
if (element.tagName === 'SELECT') return 'Select';
|
|
157
|
+
if (element.tagName === 'TEXTAREA') return 'Textarea';
|
|
158
|
+
return 'Input';
|
|
159
|
+
}
|
|
160
|
+
if (classes.includes('form-select')) return 'Select';
|
|
161
|
+
if (classes.includes('form-check-input')) {
|
|
162
|
+
if (element.type === 'checkbox') return 'Checkbox';
|
|
163
|
+
if (element.type === 'radio') return 'Radio';
|
|
164
|
+
}
|
|
165
|
+
if (classes.some(c => c.startsWith('btn'))) return 'Button';
|
|
166
|
+
return 'unknown';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Detect Vuetify component type
|
|
171
|
+
*/
|
|
172
|
+
function detectVuetifyComponent(element, classes) {
|
|
173
|
+
if (classes.includes('v-text-field')) return 'TextField';
|
|
174
|
+
if (classes.includes('v-select')) return 'Select';
|
|
175
|
+
if (classes.includes('v-autocomplete')) return 'Autocomplete';
|
|
176
|
+
if (classes.includes('v-btn')) return 'Button';
|
|
177
|
+
if (classes.includes('v-checkbox')) return 'Checkbox';
|
|
178
|
+
if (classes.includes('v-radio')) return 'Radio';
|
|
179
|
+
if (classes.includes('v-switch')) return 'Switch';
|
|
180
|
+
return 'unknown';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Detect Semantic UI component type
|
|
185
|
+
*/
|
|
186
|
+
function detectSemanticUIComponent(element, classes) {
|
|
187
|
+
if (classes.includes('input')) return 'Input';
|
|
188
|
+
if (classes.includes('dropdown')) return 'Dropdown';
|
|
189
|
+
if (classes.includes('button')) return 'Button';
|
|
190
|
+
if (classes.includes('checkbox')) return 'Checkbox';
|
|
191
|
+
if (classes.includes('radio')) return 'Radio';
|
|
192
|
+
return 'unknown';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Extract options from a select/dropdown element
|
|
197
|
+
* Handles both native <select> and custom UI framework dropdowns
|
|
198
|
+
*/
|
|
199
|
+
function extractSelectOptions(element) {
|
|
200
|
+
const framework = detectUIFramework(element);
|
|
201
|
+
|
|
202
|
+
switch (framework.name) {
|
|
203
|
+
case 'vanilla':
|
|
204
|
+
return extractVanillaSelectOptions(element);
|
|
205
|
+
|
|
206
|
+
case 'mui':
|
|
207
|
+
return extractMUISelectOptions(element);
|
|
208
|
+
|
|
209
|
+
case 'antd':
|
|
210
|
+
return extractAntDSelectOptions(element);
|
|
211
|
+
|
|
212
|
+
case 'chakra':
|
|
213
|
+
return extractChakraSelectOptions(element);
|
|
214
|
+
|
|
215
|
+
case 'bootstrap':
|
|
216
|
+
return extractVanillaSelectOptions(element); // Bootstrap uses native select
|
|
217
|
+
|
|
218
|
+
case 'vuetify':
|
|
219
|
+
return extractVuetifySelectOptions(element);
|
|
220
|
+
|
|
221
|
+
case 'semantic-ui':
|
|
222
|
+
return extractSemanticUISelectOptions(element);
|
|
223
|
+
|
|
224
|
+
default:
|
|
225
|
+
return extractVanillaSelectOptions(element);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Extract options from vanilla HTML <select>
|
|
231
|
+
*/
|
|
232
|
+
function extractVanillaSelectOptions(element) {
|
|
233
|
+
if (element.tagName !== 'SELECT') return null;
|
|
234
|
+
|
|
235
|
+
const options = [];
|
|
236
|
+
let currentGroup = null;
|
|
237
|
+
|
|
238
|
+
Array.from(element.children).forEach((child, idx) => {
|
|
239
|
+
if (child.tagName === 'OPTGROUP') {
|
|
240
|
+
currentGroup = child.label;
|
|
241
|
+
Array.from(child.children).forEach((opt, subIdx) => {
|
|
242
|
+
if (opt.tagName === 'OPTION') {
|
|
243
|
+
options.push({
|
|
244
|
+
value: opt.value,
|
|
245
|
+
text: opt.textContent.trim(),
|
|
246
|
+
index: options.length,
|
|
247
|
+
selected: opt.selected,
|
|
248
|
+
disabled: opt.disabled,
|
|
249
|
+
group: currentGroup
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
} else if (child.tagName === 'OPTION') {
|
|
254
|
+
options.push({
|
|
255
|
+
value: child.value,
|
|
256
|
+
text: child.textContent.trim(),
|
|
257
|
+
index: idx,
|
|
258
|
+
selected: child.selected,
|
|
259
|
+
disabled: child.disabled,
|
|
260
|
+
group: null
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
options,
|
|
267
|
+
selectedIndex: element.selectedIndex,
|
|
268
|
+
selectedValue: element.value,
|
|
269
|
+
selectedText: element.options[element.selectedIndex]?.textContent.trim() || null,
|
|
270
|
+
multiple: element.multiple
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Extract options from MUI Select
|
|
276
|
+
*/
|
|
277
|
+
function extractMUISelectOptions(element) {
|
|
278
|
+
// MUI Select renders native select or custom menu
|
|
279
|
+
const nativeSelect = element.querySelector('select');
|
|
280
|
+
if (nativeSelect) {
|
|
281
|
+
return extractVanillaSelectOptions(nativeSelect);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// For custom MUI Select, options are in menu (not always rendered)
|
|
285
|
+
// We need to look for data attributes or aria-attributes
|
|
286
|
+
const selectedValue = element.getAttribute('aria-activedescendant') ||
|
|
287
|
+
element.querySelector('input')?.value || '';
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
options: [], // Options may not be in DOM until dropdown opens
|
|
291
|
+
selectedValue,
|
|
292
|
+
selectedText: element.textContent.trim(),
|
|
293
|
+
note: 'MUI Select options may require opening the dropdown to extract'
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Extract options from Ant Design Select
|
|
299
|
+
*/
|
|
300
|
+
function extractAntDSelectOptions(element) {
|
|
301
|
+
// Ant Design Select stores value in hidden input
|
|
302
|
+
const input = element.querySelector('.ant-select-selection-item');
|
|
303
|
+
const selectedText = input?.textContent.trim() || '';
|
|
304
|
+
|
|
305
|
+
// Options are rendered in dropdown (may not be in DOM)
|
|
306
|
+
const dropdown = document.querySelector('.ant-select-dropdown:not(.ant-select-dropdown-hidden)');
|
|
307
|
+
const options = [];
|
|
308
|
+
|
|
309
|
+
if (dropdown) {
|
|
310
|
+
dropdown.querySelectorAll('.ant-select-item-option').forEach((opt, idx) => {
|
|
311
|
+
options.push({
|
|
312
|
+
value: opt.getAttribute('data-value') || opt.textContent.trim(),
|
|
313
|
+
text: opt.textContent.trim(),
|
|
314
|
+
index: idx,
|
|
315
|
+
selected: opt.classList.contains('ant-select-item-option-selected'),
|
|
316
|
+
disabled: opt.classList.contains('ant-select-item-option-disabled'),
|
|
317
|
+
group: null
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
options,
|
|
324
|
+
selectedText,
|
|
325
|
+
note: 'Ant Design Select options may require opening the dropdown to extract'
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Extract options from Chakra UI Select
|
|
331
|
+
*/
|
|
332
|
+
function extractChakraSelectOptions(element) {
|
|
333
|
+
// Chakra UI typically uses native select
|
|
334
|
+
const nativeSelect = element.querySelector('select');
|
|
335
|
+
if (nativeSelect) {
|
|
336
|
+
return extractVanillaSelectOptions(nativeSelect);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
options: [],
|
|
341
|
+
note: 'Chakra UI Select options need specific extraction logic'
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Extract options from Vuetify Select
|
|
347
|
+
*/
|
|
348
|
+
function extractVuetifySelectOptions(element) {
|
|
349
|
+
// Vuetify stores selected value in data attributes or input
|
|
350
|
+
const input = element.querySelector('input');
|
|
351
|
+
const selectedText = input?.value || '';
|
|
352
|
+
|
|
353
|
+
// Options are in menu (may not be rendered)
|
|
354
|
+
return {
|
|
355
|
+
options: [],
|
|
356
|
+
selectedText,
|
|
357
|
+
note: 'Vuetify Select options may require opening the dropdown to extract'
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Extract options from Semantic UI Dropdown
|
|
363
|
+
*/
|
|
364
|
+
function extractSemanticUISelectOptions(element) {
|
|
365
|
+
const options = [];
|
|
366
|
+
|
|
367
|
+
element.querySelectorAll('.item').forEach((item, idx) => {
|
|
368
|
+
options.push({
|
|
369
|
+
value: item.getAttribute('data-value') || item.textContent.trim(),
|
|
370
|
+
text: item.textContent.trim(),
|
|
371
|
+
index: idx,
|
|
372
|
+
selected: item.classList.contains('selected'),
|
|
373
|
+
disabled: item.classList.contains('disabled'),
|
|
374
|
+
group: null
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const selectedText = element.querySelector('.text')?.textContent.trim() || '';
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
options,
|
|
382
|
+
selectedText
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Export for use in page context
|
|
387
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
388
|
+
module.exports = {
|
|
389
|
+
detectUIFramework,
|
|
390
|
+
extractSelectOptions
|
|
391
|
+
};
|
|
392
|
+
}
|
package/RELEASE_NOTES_v2.5.0.md
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# Release v2.5.0 - Major Update: 4 New Tools + Critical Fixes
|
|
2
|
-
|
|
3
|
-
## 🎉 New Tools (4)
|
|
4
|
-
|
|
5
|
-
### 1. `selectOption` - Dropdown Selection
|
|
6
|
-
Select options in HTML dropdown elements with intelligent priority-based selection.
|
|
7
|
-
- **Parameters**: `selector`, `value`, `text`, or `index`
|
|
8
|
-
- **Priority**: value → text → index
|
|
9
|
-
- **React/Vue support**: Automatically triggers input/change events
|
|
10
|
-
- Perfect for automated form filling
|
|
11
|
-
|
|
12
|
-
### 2. `drag` - Mouse Drag Emulation
|
|
13
|
-
Drag elements by mouse (click-hold-move-release) in any direction.
|
|
14
|
-
- **8 directions**: up, down, left, right, + 4 diagonals
|
|
15
|
-
- **Use cases**: Interactive maps, Gantt charts, SVG diagrams, canvas
|
|
16
|
-
- **How it works**: Puppeteer's `page.mouse` API for real drag events
|
|
17
|
-
- **NOT for**: Standard overflow scrollbars (use `scrollTo` instead)
|
|
18
|
-
|
|
19
|
-
### 3. `scrollHorizontal` - Horizontal Scrolling
|
|
20
|
-
Scroll elements horizontally for tables, carousels, and wide content.
|
|
21
|
-
- **Directions**: left, right
|
|
22
|
-
- **Amount**: Pixels or 'full' to scroll to end
|
|
23
|
-
- **Behavior**: smooth or auto
|
|
24
|
-
- Returns detailed scroll state
|
|
25
|
-
|
|
26
|
-
### 4. `convertFigmaToCode` ⭐
|
|
27
|
-
Convert Figma designs to React+Tailwind code.
|
|
28
|
-
- **Frameworks**: React, React TypeScript, HTML
|
|
29
|
-
- **Styling**: Tailwind CSS optimized
|
|
30
|
-
- **Input**: Figma file key + node ID
|
|
31
|
-
- **Output**: Clean, semantic component code with AI-generated instructions
|
|
32
|
-
|
|
33
|
-
## 🔥 Critical Fixes
|
|
34
|
-
|
|
35
|
-
### Fixed Tailwind CSS Selector Generation Bug
|
|
36
|
-
**Impact**: ALL AI tools failed on Tailwind/styled-components apps with `SyntaxError: invalid selector`
|
|
37
|
-
|
|
38
|
-
**Affected tools**:
|
|
39
|
-
- ❌ `analyzePage` - couldn't read page state
|
|
40
|
-
- ❌ `findElementsByText` - couldn't find elements
|
|
41
|
-
- ❌ `smartFindElement` - couldn't locate elements
|
|
42
|
-
- ❌ `getAllInteractiveElements` - couldn't list interactive elements
|
|
43
|
-
|
|
44
|
-
**Solution**:
|
|
45
|
-
- New `isTailwindClass()` function filters utility classes with `:`, `/`, `[]`
|
|
46
|
-
- Smart priority: id → data-testid → aria-label → semantic classes
|
|
47
|
-
- CSS.escape() for all selectors
|
|
48
|
-
- **Result**: ✅ Unblocks testing of ALL modern apps (Tailwind, styled-components, CSS modules, Emotion)
|
|
49
|
-
|
|
50
|
-
### Fixed `drag` Tool Implementation
|
|
51
|
-
Previously used `scrollLeft`/`scrollTop` which only works with `overflow: auto/scroll` containers.
|
|
52
|
-
|
|
53
|
-
**Now works with**:
|
|
54
|
-
- ✅ Interactive maps (Google Maps, Leaflet, Mapbox)
|
|
55
|
-
- ✅ Gantt charts and SVG diagrams
|
|
56
|
-
- ✅ Canvas elements with pan/zoom
|
|
57
|
-
- ✅ Custom drag handlers (React DnD, interact.js)
|
|
58
|
-
|
|
59
|
-
### Fixed `analyzePage` SVG Crash
|
|
60
|
-
Fixed `className.split is not a function` error when page contains SVG elements.
|
|
61
|
-
- Now handles both HTML (string) and SVG (SVGAnimatedString) className types
|
|
62
|
-
|
|
63
|
-
## 🔧 Improvements
|
|
64
|
-
|
|
65
|
-
### Better AI Agent Behavior
|
|
66
|
-
- Improved tool descriptions to prevent premature `executeScript` usage
|
|
67
|
-
- `click`, `type`, `analyzePage` emphasized as PRIMARY tools
|
|
68
|
-
- `executeScript` marked as ⚠️ LAST RESORT
|
|
69
|
-
- New "Tool Usage Priority" section in README
|
|
70
|
-
|
|
71
|
-
### Enhanced `analyzePage`
|
|
72
|
-
- Now detects HTML select elements with all options
|
|
73
|
-
- Returns: value, text, index, selected, disabled status
|
|
74
|
-
- Enables smarter `selectOption` usage
|
|
75
|
-
|
|
76
|
-
## 📊 Stats
|
|
77
|
-
|
|
78
|
-
- **Total tools**: 44 (was 40)
|
|
79
|
-
- **Files changed**: 9
|
|
80
|
-
- **Lines added**: +957
|
|
81
|
-
- **Lines removed**: -48
|
|
82
|
-
|
|
83
|
-
## 🚀 Installation
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npx chrometools-mcp
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Or update in your MCP config:
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"mcpServers": {
|
|
93
|
-
"chrometools": {
|
|
94
|
-
"command": "npx",
|
|
95
|
-
"args": ["chrometools-mcp"]
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## 🙏 Contributors
|
|
102
|
-
|
|
103
|
-
Special thanks to users who reported issues:
|
|
104
|
-
- Tailwind CSS selector bug reporter
|
|
105
|
-
- Gantt chart drag testing feedback
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
**Full Changelog**: https://github.com/docentovich/chrometools-mcp/blob/main/CHANGELOG.md
|
package/npm_publish_output.txt
DELETED
|
File without changes
|