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 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) - **39+ Tools Total**
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 various frameworks. Automatically cleans unstable selectors (CSS Modules, styled-components, Emotion). Optionally generates Page Object class for the page. Can append tests to existing files. Searches all projects to find the scenario.
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**: Convert recorded scenarios into maintainable test code with optional Page Objects, or append to existing test suites
663
+ - **Use case**: Create new test files from recorded scenarios with optional Page Objects
668
664
 
669
- - **Returns**:
670
- - **Without `appendToFile`**: Test code as string (or JSON with Page Object)
671
- - **With `appendToFile`**: JSON with `{success, mode: "append", file, testName, message}`
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** (default behavior):
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 clean test code:
684
- // import { test, expect } from '@playwright/test';
685
- //
686
- // test('checkout_flow', async ({ page }) => {
687
- // await page.goto('https://example.com');
688
- // await page.locator('button[data-testid="add-to-cart"]').click();
689
- // await expect(page).toHaveURL(/checkout/);
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** ⭐ **NEW**:
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
- "success": true,
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
- "pageObjectCode": "import { Page, Locator } from '@playwright/test';\n\nexport class LoginPage {\n readonly page: Page;\n readonly emailInput: Locator;\n readonly loginButton: Locator;\n \n constructor(page: Page) {\n this.page = page;\n this.emailInput = page.locator('#email');\n this.loginButton = page.locator('button[type=\"submit\"]');\n }\n \n async goto() {\n await this.page.goto('https://example.com/login');\n }\n \n async fillEmailInput(text: string) {\n await this.emailInput.fill(text);\n }\n \n async clickLoginButton() {\n await this.loginButton.click();\n }\n}",
708
- "pageObjectClassName": "LoginPage",
709
- "framework": "playwright-typescript",
710
- "elementCount": 12
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
- - **Example 3 - Append Mode** ⭐ **NEW**:
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
- exportScenarioAsCode({
753
+ appendScenarioToFile({
718
754
  scenarioName: "new_feature_test",
719
755
  language: "playwright-typescript",
720
- appendToFile: "./tests/features.spec.ts"
756
+ targetFile: "./tests/features.spec.ts"
721
757
  })
722
758
 
723
- // Returns:
759
+ // Returns JSON:
724
760
  {
725
- "success": true,
726
- "mode": "append",
727
- "file": "./tests/features.spec.ts",
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
- "message": "Test 'new_feature_test' successfully appended to ./tests/features.spec.ts"
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 4 - Append with Position Control** ⭐ **NEW**:
771
+ - **Example 2 - Insert before specific test**:
735
772
  ```javascript
736
773
  // Insert test before specific test
737
- exportScenarioAsCode({
774
+ appendScenarioToFile({
738
775
  scenarioName: "setup_test",
739
776
  language: "selenium-python",
740
- appendToFile: "./tests/test_suite.py",
777
+ targetFile: "./tests/test_suite.py",
741
778
  insertPosition: "before",
742
- referenceTestName: "main_test",
743
- testName: "test_setup_data" // Override name
779
+ referenceTestName: "test_main",
780
+ testName: "test_setup_data"
744
781
  })
782
+ ```
745
783
 
746
- // Or insert after specific test
747
- exportScenarioAsCode({
748
- scenarioName: "cleanup",
749
- language: "playwright-python",
750
- appendToFile: "./tests/test_flow.py",
751
- insertPosition: "after",
752
- referenceTestName: "test_main_flow"
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
- - **Selector Cleaning**: Automatically removes unstable patterns:
757
- - CSS Modules: `Button_primary__2x3yZ` → removed
758
- - Styled-components: `sc-AbCdEf-0` → removed
759
- - Emotion: `css-1a2b3c4d` → removed
760
- - Hash suffixes: `component_a1b2c3d` removed
761
- - Prefers stable selectors: `data-testid`, `role`, `aria-label`, semantic attributes
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 === "exportScenarioAsCode") {
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
- // Check if append mode is requested
2089
- if (args.appendToFile) {
2090
- try {
2091
- // Validate file path and extension
2092
- FileAppender.validateFile(args.appendToFile, args.language);
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
- // Check if file is empty - if so, generate full test with imports
2098
- if (FileAppender.isEmpty(existingContent)) {
2099
- const fullCode = generator.generate(scenario, options);
2100
- FileAppender.writeFile(args.appendToFile, fullCode);
2133
+ // Prepare append options for Claude Code
2134
+ const appendOptions = {
2135
+ insertPosition: args.insertPosition || 'end',
2136
+ referenceTestName: args.referenceTestName
2137
+ };
2101
2138
 
2102
- return {
2103
- content: [{
2104
- type: 'text',
2105
- text: JSON.stringify({
2106
- success: true,
2107
- mode: 'append',
2108
- file: args.appendToFile,
2109
- testName: args.testName || scenario.metadata?.name,
2110
- message: `Test written to empty file: ${args.appendToFile}`
2111
- }, null, 2)
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
- // Generate only test function (without imports)
2117
- const testOnly = generator.generateTestOnly(scenario, {
2118
- ...options,
2119
- testName: args.testName
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
- // Append test to existing content
2123
- const appendOptions = {
2124
- insertPosition: args.insertPosition || 'end',
2125
- referenceTestName: args.referenceTestName
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
- const updatedContent = generator.appendTest(existingContent, testOnly, appendOptions);
2218
+ if (name === "exportScenarioAsCode") {
2219
+ const scenario = await loadScenario(args.scenarioName, false, null);
2129
2220
 
2130
- // Write updated content to file
2131
- FileAppender.writeFile(args.appendToFile, updatedContent);
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
- success: true,
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
- // Non-append mode: Generate test code
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
- success: true,
2330
+ action: 'create_new_file',
2331
+ suggestedFileName: suggestedFileName,
2214
2332
  testCode: testCode,
2215
- pageObjectCode: pageObjectResult.code,
2216
- pageObjectClassName: pageObjectResult.className,
2217
- framework: args.language,
2218
- scenarioName: args.scenarioName,
2219
- url: pageObjectResult.url,
2220
- elementCount: pageObjectResult.elementCount
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
- success: true,
2349
+ action: 'create_new_file',
2350
+ suggestedFileName: suggestedFileName,
2231
2351
  testCode: testCode,
2232
- pageObjectCode: null,
2233
- warning: 'Page Object generation failed: ' + (pageObjectResult.error || 'Unknown error')
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
- success: true,
2364
+ action: 'create_new_file',
2365
+ suggestedFileName: suggestedFileName,
2245
2366
  testCode: testCode,
2246
- pageObjectCode: null,
2247
- warning: 'Page Object generation error: ' + error.message
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: testCode
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.0",
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 various frameworks. Automatically cleans unstable selectors (CSS modules, styled-components). Optionally generates Page Object class for the page. Can append to existing test files. 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.",
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
- appendToFile: {
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 (enables append mode)"
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' a reference test"
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
  {