| name | local-testing |
| description | Local app and bot testing. Uses agent-browser CLI for Electron/web app UI testing, and osascript (AppleScript) for controlling native macOS apps (WeChat, Discord, Telegram, Slack, Lark/飞书, QQ) to test bots. Triggers on 'local test', 'test in electron', 'test desktop', 'test bot', 'bot test', 'test in discord', 'test in telegram', 'test in slack', 'test in weixin', 'test in wechat', 'test in lark', 'test in feishu', 'test in qq', 'manual test', 'osascript', or UI/bot verification tasks.
|
Local App & Bot Testing
Two approaches for local testing on macOS:
| Approach | Tool | Best For |
|---|
| agent-browser + CDP | agent-browser CLI | Electron apps, web apps (DOM access, JS eval) |
| osascript (AppleScript) | osascript -e | Native macOS apps (WeChat, Discord, Telegram, Slack) |
Part 1: agent-browser (Electron / Web Apps)
Use agent-browser to automate Chromium-based apps via Chrome DevTools Protocol.
Install via npm i -g agent-browser, brew install agent-browser, or cargo install agent-browser. Run agent-browser install to download Chrome. Run agent-browser upgrade to update.
Core Workflow
Every browser automation follows this pattern:
- Navigate:
agent-browser open <url>
- Snapshot:
agent-browser snapshot -i (get element refs like @e1, @e2)
- Interact: Use refs to click, fill, select
- Re-snapshot: After navigation or DOM changes, get fresh refs
agent-browser open https://example.com/form
agent-browser snapshot -i
agent-browser fill @e1 "user@example.com"
agent-browser fill @e2 "password123"
agent-browser click @e3
agent-browser wait --load networkidle
agent-browser snapshot -i
Command Chaining
agent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i
Use && when you don't need to read intermediate output. Run commands separately when you need to parse output first (e.g., snapshot to discover refs, then interact).
Essential Commands
agent-browser open <url>
agent-browser close
agent-browser close --all
agent-browser snapshot -i
agent-browser snapshot -s "#selector"
agent-browser click @e1
agent-browser click @e1 --new-tab
agent-browser fill @e2 "text"
agent-browser type @e2 "text"
agent-browser select @e1 "option"
agent-browser check @e1
agent-browser press Enter
agent-browser keyboard type "text"
agent-browser keyboard inserttext "text"
agent-browser scroll down 500
agent-browser scroll down 500 --selector "div.content"
agent-browser get text @e1
agent-browser get url
agent-browser get title
agent-browser get cdp-url
agent-browser wait @e1
agent-browser wait --load networkidle
agent-browser wait --url "**/page"
agent-browser wait 2000
agent-browser wait --text "Welcome"
agent-browser wait --fn "!document.body.innerText.includes('Loading...')"
agent-browser wait "#spinner" --state hidden
agent-browser download @e1 ./file.pdf
agent-browser wait --download ./output.zip
agent-browser network requests
agent-browser network requests --type xhr,fetch
agent-browser network requests --method POST
agent-browser network route "**/api/*" --abort
agent-browser network har start
agent-browser network har stop ./capture.har
agent-browser set viewport 1920 1080
agent-browser set viewport 1920 1080 2
agent-browser set device "iPhone 14"
agent-browser screenshot
agent-browser screenshot --full
agent-browser screenshot --annotate
agent-browser pdf output.pdf
agent-browser clipboard read
agent-browser clipboard write "text"
agent-browser clipboard copy
agent-browser clipboard paste
agent-browser dialog accept
agent-browser dialog accept "input"
agent-browser dialog dismiss
agent-browser dialog status
agent-browser diff snapshot
agent-browser diff screenshot --baseline before.png
agent-browser diff url <url1> <url2>
agent-browser stream enable
agent-browser stream status
agent-browser stream disable
Batch Execution
echo '[
["open", "https://example.com"],
["snapshot", "-i"],
["click", "@e1"],
["screenshot", "result.png"]
]' | agent-browser batch --json
Authentication
echo "$PASSWORD" | agent-browser auth save myapp --url https://app.example.com/login --username user --password-stdin
agent-browser auth login myapp
agent-browser --session-name myapp open https://app.example.com/login
agent-browser close
agent-browser --session-name myapp open https://app.example.com/dashboard
agent-browser --profile ~/.myapp open https://app.example.com/login
agent-browser state save auth.json
agent-browser state load auth.json
LobeHub dev server — inject better-auth cookie
agent-browser --headed on macOS can create an off-screen Chromium window, blocking manual login. For a local LobeHub dev server (e.g. localhost:3011), copy the better-auth.session_token cookie out of a Network request in the user's own Chrome DevTools and load it via state load. See references/agent-browser-login.md for the full recipe.
Semantic Locators (Alternative to Refs)
agent-browser find text "Sign In" click
agent-browser find label "Email" fill "user@test.com"
agent-browser find role button click --name "Submit"
agent-browser find placeholder "Search" type "query"
agent-browser find testid "submit-btn" click
JavaScript Evaluation (eval)
agent-browser eval 'document.title'
agent-browser eval --stdin << 'EVALEOF'
JSON.stringify(
Array.from(document.querySelectorAll("img"))
.filter(i => !i.alt)
.map(i => ({ src: i.src.split("/").pop(), width: i.width }))
)
EVALEOF
agent-browser eval -b "$(echo -n 'document.title' | base64)"
Ref Lifecycle
Refs (@e1, @e2, etc.) are invalidated when the page changes. Always re-snapshot after clicking links/buttons that navigate, form submissions, or dynamic content loading.
Annotated Screenshots (Vision Mode)
agent-browser screenshot --annotate
agent-browser click @e2
Parallel Sessions
agent-browser --session site1 open https://site-a.com
agent-browser --session site2 open https://site-b.com
agent-browser session list
Connect to Existing Chrome
agent-browser --auto-connect snapshot
agent-browser --cdp 9222 snapshot
iOS Simulator (Mobile Safari)
agent-browser device list
agent-browser -p ios --device "iPhone 16 Pro" open https://example.com
agent-browser -p ios snapshot -i
agent-browser -p ios tap @e1
agent-browser -p ios swipe up
agent-browser -p ios screenshot mobile.png
agent-browser -p ios close
Observability Dashboard
agent-browser dashboard install
agent-browser dashboard start
agent-browser dashboard stop
Cloud Providers
Use -p <provider> to run against cloud browsers: agentcore, browserbase, browserless, browseruse, kernel.
Browser Engine Selection
agent-browser --engine lightpanda open example.com
Electron (LobeHub Desktop)
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).
SCRIPT=".agents/skills/local-testing/scripts/electron-dev.sh"
$SCRIPT start
$SCRIPT status
$SCRIPT stop
$SCRIPT restart
After start succeeds, connect with: agent-browser --cdp 9222 snapshot -i
Always run $SCRIPT stop when done testing — pkill -f "Electron" alone won't catch all helper processes.
Environment Variables
| Variable | Default | Description |
|---|
CDP_PORT | 9222 | Chrome DevTools Protocol port |
ELECTRON_LOG | /tmp/electron-dev.log | Electron process log |
ELECTRON_WAIT_S | 60 | Max seconds to wait for Electron process |
RENDERER_WAIT_S | 60 | Max seconds to wait for SPA to load |
LobeHub-Specific Patterns
Access Zustand Store State
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
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
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
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
agent-browser --cdp 9222 eval "JSON.stringify(window.__CAPTURED_ERRORS)"
Chrome / Web Apps
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-test-profile \
"<URL>" &
sleep 5
agent-browser --cdp 9222 snapshot -i
agent-browser --auto-connect snapshot -i
Part 2: osascript (Native macOS App Bot Testing)
Use AppleScript via osascript to control native macOS desktop apps for bot testing. Works with any app that supports macOS Accessibility, no CDP or Chromium needed.
The pattern is the same for every platform:
- Activate the app (
tell application "X" to activate)
- Navigate to a channel/chat (Quick Switcher
Cmd+K or Search Cmd+F)
- Send a message (clipboard paste
Cmd+V + Enter)
- Wait for the bot response
- Screenshot for verification (
screencapture + Read tool)
Per-Platform References
Pick the file for your target platform — each contains activation, navigation, send-message, and verification snippets specific to that app:
For shared osascript patterns (activate, type, paste, screenshot, read accessibility, common workflow template, gotchas), see references/osascript-common.md. Read this first if you're new to osascript automation.
Scripts
Ready-to-use scripts in .agents/skills/local-testing/scripts/:
| Script | Usage |
|---|
electron-dev.sh | Manage Electron dev env (start/stop/status/restart) |
capture-app-window.sh | Capture screenshot of a specific app window |
record-electron-demo.sh | Record Electron app demo with ffmpeg |
record-app-screen.sh | Record app screen (video + screenshots, start/stop) |
test-discord-bot.sh | Send message to Discord bot via osascript |
test-slack-bot.sh | Send message to Slack bot via osascript |
test-telegram-bot.sh | Send message to Telegram bot via osascript |
test-wechat-bot.sh | Send message to WeChat bot via osascript |
test-lark-bot.sh | Send message to Lark / 飞书 bot via osascript |
test-qq-bot.sh | Send message to QQ bot via osascript |
Window Screenshot Utility
capture-app-window.sh captures a screenshot of a specific app window using screencapture -l <windowID>. It uses Swift + CGWindowList to find the window by process name, so screenshots work correctly even when the window is on an external monitor or behind other windows.
./.agents/skills/local-testing/scripts/capture-app-window.sh "Discord" /tmp/discord.png
./.agents/skills/local-testing/scripts/capture-app-window.sh "Slack" /tmp/slack.png
./.agents/skills/local-testing/scripts/capture-app-window.sh "WeChat" /tmp/wechat.png
All bot test scripts use this utility automatically for their screenshots.
Bot Test Scripts
All bot test scripts share the same interface:
./scripts/test-<platform>-bot.sh <channel_or_contact> <message> [wait_seconds] [screenshot_path]
Examples:
./.agents/skills/local-testing/scripts/test-discord-bot.sh "bot-testing" "!ping"
./.agents/skills/local-testing/scripts/test-discord-bot.sh "bot-testing" "/ask Tell me a joke" 30
./.agents/skills/local-testing/scripts/test-slack-bot.sh "bot-testing" "@mybot hello"
./.agents/skills/local-testing/scripts/test-slack-bot.sh "bot-testing" "/ask What is 2+2?" 20
./.agents/skills/local-testing/scripts/test-telegram-bot.sh "MyTestBot" "/start"
./.agents/skills/local-testing/scripts/test-telegram-bot.sh "GPTBot" "Hello" 60
./.agents/skills/local-testing/scripts/test-wechat-bot.sh "文件传输助手" "test message" 5
./.agents/skills/local-testing/scripts/test-wechat-bot.sh "MyBot" "Tell me a joke" 30
./.agents/skills/local-testing/scripts/test-lark-bot.sh "bot-testing" "@MyBot hello"
./.agents/skills/local-testing/scripts/test-lark-bot.sh "bot-testing" "Help me with this" 30
./.agents/skills/local-testing/scripts/test-qq-bot.sh "bot-testing" "Hello bot" 15
./.agents/skills/local-testing/scripts/test-qq-bot.sh "MyBot" "/help" 10
Each script: activates the app, navigates to the channel/contact, pastes the message via clipboard, sends, waits, and takes a screenshot. Use the Read tool on the screenshot for visual verification.
Screen Recording
Record automated demos using record-app-screen.sh (start/stop lifecycle, CDP screenshots + ffmpeg assembly). See references/record-app-screen.md for full documentation.
./.agents/skills/local-testing/scripts/electron-dev.sh start
./.agents/skills/local-testing/scripts/record-app-screen.sh start my-demo
./.agents/skills/local-testing/scripts/record-app-screen.sh stop
Outputs to .records/ directory (gitignored): <name>.mp4 (video) + <name>/ (screenshots every 3s).
Gotchas
agent-browser
- Daemon can get stuck — if commands hang,
agent-browser close --all or pkill -f agent-browser to reset
- HMR invalidates everything — after code changes, refs break. Re-snapshot or restart
snapshot -i doesn't find contenteditable — use snapshot -i -C for rich text editors
fill doesn't work on contenteditable — use type for chat inputs
- Screenshots go to
~/.agent-browser/tmp/screenshots/ — read them with the Read tool
- Dialogs block all commands — if commands time out, check
agent-browser dialog status
- Default timeout is 25s — override with
AGENT_BROWSER_DEFAULT_TIMEOUT (ms) or use explicit waits
- Shell quoting corrupts eval — use
eval --stdin <<'EVALEOF' for complex JS
Electron-specific
- Always use
electron-dev.sh stop to clean up — pkill -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.
- Don't resize the Electron window after load — resizing triggers full SPA reload
- Store is at
window.__LOBE_STORES not window.__ZUSTAND_STORES__
osascript
See references/osascript-common.md for the full osascript gotchas list (accessibility permissions, keystroke non-ASCII issues, locale-specific app names, rate limiting, etc.).