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.
Files changed (48) hide show
  1. package/CHANGELOG.md +420 -0
  2. package/COMPONENT_MAPPING_SPEC.md +1217 -0
  3. package/README.md +406 -38
  4. package/bridge/bridge-client.js +472 -0
  5. package/bridge/bridge-service.js +399 -0
  6. package/bridge/install.js +241 -0
  7. package/browser/browser-manager.js +107 -2
  8. package/browser/page-manager.js +226 -69
  9. package/docs/CHROME_EXTENSION.md +219 -0
  10. package/docs/PAGE_OBJECT_MODEL_CONCEPT.md +1756 -0
  11. package/extension/background.js +643 -0
  12. package/extension/content.js +715 -0
  13. package/extension/icons/create-icons.js +164 -0
  14. package/extension/icons/icon128.png +0 -0
  15. package/extension/icons/icon16.png +0 -0
  16. package/extension/icons/icon48.png +0 -0
  17. package/extension/manifest.json +58 -0
  18. package/extension/popup/popup.css +437 -0
  19. package/extension/popup/popup.html +102 -0
  20. package/extension/popup/popup.js +415 -0
  21. package/extension/recorder-overlay.css +93 -0
  22. package/index.js +3347 -2901
  23. package/models/BaseInputModel.js +93 -0
  24. package/models/CheckboxGroupModel.js +199 -0
  25. package/models/CheckboxModel.js +103 -0
  26. package/models/ColorInputModel.js +53 -0
  27. package/models/DateInputModel.js +67 -0
  28. package/models/RadioGroupModel.js +126 -0
  29. package/models/RangeInputModel.js +60 -0
  30. package/models/SelectModel.js +97 -0
  31. package/models/TextInputModel.js +34 -0
  32. package/models/TextareaModel.js +59 -0
  33. package/models/TimeInputModel.js +49 -0
  34. package/models/index.js +122 -0
  35. package/package.json +3 -2
  36. package/pom/apom-converter.js +267 -0
  37. package/pom/apom-tree-converter.js +515 -0
  38. package/pom/element-id-generator.js +175 -0
  39. package/recorder/page-object-generator.js +16 -0
  40. package/recorder/scenario-executor.js +80 -2
  41. package/server/tool-definitions.js +839 -713
  42. package/server/tool-groups.js +1 -1
  43. package/server/tool-schemas.js +367 -326
  44. package/server/websocket-bridge.js +447 -0
  45. package/utils/selector-resolver.js +186 -0
  46. package/utils/ui-framework-detector.js +392 -0
  47. package/RELEASE_NOTES_v2.5.0.md +0 -109
  48. 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
+ }
@@ -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
File without changes