| name | dev-test-electron |
| description | This skill should be used when the user asks to "test Electron app", "automate Electron desktop app", "debug Electron renderer", "test VS Code extension", "E2E test Electron", or needs Chrome DevTools Protocol automation for Electron applications. Use for renderer process debugging, main process control, native menu automation, and file dialog testing. |
| user-invocable | false |
| disable-model-invocation | true |
Announce: "I'm using dev-test-electron for Electron app automation via Chrome DevTools Protocol."
## REAL Test Requirements for Electron Apps
A REAL Electron test must replicate what the user does. FAKE tests test something else.
Before writing ANY test, verify from SPEC.md/PLAN.md:
| REAL Test Criteria | Your Test Must |
|---|
| User workflow | Replicate exact steps (click → type → see result) |
| Protocol | Use SAME protocol as production (WebSocket, IPC, etc.) |
| UI interaction | Interact with ACTUAL UI elements user sees |
| Verification | Check what USER sees, not internal state |
The Electron-Specific Fake Test Trap
Electron apps often use WebSocket/IPC internally. Testing HTTP is a FAKE test.
| FAKE Electron Test | Why It's Fake | REAL Test |
|---|
| HTTP endpoint test | App uses WebSocket | Test WebSocket connection |
| Direct function call | User clicks button | CDP Input.dispatchMouseEvent or Runtime.evaluate click |
| Check internal state | User sees panel/status | CDP screenshot or DOM query |
| Mock IPC layer | Production uses real IPC | Test actual IPC messages |
| Skip main process | Main process has logic | Test BOTH renderer AND main |
Before You Write a Test, Ask:
- What protocol does this feature use? (WebSocket? IPC? HTTP?)
- What does the user actually click/type?
- What does the user actually SEE?
- Am I testing the SAME code path as production?
If any answer is "I don't know" → Go back to SPEC.md. Don't guess.
Electron Testing Facts
- Production code paths are the test target: users click buttons (CDP Input events, not direct function calls), the app talks over its real transport (WebSocket, not an easier HTTP stand-in), and the user SEES the panel even if its state is "internal". A test that bypasses any of these exercises a code path production never runs — its pass is an unverified claim about the real one.
- The main process and IPC need coverage as much as the renderer: main-process crashes take down the whole app, and IPC bugs fail silently. Renderer-only testing asserts whole-app health from partial evidence.
- Mocking CDP hides exactly the bugs this skill exists to catch; use the real CDP connection.
## Gate Reminder
Before taking screenshots or running E2E tests, you MUST complete all 6 gates from dev-tdd:
GATE 1: BUILD
GATE 2: LAUNCH (with file-based logging)
GATE 3: WAIT
GATE 4: CHECK PROCESS
GATE 5: READ LOGS ← MANDATORY, CANNOT SKIP
GATE 6: VERIFY LOGS
THEN: E2E tests/screenshots
You loaded dev-tdd earlier. Follow the gates now.
Contents
Electron E2E Testing via Chrome DevTools Protocol
## Tool Availability Gate
Verify CDP tooling is available before proceeding.
Check for these tools:
which curl || echo "MISSING: curl"
which jq || echo "MISSING: jq"
which websocat || which wscat || echo "MISSING: WebSocket CLI"
If missing tools:
STOP: Cannot proceed with Electron CDP automation.
Missing tools needed for CDP:
- curl (for HTTP requests)
- jq (for JSON parsing)
- websocat or wscat (for WebSocket communication)
Install with:
# macOS: Install via nix-darwin (see ~/nix/). Do NOT use brew.
# Linux: sudo apt install curl jq websocat
Reply when installed and I'll continue testing.
This gate is non-negotiable. Missing tools = full stop.
## When to Use Electron CDP
USE Electron CDP when you need:
- Test Electron desktop applications (VS Code, Slack, etc.)
- Debug Electron renderer process (console, DOM, network)
- Automate Electron main process (native menus, dialogs, IPC)
- Multi-window Electron testing
- Electron-specific features (webContents, BrowserWindow)
- File system operations from Electron app
DO NOT use Electron CDP when:
- Testing web applications only (use Chrome MCP or Playwright)
- Testing non-Electron desktop apps (use Hammerspoon for macOS, dev-test-linux for Linux)
- Need headless CI/CD for web apps (use Playwright MCP)
For web apps or native desktop apps, discover and read the relevant skill:
Related skills:
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-test-chrome/SKILL.md and follow its instructions.
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-test-playwright/SKILL.md and follow its instructions.
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-test-hammerspoon/SKILL.md and follow its instructions.
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-test-linux/SKILL.md and follow its instructions.
- Chrome MCP skill - web debugging
- Playwright skill - headless CI/CD
- Hammerspoon skill - macOS native
- Linux skill - Linux native
Tool Boundary Facts
- Chrome MCP cannot attach to Electron's main process, and Playwright MCP targets web browsers — neither reaches Electron's main process. Electron-specific CDP is the only tool that covers both main process and renderer (see Capability Comparison below); picking a familiar web tool instead produces a test blind to file dialogs, native menus, and main-process crashes.
Capability Comparison
| Capability | Electron CDP | Chrome MCP | Playwright MCP | Hammerspoon |
|---|
| Electron renderer | ✅ | ❌ | ❌ | ❌ |
| Electron main process | ✅ | ❌ | ❌ | ❌ |
| Native menus/dialogs | ✅ | ❌ | ❌ | ✅ (macOS only) |
| Multi-window Electron | ✅ | ❌ | ❌ | ✅ (macOS only) |
| Console/network debugging | ✅ | ✅ (web only) | ❌ | ❌ |
| Headless mode | ✅ | ❌ | ✅ (web only) | ❌ |
| WebSocket IPC | ✅ | ❌ | ❌ | ❌ |
Connecting to Electron
### The Iron Law of Connection
EVERY Electron E2E test MUST establish CDP connection BEFORE any automation.
You CANNOT automate without:
- Launching Electron with CDP enabled
- Discovering the CDP WebSocket URL
- Connecting to the WebSocket
- Verifying the connection works
| Action | Why It Fails Without Connection |
|---|
| Send CDP command | No connection = command never sent |
| Read console logs | Can't receive events without WebSocket |
| Navigate to page | CDP Page.navigate requires connection |
| Take screenshot | CDP Page.captureScreenshot requires connection |
"App is running" ≠ "CDP is connected". Verify connection first.
Enable Remote Debugging
Launch Electron with remote debugging port:
/path/to/electron-app --remote-debugging-port=9222
/path/to/electron-app --remote-debugging-port=0
/path/to/electron-app --remote-debugging-port=9222 --enable-logging --log-file=/tmp/electron.log 2>&1 &
CRITICAL: For GATE 2 (LAUNCH), always use --log-file flag for file-based logging.
Discover CDP WebSocket URL
curl -s http://localhost:9222/json/list | jq '.'
WS_URL=$(curl -s http://localhost:9222/json/list | jq -r '.[0].webSocketDebuggerUrl')
echo "WebSocket URL: $WS_URL"
Example response:
[
{
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/...",
"id": "page-id",
"title": "My Electron App",
"type": "page",
"url": "file:///app/index.html",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/..."
}
]
Connect via WebSocket
websocat "$WS_URL"
{"id":1,"method":"Runtime.enable"}
{"id":2,"method":"Page.enable"}
{"id":3,"method":"Runtime.evaluate","params":{"expression":"document.title"}}
Helper script: See scripts/connect-electron-cdp.sh for automated connection.
CDP Domains
Essential Domains for Electron
| Domain | Purpose | Example |
|---|
| Runtime | Execute JavaScript, console logs | Runtime.evaluate, Runtime.consoleAPICalled |
| Page | Navigation, screenshots, DOM events | Page.navigate, Page.captureScreenshot |
| DOM | Query and manipulate DOM | DOM.getDocument, DOM.querySelector |
| Debugger | Breakpoints, step debugging | Debugger.setBreakpoint |
| Network | Network requests/responses | Network.enable, Network.responseReceived |
| Input | Keyboard/mouse events | Input.dispatchKeyEvent, Input.dispatchMouseEvent |
Enable domains before use:
{"id":1,"method":"Runtime.enable"}
{"id":2,"method":"Page.enable"}
{"id":3,"method":"DOM.enable"}
{"id":4,"method":"Network.enable"}
Renderer Process Automation
Execute JavaScript
echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"document.title"}}' | websocat "$WS_URL"
echo '{"id":2,"method":"Runtime.evaluate","params":{"expression":"2 + 2","returnByValue":true}}' | websocat "$WS_URL"
SCRIPT='document.querySelector("#username").value = "testuser"'
echo "{\"id\":3,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"$SCRIPT\"}}" | websocat "$WS_URL"
Navigate to URL
echo '{"id":10,"method":"Page.navigate","params":{"url":"file:///app/index.html"}}' | websocat "$WS_URL"
echo '{"id":11,"method":"Page.enable"}' | websocat "$WS_URL"
Read Console Messages
echo '{"id":20,"method":"Runtime.enable"}' | websocat "$WS_URL"
For complete console reading, see references/cdp-api.md
Take Screenshots
echo '{"id":30,"method":"Page.captureScreenshot"}' | websocat "$WS_URL" > response.json
jq -r '.result.data' response.json | base64 -d > screenshot.png
Query DOM
echo '{"id":40,"method":"DOM.getDocument"}' | websocat "$WS_URL"
ROOT_ID=$(jq -r '.result.root.nodeId' response.json)
echo "{\"id\":41,\"method\":\"DOM.querySelector\",\"params\":{\"nodeId\":$ROOT_ID,\"selector\":\"#submit-btn\"}}" | websocat "$WS_URL"
Main Process Control
### Electron Main Process vs Renderer Process
Electron has TWO processes:
| Process | What It Does | How to Test |
|---|
| Main | Node.js runtime, native APIs, file system, menus, dialogs | CDP Runtime.evaluate in main context OR IPC |
| Renderer | Browser/Chromium runtime, web content, DOM | CDP commands (Page, DOM, Runtime) |
Both processes MUST be tested. Renderer-only testing is incomplete.
Access Main Process via CDP
Some Electron apps expose main process debugging:
curl -s http://localhost:9222/json/list | jq '.[] | select(.type == "node")'
If main process is available:
MAIN_WS=$(curl -s http://localhost:9222/json/list | jq -r '.[] | select(.type == "node") | .webSocketDebuggerUrl')
echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"process.version"}}' | websocat "$MAIN_WS"
Trigger Native Dialogs (via IPC)
SCRIPT='require("electron").ipcRenderer.send("open-file-dialog")'
echo "{\"id\":50,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"$SCRIPT\"}}" | websocat "$WS_URL"
For advanced main process patterns, see references/electron-specific.md
Verification
### The Iron Law of Verification
EVERY CDP command must be VERIFIED. Sending the command is not enough.
After sending a CDP command, you MUST:
- Read the response
- Check for errors (
error field in response)
- Verify the result matches expectations
- Document the verification
| Command | Verification |
|---|
Runtime.evaluate | Check result.value or result.exceptionDetails |
Page.navigate | Wait for Page.loadEventFired event |
Page.captureScreenshot | Verify result.data exists and decode base64 |
DOM.querySelector | Check result.nodeId exists (not 0) |
"I sent the command" is not verification. Read the response and verify success.
Response Verification Pattern
RESPONSE=$(echo '{"id":100,"method":"Runtime.evaluate","params":{"expression":"2 + 2"}}' | websocat --one-message "$WS_URL")
if echo "$RESPONSE" | jq -e '.error' > /dev/null; then
echo "ERROR: CDP command failed"
echo "$RESPONSE" | jq '.error'
exit 1
fi
RESULT=$(echo "$RESPONSE" | jq -r '.result.result.value')
if [ "$RESULT" != "4" ]; then
echo "ERROR: Expected 4, got $RESULT"
exit 1
fi
echo "✓ VERIFIED: 2 + 2 = $RESULT"
Event-Based Verification
echo '{"id":1,"method":"Page.enable"}' | websocat "$WS_URL" &
echo '{"id":2,"method":"Page.navigate","params":{"url":"file:///app/index.html"}}' | websocat "$WS_URL"
Complete E2E Examples
Basic Electron App Test (All 6 Gates)
#!/bin/bash
set -e
echo "GATE 1: Building Electron app..."
cd /path/to/electron-app
npm run build
echo "✓ GATE 1 PASSED"
echo "GATE 2: Launching with CDP and logging..."
npm start -- --remote-debugging-port=9222 --enable-logging --log-file=/tmp/electron.log 2>&1 &
APP_PID=$!
echo "✓ GATE 2 PASSED (PID: $APP_PID)"
echo "GATE 3: Waiting for Electron initialization..."
sleep 3
echo "✓ GATE 3 PASSED"
echo "GATE 4: Checking Electron process..."
if ! ps -p $APP_PID > /dev/null; then
echo "✗ GATE 4 FAILED: Electron process crashed"
echo "Reading logs from GATE 5..."
cat /tmp/electron.log
exit 1
fi
if ! curl -s http://localhost:9222/json/list > /dev/null; then
echo "✗ GATE 4 FAILED: CDP port not accessible"
cat /tmp/electron.log
exit 1
fi
echo "✓ GATE 4 PASSED"
/tmp/electron.log
grep -qE /tmp/electron.log;
1
WS_URL=$(curl -s http://localhost:9222/json/list | jq -r )
| websocat --one-message
RESPONSE=$( | websocat --one-message )
| jq -e > /dev/null;
| jq
1
TITLE=$( | jq -r )
SCREENSHOT_RESPONSE=$( | websocat --one-message )
| jq -r | -d > /tmp/electron_screenshot.png
Tool description: Execute all 6 gates, then run Electron E2E test with CDP
Form Automation Example
#!/bin/bash
echo '{"id":1,"method":"Runtime.enable"}' | websocat --one-message "$WS_URL"
echo '{"id":2,"method":"Page.enable"}' | websocat --one-message "$WS_URL"
FILL_USERNAME='document.querySelector("#username").value = "testuser"'
RESPONSE=$(echo "{\"id\":10,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"$FILL_USERNAME\"}}" | websocat --one-message "$WS_URL")
if echo "$RESPONSE" | jq -e '.error' > /dev/null; then
echo "✗ FAILED: Could not fill username"
exit 1
fi
FILL_PASSWORD='document.querySelector("#password").value = "testpass"'
echo "{\"id\":11,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"$FILL_PASSWORD\"}}" | websocat --one-message "$WS_URL"
CLICK_SUBMIT='document.querySelector("#submit-btn").click()'
echo "{\"id\":12,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"$CLICK_SUBMIT\"}}" | websocat --one-message "$WS_URL"
sleep 1
CHECK_SUCCESS=
VERIFY_RESPONSE=$( | websocat --one-message )
SUCCESS=$( | jq -r )
[ != ];
1
| websocat --one-message | jq -r | -d > /tmp/form_success.png
Multi-Window Testing
curl -s http://localhost:9222/json/list | jq '.[] | {title: .title, url: .url, wsUrl: .webSocketDebuggerUrl}'
WINDOW_WS=$(curl -s http://localhost:9222/json/list | jq -r '.[] | select(.title == "Settings Window") | .webSocketDebuggerUrl')
echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"document.querySelector(\"#theme\").value = \"dark\""}}' | websocat --one-message "$WINDOW_WS"
For more advanced patterns, see references/advanced-patterns.md
Error Handling
Common CDP Errors
| Error | Cause | Solution |
|---|
| Connection refused | Electron not started with --remote-debugging-port | Restart with flag |
| WebSocket timeout | App crashed or port blocked | Check GATE 4 (process) and GATE 5 (logs) |
"error":{"code":-32601} | Method not found | Enable domain first (e.g., Runtime.enable) |
exceptionDetails in result | JavaScript error in evaluated code | Check expression syntax |
| Empty response | WebSocket closed | Reconnect to WebSocket |
Retry Pattern
for i in {1..3}; do
RESPONSE=$(echo "$CDP_COMMAND" | websocat --one-message "$WS_URL")
if echo "$RESPONSE" | jq -e '.result' > /dev/null; then
echo "✓ Command succeeded on attempt $i"
break
fi
if [ $i -eq 3 ]; then
echo "✗ Command failed after 3 attempts"
echo "$RESPONSE"
exit 1
fi
echo "Retry $i failed, waiting 1s..."
sleep 1
done
Limitations
### What Electron CDP Cannot Do
| Need | Why Electron CDP Fails | Use Instead |
|---|
| Native macOS window management | CDP doesn't control OS | Hammerspoon (macOS) |
| Cross-platform native automation | CDP is Chromium-only | Platform-specific tools |
| Test non-Electron apps | CDP requires Electron/Chromium | Hammerspoon, dev-test-linux |
| Headless CI/CD for web apps | Electron is for desktop apps | Playwright MCP |
For web apps, use Playwright or Chrome MCP. For native desktop, use platform tools.
Additional Resources
Reference Files
For detailed CDP API documentation and Electron-specific features:
references/cdp-api.md - Complete CDP domains reference (Runtime, Page, DOM, Network, Input, Debugger)
references/electron-specific.md - Electron main process, IPC, native APIs, file dialogs
references/advanced-patterns.md - Multi-window, devtools, event listeners, WebSocket streaming
Example Files
Working examples in examples/:
basic-test.sh - Complete E2E test with all 6 gates
cdp-commands.json - Common CDP command reference
Scripts
Utility scripts in scripts/:
connect-electron-cdp.sh - Automated CDP connection discovery
launch-electron-with-logging.sh - Launch template with proper logging
verify-electron-process.sh - Health check for main + renderer
VS Code Extension Testing (Common Case)
**VS Code extensions are a common Electron test case. Here's how to test them REAL.**
What Makes VS Code Extension Tests REAL
| User Action | FAKE Test | REAL Test |
|---|
| Highlight text in editor | editor.setSelection() programmatically | CDP simulate actual text selection |
| Click Claude panel | Call panel function directly | CDP click on actual panel element |
| See status in panel | Check internal state variable | CDP query panel DOM for displayed text |
| Extension uses WebSocket | Test HTTP endpoint | Test WebSocket connection |
VS Code Extension Protocol Discovery
Before testing, discover what protocol the extension uses:
rg "WebSocket|ws://" --type ts
rg "fetch|axios|http" --type ts
rg "ipcRenderer|ipcMain" --type ts
If extension uses WebSocket → Your test MUST use WebSocket, not HTTP.
Example: Testing Selection → Panel Status
FAKE test (DON'T DO THIS):
const selection = await vscode.window.activeTextEditor.selection;
await extensionApi.updateSelection(selection);
expect(internalState.selectionCount).toBe(5);
REAL test (DO THIS):
SCRIPT='
const editor = document.querySelector(".monaco-editor");
// Simulate actual selection via CDP Input events
'
echo "{\"id\":1,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"$SCRIPT\"}}" | websocat "$WS_URL"
VERIFY='document.querySelector(".claude-panel .status-text").textContent'
RESULT=$(echo "{\"id\":2,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"$VERIFY\",\"returnByValue\":true}}" | websocat --one-message "$WS_URL")
STATUS=$(echo "$RESULT" | jq -r '.result.result.value')
if [[ "$STATUS" != *"5 lines selected"* ]]; then
echo "✗ FAKE TEST: Panel doesn't show expected status"
exit 1
fi
echo "✓ REAL TEST: Panel shows '$STATUS'"
VS Code Extension Test Checklist
Before writing VS Code extension test, verify:
[ ] Protocol discovered (WebSocket/HTTP/IPC)
[ ] User workflow documented (what user clicks/sees)
[ ] Test uses SAME protocol as extension
[ ] Test simulates ACTUAL user actions (not API calls)
[ ] Test verifies PANEL DISPLAY (not internal state)
[ ] Test covers BOTH main and renderer processes
If any box is unchecked → Your test is probably FAKE.
Integration
This skill is referenced by dev-test for Electron desktop application testing.
Related skills:
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-test-chrome/SKILL.md and follow its instructions.
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-test-playwright/SKILL.md and follow its instructions.
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-test-hammerspoon/SKILL.md and follow its instructions.
- Read
${CLAUDE_SKILL_DIR}/../../skills/dev-tdd/SKILL.md and follow its instructions.
- Chrome MCP skill - web debugging
- Playwright skill - headless web CI/CD
- Hammerspoon skill - macOS native apps
- TDD skill - TDD protocol and gate enforcement