chrometools-mcp 2.2.0 → 2.3.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.
- package/CHANGELOG.md +80 -0
- package/README.md +107 -57
- package/index.js +198 -72
- package/package.json +1 -1
- package/server/tool-definitions.js +41 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,86 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.3.2] - 2025-12-25
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **appendScenarioToFile** - Simplified architecture: MCP server no longer reads test files
|
|
9
|
+
- Removed `FileAppender.validateFile()` and `FileAppender.readFile()` calls
|
|
10
|
+
- Removed `generator.appendTest()` call - no longer merges content server-side
|
|
11
|
+
- Returns only test code (without imports) via `testCode` field
|
|
12
|
+
- Changed `action: "write_file"` → `action: "append_test"` (more accurate description)
|
|
13
|
+
- Claude Code now responsible for reading file, appending test, and writing back
|
|
14
|
+
- Clearer separation of concerns: MCP generates code, Claude Code handles file I/O
|
|
15
|
+
- Location: `index.js:2126-2215`
|
|
16
|
+
|
|
17
|
+
## [2.3.1] - 2024-12-25
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **executeScenario** - Fixed scenario execution when current page URL doesn't match scenario's entryUrl
|
|
21
|
+
- Added automatic navigation to entryUrl before executing scenario
|
|
22
|
+
- Normalizes URLs for comparison (ignores trailing slashes, query params like `nr`, `redirect_ts`)
|
|
23
|
+
- Prevents timeout errors when scenario expects different page than currently open
|
|
24
|
+
- Example: If scenario recorded on `ya.ru` but current page is `ya.ru/search/`, automatically navigates to `ya.ru`
|
|
25
|
+
- Logs navigation events to console for debugging
|
|
26
|
+
- Location: `index.js:1951-1987`
|
|
27
|
+
|
|
28
|
+
- **appendScenarioToFile** - Unified response format with exportScenarioAsCode for better Claude Code compatibility
|
|
29
|
+
- Changed `action: "append_to_file"` → `action: "write_file"` (clearer action)
|
|
30
|
+
- Changed `updatedContent` → `testCode` (same field name as exportScenarioAsCode)
|
|
31
|
+
- Added `content` field (duplicate of testCode for compatibility)
|
|
32
|
+
- Simplified instruction: single-step "Write the testCode..." instead of two-step "Read... then write..."
|
|
33
|
+
- Improved error message when file not found: now suggests using exportScenarioAsCode instead
|
|
34
|
+
- Location: `index.js:2191-2229`
|
|
35
|
+
|
|
36
|
+
## [2.3.0] - 2024-12-25
|
|
37
|
+
|
|
38
|
+
### Breaking Changes
|
|
39
|
+
- **exportScenarioAsCode** - Removed append-to-file functionality to eliminate confusion
|
|
40
|
+
- **REMOVED** parameters: `appendToFile`, `testName`, `insertPosition`, `referenceTestName`
|
|
41
|
+
- Now exclusively creates NEW test files (returns JSON with `action: "create_new_file"`)
|
|
42
|
+
- Returns JSON format with `action`, `suggestedFileName`, `testCode`, `instruction` fields
|
|
43
|
+
- Claude Code writes files based on returned JSON (MCP server no longer writes files directly)
|
|
44
|
+
- Migration: Use new `appendScenarioToFile` tool instead of `appendToFile` parameter
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- **appendScenarioToFile** - NEW tool for appending tests to existing files
|
|
48
|
+
- Parameters: `scenarioName`, `language`, `targetFile` (required)
|
|
49
|
+
- Optional: `testName`, `insertPosition`, `referenceTestName`, `cleanSelectors`, `includeComments`, `generatePageObject`, `pageObjectClassName`
|
|
50
|
+
- Returns JSON with `action: "append_to_file"`, `targetFile`, `updatedContent`, `instruction`
|
|
51
|
+
- Safely appends tests without overwriting existing tests
|
|
52
|
+
- Claude Code writes updated file content based on returned JSON
|
|
53
|
+
- Location: `index.js:2041-2186`, `server/tool-definitions.js:577-628`
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- **exportScenarioAsCode** - Changed return format from plain text to structured JSON
|
|
57
|
+
- Returns: `{action, suggestedFileName, testCode, instruction, pageObject?}`
|
|
58
|
+
- Suggests filename based on scenario name and language
|
|
59
|
+
- Includes clear instructions for Claude Code to create files
|
|
60
|
+
- Location: `index.js:2188-2357`, `server/tool-definitions.js:542-576`
|
|
61
|
+
- **File writing responsibility** - Moved from MCP server to Claude Code
|
|
62
|
+
- MCP tools now return JSON with file content + instructions
|
|
63
|
+
- Claude Code uses Write tool to create/update files
|
|
64
|
+
- Eliminates risk of MCP server directly overwriting files
|
|
65
|
+
|
|
66
|
+
### Documentation
|
|
67
|
+
- **README.md** - Split exportScenarioAsCode documentation into two sections
|
|
68
|
+
- exportScenarioAsCode: For creating new test files
|
|
69
|
+
- appendScenarioToFile: For appending to existing files
|
|
70
|
+
- Updated tool count: 39+ → 40+ tools
|
|
71
|
+
- Added clear examples for both tools with JSON response formats
|
|
72
|
+
- Location: `README.md:11,17,652-806`
|
|
73
|
+
|
|
74
|
+
## [2.2.1] - 2024-12-24
|
|
75
|
+
|
|
76
|
+
### Improved
|
|
77
|
+
- **exportScenarioAsCode** - Clarified tool description to prevent accidental test file overwrites
|
|
78
|
+
- Added explicit warning: default mode (without `appendToFile`) returns code for NEW file
|
|
79
|
+
- Emphasized that `appendToFile` parameter is **REQUIRED** to safely add tests to existing files
|
|
80
|
+
- Updated `appendToFile` parameter description to highlight safety aspect
|
|
81
|
+
- Updated `insertPosition` and `referenceTestName` descriptions to clarify they only work with `appendToFile`
|
|
82
|
+
- Added warning section in README.md documentation
|
|
83
|
+
- Location: `server/tool-definitions.js:544,575-589`, `README.md:667-669`
|
|
84
|
+
|
|
5
85
|
## [2.2.0] - 2025-12-21
|
|
6
86
|
|
|
7
87
|
### Added
|
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@ MCP server for Chrome automation using Puppeteer with persistent browser session
|
|
|
8
8
|
- [Usage](#usage)
|
|
9
9
|
- [AI Optimization Features](#ai-optimization-features) ⭐ **NEW**
|
|
10
10
|
- [Scenario Recorder](#scenario-recorder) ⭐ **NEW** - Visual UI-based recording with smart optimization
|
|
11
|
-
- [Available Tools](#available-tools) - **
|
|
11
|
+
- [Available Tools](#available-tools) - **40+ Tools Total**
|
|
12
12
|
- [AI-Powered Tools](#ai-powered-tools) ⭐ **NEW** - smartFindElement, analyzePage, getAllInteractiveElements, findElementsByText
|
|
13
13
|
- [Core Tools](#1-core-tools) - ping, openBrowser
|
|
14
14
|
- [Interaction Tools](#2-interaction-tools) - click, type, scrollTo
|
|
15
15
|
- [Inspection Tools](#3-inspection-tools) - getElement, getComputedCss, getBoxModel, screenshot
|
|
16
16
|
- [Advanced Tools](#4-advanced-tools) - executeScript, getConsoleLogs, listNetworkRequests, getNetworkRequest, filterNetworkRequests, hover, setStyles, setViewport, getViewport, navigateTo
|
|
17
|
-
- [Recorder Tools](#6-recorder-tools) ⭐ **NEW** - enableRecorder, executeScenario, listScenarios, searchScenarios, getScenarioInfo, deleteScenario, exportScenarioAsCode, generatePageObject
|
|
17
|
+
- [Recorder Tools](#6-recorder-tools) ⭐ **NEW** - enableRecorder, executeScenario, listScenarios, searchScenarios, getScenarioInfo, deleteScenario, exportScenarioAsCode, appendScenarioToFile, generatePageObject
|
|
18
18
|
- [Typical Workflow Example](#typical-workflow-example)
|
|
19
19
|
- [Tool Usage Tips](#tool-usage-tips)
|
|
20
20
|
- [Configuration](#configuration)
|
|
@@ -650,7 +650,7 @@ Delete a scenario and its associated secrets. Searches all projects to find the
|
|
|
650
650
|
- **Returns**: Success confirmation
|
|
651
651
|
|
|
652
652
|
#### exportScenarioAsCode ⭐ **NEW**
|
|
653
|
-
Export recorded scenario as executable test code for
|
|
653
|
+
Export recorded scenario as executable test code for creating a **NEW** test file. Automatically cleans unstable selectors (CSS Modules, styled-components, Emotion). Optionally generates Page Object class. Returns JSON with code and suggested filename - Claude Code will create the file. To add tests to **EXISTING** files, use `appendScenarioToFile` instead.
|
|
654
654
|
|
|
655
655
|
- **Parameters**:
|
|
656
656
|
- `scenarioName` (required): Name of scenario to export
|
|
@@ -659,38 +659,34 @@ Export recorded scenario as executable test code for various frameworks. Automat
|
|
|
659
659
|
- `includeComments` (optional): Include descriptive comments (default: true)
|
|
660
660
|
- `generatePageObject` (optional): Also generate Page Object class for the page (default: false)
|
|
661
661
|
- `pageObjectClassName` (optional): Custom Page Object class name (auto-generated if not provided)
|
|
662
|
-
- `appendToFile` (optional): Path to existing test file to append to (enables **append mode**) ⭐ **NEW**
|
|
663
|
-
- `testName` (optional): Override test name (default: from scenario name) ⭐ **NEW**
|
|
664
|
-
- `insertPosition` (optional): Where to insert: `'end'` (default), `'before'`, `'after'` ⭐ **NEW**
|
|
665
|
-
- `referenceTestName` (optional): Reference test name for before/after insertion ⭐ **NEW**
|
|
666
662
|
|
|
667
|
-
- **Use case**:
|
|
663
|
+
- **Use case**: Create new test files from recorded scenarios with optional Page Objects
|
|
668
664
|
|
|
669
|
-
- **Returns**:
|
|
670
|
-
-
|
|
671
|
-
-
|
|
665
|
+
- **Returns**: JSON with:
|
|
666
|
+
- `action`: `"create_new_file"`
|
|
667
|
+
- `suggestedFileName`: Suggested test filename
|
|
668
|
+
- `testCode`: Full test code with imports
|
|
669
|
+
- `instruction`: Instructions for Claude Code
|
|
670
|
+
- `pageObject` (if `generatePageObject=true`): Page Object code and metadata
|
|
672
671
|
|
|
673
|
-
- **Example 1 - Test only
|
|
672
|
+
- **Example 1 - Test only**:
|
|
674
673
|
```javascript
|
|
675
|
-
// Export scenario as Playwright TypeScript
|
|
674
|
+
// Export scenario as new Playwright TypeScript file
|
|
676
675
|
exportScenarioAsCode({
|
|
677
676
|
scenarioName: "checkout_flow",
|
|
678
|
-
language: "playwright-typescript"
|
|
679
|
-
cleanSelectors: true,
|
|
680
|
-
includeComments: true
|
|
677
|
+
language: "playwright-typescript"
|
|
681
678
|
})
|
|
682
679
|
|
|
683
|
-
// Returns
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
// });
|
|
680
|
+
// Returns JSON:
|
|
681
|
+
{
|
|
682
|
+
"action": "create_new_file",
|
|
683
|
+
"suggestedFileName": "checkout_flow.spec.ts",
|
|
684
|
+
"testCode": "import { test, expect } from '@playwright/test';\n\ntest('checkout_flow', async ({ page }) => {\n await page.goto('https://example.com');\n await page.locator('button[data-testid=\"add-to-cart\"]').click();\n await expect(page).toHaveURL(/checkout/);\n});",
|
|
685
|
+
"instruction": "Create a new test file 'checkout_flow.spec.ts' with the testCode."
|
|
686
|
+
}
|
|
691
687
|
```
|
|
692
688
|
|
|
693
|
-
- **Example 2 - Test + Page Object
|
|
689
|
+
- **Example 2 - Test + Page Object**:
|
|
694
690
|
```javascript
|
|
695
691
|
// Export with Page Object class
|
|
696
692
|
exportScenarioAsCode({
|
|
@@ -702,63 +698,117 @@ Export recorded scenario as executable test code for various frameworks. Automat
|
|
|
702
698
|
|
|
703
699
|
// Returns JSON with both files:
|
|
704
700
|
{
|
|
705
|
-
"
|
|
701
|
+
"action": "create_new_file",
|
|
702
|
+
"suggestedFileName": "login_test.spec.ts",
|
|
706
703
|
"testCode": "import { test } from '@playwright/test';\nimport { LoginPage } from './LoginPage';\n\ntest('login_test', async ({ page }) => {\n const loginPage = new LoginPage(page);\n await loginPage.goto();\n await loginPage.fillEmailInput('user@test.com');\n await loginPage.clickLoginButton();\n});",
|
|
707
|
-
"
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
704
|
+
"pageObject": {
|
|
705
|
+
"code": "import { Page, Locator } from '@playwright/test';\n\nexport class LoginPage { ... }",
|
|
706
|
+
"className": "LoginPage",
|
|
707
|
+
"suggestedFileName": "LoginPage.ts",
|
|
708
|
+
"elementCount": 12
|
|
709
|
+
},
|
|
710
|
+
"instruction": "Create a new test file 'login_test.spec.ts' with the testCode. Also create a Page Object file 'LoginPage.ts' with the pageObject.code."
|
|
711
711
|
}
|
|
712
712
|
```
|
|
713
713
|
|
|
714
|
-
- **
|
|
714
|
+
- **Selector Cleaning**: Automatically removes unstable patterns:
|
|
715
|
+
- CSS Modules: `Button_primary__2x3yZ` → removed
|
|
716
|
+
- Styled-components: `sc-AbCdEf-0` → removed
|
|
717
|
+
- Emotion: `css-1a2b3c4d` → removed
|
|
718
|
+
- Hash suffixes: `component_a1b2c3d` → removed
|
|
719
|
+
- Prefers stable selectors: `data-testid`, `role`, `aria-label`, semantic attributes
|
|
720
|
+
|
|
721
|
+
#### appendScenarioToFile ⭐ **NEW v2.3.0**
|
|
722
|
+
Append recorded scenario as test code to an **EXISTING** test file. Automatically cleans unstable selectors (CSS Modules, styled-components, Emotion). Optionally generates Page Object class. Returns JSON with test code (without imports) - Claude Code will read the file, append the test, and write back. To create **NEW** test files, use `exportScenarioAsCode` instead.
|
|
723
|
+
|
|
724
|
+
- **Parameters**:
|
|
725
|
+
- `scenarioName` (required): Name of scenario to export
|
|
726
|
+
- `language` (required): Target framework - `"playwright-typescript"`, `"playwright-python"`, `"selenium-python"`, `"selenium-java"`
|
|
727
|
+
- `targetFile` (required): Path to existing test file to append to
|
|
728
|
+
- `testName` (optional): Override test name (default: from scenario name)
|
|
729
|
+
- `insertPosition` (optional): Where to insert: `'end'` (default), `'before'`, `'after'`
|
|
730
|
+
- `referenceTestName` (optional): Reference test name for 'before'/'after' insertion
|
|
731
|
+
- `cleanSelectors` (optional): Remove unstable CSS classes (default: true)
|
|
732
|
+
- `includeComments` (optional): Include descriptive comments (default: true)
|
|
733
|
+
- `generatePageObject` (optional): Also generate Page Object class for the page (default: false)
|
|
734
|
+
- `pageObjectClassName` (optional): Custom Page Object class name (auto-generated if not provided)
|
|
735
|
+
|
|
736
|
+
- **Use case**: Add tests to existing test files without overwriting current tests
|
|
737
|
+
|
|
738
|
+
- **Architecture**: MCP server generates only test code (without imports). Claude Code reads the target file, appends the test at the specified position, and writes the file back. This separation ensures MCP doesn't need file system access to test files.
|
|
739
|
+
|
|
740
|
+
- **Returns**: JSON with:
|
|
741
|
+
- `action`: `"append_test"`
|
|
742
|
+
- `targetFile`: Path to file to update
|
|
743
|
+
- `testCode`: Test code only (without imports/headers)
|
|
744
|
+
- `testName`: Name of test to append
|
|
745
|
+
- `insertPosition`: Where to insert test
|
|
746
|
+
- `referenceTestName`: Reference test for 'before'/'after' positioning
|
|
747
|
+
- `instruction`: Instructions for Claude Code to read/append/write
|
|
748
|
+
- `pageObject` (if `generatePageObject=true`): Page Object code and metadata
|
|
749
|
+
|
|
750
|
+
- **Example 1 - Append to end**:
|
|
715
751
|
```javascript
|
|
716
752
|
// Append test to end of existing file
|
|
717
|
-
|
|
753
|
+
appendScenarioToFile({
|
|
718
754
|
scenarioName: "new_feature_test",
|
|
719
755
|
language: "playwright-typescript",
|
|
720
|
-
|
|
756
|
+
targetFile: "./tests/features.spec.ts"
|
|
721
757
|
})
|
|
722
758
|
|
|
723
|
-
// Returns:
|
|
759
|
+
// Returns JSON:
|
|
724
760
|
{
|
|
725
|
-
"
|
|
726
|
-
"
|
|
727
|
-
"
|
|
761
|
+
"action": "append_test",
|
|
762
|
+
"targetFile": "./tests/features.spec.ts",
|
|
763
|
+
"testCode": "test('new_feature_test', async ({ page }) => {\n // Test implementation\n await page.click('#submit');\n await expect(page.locator('.result')).toBeVisible();\n});",
|
|
728
764
|
"testName": "new_feature_test",
|
|
729
765
|
"insertPosition": "end",
|
|
730
|
-
"
|
|
766
|
+
"referenceTestName": null,
|
|
767
|
+
"instruction": "Read file './tests/features.spec.ts', append the testCode at position 'end', then write the file back."
|
|
731
768
|
}
|
|
732
769
|
```
|
|
733
770
|
|
|
734
|
-
- **Example
|
|
771
|
+
- **Example 2 - Insert before specific test**:
|
|
735
772
|
```javascript
|
|
736
773
|
// Insert test before specific test
|
|
737
|
-
|
|
774
|
+
appendScenarioToFile({
|
|
738
775
|
scenarioName: "setup_test",
|
|
739
776
|
language: "selenium-python",
|
|
740
|
-
|
|
777
|
+
targetFile: "./tests/test_suite.py",
|
|
741
778
|
insertPosition: "before",
|
|
742
|
-
referenceTestName: "
|
|
743
|
-
testName: "test_setup_data"
|
|
779
|
+
referenceTestName: "test_main",
|
|
780
|
+
testName: "test_setup_data"
|
|
744
781
|
})
|
|
782
|
+
```
|
|
745
783
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
784
|
+
- **Example 3 - Append with Page Object**:
|
|
785
|
+
```javascript
|
|
786
|
+
// Append test and generate Page Object
|
|
787
|
+
appendScenarioToFile({
|
|
788
|
+
scenarioName: "login_test",
|
|
789
|
+
language: "playwright-typescript",
|
|
790
|
+
targetFile: "./tests/auth.spec.ts",
|
|
791
|
+
generatePageObject: true,
|
|
792
|
+
pageObjectClassName: "LoginPage"
|
|
753
793
|
})
|
|
754
|
-
```
|
|
755
794
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
795
|
+
// Returns JSON with both test code and Page Object:
|
|
796
|
+
{
|
|
797
|
+
"action": "append_test",
|
|
798
|
+
"targetFile": "./tests/auth.spec.ts",
|
|
799
|
+
"testCode": "test('login_test', async ({ page }) => {\n await page.fill('#username', 'user');\n await page.fill('#password', 'pass');\n await page.click('button[type=\"submit\"]');\n});",
|
|
800
|
+
"testName": "login_test",
|
|
801
|
+
"insertPosition": "end",
|
|
802
|
+
"referenceTestName": null,
|
|
803
|
+
"pageObject": {
|
|
804
|
+
"code": "export class LoginPage { ... }",
|
|
805
|
+
"className": "LoginPage",
|
|
806
|
+
"suggestedFileName": "LoginPage.ts",
|
|
807
|
+
"elementCount": 8
|
|
808
|
+
},
|
|
809
|
+
"instruction": "Read file './tests/auth.spec.ts', append the testCode at position 'end', then write the file back. Also create a Page Object file 'LoginPage.ts' with the provided pageObject.code."
|
|
810
|
+
}
|
|
811
|
+
```
|
|
762
812
|
|
|
763
813
|
#### generatePageObject ⭐ **NEW**
|
|
764
814
|
Generate Page Object Model (POM) class from current page structure. Analyzes page, extracts interactive elements, and generates framework-specific code with smart naming and helper methods.
|
package/index.js
CHANGED
|
@@ -1948,6 +1948,44 @@ async function executeToolInternal(name, args) {
|
|
|
1948
1948
|
}
|
|
1949
1949
|
}
|
|
1950
1950
|
|
|
1951
|
+
// Check if current page URL matches scenario's entryUrl
|
|
1952
|
+
// If not, navigate to entryUrl before executing scenario
|
|
1953
|
+
const entryUrl = scenario.metadata?.entryUrl;
|
|
1954
|
+
if (entryUrl) {
|
|
1955
|
+
try {
|
|
1956
|
+
const currentUrl = page.url();
|
|
1957
|
+
|
|
1958
|
+
// Normalize URLs for comparison (remove trailing slashes, hash, some query params)
|
|
1959
|
+
const normalizeUrl = (url) => {
|
|
1960
|
+
try {
|
|
1961
|
+
const urlObj = new URL(url);
|
|
1962
|
+
// Keep protocol, hostname, pathname - ignore some query params like nr, redirect_ts
|
|
1963
|
+
return `${urlObj.protocol}//${urlObj.hostname}${urlObj.pathname}`.replace(/\/$/, '');
|
|
1964
|
+
} catch (e) {
|
|
1965
|
+
return url;
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
const normalizedCurrent = normalizeUrl(currentUrl);
|
|
1970
|
+
const normalizedEntry = normalizeUrl(entryUrl);
|
|
1971
|
+
|
|
1972
|
+
if (normalizedCurrent !== normalizedEntry) {
|
|
1973
|
+
console.error(`[executeScenario] Current URL (${currentUrl}) doesn't match scenario entryUrl (${entryUrl})`);
|
|
1974
|
+
console.error(`[executeScenario] Navigating to entryUrl...`);
|
|
1975
|
+
|
|
1976
|
+
await page.goto(entryUrl, {
|
|
1977
|
+
waitUntil: 'networkidle2',
|
|
1978
|
+
timeout: 30000
|
|
1979
|
+
});
|
|
1980
|
+
|
|
1981
|
+
console.error(`[executeScenario] Navigation completed`);
|
|
1982
|
+
}
|
|
1983
|
+
} catch (navError) {
|
|
1984
|
+
console.error(`[executeScenario] Warning: Failed to navigate to entryUrl: ${navError.message}`);
|
|
1985
|
+
// Continue anyway - scenario might still work
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1951
1989
|
const options = {};
|
|
1952
1990
|
|
|
1953
1991
|
// Pass executeDependencies option if provided
|
|
@@ -2038,7 +2076,7 @@ async function executeToolInternal(name, args) {
|
|
|
2038
2076
|
};
|
|
2039
2077
|
}
|
|
2040
2078
|
|
|
2041
|
-
if (name === "
|
|
2079
|
+
if (name === "appendScenarioToFile") {
|
|
2042
2080
|
const scenario = await loadScenario(args.scenarioName, false, null);
|
|
2043
2081
|
|
|
2044
2082
|
if (!scenario) {
|
|
@@ -2085,83 +2123,157 @@ async function executeToolInternal(name, args) {
|
|
|
2085
2123
|
};
|
|
2086
2124
|
}
|
|
2087
2125
|
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
// Read existing file content
|
|
2095
|
-
const existingContent = FileAppender.readFile(args.appendToFile);
|
|
2126
|
+
try {
|
|
2127
|
+
// Generate test code only (without imports)
|
|
2128
|
+
const testOnly = generator.generateTestOnly(scenario, {
|
|
2129
|
+
...options,
|
|
2130
|
+
testName: args.testName
|
|
2131
|
+
});
|
|
2096
2132
|
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2133
|
+
// Prepare append options for Claude Code
|
|
2134
|
+
const appendOptions = {
|
|
2135
|
+
insertPosition: args.insertPosition || 'end',
|
|
2136
|
+
referenceTestName: args.referenceTestName
|
|
2137
|
+
};
|
|
2101
2138
|
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2139
|
+
// Generate Page Object if requested
|
|
2140
|
+
let pageObjectData = null;
|
|
2141
|
+
if (args.generatePageObject) {
|
|
2142
|
+
try {
|
|
2143
|
+
const entryUrl = scenario.metadata?.entryUrl;
|
|
2144
|
+
if (entryUrl) {
|
|
2145
|
+
let page;
|
|
2146
|
+
try {
|
|
2147
|
+
page = await getLastOpenPage();
|
|
2148
|
+
const currentUrl = page.url();
|
|
2149
|
+
if (currentUrl !== entryUrl) {
|
|
2150
|
+
await page.goto(entryUrl, { waitUntil: 'networkidle2' });
|
|
2151
|
+
}
|
|
2152
|
+
} catch (error) {
|
|
2153
|
+
page = await getOrCreatePage(entryUrl);
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
const pageObjectOptions = {
|
|
2157
|
+
className: args.pageObjectClassName || null,
|
|
2158
|
+
framework: args.language,
|
|
2159
|
+
includeComments: args.includeComments !== false,
|
|
2160
|
+
groupElements: true
|
|
2161
|
+
};
|
|
2162
|
+
|
|
2163
|
+
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
2164
|
+
if (pageObjectResult.success) {
|
|
2165
|
+
// Suggest filename based on className
|
|
2166
|
+
const extension = args.language.includes('typescript') ? '.ts' :
|
|
2167
|
+
args.language.includes('java') ? '.java' : '.py';
|
|
2168
|
+
pageObjectData = {
|
|
2169
|
+
code: pageObjectResult.code,
|
|
2170
|
+
className: pageObjectResult.className,
|
|
2171
|
+
suggestedFileName: `${pageObjectResult.className}${extension}`,
|
|
2172
|
+
elementCount: pageObjectResult.elementCount
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
// Page Object generation failed, continue without it
|
|
2114
2178
|
}
|
|
2179
|
+
}
|
|
2115
2180
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2181
|
+
// Return JSON with instructions for Claude Code to append the test
|
|
2182
|
+
const result = {
|
|
2183
|
+
action: 'append_test',
|
|
2184
|
+
targetFile: args.targetFile,
|
|
2185
|
+
testCode: testOnly, // Only test code, no imports
|
|
2186
|
+
testName: args.testName || scenario.metadata?.name,
|
|
2187
|
+
insertPosition: appendOptions.insertPosition,
|
|
2188
|
+
referenceTestName: appendOptions.referenceTestName,
|
|
2189
|
+
instruction: `Read file '${args.targetFile}', append the testCode at position '${appendOptions.insertPosition}', then write the file back.`
|
|
2190
|
+
};
|
|
2121
2191
|
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2192
|
+
if (pageObjectData) {
|
|
2193
|
+
result.pageObject = pageObjectData;
|
|
2194
|
+
result.instruction += ` Also create a Page Object file '${pageObjectData.suggestedFileName}' with the provided pageObject.code.`;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
return {
|
|
2198
|
+
content: [{
|
|
2199
|
+
type: 'text',
|
|
2200
|
+
text: JSON.stringify(result, null, 2)
|
|
2201
|
+
}]
|
|
2202
|
+
};
|
|
2203
|
+
} catch (error) {
|
|
2204
|
+
return {
|
|
2205
|
+
content: [{
|
|
2206
|
+
type: 'text',
|
|
2207
|
+
text: JSON.stringify({
|
|
2208
|
+
error: error.message,
|
|
2209
|
+
action: 'append_test',
|
|
2210
|
+
targetFile: args.targetFile
|
|
2211
|
+
}, null, 2)
|
|
2212
|
+
}],
|
|
2213
|
+
isError: true
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2127
2217
|
|
|
2128
|
-
|
|
2218
|
+
if (name === "exportScenarioAsCode") {
|
|
2219
|
+
const scenario = await loadScenario(args.scenarioName, false, null);
|
|
2129
2220
|
|
|
2130
|
-
|
|
2131
|
-
|
|
2221
|
+
if (!scenario) {
|
|
2222
|
+
return {
|
|
2223
|
+
content: [{
|
|
2224
|
+
type: 'text',
|
|
2225
|
+
text: JSON.stringify({
|
|
2226
|
+
error: `Scenario "${args.scenarioName}" not found`
|
|
2227
|
+
}, null, 2)
|
|
2228
|
+
}],
|
|
2229
|
+
isError: true
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2132
2232
|
|
|
2233
|
+
// Select generator based on language
|
|
2234
|
+
let generator;
|
|
2235
|
+
const options = {
|
|
2236
|
+
cleanSelectors: args.cleanSelectors !== false, // default true
|
|
2237
|
+
includeComments: args.includeComments !== false, // default true
|
|
2238
|
+
};
|
|
2239
|
+
|
|
2240
|
+
switch (args.language) {
|
|
2241
|
+
case 'playwright-typescript':
|
|
2242
|
+
generator = new PlaywrightTypeScriptGenerator(options);
|
|
2243
|
+
break;
|
|
2244
|
+
case 'playwright-python':
|
|
2245
|
+
generator = new PlaywrightPythonGenerator(options);
|
|
2246
|
+
break;
|
|
2247
|
+
case 'selenium-python':
|
|
2248
|
+
generator = new SeleniumPythonGenerator(options);
|
|
2249
|
+
break;
|
|
2250
|
+
case 'selenium-java':
|
|
2251
|
+
generator = new SeleniumJavaGenerator(options);
|
|
2252
|
+
break;
|
|
2253
|
+
default:
|
|
2133
2254
|
return {
|
|
2134
2255
|
content: [{
|
|
2135
2256
|
type: 'text',
|
|
2136
2257
|
text: JSON.stringify({
|
|
2137
|
-
|
|
2138
|
-
mode: 'append',
|
|
2139
|
-
file: args.appendToFile,
|
|
2140
|
-
testName: args.testName || scenario.metadata?.name,
|
|
2141
|
-
insertPosition: appendOptions.insertPosition,
|
|
2142
|
-
message: `Test '${args.testName || scenario.metadata?.name}' successfully appended to ${args.appendToFile}`
|
|
2143
|
-
}, null, 2)
|
|
2144
|
-
}]
|
|
2145
|
-
};
|
|
2146
|
-
} catch (error) {
|
|
2147
|
-
return {
|
|
2148
|
-
content: [{
|
|
2149
|
-
type: 'text',
|
|
2150
|
-
text: JSON.stringify({
|
|
2151
|
-
success: false,
|
|
2152
|
-
mode: 'append',
|
|
2153
|
-
file: args.appendToFile,
|
|
2154
|
-
error: error.message
|
|
2258
|
+
error: `Unknown language: ${args.language}. Supported: playwright-typescript, playwright-python, selenium-python, selenium-java`
|
|
2155
2259
|
}, null, 2)
|
|
2156
2260
|
}],
|
|
2157
2261
|
isError: true
|
|
2158
2262
|
};
|
|
2159
|
-
}
|
|
2160
2263
|
}
|
|
2161
2264
|
|
|
2162
|
-
//
|
|
2265
|
+
// Generate test code with full imports
|
|
2163
2266
|
const testCode = generator.generate(scenario, options);
|
|
2164
2267
|
|
|
2268
|
+
// Generate suggested filename
|
|
2269
|
+
const testName = scenario.metadata?.name || 'test';
|
|
2270
|
+
const extension = args.language.includes('typescript') ? '.spec.ts' :
|
|
2271
|
+
args.language.includes('java') ? 'Test.java' :
|
|
2272
|
+
args.language.includes('python') ? '_test.py' : '.test.js';
|
|
2273
|
+
const suggestedFileName = args.language.includes('java')
|
|
2274
|
+
? testName.charAt(0).toUpperCase() + testName.slice(1) + 'Test.java'
|
|
2275
|
+
: testName.replace(/\s+/g, '_').toLowerCase() + extension;
|
|
2276
|
+
|
|
2165
2277
|
// If generatePageObject is requested, also generate Page Object class
|
|
2166
2278
|
if (args.generatePageObject) {
|
|
2167
2279
|
try {
|
|
@@ -2205,19 +2317,26 @@ async function executeToolInternal(name, args) {
|
|
|
2205
2317
|
const pageObjectResult = await generatePageObject(page, pageObjectOptions);
|
|
2206
2318
|
|
|
2207
2319
|
if (pageObjectResult.success) {
|
|
2320
|
+
// Suggest Page Object filename
|
|
2321
|
+
const poExtension = args.language.includes('typescript') ? '.ts' :
|
|
2322
|
+
args.language.includes('java') ? '.java' : '.py';
|
|
2323
|
+
const pageObjectFileName = `${pageObjectResult.className}${poExtension}`;
|
|
2324
|
+
|
|
2208
2325
|
// Return both test code and Page Object code
|
|
2209
2326
|
return {
|
|
2210
2327
|
content: [{
|
|
2211
2328
|
type: 'text',
|
|
2212
2329
|
text: JSON.stringify({
|
|
2213
|
-
|
|
2330
|
+
action: 'create_new_file',
|
|
2331
|
+
suggestedFileName: suggestedFileName,
|
|
2214
2332
|
testCode: testCode,
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2333
|
+
pageObject: {
|
|
2334
|
+
code: pageObjectResult.code,
|
|
2335
|
+
className: pageObjectResult.className,
|
|
2336
|
+
suggestedFileName: pageObjectFileName,
|
|
2337
|
+
elementCount: pageObjectResult.elementCount
|
|
2338
|
+
},
|
|
2339
|
+
instruction: `Create a new test file '${suggestedFileName}' with the testCode. Also create a Page Object file '${pageObjectFileName}' with the pageObject.code.`
|
|
2221
2340
|
}, null, 2)
|
|
2222
2341
|
}]
|
|
2223
2342
|
};
|
|
@@ -2227,10 +2346,11 @@ async function executeToolInternal(name, args) {
|
|
|
2227
2346
|
content: [{
|
|
2228
2347
|
type: 'text',
|
|
2229
2348
|
text: JSON.stringify({
|
|
2230
|
-
|
|
2349
|
+
action: 'create_new_file',
|
|
2350
|
+
suggestedFileName: suggestedFileName,
|
|
2231
2351
|
testCode: testCode,
|
|
2232
|
-
|
|
2233
|
-
|
|
2352
|
+
warning: 'Page Object generation failed: ' + (pageObjectResult.error || 'Unknown error'),
|
|
2353
|
+
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
2234
2354
|
}, null, 2)
|
|
2235
2355
|
}]
|
|
2236
2356
|
};
|
|
@@ -2241,10 +2361,11 @@ async function executeToolInternal(name, args) {
|
|
|
2241
2361
|
content: [{
|
|
2242
2362
|
type: 'text',
|
|
2243
2363
|
text: JSON.stringify({
|
|
2244
|
-
|
|
2364
|
+
action: 'create_new_file',
|
|
2365
|
+
suggestedFileName: suggestedFileName,
|
|
2245
2366
|
testCode: testCode,
|
|
2246
|
-
|
|
2247
|
-
|
|
2367
|
+
warning: 'Page Object generation error: ' + error.message,
|
|
2368
|
+
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
2248
2369
|
}, null, 2)
|
|
2249
2370
|
}]
|
|
2250
2371
|
};
|
|
@@ -2255,7 +2376,12 @@ async function executeToolInternal(name, args) {
|
|
|
2255
2376
|
return {
|
|
2256
2377
|
content: [{
|
|
2257
2378
|
type: 'text',
|
|
2258
|
-
text:
|
|
2379
|
+
text: JSON.stringify({
|
|
2380
|
+
action: 'create_new_file',
|
|
2381
|
+
suggestedFileName: suggestedFileName,
|
|
2382
|
+
testCode: testCode,
|
|
2383
|
+
instruction: `Create a new test file '${suggestedFileName}' with the testCode.`
|
|
2384
|
+
}, null, 2)
|
|
2259
2385
|
}]
|
|
2260
2386
|
};
|
|
2261
2387
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrometools-mcp",
|
|
3
|
-
"version": "2.2
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for Chrome automation using Puppeteer. Persistent browser sessions, visual testing, Figma comparison, and design validation. Works seamlessly in WSL, Linux, and macOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -541,7 +541,7 @@ export const toolDefinitions = [
|
|
|
541
541
|
},
|
|
542
542
|
{
|
|
543
543
|
name: "exportScenarioAsCode",
|
|
544
|
-
description: "Export recorded scenario as executable test code for
|
|
544
|
+
description: "Export recorded scenario as executable test code for creating a NEW test file. Automatically cleans unstable selectors (CSS modules, styled-components). Optionally generates Page Object class. Returns JSON with code and suggested filename - Claude Code will create the file. To add tests to EXISTING files, use 'appendScenarioToFile' instead. Scenarios are stored in ~/.config/chrometools-mcp/projects/{projectName}/scenarios/. Use global index at ~/.config/chrometools-mcp/index.json to discover available projects and scenarios.",
|
|
545
545
|
inputSchema: {
|
|
546
546
|
type: "object",
|
|
547
547
|
properties: {
|
|
@@ -570,9 +570,28 @@ export const toolDefinitions = [
|
|
|
570
570
|
type: "string",
|
|
571
571
|
description: "Page Object class name (optional, auto-generated if not provided)"
|
|
572
572
|
},
|
|
573
|
-
|
|
573
|
+
},
|
|
574
|
+
required: ["scenarioName", "language"],
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "appendScenarioToFile",
|
|
579
|
+
description: "Append recorded scenario as test code to an EXISTING test file. Automatically cleans unstable selectors (CSS modules, styled-components). Optionally generates Page Object class. Returns JSON with test code and target file - Claude Code will append to the file without overwriting existing tests. To create NEW test files, use 'exportScenarioAsCode' instead. Scenarios are stored in ~/.config/chrometools-mcp/projects/{projectName}/scenarios/. Use global index at ~/.config/chrometools-mcp/index.json to discover available projects and scenarios.",
|
|
580
|
+
inputSchema: {
|
|
581
|
+
type: "object",
|
|
582
|
+
properties: {
|
|
583
|
+
scenarioName: {
|
|
584
|
+
type: "string",
|
|
585
|
+
description: "Name of scenario to export"
|
|
586
|
+
},
|
|
587
|
+
language: {
|
|
588
|
+
type: "string",
|
|
589
|
+
enum: ["playwright-typescript", "playwright-python", "selenium-python", "selenium-java"],
|
|
590
|
+
description: "Target test framework and language"
|
|
591
|
+
},
|
|
592
|
+
targetFile: {
|
|
574
593
|
type: "string",
|
|
575
|
-
description: "Path to existing test file to append to (
|
|
594
|
+
description: "Path to existing test file to append to (REQUIRED)"
|
|
576
595
|
},
|
|
577
596
|
testName: {
|
|
578
597
|
type: "string",
|
|
@@ -581,14 +600,30 @@ export const toolDefinitions = [
|
|
|
581
600
|
insertPosition: {
|
|
582
601
|
type: "string",
|
|
583
602
|
enum: ["end", "before", "after"],
|
|
584
|
-
description: "Where to insert test: 'end' (default), 'before', or 'after'
|
|
603
|
+
description: "Where to insert test: 'end' (default - after all tests), 'before' (before reference test), or 'after' (after reference test)"
|
|
585
604
|
},
|
|
586
605
|
referenceTestName: {
|
|
587
606
|
type: "string",
|
|
588
|
-
description: "Reference test name for 'before'/'after' insertion"
|
|
607
|
+
description: "Reference test name for 'before'/'after' insertion. Required when insertPosition is 'before' or 'after'"
|
|
608
|
+
},
|
|
609
|
+
cleanSelectors: {
|
|
610
|
+
type: "boolean",
|
|
611
|
+
description: "Remove unstable CSS classes (default: true)"
|
|
612
|
+
},
|
|
613
|
+
includeComments: {
|
|
614
|
+
type: "boolean",
|
|
615
|
+
description: "Include descriptive comments (default: true)"
|
|
616
|
+
},
|
|
617
|
+
generatePageObject: {
|
|
618
|
+
type: "boolean",
|
|
619
|
+
description: "Also generate Page Object class for the page (default: false)"
|
|
620
|
+
},
|
|
621
|
+
pageObjectClassName: {
|
|
622
|
+
type: "string",
|
|
623
|
+
description: "Page Object class name (optional, auto-generated if not provided)"
|
|
589
624
|
},
|
|
590
625
|
},
|
|
591
|
-
required: ["scenarioName", "language"],
|
|
626
|
+
required: ["scenarioName", "language", "targetFile"],
|
|
592
627
|
},
|
|
593
628
|
},
|
|
594
629
|
{
|