cc-haha/desktop/scripts/e2e-slash-plugin-agent-browser.sh
程序员阿江(Relakkes) de736c49f7 Unify plugin capabilities with the desktop management views
Desktop plugin details now route into the shared Skills, Agents, and MCP management surfaces instead of maintaining separate read-only drilldowns. This also extends the desktop/server skill aggregation so plugin-provided skills appear in the shared list, groups MCP entries by source, and preserves detail-view back navigation based on where the user entered the page.

The implementation keeps plugin detail as the high-level capability hub while pushing real inspection into the existing management pages. Disabled plugins no longer expose false navigation paths into shared views, and the agent-browser regression script was expanded to exercise the new end-to-end flows.

Constraint: Shared Agents data only includes enabled plugin agents, so disabled plugins cannot deep-link into agent detail
Rejected: Keep duplicating full Skills/Agents/MCP detail inside Plugin detail | creates divergent UI flows and stale data paths
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If a detail view can be opened from multiple entry points, keep the return target in store state rather than hardcoding a single back destination
Tested: desktop vitest for plugins/skills/agents/mcp; desktop tsc --noEmit; desktop vite build; server skills API test; agent-browser web regression on plugin->skill/mcp and plugin->agent back navigation
Not-tested: packaged desktop app regression after rebuilding the Tauri bundle
2026-04-22 12:31:57 +08:00

255 lines
9.9 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
if ! command -v agent-browser >/dev/null 2>&1; then
echo "agent-browser is required but was not found in PATH" >&2
exit 1
fi
API_URL="${API_URL:-http://127.0.0.1:3457}"
WEB_URL="${WEB_URL:-http://127.0.0.1:5175/?serverUrl=http://127.0.0.1:3457}"
RUN_ID="$(date +%s)-$RANDOM"
SESSION_NAME="cc-haha-webui-e2e-${RUN_ID}"
ARTIFACT_DIR="$(mktemp -d "/tmp/cc-haha-webui-e2e-${RUN_ID}-XXXX")"
AB=(agent-browser --session "${SESSION_NAME}")
cleanup() {
local exit_code=$?
"${AB[@]}" close >/dev/null 2>&1 || true
echo "Artifacts kept at: ${ARTIFACT_DIR}" >&2
}
trap cleanup EXIT
wait_for_text() {
local needle="$1"
if ! "${AB[@]}" wait "text=${needle}" >/dev/null 2>&1; then
echo "Timed out waiting for page text: ${needle}" >&2
"${AB[@]}" screenshot "${ARTIFACT_DIR}/failure-wait-$(echo "${needle}" | tr ' /' '__').png" >/dev/null 2>&1 || true
return 1
fi
}
press_escape() {
"${AB[@]}" press Escape >/dev/null 2>&1 || true
}
focus_composer() {
"${AB[@]}" click textarea >/dev/null 2>&1 || "${AB[@]}" focus textarea >/dev/null 2>&1 || true
}
submit_slash_command() {
local command="$1"
focus_composer
"${AB[@]}" fill textarea "${command}"
"${AB[@]}" press Enter
}
healthcheck() {
curl -fsS "${API_URL}/health" >/dev/null
}
select_plugin_targets() {
DETAIL_PLUGIN_ID="$(curl -fsS "${API_URL}/api/plugins" | jq -r '
.plugins
| map(select((.componentCounts.commands + .componentCounts.agents + .componentCounts.hooks + .componentCounts.skills) > 0))
| sort_by(-(.componentCounts.commands + .componentCounts.agents + .componentCounts.hooks + .componentCounts.skills))
| .[0].id // empty
')"
ENABLED_SKILL_PLUGIN_ID="$(curl -fsS "${API_URL}/api/plugins" | jq -r '
.plugins
| map(select(.enabled == true and .componentCounts.skills > 0))
| sort_by(-.componentCounts.skills)
| .[0].id // empty
')"
MCP_PLUGIN_ID="$(curl -fsS "${API_URL}/api/plugins" | jq -r '
.plugins
| map(select(.enabled == true and .componentCounts.mcpServers > 0))
| sort_by(-.componentCounts.mcpServers)
| .[0].id // empty
')"
if [[ -z "${DETAIL_PLUGIN_ID}" ]]; then
echo "No plugin with commands/agents/hooks/skills was found in current API data." >&2
exit 1
fi
if [[ -z "${ENABLED_SKILL_PLUGIN_ID}" ]]; then
echo "No enabled plugin with skills was found in current API data." >&2
exit 1
fi
if [[ -z "${MCP_PLUGIN_ID}" ]]; then
echo "No plugin with MCP servers was found in current API data." >&2
exit 1
fi
DETAIL_PLUGIN_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.name')"
DETAIL_PLUGIN_ENABLED="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.enabled')"
DETAIL_COMMAND="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.commandEntries[0].name // empty')"
DETAIL_AGENT="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.agentEntries[0].name // empty')"
DETAIL_AGENT_LABEL="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.agentEntries[0].displayName // .detail.agentEntries[0].name // empty')"
DETAIL_HOOK_EVENT="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.hookEntries[0].event // empty')"
DETAIL_HOOK_ACTION="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.hookEntries[0].actions[0] // empty')"
DETAIL_SKILL_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${ENABLED_SKILL_PLUGIN_ID}" | jq -r '.detail.skillEntries[0].name // empty')"
DETAIL_SKILL_LABEL="$(curl -fsS "${API_URL}/api/plugins/detail?id=${ENABLED_SKILL_PLUGIN_ID}" | jq -r '.detail.skillEntries[0].displayName // .detail.skillEntries[0].name // empty')"
DETAIL_SKILL_DESCRIPTION="$(curl -fsS --get --data-urlencode "source=plugin" --data-urlencode "name=${DETAIL_SKILL_NAME}" "${API_URL}/api/skills/detail" | jq -r '.detail.meta.description // empty')"
DETAIL_SKILL_PLUGIN_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${ENABLED_SKILL_PLUGIN_ID}" | jq -r '.detail.name')"
MCP_PLUGIN_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.name')"
MCP_SERVER_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.mcpServerEntries[0].name // empty')"
MCP_SERVER_LABEL="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.mcpServerEntries[0].displayName // .detail.mcpServerEntries[0].name // empty')"
MCP_SKILL_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.skillEntries[0].name // empty')"
}
open_plugin_detail() {
local plugin_name="$1"
local escaped_name="${plugin_name//\"/\\\"}"
if "${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.includes(\"${escaped_name}\")); if(!target) throw new Error('plugin button not found'); target.click();" >/dev/null 2>&1; then
wait_for_text "Bundled capabilities"
return 0
fi
echo "Failed to open plugin detail for: ${plugin_name}" >&2
"${AB[@]}" screenshot "${ARTIFACT_DIR}/failure-open-plugin-${plugin_name}.png" >/dev/null 2>&1 || true
return 1
}
go_back_to_plugin_list() {
"${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.includes('Back to list')); if(!target) throw new Error('back button not found'); target.click();" >/dev/null
wait_for_text "Browse installed plugins"
}
open_settings_sidebar_tab() {
local tab_name="$1"
"${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.trim()==='${tab_name}'); if(!target) throw new Error('settings tab not found: ${tab_name}'); target.click();" >/dev/null
}
click_visible_card_text() {
local text="$1"
local escaped_text="${text//\"/\\\"}"
"${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.includes(\"${escaped_text}\")); if(!target) throw new Error('button not found: ${escaped_text}'); target.click();" >/dev/null
}
healthcheck
select_plugin_targets
echo "Using detail plugin: ${DETAIL_PLUGIN_NAME} (${DETAIL_PLUGIN_ID})"
echo "Using enabled skill plugin: ${DETAIL_SKILL_PLUGIN_NAME} (${ENABLED_SKILL_PLUGIN_ID})"
echo "Using MCP plugin: ${MCP_PLUGIN_NAME} (${MCP_PLUGIN_ID})"
"${AB[@]}" open "${WEB_URL}"
"${AB[@]}" wait --load networkidle
wait_for_text "Claude Code Haha"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/01-home.png" >/dev/null
# Always work from a fresh chat surface so slash-command behavior is deterministic.
"${AB[@]}" find role button click --name "New session"
"${AB[@]}" wait textarea >/dev/null
submit_slash_command "/mcp"
wait_for_text "Available MCP tools"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/02-mcp-panel.png" >/dev/null
press_escape
"${AB[@]}" wait 300 >/dev/null
submit_slash_command "/skills"
wait_for_text "Available skills"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/03-skills-panel.png" >/dev/null
press_escape
"${AB[@]}" wait 300 >/dev/null
submit_slash_command "/plugin"
wait_for_text "Browse installed plugins"
wait_for_text "Plugin Manager"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/04-plugins-list.png" >/dev/null
open_plugin_detail "${DETAIL_PLUGIN_NAME}"
wait_for_text "Commands"
wait_for_text "Agents"
wait_for_text "Hooks"
wait_for_text "Skills"
if [[ -n "${DETAIL_COMMAND}" ]]; then
wait_for_text "/${DETAIL_COMMAND}"
fi
if [[ -n "${DETAIL_AGENT}" ]]; then
wait_for_text "${DETAIL_AGENT}"
fi
if [[ -n "${DETAIL_HOOK_EVENT}" ]]; then
wait_for_text "${DETAIL_HOOK_EVENT}"
fi
if [[ -n "${DETAIL_HOOK_ACTION}" ]]; then
wait_for_text "${DETAIL_HOOK_ACTION}"
fi
if [[ -n "${DETAIL_SKILL_NAME}" ]]; then
wait_for_text "/${DETAIL_SKILL_NAME}"
fi
"${AB[@]}" screenshot "${ARTIFACT_DIR}/05-plugin-detail-main.png" >/dev/null
open_settings_sidebar_tab "Plugins"
wait_for_text "Plugin Detail"
go_back_to_plugin_list
open_plugin_detail "${DETAIL_SKILL_PLUGIN_NAME}"
if [[ -n "${DETAIL_SKILL_LABEL}" ]]; then
click_visible_card_text "${DETAIL_SKILL_LABEL}"
wait_for_text "Skill metadata"
wait_for_text "${DETAIL_SKILL_LABEL}"
if [[ -n "${DETAIL_SKILL_DESCRIPTION}" ]]; then
wait_for_text "${DETAIL_SKILL_DESCRIPTION}"
fi
"${AB[@]}" screenshot "${ARTIFACT_DIR}/06-skill-detail-from-plugin.png" >/dev/null
open_settings_sidebar_tab "Skills"
wait_for_text "Skill Browser"
wait_for_text "Plugin"
wait_for_text "${DETAIL_SKILL_LABEL}"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/07-skills-list-with-plugin-group.png" >/dev/null
fi
open_settings_sidebar_tab "Plugins"
wait_for_text "Plugin Detail"
if [[ "${DETAIL_PLUGIN_ENABLED}" == "true" && -n "${DETAIL_AGENT_LABEL}" ]]; then
click_visible_card_text "${DETAIL_AGENT_LABEL}"
wait_for_text "Agent Profile"
wait_for_text "${DETAIL_AGENT}"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/08-agent-detail-from-plugin.png" >/dev/null
open_settings_sidebar_tab "Agents"
wait_for_text "Agent Browser"
wait_for_text "Plugin"
wait_for_text "${DETAIL_AGENT}"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/09-agents-list-with-plugin-group.png" >/dev/null
fi
go_back_to_plugin_list
open_plugin_detail "${MCP_PLUGIN_NAME}"
wait_for_text "MCP servers"
if [[ -n "${MCP_SERVER_NAME}" ]]; then
wait_for_text "${MCP_SERVER_LABEL:-$MCP_SERVER_NAME}"
fi
if [[ -n "${MCP_SKILL_NAME}" ]]; then
wait_for_text "/${MCP_SKILL_NAME}"
fi
"${AB[@]}" screenshot "${ARTIFACT_DIR}/10-plugin-detail-mcp.png" >/dev/null
if [[ -n "${MCP_SERVER_LABEL}" ]]; then
click_visible_card_text "${MCP_SERVER_LABEL}"
wait_for_text "${MCP_SERVER_NAME}"
wait_for_text "Plugin"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/11-mcp-detail-from-plugin.png" >/dev/null
open_settings_sidebar_tab "MCP"
wait_for_text "MCP servers"
wait_for_text "Plugin"
wait_for_text "${MCP_SERVER_NAME}"
"${AB[@]}" screenshot "${ARTIFACT_DIR}/12-mcp-list-with-plugin-group.png" >/dev/null
fi
echo "agent-browser web UI regression passed"
echo "Artifacts: ${ARTIFACT_DIR}"