Back to Lobehub

Electron (LobeHub Desktop) UI Testing

.agents/skills/agent-testing/ui/electron.md

2.2.46.9 KB
Original Source

Electron (LobeHub Desktop) UI Testing

Default surface for verifying pure frontend changes (components, store logic, styles, interactions) in the primary product shape. Drives the Electron renderer over CDP with agent-browser — see ../references/agent-browser.md for the full command reference.

Auth: the Electron app keeps its own persistent login state — log in once manually in the app; sessions survive restarts. Run ../scripts/setup-auth.sh status before testing (see ../references/auth.md).

Linux / headless (cloud): Electron itself runs on Linux, but it has no true headless mode — it needs a display server. In a headless environment wrap the launch with xvfb-run (virtual framebuffer). Everything CDP-based keeps working under Xvfb: the agent-browser --cdp 9222 connection, snapshots, eval, and agent-browser screenshot (captured from the renderer via CDP, not the OS screen). What does NOT work on Linux: capture-app-window.sh (macOS screencapture), osascript, and the ffmpeg recording scripts in their current form.

Setup / Teardown

Use the electron-dev.sh script to manage the Electron dev environment. It handles process lifecycle, waits for SPA readiness, and reliably kills all child processes (main + helpers + vite).

bash
SCRIPT=".agents/skills/agent-testing/scripts/electron-dev.sh"

# Start Electron dev with CDP (idempotent — skips if already running)
$SCRIPT start

# Check if Electron is running and CDP is reachable
$SCRIPT status

# Kill all Electron-related processes (main + helper + vite)
$SCRIPT stop

# Force fresh restart
$SCRIPT restart

After start succeeds, connect with: agent-browser --cdp 9222 snapshot -i

Always run $SCRIPT stop when done testingpkill -f "Electron" alone won't catch all helper processes.

Environment Variables

VariableDefaultDescription
CDP_PORT9222Chrome DevTools Protocol port
ELECTRON_LOG/tmp/electron-dev.logElectron process log
ELECTRON_WAIT_S60Max seconds to wait for Electron process
RENDERER_WAIT_S60Max seconds to wait for SPA to load

LobeHub Probes & Quick Navigation

scripts/app-probe.sh is the standard fast path into app state — use it instead of hand-rolling __LOBE_STORES eval snippets for these common needs:

bash
PROBE=".agents/skills/agent-testing/scripts/app-probe.sh"

$PROBE auth              # login check (Step 0.3) → { isSignedIn, userId }
$PROBE route             # current SPA route
$PROBE ops               # running chat operations (type / startTime)
$PROBE goto /settings    # jump the SPA straight to a route (full reload)
$PROBE errors-install    # install console.error interceptor
$PROBE errors            # dump captured errors

goto lets a test enter the state under test directly instead of clicking through the UI. Common desktop routes:

RouteWhere it lands
/Home (has a chat input)
/agent/<agentId>Agent conversation (latest topic)
/agent/<agentId>/<topicId>Specific topic in a conversation
/task · /task/<taskId>Task list / task detail
/pageDocuments (文稿)
/settingsSettings
/communityDiscover / community

Targets default to Electron (--cdp 9222); set AB_TARGET="--session <name>" for web sessions. For deeper or one-off state inspection, fall back to raw eval below.

LobeHub-Specific Patterns

Access Zustand Store State

bash
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(function() {
  var chat = window.__LOBE_STORES.chat();
  var ops = Object.values(chat.operations);
  return JSON.stringify({
    ops: ops.map(function(o) { return { type: o.type, status: o.status }; }),
    activeAgent: chat.activeAgentId,
    activeTopic: chat.activeTopicId,
  });
})()
EVALEOF

Find and Use the Chat Input

bash
# The chat input is contenteditable — must use -C flag
agent-browser --cdp 9222 snapshot -i -C 2>&1 | grep "editable"

agent-browser --cdp 9222 click @e48
agent-browser --cdp 9222 type @e48 "Hello world"
agent-browser --cdp 9222 press Enter

Wait for Agent to Complete

bash
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(function() {
  var chat = window.__LOBE_STORES.chat();
  var ops = Object.values(chat.operations);
  var running = ops.filter(function(o) { return o.status === 'running'; });
  return running.length === 0 ? 'done' : 'running: ' + running.length;
})()
EVALEOF

Install Error Interceptor

bash
agent-browser --cdp 9222 eval --stdin << 'EVALEOF'
(function() {
  window.__CAPTURED_ERRORS = [];
  var orig = console.error;
  console.error = function() {
    var msg = Array.from(arguments).map(function(a) {
      if (a instanceof Error) return a.message;
      return typeof a === 'object' ? JSON.stringify(a) : String(a);
    }).join(' ');
    window.__CAPTURED_ERRORS.push(msg);
    orig.apply(console, arguments);
  };
  return 'installed';
})()
EVALEOF

# Later, check captured errors:
agent-browser --cdp 9222 eval "JSON.stringify(window.__CAPTURED_ERRORS)"

Electron Gotchas

  • Always use electron-dev.sh stop to clean uppkill -f "Electron" only kills the main process; helper processes (GPU, renderer, network) survive. The script finds and kills all of them via PID matching against the project's electron binary path.

  • npx electron-vite dev must run from apps/desktop/ — running from project root fails silently. The electron-dev.sh script handles this automatically.

  • Dev build auto-opens DevTools, which hijacks the CDP targetagent-browser --cdp 9222 may attach to the DevTools page (devtools://…) instead of the app (app://renderer/). Symptom: get url returns a devtools:// URL. Fix: close the DevTools target and reconnect:

    bash
    DT_ID=$(curl -s http://localhost:9222/json/list | python3 -c "import json,sys; ts=json.load(sys.stdin); print(next(t['id'] for t in ts if t['type']=='page' and t['url'].startswith('devtools://')))")
    curl -s "http://localhost:9222/json/close/$DT_ID" > /dev/null
    agent-browser close --all && agent-browser --cdp 9222 get url   # expect app://renderer/
    
  • Don't resize the Electron window after load — resizing triggers full SPA reload

  • Store is at window.__LOBE_STORES not window.__ZUSTAND_STORES__

  • Streaming / ticking UI needs GIF evidence — see scripts/record-gif.sh; a static screenshot cannot prove time-based behavior.