mirror of
https://github.com/actionbook/actionbook.git
synced 2026-09-19 03:28:06 +08:00
7f32f83815
* [packages/actionbook-rs]feat: add browser extension Implements the WebSocket bridge server and Chrome Extension skeleton for controlling the user's own browser via extension mode. New components: - Chrome Extension (MV3): background service worker, popup UI, WebSocket client connecting to bridge on localhost:19222 - Bridge server: `actionbook extension serve` - routes commands between CLI and extension with request ID rewriting and timeouts - CLI: --extension flag, `actionbook extension serve/status/ping` - Extension mode routing for `eval` and `pages` browser commands Architecture: CLI -> WebSocket -> Bridge -> Extension -> chrome.debugger * [packages/actionbook-rs]fix: merge duplicate extension_send fns and fix is_bridge_running side effect - Merge extension_send_cdp and extension_send into a single function - Replace WebSocket-based is_bridge_running with plain TCP connect to avoid orphan connections and spurious bridge warnings * [packages/actionbook-extension]feat: add offscreen keepalive, CDP event forwarding, createTab/activateTab - Add offscreen document for Service Worker keepalive (ping every 20s) - Forward CDP events from chrome.debugger.onEvent to CLI via WebSocket - Add exponential backoff reconnect (1s base, 2x growth, 30s max) - Auto-attach to active tab when no tab is attached - Add Extension.createTab and Extension.activateTab command handlers - Improve error handling for debugger detachment detection * [packages/actionbook-rs]feat: add extension mode to all browser commands (Phase 2) Add --extension flag support to all browser subcommands, routing CDP operations through the WebSocket bridge instead of direct chromiumoxide. Commands implemented: goto, back, forward, reload, open, switch, wait, click, type, fill, select, hover, focus, press, screenshot, pdf, html, text, snapshot, inspect, viewport, cookies, close, restart. Includes js_resolve_selector helper for [ref=eN] snapshot references and extension_eval helper with exception handling. Also fixes escape order bug in js_resolve_selector (backslashes must be escaped before single quotes). * [packages/actionbook-rs]test: add extension bridge integration tests and mock extension - Add [lib] crate to expose modules for integration tests - Add 9 integration tests covering: CLI without extension error, full round-trip with id remapping, error forwarding, multiple concurrent CLIs, is_bridge_running detection, connection failure - Add mock-extension.mjs for manual E2E testing with mock CDP responses * [packages/actionbook-rs]test: add Chrome E2E test script and expand bridge unit tests - Add extension-chrome-e2e.sh: real Chrome + arxiv.org E2E test covering navigation, content extraction, screenshots, cookies, tab management, interactions, and JSON output mode - Expand bridge_tests module with additional assertions and helpers * [packages/actionbook-rs]feat: add extension install/path/uninstall CLI commands Embed all 9 Chrome extension files into the Rust binary via include_str!/include_bytes! and provide CLI commands to extract them to ~/.config/actionbook/extension/ for easy Chrome sideloading. New commands: - `actionbook extension install [--force]` - `actionbook extension path` - `actionbook extension uninstall` Also updates `extension serve` to show the real installed path with outdated/not-installed warnings. All commands support --json. * [packages/actionbook-rs,actionbook-extension]feat: implement extension security Phase 0-2 Phase 0 - Connection Hardening: - Lazy connect (idle start, user-triggered connection) - Bounded reconnect with MAX_RETRIES=8 and exponential backoff - Distinguish network errors from auth failures in pre-handshake close Phase 1 - Session Token Auth: - Token generation (abk_ + 32 hex), atomic file write (0600) - Hello/hello_ack handshake protocol with token validation - Constant-time token comparison (subtle crate) to prevent timing attacks - TTL watchdog with 30min idle token rotation - Token format validation (abk_ prefix + length) in both CLI and extension Phase 2 - CDP Allowlist & Risk Levels: - CDP method allowlist with L1/L2/L3 risk classification - L3 human-in-the-loop confirmation via popup with nonce binding - Sender validation (only popup.html can approve L3 or set tokens) - Sensitive domain detection with L2→L3 risk elevation - Origin validation (loopback + chrome-extension:// only) Security hardening from dual review: - Replace all console.log with debug utility behind DEBUG_ENABLED flag - Remove token pre-population in popup input - Reject concurrent L3 requests (no slot overwrite) * [packages/actionbook-rs]chore: remove browser-extension-design.md from tracking * [actionbook-extension]chore: replace placeholder icons with Actionbook logomark Extract the logomark from docs/logo SVG and generate proper PNG icons at 16x16, 48x48, and 128x128 to replace the blue placeholder squares. * [packages/actionbook-rs,actionbook-extension]feat: switch cookie ops to chrome.cookies API with security gating Replace CDP Network.* cookie methods (which required L3 popup confirmation and always timed out in automated usage) with chrome.cookies.* API via new Extension.getCookies/setCookie/removeCookie/clearCookies commands. Security hardening from code review: - All cookie ops require a valid http(s) URL scope — no cross-domain wildcard - Cookie writes (set/remove/clear) gated by L3 confirmation on sensitive domains - Input validation for url, name, value params in background.js - Native messaging entry point validates exact extension ID (not prefix) - setCookie checks for null return (browser rejection) - clearCookies uses Promise.allSettled for parallel removal Also adds native messaging auto-connect, bridge token rotation handling, and fixes .gitignore typo (.doc -> .docs/). * [packages/actionbook-rs]test: replace arxiv.org with automation-friendly test sites Switch E2E test targets from arxiv.org (rate-limited) to the-internet.herokuapp.com and example.com for reliable CI. - Phase 2: use `browser open` to avoid chrome:// tab issues - Phase 6: login form interactions on /login (public test creds) - Phase 7: hover on /hovers `.figure`, focus link on homepage - Phase 9: example.com for new-tab test - login-result-page: check URL path instead of page title * [actionbook-extension]docs: add extension usage guide Cover installation, bridge setup, token pairing, all browser subcommands (navigation, interaction, tabs, debug, cookies, connect), Action Manual integration, and common troubleshooting. * [packages/actionbook-rs,actionbook-extension]fix: address PR #89 review — bridge security, reliability, and correctness - Add awaitPromise to Runtime.evaluate for async extension commands - Fix origin validation prefix-matching bypass (exact host matching) - Send token_expired notification before closing extension WebSocket - Return immediate error when extension channel is closed - Propagate custom bridge port to extension via native messaging - Prioritize explicit --domain over current URL in cookie ops - Retry native messaging with backoff instead of permanent disable - Use serde_json for comprehensive JS string escaping - Validate protocol version in bridge handshake with semver * [packages/actionbook-rs,actionbook-extension]fix: tab auto-attach, profile validation, and cookie clear safety 1. (Critical) Extension.createTab and Extension.activateTab now auto-attach the Chrome debugger to the new/activated tab, so subsequent CDP commands target the correct tab instead of a stale attachedTabId. 2. (High) --profile flag combined with --extension now produces a hard error instead of being silently ignored. 3. (Medium) cookies clear gains --domain, --dry-run, and --yes flags. --yes is required to actually clear (both interactive and JSON modes). --dry-run previews which cookies would be cleared without deleting. --domain scopes the clear to a specific domain. CDP mode rejects --domain/--dry-run with a clear error message. Tests: 6 new tests covering all three fixes (bridge-level + CLI parser). * [skills/actionbook]docs: fix SKILL.md extension mode documentation - Remove non-existent --path flag from extension install - Add missing extension uninstall command and --extension-port flag - Simplify Phase 2 alt to avoid redundant duplication of CDP example - Move Extension Setup section after Browser Commands for logical flow - Extract troubleshooting content to references/extension-troubleshooting.md - Fix misleading "runs in background" description for serve (it's foreground) - Replace opaque "L3 operations" term with concrete description - Add browser close safety note for extension mode * [packages/actionbook-rs,actionbook-extension]fix: use domain-based query for cookie clear to cover all paths chrome.cookies.getAll({ url }) only returns cookies whose path matches the URL path. When --domain builds https://domain/, cookies scoped to /app, /account, etc. are silently missed. Pass an explicit domain param so the extension uses getAll({ domain }) which covers all paths.
565 lines
16 KiB
Bash
Executable File
565 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Extension Bridge E2E Test — Real Chrome + Real Website
|
|
#
|
|
# Uses automation-friendly test sites (the-internet.herokuapp.com) to avoid
|
|
# rate limiting. Connects to the user's EXISTING Chrome browser (with extension
|
|
# already installed) and tests all CLI commands against real websites.
|
|
#
|
|
# Prerequisites:
|
|
# 1. Extension installed in your Chrome:
|
|
# - Open chrome://extensions → Enable Developer Mode
|
|
# - Load unpacked → select packages/actionbook-extension/
|
|
# 2. CLI binary built: cargo build --manifest-path packages/actionbook-rs/Cargo.toml
|
|
# 3. Port 19222 available (extension hard-codes this port)
|
|
#
|
|
# Usage:
|
|
# bash packages/actionbook-rs/tests/scripts/extension-chrome-e2e.sh
|
|
#
|
|
# Options:
|
|
# --skip-build Skip cargo build step
|
|
# --keep-tabs Don't close tabs opened during test
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Configuration ---
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TESTS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
RS_DIR="$(cd "$TESTS_DIR/.." && pwd)"
|
|
PROJECT_ROOT="$(cd "$RS_DIR/../.." && pwd)"
|
|
|
|
AB="$RS_DIR/target/debug/actionbook"
|
|
BRIDGE_PORT=19222
|
|
SKIP_BUILD=false
|
|
KEEP_TABS=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--skip-build) SKIP_BUILD=true; shift ;;
|
|
--keep-tabs) KEEP_TABS=true; shift ;;
|
|
*) echo "Unknown option: $1"; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
EXT="--extension --extension-port $BRIDGE_PORT"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
SKIP=0
|
|
TOTAL=0
|
|
FAILURES=""
|
|
|
|
TMPDIR=$(mktemp -d)
|
|
BRIDGE_PID=""
|
|
|
|
# Tabs we opened (for cleanup)
|
|
OPENED_TABS=()
|
|
|
|
# --- Cleanup ---
|
|
cleanup() {
|
|
local exit_code=$?
|
|
set +e
|
|
|
|
# Close tabs we opened (unless --keep-tabs)
|
|
if [ "$KEEP_TABS" = false ] && [ ${#OPENED_TABS[@]} -gt 0 ]; then
|
|
printf "\n Cleaning up opened tabs..."
|
|
for tab_id in "${OPENED_TABS[@]}"; do
|
|
"$AB" $EXT browser eval "
|
|
chrome_tab_id = $tab_id;
|
|
" &>/dev/null 2>&1
|
|
done
|
|
# Detach so the debug banner goes away
|
|
"$AB" $EXT browser close &>/dev/null 2>&1
|
|
printf " done\n"
|
|
fi
|
|
|
|
# Stop bridge
|
|
if [ -n "$BRIDGE_PID" ]; then
|
|
kill "$BRIDGE_PID" 2>/dev/null
|
|
wait "$BRIDGE_PID" 2>/dev/null
|
|
fi
|
|
|
|
rm -rf "$TMPDIR"
|
|
exit $exit_code
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# --- Helpers ---
|
|
log_pass() {
|
|
TOTAL=$((TOTAL + 1))
|
|
PASS=$((PASS + 1))
|
|
printf " \033[32mPASS\033[0m %s\n" "$1"
|
|
}
|
|
|
|
log_fail() {
|
|
TOTAL=$((TOTAL + 1))
|
|
FAIL=$((FAIL + 1))
|
|
FAILURES="${FAILURES}\n - $1: $2"
|
|
printf " \033[31mFAIL\033[0m %s: %s\n" "$1" "$2"
|
|
}
|
|
|
|
log_skip() {
|
|
TOTAL=$((TOTAL + 1))
|
|
SKIP=$((SKIP + 1))
|
|
printf " \033[33mSKIP\033[0m %s: %s\n" "$1" "$2"
|
|
}
|
|
|
|
log_info() {
|
|
printf " \033[36mINFO\033[0m %s\n" "$1"
|
|
}
|
|
|
|
run() {
|
|
set +e
|
|
OUTPUT=$("$AB" "$@" 2>&1)
|
|
EXIT_CODE=$?
|
|
set -e
|
|
}
|
|
|
|
assert_contains() {
|
|
local test_name="$1"
|
|
local pattern="$2"
|
|
if echo "$OUTPUT" | grep -qi "$pattern"; then
|
|
log_pass "$test_name"
|
|
else
|
|
log_fail "$test_name" "Expected '$pattern' in: $(echo "$OUTPUT" | head -3 | tr '\n' ' ')"
|
|
fi
|
|
}
|
|
|
|
assert_success() {
|
|
local test_name="$1"
|
|
if [ "$EXIT_CODE" -eq 0 ]; then
|
|
log_pass "$test_name"
|
|
else
|
|
log_fail "$test_name" "exit $EXIT_CODE: $(echo "$OUTPUT" | head -2 | tr '\n' ' ')"
|
|
fi
|
|
}
|
|
|
|
assert_file_exists() {
|
|
local test_name="$1"
|
|
local filepath="$2"
|
|
if [ -f "$filepath" ]; then
|
|
log_pass "$test_name"
|
|
else
|
|
log_fail "$test_name" "File not found: $filepath"
|
|
fi
|
|
}
|
|
|
|
assert_file_size_gt() {
|
|
local test_name="$1"
|
|
local filepath="$2"
|
|
local min_size="$3"
|
|
if [ -f "$filepath" ]; then
|
|
local size
|
|
size=$(wc -c < "$filepath" | tr -d ' ')
|
|
if [ "$size" -gt "$min_size" ]; then
|
|
log_pass "$test_name ($size bytes)"
|
|
else
|
|
log_fail "$test_name" "File too small: $size bytes (expected > $min_size)"
|
|
fi
|
|
else
|
|
log_fail "$test_name" "File not found: $filepath"
|
|
fi
|
|
}
|
|
|
|
wait_for_port() {
|
|
local port=$1
|
|
local max_wait=${2:-10}
|
|
local i=0
|
|
while [ $i -lt "$max_wait" ]; do
|
|
if lsof -i ":$port" -sTCP:LISTEN &>/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
i=$((i + 1))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
wait_for_extension() {
|
|
local max_wait=${1:-30}
|
|
local i=0
|
|
while [ $i -lt "$max_wait" ]; do
|
|
if "$AB" extension ping --port "$BRIDGE_PORT" 2>&1 | grep -qi "pong\|responded"; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
i=$((i + 1))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# --- Test Sites (automation-friendly, no rate limiting) ---
|
|
SITE="https://the-internet.herokuapp.com"
|
|
SITE_NAME="the-internet"
|
|
|
|
# ============================================================
|
|
printf "\n\033[1m=== Extension Bridge E2E Test (Real Chrome) ===\033[0m\n\n"
|
|
printf " This test connects to your EXISTING Chrome browser.\n"
|
|
printf " Make sure the Actionbook extension is installed and Chrome is open.\n"
|
|
printf " Test site: %s\n\n" "$SITE"
|
|
|
|
# --- Pre-flight ---
|
|
printf "\033[1m--- Pre-flight ---\033[0m\n"
|
|
|
|
if [ "$SKIP_BUILD" = false ]; then
|
|
printf " Building CLI... "
|
|
if cargo build --manifest-path "$RS_DIR/Cargo.toml" 2>/dev/null; then
|
|
printf "done\n"
|
|
else
|
|
printf "FAILED\n"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ ! -f "$AB" ]; then
|
|
echo "ERROR: Binary not found at $AB"
|
|
exit 1
|
|
fi
|
|
log_pass "binary-exists"
|
|
|
|
# Check port is free
|
|
if lsof -i ":$BRIDGE_PORT" -sTCP:LISTEN &>/dev/null 2>&1; then
|
|
echo ""
|
|
echo " Port $BRIDGE_PORT is in use. Killing existing bridge..."
|
|
lsof -ti ":$BRIDGE_PORT" | xargs kill 2>/dev/null || true
|
|
sleep 1
|
|
fi
|
|
log_pass "port-$BRIDGE_PORT-available"
|
|
|
|
# ============================================================
|
|
# Phase 1: Bridge + Extension Connection
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 1: Bridge + Extension ---\033[0m\n"
|
|
|
|
"$AB" extension serve --port "$BRIDGE_PORT" &>"$TMPDIR/bridge.log" &
|
|
BRIDGE_PID=$!
|
|
|
|
if wait_for_port "$BRIDGE_PORT" 5; then
|
|
log_pass "bridge-started"
|
|
else
|
|
log_fail "bridge-started" "Bridge failed to start"
|
|
cat "$TMPDIR/bridge.log" 2>/dev/null
|
|
exit 1
|
|
fi
|
|
|
|
printf " Waiting for Chrome extension to connect (open Chrome if not already)...\n"
|
|
if wait_for_extension 30; then
|
|
log_pass "extension-connected"
|
|
else
|
|
log_fail "extension-connected" "Timed out after 30s"
|
|
echo ""
|
|
echo " Make sure:"
|
|
echo " 1. Chrome is open"
|
|
echo " 2. Extension is installed: chrome://extensions → Load unpacked → packages/actionbook-extension/"
|
|
echo " 3. Extension shows green dot (connected) in popup"
|
|
echo ""
|
|
echo " Bridge log:"
|
|
tail -5 "$TMPDIR/bridge.log" 2>/dev/null
|
|
exit 1
|
|
fi
|
|
|
|
run extension ping --port "$BRIDGE_PORT"
|
|
assert_contains "ping" "pong\|responded"
|
|
|
|
run extension status --port "$BRIDGE_PORT"
|
|
assert_contains "status" "running"
|
|
|
|
# ============================================================
|
|
# Phase 2: Navigate to test site
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 2: Navigate to $SITE_NAME ---\033[0m\n"
|
|
|
|
# Open a fresh tab first (avoids chrome:// URL issues after extension reload)
|
|
run $EXT browser open "$SITE"
|
|
assert_success "goto-site"
|
|
sleep 3
|
|
|
|
# Verify we're on the test site
|
|
run $EXT browser eval "document.title"
|
|
log_info "Page title: $(echo "$OUTPUT" | head -1)"
|
|
assert_contains "site-title" "Internet"
|
|
|
|
# List tabs — should include test site
|
|
run $EXT browser pages
|
|
assert_contains "pages-has-site" "internet\|Internet\|herokuapp"
|
|
|
|
# ============================================================
|
|
# Phase 3: Content Extraction
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 3: Content Extraction ---\033[0m\n"
|
|
|
|
# Eval: simple arithmetic
|
|
run $EXT browser eval "1+1"
|
|
assert_contains "eval-arithmetic" "2"
|
|
|
|
# Eval: read real DOM
|
|
run $EXT browser eval "document.querySelector('h1,h2')?.textContent?.trim() || document.title"
|
|
assert_success "eval-dom-query"
|
|
log_info "Heading text: $(echo "$OUTPUT" | head -1)"
|
|
|
|
# HTML: full page contains site content
|
|
run $EXT browser html
|
|
assert_contains "html-full-page" "Internet\|herokuapp"
|
|
|
|
# HTML: with selector
|
|
run $EXT browser html "h1"
|
|
assert_success "html-with-selector"
|
|
|
|
# Text: page text
|
|
run $EXT browser text
|
|
assert_contains "text-page" "Available Examples\|Internet"
|
|
|
|
# Viewport
|
|
run $EXT browser viewport
|
|
assert_contains "viewport" "[Vv]iewport\|[0-9]*x[0-9]"
|
|
log_info "Viewport: $(echo "$OUTPUT" | head -1)"
|
|
|
|
# ============================================================
|
|
# Phase 4: Screenshot & PDF
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 4: Screenshot & PDF ---\033[0m\n"
|
|
|
|
SCREENSHOT_PATH="$TMPDIR/e2e-screenshot.png"
|
|
run $EXT browser screenshot "$SCREENSHOT_PATH"
|
|
assert_success "screenshot-command"
|
|
assert_file_exists "screenshot-file" "$SCREENSHOT_PATH"
|
|
assert_file_size_gt "screenshot-has-content" "$SCREENSHOT_PATH" 5000
|
|
|
|
PDF_PATH="$TMPDIR/e2e-page.pdf"
|
|
run $EXT browser pdf "$PDF_PATH"
|
|
assert_success "pdf-command"
|
|
assert_file_exists "pdf-file" "$PDF_PATH"
|
|
assert_file_size_gt "pdf-has-content" "$PDF_PATH" 5000
|
|
|
|
# ============================================================
|
|
# Phase 5: Navigation (back / forward / reload)
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 5: Navigation ---\033[0m\n"
|
|
|
|
# Navigate to a sub-page
|
|
run $EXT browser goto "$SITE/login"
|
|
assert_success "goto-login"
|
|
sleep 3
|
|
|
|
run $EXT browser eval "document.title"
|
|
log_info "After goto: $(echo "$OUTPUT" | head -1)"
|
|
|
|
# Reload
|
|
run $EXT browser reload
|
|
assert_success "reload"
|
|
sleep 2
|
|
|
|
# Go back to homepage
|
|
run $EXT browser back
|
|
assert_success "back"
|
|
sleep 2
|
|
|
|
run $EXT browser eval "document.title"
|
|
log_info "After back: $(echo "$OUTPUT" | head -1)"
|
|
|
|
# Go forward again
|
|
run $EXT browser forward
|
|
assert_success "forward"
|
|
sleep 2
|
|
|
|
run $EXT browser eval "document.title"
|
|
log_info "After forward: $(echo "$OUTPUT" | head -1)"
|
|
|
|
# ============================================================
|
|
# Phase 6: Interactions (login form on the-internet)
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 6: Interactions (login form) ---\033[0m\n"
|
|
|
|
# Go to login page (has username + password fields + Login button)
|
|
run $EXT browser goto "$SITE/login"
|
|
assert_success "goto-login-page"
|
|
sleep 2
|
|
|
|
# Check that form fields exist
|
|
run $EXT browser eval "document.querySelector('#username') ? 'found' : 'not-found'"
|
|
LOGIN_INPUT_EXISTS=$(echo "$OUTPUT" | head -1)
|
|
|
|
if echo "$LOGIN_INPUT_EXISTS" | grep -q "found"; then
|
|
# Fill the username field (public test credentials for the-internet.herokuapp.com)
|
|
run $EXT browser fill '#username' "tomsmith"
|
|
assert_success "fill-username"
|
|
|
|
# Verify value was filled
|
|
sleep 0.3
|
|
run $EXT browser eval "document.querySelector('#username').value"
|
|
assert_contains "fill-effect" "tomsmith"
|
|
|
|
# Fill the password field
|
|
run $EXT browser fill '#password' "SuperSecretPassword!"
|
|
assert_success "fill-password"
|
|
|
|
# Click the Login button
|
|
run $EXT browser click 'button[type="submit"]'
|
|
assert_success "click-login"
|
|
sleep 3
|
|
|
|
# Verify login success (redirects to /secure with success flash message)
|
|
run $EXT browser eval "window.location.pathname"
|
|
log_info "After login path: $(echo "$OUTPUT" | head -1)"
|
|
assert_contains "login-result-page" "secure"
|
|
|
|
# Click the Logout button to return to login page
|
|
run $EXT browser click 'a[href="/logout"]'
|
|
assert_success "click-logout"
|
|
sleep 2
|
|
|
|
run $EXT browser eval "window.location.href"
|
|
assert_contains "after-logout" "login"
|
|
else
|
|
log_skip "fill-username" "Login input not found on page"
|
|
log_skip "fill-effect" "Skipped (no login input)"
|
|
log_skip "fill-password" "Skipped"
|
|
log_skip "click-login" "Skipped"
|
|
log_skip "login-result-page" "Skipped"
|
|
log_skip "click-logout" "Skipped"
|
|
log_skip "after-logout" "Skipped"
|
|
fi
|
|
|
|
# ============================================================
|
|
# Phase 7: Hover / Focus / Press
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 7: Hover / Focus / Press ---\033[0m\n"
|
|
|
|
# Navigate to hovers page (has elements that respond to hover)
|
|
run $EXT browser goto "$SITE/hovers"
|
|
assert_success "goto-hovers"
|
|
sleep 2
|
|
|
|
# Hover on an image (triggers hover effect showing user info)
|
|
run $EXT browser hover ".figure"
|
|
assert_success "hover-figure"
|
|
|
|
# Navigate to a page with links for focus test
|
|
run $EXT browser goto "$SITE"
|
|
sleep 2
|
|
|
|
# Focus on a link (homepage has many <a> tags with href)
|
|
run $EXT browser focus "a"
|
|
assert_success "focus-link"
|
|
|
|
# Press Escape key
|
|
run $EXT browser press Escape
|
|
assert_success "press-escape"
|
|
|
|
# Press Tab key
|
|
run $EXT browser press Tab
|
|
assert_success "press-tab"
|
|
|
|
# ============================================================
|
|
# Phase 8: Cookies (uses chrome.cookies API via Extension.*)
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 8: Cookies ---\033[0m\n"
|
|
|
|
# List cookies for current site
|
|
run $EXT browser cookies
|
|
assert_success "cookies-list"
|
|
|
|
# Set a test cookie
|
|
run $EXT browser cookies set e2e_test_cookie "hello_from_e2e"
|
|
assert_success "cookies-set"
|
|
|
|
# Verify cookie was set by listing again
|
|
run $EXT browser cookies
|
|
log_info "Cookies after set: $(echo "$OUTPUT" | head -3 | tr '\n' ' ')"
|
|
|
|
# Delete the test cookie
|
|
run $EXT browser cookies delete e2e_test_cookie
|
|
assert_success "cookies-delete"
|
|
|
|
# Clear all cookies for this site
|
|
run $EXT browser cookies clear
|
|
assert_success "cookies-clear"
|
|
|
|
# ============================================================
|
|
# Phase 9: Tab Management
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 9: Tab Management ---\033[0m\n"
|
|
|
|
# List all tabs
|
|
run $EXT browser pages
|
|
assert_success "pages-list"
|
|
log_info "Tabs: $(echo "$OUTPUT" | grep -c 'tab\|Tab' || echo '?') visible"
|
|
|
|
# Open a new tab (use example.com — always available, fast)
|
|
run $EXT browser open "https://example.com"
|
|
assert_success "open-new-tab"
|
|
sleep 3
|
|
|
|
run $EXT browser eval "document.title"
|
|
log_info "New tab: $(echo "$OUTPUT" | head -1)"
|
|
|
|
# Switch between tabs
|
|
run $EXT browser pages
|
|
TAB_IDS=$(echo "$OUTPUT" | grep -o 'tab:[0-9]*' | cut -d: -f2)
|
|
FIRST_TAB=$(echo "$TAB_IDS" | head -1)
|
|
LAST_TAB=$(echo "$TAB_IDS" | tail -1)
|
|
|
|
if [ -n "$FIRST_TAB" ] && [ -n "$LAST_TAB" ] && [ "$FIRST_TAB" != "$LAST_TAB" ]; then
|
|
run $EXT browser switch "$FIRST_TAB"
|
|
assert_success "switch-to-first-tab"
|
|
sleep 1
|
|
|
|
run $EXT browser switch "$LAST_TAB"
|
|
assert_success "switch-to-last-tab"
|
|
sleep 1
|
|
else
|
|
log_skip "switch-to-first-tab" "Need at least 2 tabs"
|
|
log_skip "switch-to-last-tab" "Need at least 2 tabs"
|
|
fi
|
|
|
|
# ============================================================
|
|
# Phase 10: Snapshot / Inspect / JSON / Close / Restart
|
|
# ============================================================
|
|
printf "\033[1m--- Phase 10: Advanced ---\033[0m\n"
|
|
|
|
# Accessibility snapshot
|
|
run $EXT browser snapshot
|
|
assert_success "snapshot"
|
|
|
|
# Inspect element at coordinates
|
|
run $EXT browser inspect 200 200
|
|
assert_success "inspect"
|
|
|
|
# JSON output mode
|
|
run --json $EXT browser eval "1+1"
|
|
assert_success "json-eval"
|
|
if echo "$OUTPUT" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
|
|
log_pass "json-valid"
|
|
elif echo "$OUTPUT" | grep -q '^[{\[0-9]'; then
|
|
log_pass "json-valid"
|
|
else
|
|
log_fail "json-valid" "Not valid JSON: $(echo "$OUTPUT" | head -1)"
|
|
fi
|
|
|
|
# Close (detach debugger from tab)
|
|
run $EXT browser close
|
|
assert_success "close"
|
|
|
|
# Restart (re-navigate)
|
|
run $EXT browser goto "$SITE"
|
|
sleep 2
|
|
run $EXT browser restart
|
|
assert_success "restart"
|
|
|
|
# ============================================================
|
|
# Summary
|
|
# ============================================================
|
|
printf "\n\033[1m=== E2E Results ===\033[0m\n"
|
|
printf "Total: %d | \033[32mPass: %d\033[0m | \033[31mFail: %d\033[0m | \033[33mSkip: %d\033[0m\n" \
|
|
"$TOTAL" "$PASS" "$FAIL" "$SKIP"
|
|
|
|
if [ "$FAIL" -gt 0 ]; then
|
|
printf "\n\033[31mFailures:\033[0m"
|
|
printf "%b\n" "$FAILURES"
|
|
echo ""
|
|
echo " Bridge log: $TMPDIR/bridge.log"
|
|
exit 1
|
|
else
|
|
printf "\n\033[32mAll E2E tests passed!\033[0m\n"
|
|
exit 0
|
|
fi
|