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/CHANGELOG.md +40 -0
- package/README.md +129 -24
- package/SPEC-pom-integration.md +227 -0
- package/SPEC-swagger-api-tools.md +3101 -0
- package/index.js +503 -198
- package/package.json +2 -1
- package/pom/apom-tree-converter.js +5 -26
- package/recorder/page-object-generator.js +45 -1
- package/server/tool-definitions.js +54 -5
- package/server/tool-schemas.js +29 -0
- package/test-swagger-phase1.mjs +959 -0
- package/utils/api-generators/api-models-python.js +448 -0
- package/utils/api-generators/api-models-typescript.js +375 -0
- package/utils/code-generators/code-generator-base.js +111 -6
- package/utils/code-generators/playwright-python.js +74 -0
- package/utils/code-generators/playwright-typescript.js +69 -0
- package/utils/code-generators/pom-integrator.js +373 -0
- package/utils/code-generators/selenium-java.js +72 -0
- package/utils/code-generators/selenium-python.js +75 -0
- package/utils/hints-generator.js +114 -19
- package/utils/openapi/helpers.js +25 -0
- package/utils/openapi/parser.js +448 -0
- package/utils/openapi/ref-resolver.js +149 -0
- package/utils/openapi/type-mapper.js +174 -0
- package/nul +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrometools-mcp",
|
|
3
|
-
"version": "3.3.
|
|
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
|
-
//
|
|
359
|
-
|
|
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 (
|
|
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.
|
|
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.
|
|
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
|
];
|
package/server/tool-schemas.js
CHANGED
|
@@ -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
|
+
|