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
@@ -159,10 +159,22 @@ async function executeSingleScenario(scenario, page, params = {}, options = {})
159
159
  };
160
160
 
161
161
  try {
162
- for (const action of scenario.chain) {
162
+ for (let i = 0; i < scenario.chain.length; i++) {
163
+ const action = scenario.chain[i];
164
+
163
165
  // Substitute parameters in action
164
166
  const resolvedAction = substituteParameters(action, params);
165
167
 
168
+ // Special handling for openTab with empty URL: look ahead to next action's URL
169
+ if (resolvedAction.type === 'openTab' && (!resolvedAction.data.url || resolvedAction.data.url === '')) {
170
+ const nextAction = scenario.chain[i + 1];
171
+ if (nextAction && nextAction.tabUrl && nextAction.tabUrl !== '') {
172
+ // Next action has a real URL, use it for openTab
173
+ debugLog(`[Executor] openTab has empty URL, using next action's URL: ${nextAction.tabUrl}`);
174
+ resolvedAction.data.url = nextAction.tabUrl;
175
+ }
176
+ }
177
+
166
178
  // Execute action with retry
167
179
  const actionResult = await executeActionWithRetry(
168
180
  resolvedAction,
@@ -178,6 +190,11 @@ async function executeSingleScenario(scenario, page, params = {}, options = {})
178
190
  return result;
179
191
  }
180
192
 
193
+ // Update page if action returned a new page (e.g., openTab switched tabs)
194
+ if (actionResult.page) {
195
+ page = actionResult.page;
196
+ }
197
+
181
198
  // Store outputs if action produces any
182
199
  if (actionResult.output) {
183
200
  Object.assign(result.outputs, actionResult.output);
@@ -270,6 +287,7 @@ async function executeActionWithRetry(action, page, maxRetries, timeout) {
270
287
 
271
288
  result.success = true;
272
289
  result.output = actionResult.output;
290
+ result.page = actionResult.page; // Pass through the updated page
273
291
  attemptInfo.success = true;
274
292
  result.errorDetails.attempts.push(attemptInfo);
275
293
  return result;
@@ -491,7 +509,7 @@ function formatDetailedError(action, errorDetails) {
491
509
  * Execute single action
492
510
  */
493
511
  async function executeAction(action, page, timeout) {
494
- const result = { output: null };
512
+ const result = { output: null, page: page }; // Return page in result
495
513
 
496
514
  switch (action.type) {
497
515
  case 'click':
@@ -538,6 +556,14 @@ async function executeAction(action, page, timeout) {
538
556
  result.output = await executeExtract(action, page);
539
557
  break;
540
558
 
559
+ case 'openTab':
560
+ // openTab may return a new page to switch to
561
+ const newPage = await executeOpenTab(action, page, timeout);
562
+ if (newPage) {
563
+ result.page = newPage;
564
+ }
565
+ break;
566
+
541
567
  default:
542
568
  throw new Error(`Unknown action type: ${action.type}`);
543
569
  }
@@ -948,6 +974,58 @@ function substituteParameters(action, params) {
948
974
  return resolved;
949
975
  }
950
976
 
977
+ /**
978
+ * Open a tab with URL and optionally switch to it
979
+ * This ensures the tab exists during playback
980
+ */
981
+ async function executeOpenTab(action, page, timeout) {
982
+ const { url, switchToTab } = action.data;
983
+
984
+ // Skip openTab actions with empty URL or about:blank (after look-ahead failed to find URL)
985
+ if (!url || url === '' || url === 'about:blank') {
986
+ debugLog(`[openTab] Skipping empty/blank tab switch - keeping current page`);
987
+ return null; // Don't switch, keep current page
988
+ }
989
+
990
+ // Wait for previous tab's processes to complete before switching
991
+ // This prevents issues with pending navigations, AJAX calls, or form submissions
992
+ debugLog(`[openTab] Waiting 500ms for previous tab to settle...`);
993
+ await new Promise(resolve => setTimeout(resolve, 500));
994
+
995
+ debugLog(`[openTab] Opening tab: ${url}, switchToTab: ${switchToTab}`);
996
+
997
+ // Get browser instance
998
+ const browser = page.browser();
999
+
1000
+ // Check if tab with this URL already exists
1001
+ const pages = await browser.pages();
1002
+ let targetPage = pages.find(p => p.url() === url);
1003
+
1004
+ if (targetPage) {
1005
+ // Tab already exists, just switch to it
1006
+ debugLog(`[openTab] Tab with URL ${url} already exists, switching to it`);
1007
+ } else {
1008
+ // Create new tab
1009
+ targetPage = await browser.newPage();
1010
+
1011
+ await targetPage.goto(url, {
1012
+ waitUntil: 'domcontentloaded', // Less strict for sites with continuous loading
1013
+ timeout
1014
+ });
1015
+
1016
+ debugLog(`[openTab] New tab created: ${url}`);
1017
+ }
1018
+
1019
+ // If switchToTab is true, bring the tab to front and return the new page
1020
+ if (switchToTab && targetPage) {
1021
+ await targetPage.bringToFront();
1022
+ debugLog(`[openTab] Switched to tab: ${url}`);
1023
+ return targetPage; // Return the new page for subsequent actions
1024
+ }
1025
+
1026
+ return null; // Don't switch page if switchToTab is false
1027
+ }
1028
+
951
1029
  /**
952
1030
  * Substitute {{param}} in string
953
1031
  */