chrometools-mcp 3.3.8 → 3.3.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrometools-mcp",
3
- "version": "3.3.8",
3
+ "version": "3.3.9",
4
4
  "description": "MCP (Model Context Protocol) server for Chrome automation using Puppeteer. Persistent browser sessions, UI framework detection (MUI, Ant Design, etc.), Page Object support, visual testing, Figma comparison. Works seamlessly in WSL, Linux, macOS, and Windows.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -48,6 +48,7 @@
48
48
  "dependencies": {
49
49
  "@modelcontextprotocol/sdk": "^1.20.2",
50
50
  "jimp": "^0.22.12",
51
+ "js-yaml": "^4.1.1",
51
52
  "pixelmatch": "^7.1.0",
52
53
  "puppeteer": "^24.27.0",
53
54
  "ws": "^8.18.0",
@@ -354,9 +354,9 @@ function buildAPOMTree(interactiveOnly = true, viewportOnly = false) {
354
354
  // tabindex (except -1)
355
355
  (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1') ||
356
356
  // contenteditable
357
- el.getAttribute('contenteditable') === 'true'
358
- // Note: We skip event listener check here for performance
359
- // as querySelectorAll can return thousands of elements
357
+ el.getAttribute('contenteditable') === 'true' ||
358
+ // Click event listeners (Angular, React, Vue) via monkey-patched addEventListener tracker
359
+ hasExplicitClickBinding(el)
360
360
  );
361
361
 
362
362
  if (isInteractive && isVisible(el)) {
@@ -597,27 +597,6 @@ function buildAPOMTree(interactiveOnly = true, viewportOnly = false) {
597
597
  };
598
598
  }
599
599
 
600
- /**
601
- * Check if element has click event listeners
602
- */
603
- function hasClickListener(element) {
604
- try {
605
- // Check for getEventListeners (available in Chrome DevTools context)
606
- if (typeof getEventListeners === 'function') {
607
- const listeners = getEventListeners(element);
608
- return listeners && listeners.click && listeners.click.length > 0;
609
- }
610
-
611
- // Fallback: check for common event listener markers
612
- // Note: This is not 100% reliable but catches common cases
613
- return element._events?.click ||
614
- element.__listeners?.click ||
615
- element.__eventListeners?.click;
616
- } catch (e) {
617
- return false;
618
- }
619
- }
620
-
621
600
  /**
622
601
  * Check if tag is a custom element (Web Component / Framework component)
623
602
  */
@@ -732,8 +711,8 @@ function buildAPOMTree(interactiveOnly = true, viewportOnly = false) {
732
711
  return { isInteractive: true, reason: 'cursor-pointer' };
733
712
  }
734
713
 
735
- // 6. Elements with click event listeners
736
- if (hasClickListener(element)) {
714
+ // 6. Elements with click event listeners (framework handlers: Angular, React, Vue)
715
+ if (hasExplicitClickBinding(element)) {
737
716
  return { isInteractive: true, reason: 'event-listener' };
738
717
  }
739
718
 
@@ -33,6 +33,19 @@ export async function generatePageObject(page, options = {}) {
33
33
  // Generate code based on framework
34
34
  const code = await generateCode(finalClassName, elementGroups, pageAnalysis, framework, includeComments);
35
35
 
36
+ // Build structured elements metadata for POM integration
37
+ const allElements = Object.values(elementGroups).flat();
38
+ const uniqueElements = deduplicateElements(allElements);
39
+ const lang = framework.includes('python') ? 'python' : framework.includes('java') ? 'java' : 'typescript';
40
+ const elements = uniqueElements.map(el => ({
41
+ name: sanitizeIdentifier(el.name, lang),
42
+ selector: el.selector,
43
+ tag: el.tag,
44
+ type: el.type,
45
+ methodName: generateMethodName(el, framework),
46
+ methodType: getMethodType(el)
47
+ }));
48
+
36
49
  return {
37
50
  success: true,
38
51
  className: finalClassName,
@@ -41,7 +54,8 @@ export async function generatePageObject(page, options = {}) {
41
54
  elementCount: pageAnalysis.elements.length,
42
55
  groups: Object.keys(elementGroups),
43
56
  framework,
44
- code
57
+ code,
58
+ elements
45
59
  };
46
60
  }
47
61
 
@@ -728,6 +742,36 @@ function generateSeleniumJavaActionMethods(lines, elements) {
728
742
  });
729
743
  }
730
744
 
745
+ /**
746
+ * Helper: Determine method type for element
747
+ * @param {Object} el - Element info
748
+ * @returns {string} - "fill" | "click" | "select"
749
+ */
750
+ function getMethodType(el) {
751
+ if (el.tag === 'select') return 'select';
752
+ if (el.tag === 'input' || el.tag === 'textarea') return 'fill';
753
+ return 'click';
754
+ }
755
+
756
+ /**
757
+ * Helper: Generate method name for element based on framework
758
+ * @param {Object} el - Element info
759
+ * @param {string} framework - Target framework
760
+ * @returns {string} - Method name (e.g., "fillUsername", "clickSubmit", "fill_username")
761
+ */
762
+ function generateMethodName(el, framework) {
763
+ const methodType = getMethodType(el);
764
+ const isPython = framework.includes('python');
765
+ const lang = isPython ? 'python' : framework.includes('java') ? 'java' : 'typescript';
766
+ const name = sanitizeIdentifier(el.name, lang);
767
+
768
+ if (isPython) {
769
+ return `${methodType}_${name}`;
770
+ }
771
+ // TypeScript/Java: camelCase
772
+ return `${methodType}${capitalize(name)}`;
773
+ }
774
+
731
775
  /**
732
776
  * Helper: Capitalize first letter
733
777
  */
@@ -238,7 +238,7 @@ export const toolDefinitions = [
238
238
  },
239
239
  {
240
240
  name: "drag",
241
- description: "Drag element in any direction. For maps, charts, SVG, canvas, sliders. Use scrollHorizontal for scrollbars.",
241
+ description: "Drag element in any direction. For maps, charts, SVG, canvas, sliders. Use mode='synthetic' for JS libraries (frappe-gantt, jQuery UI). Use scrollHorizontal for scrollbars.",
242
242
  inputSchema: {
243
243
  type: "object",
244
244
  properties: {
@@ -246,6 +246,7 @@ export const toolDefinitions = [
246
246
  direction: { type: "string", enum: ["up", "down", "left", "right", "up-left", "up-right", "down-left", "down-right"], description: "Drag direction" },
247
247
  distance: { type: "number", description: "Distance in pixels (default: 100)" },
248
248
  duration: { type: "number", description: "Drag duration in ms (default: 500)" },
249
+ mode: { type: "string", enum: ["native", "synthetic"], description: "Drag mode: 'native' (default, faster) or 'synthetic' (better for JS libraries)" },
249
250
  },
250
251
  required: ["selector", "direction"],
251
252
  },
@@ -685,7 +686,7 @@ export const toolDefinitions = [
685
686
  },
686
687
  {
687
688
  name: "exportScenarioAsCode",
688
- description: "Export scenario as test code for NEW file. Cleans unstable selectors, optionally generates Page Object. Use appendScenarioToFile for existing files.",
689
+ description: "Export scenario as test code for NEW file. Supports Page Object integration: 'generate-integrated' generates POM + test using it, 'use-existing' generates test using existing POM file. Use appendScenarioToFile for existing files.",
689
690
  inputSchema: {
690
691
  type: "object",
691
692
  properties: {
@@ -708,19 +709,28 @@ export const toolDefinitions = [
708
709
  },
709
710
  generatePageObject: {
710
711
  type: "boolean",
711
- description: "Also generate Page Object class for the page (default: false)"
712
+ description: "Also generate Page Object class for the page (default: false). Legacy - use pageObjectMode instead."
712
713
  },
713
714
  pageObjectClassName: {
714
715
  type: "string",
715
716
  description: "Page Object class name (optional, auto-generated if not provided)"
716
717
  },
718
+ pageObjectMode: {
719
+ type: "string",
720
+ enum: ["none", "generate", "generate-integrated", "use-existing"],
721
+ description: "POM integration: 'none' (default), 'generate' (separate POM), 'generate-integrated' (POM + test using it), 'use-existing' (test uses existing POM file)"
722
+ },
723
+ pageObjectFile: {
724
+ type: "string",
725
+ description: "Path to existing POM file (required for 'use-existing' mode)"
726
+ },
717
727
  },
718
728
  required: ["scenarioName", "language"],
719
729
  },
720
730
  },
721
731
  {
722
732
  name: "appendScenarioToFile",
723
- description: "Append scenario as test code to EXISTING file. Cleans unstable selectors, optionally generates Page Object. Use exportScenarioAsCode for new files.",
733
+ description: "Append scenario as test code to EXISTING file. Supports Page Object integration: 'generate-integrated' generates POM + test using it, 'use-existing' generates test using existing POM file. Use exportScenarioAsCode for new files.",
724
734
  inputSchema: {
725
735
  type: "object",
726
736
  properties: {
@@ -760,7 +770,16 @@ export const toolDefinitions = [
760
770
  },
761
771
  generatePageObject: {
762
772
  type: "boolean",
763
- description: "Also generate Page Object class for the page (default: false)"
773
+ description: "Also generate Page Object class for the page (default: false). Legacy - use pageObjectMode instead."
774
+ },
775
+ pageObjectMode: {
776
+ type: "string",
777
+ enum: ["none", "generate", "generate-integrated", "use-existing"],
778
+ description: "POM integration: 'none' (default), 'generate' (separate POM), 'generate-integrated' (POM + test using it), 'use-existing' (test uses existing POM file)"
779
+ },
780
+ pageObjectFile: {
781
+ type: "string",
782
+ description: "Path to existing POM file (required for 'use-existing' mode)"
764
783
  },
765
784
  pageObjectClassName: {
766
785
  type: "string",
@@ -821,4 +840,34 @@ export const toolDefinitions = [
821
840
  required: ["tab"],
822
841
  },
823
842
  },
843
+ {
844
+ name: "loadSwagger",
845
+ description: "Load and parse OpenAPI/Swagger spec from URL or local file. Returns structured summary: endpoints, schemas, auth types, base URL. Supports both OpenAPI 2.0 (Swagger) and 3.x, JSON and YAML formats. Use this first to understand an API before generating models or client code.",
846
+ inputSchema: {
847
+ type: "object",
848
+ properties: {
849
+ source: { type: "string", description: "URL (http/https) or local file path to swagger.json / openapi.yaml" },
850
+ format: { type: "string", enum: ["auto", "json", "yaml"], description: "Spec format. 'auto' (default) detects from extension/content" },
851
+ },
852
+ required: ["source"],
853
+ },
854
+ },
855
+ {
856
+ name: "generateApiModels",
857
+ description: "Generate typed data models from OpenAPI/Swagger spec. Creates TypeScript interfaces/types or Python dataclasses/pydantic/TypedDict from API schemas. Handles $ref resolution, enums, allOf/oneOf, nested objects. Use after loadSwagger to generate model files.",
858
+ inputSchema: {
859
+ type: "object",
860
+ properties: {
861
+ source: { type: "string", description: "URL or file path to OpenAPI spec" },
862
+ language: { type: "string", enum: ["typescript", "python"], description: "Target language for models" },
863
+ format: { type: "string", enum: ["auto", "json", "yaml"], description: "Spec format (default: auto)" },
864
+ style: { type: "string", enum: ["interface", "type"], description: "TypeScript only: 'interface' (default) or 'type' aliases" },
865
+ pythonStyle: { type: "string", enum: ["dataclass", "pydantic", "typeddict"], description: "Python only: 'dataclass' (default), 'pydantic', or 'typeddict'" },
866
+ includeEnums: { type: "boolean", description: "Generate enum types (default: true)" },
867
+ includeValidation: { type: "boolean", description: "Include validation constraints as comments (default: false)" },
868
+ schemas: { type: "array", items: { type: "string" }, description: "Generate only these schemas (default: all)" },
869
+ },
870
+ required: ["source", "language"],
871
+ },
872
+ },
824
873
  ];
@@ -61,6 +61,7 @@ export const DragSchema = z.object({
61
61
  .describe("Direction to drag: vertical (up, down), horizontal (left, right), or diagonal (up-left, up-right, down-left, down-right)"),
62
62
  distance: z.number().min(1).optional().describe("Distance to drag in pixels (default: 100)"),
63
63
  duration: z.number().min(100).optional().describe("Duration of drag operation in milliseconds (default: 500)"),
64
+ mode: z.enum(['native', 'synthetic']).optional().describe("Drag mode: 'native' uses Puppeteer mouse API (default, faster), 'synthetic' dispatches DOM events (better compatibility with JS libraries like frappe-gantt, jQuery UI)"),
64
65
  });
65
66
 
66
67
  export const ScrollHorizontalSchema = z.object({
@@ -341,6 +342,10 @@ export const ExportScenarioAsCodeSchema = z.object({
341
342
  includeComments: z.boolean().optional().describe("Include descriptive comments (default: true)"),
342
343
  generatePageObject: z.boolean().optional().describe("Also generate Page Object class for the page (default: false)"),
343
344
  pageObjectClassName: z.string().optional().describe("Page Object class name (optional, auto-generated if not provided)"),
345
+ pageObjectMode: z.enum(['none', 'generate', 'generate-integrated', 'use-existing']).optional()
346
+ .describe("POM integration: 'none' (default), 'generate' (separate POM, current behavior), 'generate-integrated' (POM + test using it), 'use-existing' (test uses existing POM file)"),
347
+ pageObjectFile: z.string().optional()
348
+ .describe("Path to existing POM file (for 'use-existing' mode)"),
344
349
  directory: z.string().optional().describe("Directory where scenarios are stored (optional)"),
345
350
  appendToFile: z.string().optional().describe("Path to existing test file to append to (enables append mode)"),
346
351
  testName: z.string().optional().describe("Override test name (default: from scenario name)"),
@@ -365,3 +370,27 @@ export const SwitchTabSchema = z.object({
365
370
  ]).describe("Tab identifier: index number or URL pattern to match"),
366
371
  });
367
372
 
373
+ // API / Swagger tools
374
+ export const LoadSwaggerSchema = z.object({
375
+ source: z.string().describe("URL (http/https) or local file path to swagger.json / openapi.yaml"),
376
+ format: z.enum(['auto', 'json', 'yaml']).optional()
377
+ .describe("Spec format. 'auto' (default) detects from extension/content"),
378
+ });
379
+
380
+ export const GenerateApiModelsSchema = z.object({
381
+ source: z.string().describe("URL or file path to OpenAPI spec"),
382
+ language: z.enum(['typescript', 'python']).describe("Target language for models"),
383
+ format: z.enum(['auto', 'json', 'yaml']).optional()
384
+ .describe("Spec format (default: auto)"),
385
+ style: z.enum(['interface', 'type']).optional()
386
+ .describe("TypeScript only: 'interface' (default) or 'type' aliases"),
387
+ pythonStyle: z.enum(['dataclass', 'pydantic', 'typeddict']).optional()
388
+ .describe("Python only: 'dataclass' (default), 'pydantic', or 'typeddict'"),
389
+ includeEnums: z.boolean().optional()
390
+ .describe("Generate enum types (default: true)"),
391
+ includeValidation: z.boolean().optional()
392
+ .describe("Include validation constraints as comments (default: false)"),
393
+ schemas: z.array(z.string()).optional()
394
+ .describe("Generate only these schemas (default: all)"),
395
+ });
396
+