chrometools-mcp 2.4.2 → 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 +540 -0
  2. package/COMPONENT_MAPPING_SPEC.md +1217 -0
  3. package/README.md +494 -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/element-finder-utils.js +138 -28
  12. package/extension/background.js +643 -0
  13. package/extension/content.js +715 -0
  14. package/extension/icons/create-icons.js +164 -0
  15. package/extension/icons/icon128.png +0 -0
  16. package/extension/icons/icon16.png +0 -0
  17. package/extension/icons/icon48.png +0 -0
  18. package/extension/manifest.json +58 -0
  19. package/extension/popup/popup.css +437 -0
  20. package/extension/popup/popup.html +102 -0
  21. package/extension/popup/popup.js +415 -0
  22. package/extension/recorder-overlay.css +93 -0
  23. package/figma-tools.js +120 -0
  24. package/index.js +3347 -2518
  25. package/models/BaseInputModel.js +93 -0
  26. package/models/CheckboxGroupModel.js +199 -0
  27. package/models/CheckboxModel.js +103 -0
  28. package/models/ColorInputModel.js +53 -0
  29. package/models/DateInputModel.js +67 -0
  30. package/models/RadioGroupModel.js +126 -0
  31. package/models/RangeInputModel.js +60 -0
  32. package/models/SelectModel.js +97 -0
  33. package/models/TextInputModel.js +34 -0
  34. package/models/TextareaModel.js +59 -0
  35. package/models/TimeInputModel.js +49 -0
  36. package/models/index.js +122 -0
  37. package/package.json +3 -2
  38. package/pom/apom-converter.js +267 -0
  39. package/pom/apom-tree-converter.js +515 -0
  40. package/pom/element-id-generator.js +175 -0
  41. package/recorder/page-object-generator.js +16 -0
  42. package/recorder/scenario-executor.js +80 -2
  43. package/server/tool-definitions.js +839 -656
  44. package/server/tool-groups.js +3 -2
  45. package/server/tool-schemas.js +367 -296
  46. package/server/websocket-bridge.js +447 -0
  47. package/utils/selector-resolver.js +186 -0
  48. package/utils/ui-framework-detector.js +392 -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
+ }