chrometools-mcp 1.7.1 → 1.8.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,98 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.8.2] - 2025-12-19
6
+
7
+ ### Fixed
8
+ - **Code Generator Bugs** - Fixed multiple issues in test code generators
9
+ - **Python generators**: Fixed comment syntax - now use `#` instead of `//`
10
+ - **Python generators**: Moved `import re` to top of file instead of inline
11
+ - **Java generator**: Fixed variable name conflicts - now uses unique names (`typeElement`, `hoverElement`, etc.) instead of reusing `element`
12
+ - **Java generator**: Added missing imports (`JavascriptExecutor`, `Select`)
13
+ - **All generators**: Implemented language-specific comment generation via `generateComment()` method
14
+ - Location: `utils/code-generators/code-generator-base.js`, `playwright-python.js`, `selenium-python.js`, `selenium-java.js`
15
+
16
+ ## [1.8.1] - 2025-12-19
17
+
18
+ ### Added
19
+ - **Smart Project Directory Detection** - Scenarios now automatically save to the correct project directory
20
+ - Auto-detection cascade: `CLAUDE_PROJECT_DIR` env var → `PROJECT_DIR` env var → Git root → current working directory
21
+ - Optional `directory` parameter on all recorder tools (`enableRecorder`, `executeScenario`, `listScenarios`, `searchScenarios`, `getScenarioInfo`, `deleteScenario`, `exportScenarioAsCode`)
22
+ - Session memory: once directory is set (explicitly or auto-detected), it's remembered for the entire MCP server session
23
+ - Solves issue where scenarios were saved to unpredictable locations based on where MCP process was launched
24
+ - Location: `utils/project-detector.js`, `index.js:97-115`
25
+
26
+ ### Changed
27
+ - **All recorder storage functions now accept `baseDir` parameter** - Breaking change for direct API usage
28
+ - Updated: `saveScenario()`, `loadScenario()`, `listScenarios()`, `searchScenarios()`, `deleteScenario()`, `loadIndex()`, and all other storage functions
29
+ - MCP tool users: no breaking changes, just new optional `directory` parameter
30
+ - Location: `recorder/scenario-storage.js`, `recorder/scenario-executor.js`, `recorder/recorder-script.js`
31
+
32
+ ## [1.8.0] - 2025-12-19
33
+
34
+ ### Added
35
+ - **Test Code Generation** - New MCP tool `exportScenarioAsCode` for generating executable test code from recorded scenarios
36
+ - Supports 4 test frameworks: Playwright (TypeScript/Python), Selenium (Python/Java)
37
+ - Automatic selector cleaning - removes unstable CSS classes (CSS Modules, styled-components, Emotion, hashed classes)
38
+ - Generates clean, readable test code with comments
39
+ - Smart selector stability analysis with pattern-based detection
40
+ - Fallback selector selection - chooses most stable selector from fallbacks
41
+ - Location: `utils/code-generators/`, `utils/selector-cleaner.js`
42
+
43
+ **Unstable patterns detected:**
44
+ - CSS Modules: `Button_primary__2x3yZ`
45
+ - Styled-components: `sc-AbCdEf-0`
46
+ - Emotion: `css-1a2b3c4d`
47
+ - Hash suffixes: `component_a1b2c3d`
48
+ - Random hashes: `_1a2b3c4d`
49
+
50
+ **Usage:**
51
+ ```javascript
52
+ exportScenarioAsCode('checkout', {
53
+ language: 'playwright-typescript',
54
+ cleanSelectors: true,
55
+ includeComments: true
56
+ })
57
+ ```
58
+
59
+ ## [1.7.4] - 2025-12-19
60
+
61
+ ### Changed
62
+ - **executeScenario auto-opens browser** - No longer throws error when no page is open
63
+ - Automatically opens browser at scenario's `entryUrl` if no page is currently open
64
+ - Eliminates need to manually call `openBrowser` before executing scenarios
65
+ - Improves user experience by treating browser opening as a side effect
66
+ - Falls back gracefully: shows error only if scenario has no `entryUrl`
67
+ - Location: `index.js:3417-3469`
68
+
69
+ ## [1.7.3] - 2025-12-19
70
+
71
+ ### Fixed
72
+ - **Fixed CSS selector generation crash** - `analyzePage` now handles attribute values with special characters
73
+ - Added `isSafeSelectorValue()` function to validate attribute values before using in selectors
74
+ - Filters out attributes containing problematic characters: `["'\\[]{}()]`
75
+ - Added try-catch blocks to prevent selector syntax errors
76
+ - Example: `button[data-counter="["b"]"]` (invalid) is now skipped
77
+ - Location: `element-finder-utils.js:302-360`
78
+
79
+ ### Improved
80
+ - **Recorder now skips hidden elements** - Prevents recording actions on invisible elements
81
+ - Added `isElementVisible()` function to check element visibility before recording
82
+ - Checks: offsetWidth/Height, display, visibility, opacity
83
+ - Applies to all event types: click, type, select, upload, hover, drag
84
+ - Prevents scenarios with duplicate/invisible element actions (e.g., Yandex search with hidden input)
85
+ - Console logs when skipping hidden elements for debugging
86
+ - Location: `recorder/recorder-script.js:1173-1188`
87
+
88
+ ## [1.7.2] - 2025-12-16
89
+
90
+ ### Added
91
+ - **Figma API Token Setup documentation** - Added comprehensive guide on how to obtain and configure Figma Personal Access Token
92
+ - Step-by-step instructions for getting token from Figma account settings
93
+ - Configuration examples for both Claude Desktop and Claude Code
94
+ - Environment variable setup (`FIGMA_TOKEN`)
95
+ - Note about alternative parameter-based token passing
96
+
5
97
  ## [1.7.1] - 2025-12-15
6
98
 
7
99
  ### Performance
package/README.md CHANGED
@@ -14,7 +14,7 @@ MCP server for Chrome automation using Puppeteer with persistent browser session
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
17
+ - [Recorder Tools](#6-recorder-tools) ⭐ **NEW** - enableRecorder, executeScenario, listScenarios, searchScenarios, getScenarioInfo, deleteScenario, exportScenarioAsCode
18
18
  - [Typical Workflow Example](#typical-workflow-example)
19
19
  - [Tool Usage Tips](#tool-usage-tips)
20
20
  - [Configuration](#configuration)
@@ -506,9 +506,32 @@ Extract detailed design specifications from Figma including text content, colors
506
506
 
507
507
  ### 6. Recorder Tools ⭐ NEW
508
508
 
509
+ **Directory Management**: All recorder tools support an optional `directory` parameter to specify where scenarios are stored. If not provided, the directory is auto-detected using this cascade:
510
+ 1. `CLAUDE_PROJECT_DIR` environment variable (set by Claude Code)
511
+ 2. `PROJECT_DIR` environment variable (custom)
512
+ 3. Git repository root (detected via `git rev-parse --show-toplevel`)
513
+ 4. Current working directory (fallback)
514
+
515
+ Once a directory is set (explicitly or auto-detected), it's remembered for the entire MCP server session.
516
+
517
+ **Example**:
518
+ ```javascript
519
+ // Let it auto-detect (recommended)
520
+ enableRecorder()
521
+
522
+ // Or specify explicitly
523
+ enableRecorder({ directory: "/path/to/project" })
524
+
525
+ // Later calls reuse the same directory automatically
526
+ executeScenario({ name: "test" }) // Uses remembered directory
527
+ ```
528
+
529
+ ---
530
+
509
531
  #### enableRecorder
510
532
  Inject visual recorder UI widget into the current page.
511
- - **Parameters**: None
533
+ - **Parameters**:
534
+ - `directory` (optional): Directory to save scenarios (auto-detected if not provided)
512
535
  - **Use case**: Start recording user interactions visually
513
536
  - **Returns**: Success status
514
537
  - **Features**:
@@ -524,6 +547,7 @@ Execute a previously recorded scenario by name.
524
547
  - `name` (required): Scenario name
525
548
  - `parameters` (optional): Runtime parameters (e.g., { email: "user@test.com" })
526
549
  - `executeDependencies` (optional): Execute dependencies before running scenario (default: true)
550
+ - `directory` (optional): Directory where scenarios are stored (auto-detected if not provided)
527
551
  - **Use case**: Run automated test scenarios
528
552
  - **Returns**: Execution result with success/failure status
529
553
  - **Features**:
@@ -541,7 +565,8 @@ Execute a previously recorded scenario by name.
541
565
 
542
566
  #### listScenarios
543
567
  Get all available scenarios with metadata.
544
- - **Parameters**: None
568
+ - **Parameters**:
569
+ - `directory` (optional): Directory where scenarios are stored (auto-detected if not provided)
545
570
  - **Use case**: Browse recorded scenarios
546
571
  - **Returns**: Array of scenarios with names, descriptions, tags, timestamps
547
572
 
@@ -550,6 +575,7 @@ Search scenarios by text or tags.
550
575
  - **Parameters**:
551
576
  - `text` (optional): Search in name/description
552
577
  - `tags` (optional): Array of tags to filter
578
+ - `directory` (optional): Directory where scenarios are stored (auto-detected if not provided)
553
579
  - **Use case**: Find specific scenarios
554
580
  - **Returns**: Matching scenarios
555
581
 
@@ -558,15 +584,59 @@ Get detailed information about a scenario.
558
584
  - **Parameters**:
559
585
  - `name` (required): Scenario name
560
586
  - `includeSecrets` (optional): Include secret values (default: false)
587
+ - `directory` (optional): Directory where scenarios are stored (auto-detected if not provided)
561
588
  - **Use case**: Inspect scenario actions and dependencies
562
589
  - **Returns**: Full scenario details (actions, metadata, dependencies)
563
590
 
564
591
  #### deleteScenario
565
592
  Delete a scenario and its associated secrets.
566
- - **Parameters**: `name` (required)
593
+ - **Parameters**:
594
+ - `name` (required): Scenario name
595
+ - `directory` (optional): Directory where scenarios are stored (auto-detected if not provided)
567
596
  - **Use case**: Clean up unused scenarios
568
597
  - **Returns**: Success confirmation
569
598
 
599
+ #### exportScenarioAsCode ⭐ **NEW**
600
+ Export recorded scenario as executable test code for various frameworks. Automatically cleans unstable selectors (CSS Modules, styled-components, Emotion).
601
+
602
+ - **Parameters**:
603
+ - `scenarioName` (required): Name of scenario to export
604
+ - `language` (required): Target framework - `"playwright-typescript"`, `"playwright-python"`, `"selenium-python"`, `"selenium-java"`
605
+ - `cleanSelectors` (optional): Remove unstable CSS classes (default: true)
606
+ - `includeComments` (optional): Include descriptive comments (default: true)
607
+ - `directory` (optional): Directory where scenarios are stored (auto-detected if not provided)
608
+
609
+ - **Use case**: Convert recorded scenarios into maintainable test code
610
+
611
+ - **Returns**: Generated test code as string
612
+
613
+ - **Example**:
614
+ ```javascript
615
+ // Export scenario as Playwright TypeScript
616
+ exportScenarioAsCode({
617
+ scenarioName: "checkout_flow",
618
+ language: "playwright-typescript",
619
+ cleanSelectors: true,
620
+ includeComments: true
621
+ })
622
+
623
+ // Returns clean test code:
624
+ // import { test, expect } from '@playwright/test';
625
+ //
626
+ // test('checkout_flow', async ({ page }) => {
627
+ // await page.goto('https://example.com');
628
+ // await page.locator('button[data-testid="add-to-cart"]').click();
629
+ // await expect(page).toHaveURL(/checkout/);
630
+ // });
631
+ ```
632
+
633
+ - **Selector Cleaning**: Automatically removes unstable patterns:
634
+ - CSS Modules: `Button_primary__2x3yZ` → removed
635
+ - Styled-components: `sc-AbCdEf-0` → removed
636
+ - Emotion: `css-1a2b3c4d` → removed
637
+ - Hash suffixes: `component_a1b2c3d` → removed
638
+ - Prefers stable selectors: `data-testid`, `role`, `aria-label`, semantic attributes
639
+
570
640
  ---
571
641
 
572
642
  ## Typical Workflow Example
@@ -667,6 +737,54 @@ If you don't need to see the browser window, you can use xvfb (virtual X server)
667
737
 
668
738
  This runs Chrome in GUI mode but on a virtual display (window is not visible).
669
739
 
740
+ ### Figma API Token Setup
741
+
742
+ To use Figma tools, you need to configure your Figma Personal Access Token.
743
+
744
+ **How to get your Figma token:**
745
+ 1. Go to your Figma account settings: https://www.figma.com/settings
746
+ 2. Scroll down to "Personal access tokens"
747
+ 3. Click "Create a new personal access token"
748
+ 4. Give it a name (e.g., "chrometools-mcp")
749
+ 5. Copy the generated token
750
+
751
+ **Add token to MCP configuration:**
752
+
753
+ **Claude Desktop** (`~/.claude/mcp_config.json` or `~/AppData/Roaming/Claude/mcp_config.json` on Windows):
754
+
755
+ ```json
756
+ {
757
+ "mcpServers": {
758
+ "chrometools": {
759
+ "command": "npx",
760
+ "args": ["-y", "chrometools-mcp"],
761
+ "env": {
762
+ "FIGMA_TOKEN": "your-figma-token-here"
763
+ }
764
+ }
765
+ }
766
+ }
767
+ ```
768
+
769
+ **Claude Code** (`~/.claude.json`):
770
+
771
+ ```json
772
+ {
773
+ "mcpServers": {
774
+ "chrometools": {
775
+ "type": "stdio",
776
+ "command": "npx",
777
+ "args": ["-y", "chrometools-mcp"],
778
+ "env": {
779
+ "FIGMA_TOKEN": "your-figma-token-here"
780
+ }
781
+ }
782
+ }
783
+ }
784
+ ```
785
+
786
+ **Note:** Alternatively, you can pass the token directly in each Figma tool call using the `figmaToken` parameter, but using the environment variable is more convenient.
787
+
670
788
  ---
671
789
 
672
790
  ## WSL Setup Guide
@@ -295,6 +295,16 @@ function scoreInputField(element, context, description) {
295
295
  return score;
296
296
  }
297
297
 
298
+ /**
299
+ * Check if attribute value is safe to use in CSS selector
300
+ * Returns false if value contains characters that could break selector syntax
301
+ */
302
+ function isSafeSelectorValue(value) {
303
+ if (!value || typeof value !== 'string') return false;
304
+ // Check for problematic characters: quotes, brackets, backslashes
305
+ return !/["'\\[\]{}()]/.test(value);
306
+ }
307
+
298
308
  /**
299
309
  * Generate unique CSS selector for an element
300
310
  */
@@ -320,23 +330,32 @@ function getUniqueSelectorInPage(element) {
320
330
  }
321
331
  }
322
332
 
323
- // Try name attribute
324
- if (element.name) {
333
+ // Try name attribute (only if value is safe)
334
+ if (element.name && isSafeSelectorValue(element.name)) {
325
335
  const selector = `${element.tagName.toLowerCase()}[name="${element.name}"]`;
326
- if (document.querySelectorAll(selector).length === 1) {
327
- return selector;
336
+ try {
337
+ if (document.querySelectorAll(selector).length === 1) {
338
+ return selector;
339
+ }
340
+ } catch (e) {
341
+ // Invalid selector, skip
328
342
  }
329
343
  }
330
344
 
331
- // Try data attributes
345
+ // Try data attributes (only if values are safe)
332
346
  const dataAttrs = Array.from(element.attributes)
333
- .filter(attr => attr.name.startsWith('data-'))
347
+ .filter(attr => attr.name.startsWith('data-') && isSafeSelectorValue(attr.value))
334
348
  .slice(0, 2);
335
349
 
336
350
  for (const attr of dataAttrs) {
337
351
  const selector = `${element.tagName.toLowerCase()}[${attr.name}="${attr.value}"]`;
338
- if (document.querySelectorAll(selector).length === 1) {
339
- return selector;
352
+ try {
353
+ if (document.querySelectorAll(selector).length === 1) {
354
+ return selector;
355
+ }
356
+ } catch (e) {
357
+ // Invalid selector, skip
358
+ continue;
340
359
  }
341
360
  }
342
361
 
package/index.js CHANGED
@@ -47,6 +47,18 @@ import {
47
47
  deleteScenario
48
48
  } from './recorder/scenario-storage.js';
49
49
 
50
+ // Import Project Detection
51
+ import { detectProjectRoot } from './utils/project-detector.js';
52
+
53
+ // Import Code Generators
54
+ import { PlaywrightTypeScriptGenerator } from './utils/code-generators/playwright-typescript.js';
55
+ import { PlaywrightPythonGenerator } from './utils/code-generators/playwright-python.js';
56
+ import { SeleniumPythonGenerator } from './utils/code-generators/selenium-python.js';
57
+ import { SeleniumJavaGenerator } from './utils/code-generators/selenium-java.js';
58
+
59
+ // Global state for scenarios directory
60
+ let currentScenariosDirectory = null;
61
+
50
62
  // Import Figma tools
51
63
  import {
52
64
  parseFigmaUrl,
@@ -76,6 +88,32 @@ const isWSL = (() => {
76
88
  // Detect Windows environment (including WSL)
77
89
  const isWindows = process.platform === 'win32' || isWSL;
78
90
 
91
+ /**
92
+ * Get base directory for scenarios storage
93
+ * Uses cascade strategy: explicit param → remembered → auto-detect
94
+ * @param {string|null} explicitDir - Optional explicit directory path
95
+ * @returns {string} - Base directory path
96
+ */
97
+ function getScenariosBaseDir(explicitDir = null) {
98
+ // 1. If explicitly provided - remember and return
99
+ if (explicitDir) {
100
+ currentScenariosDirectory = path.resolve(explicitDir);
101
+ console.log('[chrometools-mcp] Using explicit directory:', currentScenariosDirectory);
102
+ return currentScenariosDirectory;
103
+ }
104
+
105
+ // 2. If already remembered - return
106
+ if (currentScenariosDirectory) {
107
+ console.log('[chrometools-mcp] Using remembered directory:', currentScenariosDirectory);
108
+ return currentScenariosDirectory;
109
+ }
110
+
111
+ // 3. Auto-detect from environment
112
+ const detected = detectProjectRoot();
113
+ currentScenariosDirectory = detected;
114
+ return detected;
115
+ }
116
+
79
117
  // Get Chrome executable path based on platform
80
118
  function getChromePath() {
81
119
  if (process.platform === 'win32') {
@@ -398,7 +436,8 @@ async function setupRecorderAutoReinjection(page) {
398
436
  // Check if this page had recorder before
399
437
  if (pagesWithRecorder.has(page)) {
400
438
  try {
401
- await injectRecorder(page);
439
+ const baseDir = getScenariosBaseDir();
440
+ await injectRecorder(page, baseDir);
402
441
  } catch (error) {
403
442
  console.error('[chrometools-mcp] Failed to re-inject recorder:', error.message);
404
443
  }
@@ -411,7 +450,8 @@ async function setupRecorderAutoReinjection(page) {
411
450
  // Check if this page had recorder before
412
451
  if (pagesWithRecorder.has(page)) {
413
452
  try {
414
- await injectRecorder(page);
453
+ const baseDir = getScenariosBaseDir();
454
+ await injectRecorder(page, baseDir);
415
455
  } catch (error) {
416
456
  console.error('[chrometools-mcp] Failed to re-inject recorder after reload:', error.message);
417
457
  }
@@ -1576,7 +1616,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1576
1616
  description: "Inject recorder UI widget. Visual recording with start/stop/save controls.",
1577
1617
  inputSchema: {
1578
1618
  type: "object",
1579
- properties: {},
1619
+ properties: {
1620
+ directory: { type: "string", description: "Directory to save scenarios (optional, defaults to auto-detected project root)" },
1621
+ },
1580
1622
  },
1581
1623
  },
1582
1624
  {
@@ -1588,6 +1630,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1588
1630
  name: { type: "string", description: "Scenario name" },
1589
1631
  parameters: { type: "object", description: "Execution parameters" },
1590
1632
  executeDependencies: { type: "boolean", description: "Execute dependencies (default: true)" },
1633
+ directory: { type: "string", description: "Directory where scenarios are stored (optional, defaults to auto-detected project root)" },
1591
1634
  },
1592
1635
  required: ["name"],
1593
1636
  },
@@ -1597,7 +1640,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1597
1640
  description: "List all scenarios with metadata.",
1598
1641
  inputSchema: {
1599
1642
  type: "object",
1600
- properties: {},
1643
+ properties: {
1644
+ directory: { type: "string", description: "Directory where scenarios are stored (optional, defaults to auto-detected project root)" },
1645
+ },
1601
1646
  },
1602
1647
  },
1603
1648
  {
@@ -1608,6 +1653,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1608
1653
  properties: {
1609
1654
  text: { type: "string", description: "Search text" },
1610
1655
  tags: { type: "array", items: { type: "string" }, description: "Filter tags" },
1656
+ directory: { type: "string", description: "Directory where scenarios are stored (optional, defaults to auto-detected project root)" },
1611
1657
  },
1612
1658
  },
1613
1659
  },
@@ -1619,6 +1665,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1619
1665
  properties: {
1620
1666
  name: { type: "string", description: "Scenario name" },
1621
1667
  includeSecrets: { type: "boolean", description: "Include secrets (default: false)" },
1668
+ directory: { type: "string", description: "Directory where scenarios are stored (optional, defaults to auto-detected project root)" },
1622
1669
  },
1623
1670
  required: ["name"],
1624
1671
  },
@@ -1630,10 +1677,42 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1630
1677
  type: "object",
1631
1678
  properties: {
1632
1679
  name: { type: "string", description: "Scenario name" },
1680
+ directory: { type: "string", description: "Directory where scenarios are stored (optional, defaults to auto-detected project root)" },
1633
1681
  },
1634
1682
  required: ["name"],
1635
1683
  },
1636
1684
  },
1685
+ {
1686
+ name: "exportScenarioAsCode",
1687
+ description: "Export recorded scenario as executable test code for various frameworks. Automatically cleans unstable selectors (CSS modules, styled-components).",
1688
+ inputSchema: {
1689
+ type: "object",
1690
+ properties: {
1691
+ scenarioName: {
1692
+ type: "string",
1693
+ description: "Name of scenario to export"
1694
+ },
1695
+ language: {
1696
+ type: "string",
1697
+ enum: ["playwright-typescript", "playwright-python", "selenium-python", "selenium-java"],
1698
+ description: "Target test framework and language"
1699
+ },
1700
+ cleanSelectors: {
1701
+ type: "boolean",
1702
+ description: "Remove unstable CSS classes (default: true)"
1703
+ },
1704
+ includeComments: {
1705
+ type: "boolean",
1706
+ description: "Include descriptive comments (default: true)"
1707
+ },
1708
+ directory: {
1709
+ type: "string",
1710
+ description: "Directory where scenarios are stored (optional, defaults to auto-detected project root)"
1711
+ },
1712
+ },
1713
+ required: ["scenarioName", "language"],
1714
+ },
1715
+ },
1637
1716
  ],
1638
1717
  };
1639
1718
  });
@@ -3392,8 +3471,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3392
3471
  }
3393
3472
 
3394
3473
  if (name === "enableRecorder") {
3474
+ const baseDir = getScenariosBaseDir(args.directory);
3395
3475
  const page = await getLastOpenPage();
3396
- const result = await injectRecorder(page);
3476
+ const result = await injectRecorder(page, baseDir);
3397
3477
 
3398
3478
  // Track this page as having recorder enabled
3399
3479
  if (result.success) {
@@ -3415,8 +3495,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3415
3495
  }
3416
3496
 
3417
3497
  if (name === "executeScenario") {
3418
- const page = await getLastOpenPage();
3419
- const options = {};
3498
+ // Get base directory for scenarios
3499
+ const baseDir = getScenariosBaseDir(args.directory);
3500
+
3501
+ // Try to get existing page, or auto-open browser using scenario's entryUrl
3502
+ let page;
3503
+ try {
3504
+ page = await getLastOpenPage();
3505
+ } catch (error) {
3506
+ // No page is open - load scenario and open browser at entryUrl
3507
+ const scenario = await loadScenario(args.name, false, baseDir);
3508
+ if (!scenario) {
3509
+ return {
3510
+ content: [{
3511
+ type: 'text',
3512
+ text: JSON.stringify({
3513
+ success: false,
3514
+ error: `Scenario "${args.name}" not found`
3515
+ }, null, 2)
3516
+ }]
3517
+ };
3518
+ }
3519
+
3520
+ const entryUrl = scenario.metadata?.entryUrl;
3521
+ if (!entryUrl) {
3522
+ return {
3523
+ content: [{
3524
+ type: 'text',
3525
+ text: JSON.stringify({
3526
+ success: false,
3527
+ error: `Scenario "${args.name}" has no entryUrl. Cannot auto-open browser.`
3528
+ }, null, 2)
3529
+ }]
3530
+ };
3531
+ }
3532
+
3533
+ // Auto-open browser at scenario's entry URL
3534
+ page = await getOrCreatePage(entryUrl);
3535
+ }
3536
+
3537
+ const options = {
3538
+ baseDir: baseDir
3539
+ };
3420
3540
 
3421
3541
  // Pass executeDependencies option if provided
3422
3542
  if (args.executeDependencies !== undefined) {
@@ -3434,7 +3554,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3434
3554
  }
3435
3555
 
3436
3556
  if (name === "listScenarios") {
3437
- const scenarios = await listScenarios();
3557
+ const baseDir = getScenariosBaseDir(args.directory);
3558
+ const scenarios = await listScenarios(baseDir);
3438
3559
 
3439
3560
  return {
3440
3561
  content: [{
@@ -3445,7 +3566,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3445
3566
  }
3446
3567
 
3447
3568
  if (name === "searchScenarios") {
3448
- const results = await searchScenarios(args);
3569
+ const baseDir = getScenariosBaseDir(args.directory);
3570
+ const results = await searchScenarios(args, baseDir);
3449
3571
 
3450
3572
  return {
3451
3573
  content: [{
@@ -3456,7 +3578,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3456
3578
  }
3457
3579
 
3458
3580
  if (name === "getScenarioInfo") {
3459
- const scenario = await loadScenario(args.name, args.includeSecrets || false);
3581
+ const baseDir = getScenariosBaseDir(args.directory);
3582
+ const scenario = await loadScenario(args.name, args.includeSecrets || false, baseDir);
3460
3583
 
3461
3584
  return {
3462
3585
  content: [{
@@ -3467,7 +3590,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3467
3590
  }
3468
3591
 
3469
3592
  if (name === "deleteScenario") {
3470
- const result = await deleteScenario(args.name);
3593
+ const baseDir = getScenariosBaseDir(args.directory);
3594
+ const result = await deleteScenario(args.name, baseDir);
3471
3595
 
3472
3596
  return {
3473
3597
  content: [{
@@ -3477,6 +3601,65 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3477
3601
  };
3478
3602
  }
3479
3603
 
3604
+ if (name === "exportScenarioAsCode") {
3605
+ const baseDir = getScenariosBaseDir(args.directory);
3606
+ const scenario = await loadScenario(args.scenarioName, false, baseDir);
3607
+
3608
+ if (!scenario) {
3609
+ return {
3610
+ content: [{
3611
+ type: 'text',
3612
+ text: JSON.stringify({
3613
+ error: `Scenario "${args.scenarioName}" not found`
3614
+ }, null, 2)
3615
+ }],
3616
+ isError: true
3617
+ };
3618
+ }
3619
+
3620
+ // Select generator based on language
3621
+ let generator;
3622
+ const options = {
3623
+ cleanSelectors: args.cleanSelectors !== false, // default true
3624
+ includeComments: args.includeComments !== false, // default true
3625
+ };
3626
+
3627
+ switch (args.language) {
3628
+ case 'playwright-typescript':
3629
+ generator = new PlaywrightTypeScriptGenerator(options);
3630
+ break;
3631
+ case 'playwright-python':
3632
+ generator = new PlaywrightPythonGenerator(options);
3633
+ break;
3634
+ case 'selenium-python':
3635
+ generator = new SeleniumPythonGenerator(options);
3636
+ break;
3637
+ case 'selenium-java':
3638
+ generator = new SeleniumJavaGenerator(options);
3639
+ break;
3640
+ default:
3641
+ return {
3642
+ content: [{
3643
+ type: 'text',
3644
+ text: JSON.stringify({
3645
+ error: `Unknown language: ${args.language}. Supported: playwright-typescript, playwright-python, selenium-python, selenium-java`
3646
+ }, null, 2)
3647
+ }],
3648
+ isError: true
3649
+ };
3650
+ }
3651
+
3652
+ // Generate code
3653
+ const code = generator.generate(scenario, options);
3654
+
3655
+ return {
3656
+ content: [{
3657
+ type: 'text',
3658
+ text: code
3659
+ }]
3660
+ };
3661
+ }
3662
+
3480
3663
  return {
3481
3664
  content: [
3482
3665
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrometools-mcp",
3
- "version": "1.7.1",
3
+ "version": "1.8.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",