Files
Agent-System-Dev/scripts/e2e.sh
T
weichen fbd583fc65 Add E2E launcher for U4 audit acceptance and bump frontend pin
Why: U4 acceptance needs a reproducible real-browser run: build the
RDM binary from its origin/main, seed projects/ingest via the command
API, serve with auth on, and drive Playwright against vite preview.

Evidence: scripts/e2e.sh --seed-only smoke (in progress this task);
frontend pin points at the Playwright harness commit.

Impact: dev tooling only (.tmp artifacts ignored; no runtime changes).

Refs: task feature/rdm-e2e-acceptance; frontend feature branch of
the same name.
2026-10-11 17:58:12 +00:00

175 lines
8.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# e2e.sh —— U4 运行验收一条龙(真实浏览器:Playwright × RDM 审计工作区)。
#
# 动线(与 frontend/e2e/rdm-audit.spec.ts 的环境契约一一对应):
# 1. 构建:RDM 二进制(RDM 仓 origin/main 临时 worktree,pins 物化)
# + 基座 CLI(本 worktree modules/backend,建号用)
# + frontend dist(本 worktree modules/frontend)
# 2. 种子:RDM -bootstrap-admin(域管理员)→ 基座 user create(登录账号)
# → serve(auth on)→ API rdm.project.create×2 + rdm.ingest(主项目)
# 3. 验收:vite preview(dist+/api 代理)→ npx playwright test
# 4. 收尾:停 serve/preview,保存认证/失败工件到 $E2E_OUT
#
# 用法:bash scripts/e2e.sh [--seed-only]
# --seed-only 只做构建+种子+起服务(留端口与路径在 stdout,供人工/调试)
# 环境变量:
# RDM_REPO RDM 仓路径(默认 <本仓上级>/Agent-System-RDM)
# E2E_API_PORT/E2E_UI_PORT 服务端口(默认 18791/4173)
# E2E_OWNER/E2E_PASSWORD 登录账号(默认 e2e-auditor/E2eAudit#2026)
# E2E_PROJECT_MAIN/E2E_PROJECT_ISOLATED 项目名(默认 审计演示项目/隔离检查项目)
# E2E_OUT 工件目录(默认 .tmp/e2e/<ts>;.gitignore 已覆盖 .tmp)
# 依赖:go、node/npm、curl;首次运行会 npx playwright install chromium。
set -euo pipefail
DEV_ROOT=$(git -C "$(dirname "$0")/.." rev-parse --show-toplevel)
cd "$DEV_ROOT"
SEED_ONLY=0
[ "${1:-}" = "--seed-only" ] && SEED_ONLY=1
RDM_REPO=${RDM_REPO:-"$DEV_ROOT/../Agent-System-RDM"}
API_PORT=${E2E_API_PORT:-18791}
UI_PORT=${E2E_UI_PORT:-4173}
OWNER=${E2E_OWNER:-e2e-auditor}
PASSWORD=${E2E_PASSWORD:-E2eAudit#2026}
PROJ_MAIN=${E2E_PROJECT_MAIN:-审计演示项目}
PROJ_ISO=${E2E_PROJECT_ISOLATED:-隔离检查项目}
API_BASE="http://127.0.0.1:$API_PORT"
# 工件目录一律绝对路径(git -C / 后台进程的 cwd 解析不能依赖调用方位置)。
OUT=${E2E_OUT:-"$DEV_ROOT/.tmp/e2e/$(date +%Y%m%d-%H%M%S)"}
case "$OUT" in /*) ;; *) OUT="$PWD/$OUT" ;; esac
mkdir -p "$OUT"
log() { printf '\033[36m[e2e]\033[0m %s\n' "$*"; }
die() { printf '\033[31m[e2e][FAIL]\033[0m %s\n' "$*" >&2; exit 1; }
command -v go >/dev/null || die "go not found"
command -v node >/dev/null || die "node not found"
command -v curl >/dev/null || die "curl not found"
[ -d "$RDM_REPO/.git" ] || die "RDM repo not found at $RDM_REPO (set RDM_REPO)"
for p in "$API_PORT" "$UI_PORT"; do
if timeout 2 bash -c "echo > /dev/tcp/127.0.0.1/$p" 2>/dev/null; then
die "port $p already in use (set E2E_API_PORT/E2E_UI_PORT)"
fi
done
# ── 1. 构建 ─────────────────────────────────────────────
log "build RDM binary (worktree at origin/main, pins materialized)"
RDM_WT="$OUT/rdm-src"
mkdir -p "$RDM_WT"
git -C "$RDM_REPO" fetch origin main --quiet
RDM_SHA=$(git -C "$RDM_REPO" rev-parse origin/main)
git -C "$RDM_REPO" worktree add --detach "$RDM_WT" "$RDM_SHA" >/dev/null
RDM_WT_ADDED=1
cleanup() {
[ "${RDM_WT_ADDED:-}" = 1 ] && git -C "$RDM_REPO" worktree remove --force "$RDM_WT" 2>/dev/null || true
[ -n "${API_PID:-}" ] && kill "$API_PID" 2>/dev/null || true
[ -n "${UI_PID:-}" ] && kill "$UI_PID" 2>/dev/null || true
}
trap cleanup EXIT
git -C "$RDM_WT" submodule update --init --recursive --quiet
(cd "$RDM_WT" && go build -o "$OUT/agent-system-rdm" .) || die "rdm build failed"
log "rdm binary: $OUT/agent-system-rdm (RDM@${RDM_SHA:0:12})"
log "build base CLI (user create)"
(cd "$DEV_ROOT/modules/backend" && go build -o "$OUT/agent-system" ./cmd/agent-system) \
|| die "backend cli build failed"
log "build frontend dist (contracts + ui deps + bundle, via make frontend)"
make -C "$DEV_ROOT" frontend >/dev/null || die "frontend build failed"
# ── 2. 种子 ─────────────────────────────────────────────
DATA="$OUT/data"
mkdir -p "$DATA"
RDM="$OUT/agent-system-rdm -data $DATA"
log "bootstrap: RDM domain admin = $OWNER"
$RDM -bootstrap-admin "$OWNER" >/dev/null
log "bootstrap: base account $OWNER (admin)"
"$OUT/agent-system" user create --data "$DATA" --owner "$OWNER" --name "E2E 审计员" \
--password "$PASSWORD" --admin >/dev/null
log "serve RDM at $API_BASE (auth on)"
"$OUT/agent-system-rdm" -data "$DATA" -addr "127.0.0.1:$API_PORT" -auth on \
>"$OUT/serve.log" 2>&1 &
API_PID=$!
# auth on 时未登录 /api/me 返回 401——服务已就绪的信号是「有 HTTP 应答」而非 2xx。
for i in $(seq 1 40); do
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$API_BASE/api/me" || echo 000)
[ "$CODE" != "000" ] && break
sleep 0.25
[ "$i" = 40 ] && { tail -5 "$OUT/serve.log"; die "api not ready"; }
done
JAR="$OUT/cookies.txt"
curl -sf -c "$JAR" -X POST "$API_BASE/api/auth/login" \
-H 'Content-Type: application/json' \
-d "{\"owner\":\"$OWNER\",\"password\":\"$PASSWORD\"}" >/dev/null || die "login failed"
cmd() { # cmd <id> <params-json> → payload(失败即死,种子步骤不容忍部分成功)
local id=$1 params=${2:-{\}}
curl -sf -b "$JAR" -X POST "$API_BASE/api/commands/$id" \
-H 'Content-Type: application/json' -d "{\"params\":$params}" \
| python3 -c 'import json,sys; d=json.load(sys.stdin); r=(d or {}).get("receipt",{}); sys.exit(1) if r.get("state")=="rejected" else None; print(json.dumps(d.get("payload", d), ensure_ascii=False))' \
|| die "command $id rejected"
}
log "seed: create projects"
PID_MAIN=$(cmd rdm.project.create "{\"name\":\"$PROJ_MAIN\"}" | python3 -c 'import json,sys; print(json.load(sys.stdin)["projectId"])')
PID_ISO=$(cmd rdm.project.create "{\"name\":\"$PROJ_ISO\"}" | python3 -c 'import json,sys; print(json.load(sys.stdin)["projectId"])')
log "seed: projects main=$PID_MAIN iso=$PID_ISO"
# 主项目语料:需求行(含表格)→ 切片+骨架挂载+事件,供概览/树/追溯/时间线消费。
SEED_MD='# 用户需求与系统需求
| UR-01 | 离线可用:断网时已有会话可继续浏览 |
| UR-02 | 权限收敛:普通用户无审计入口 |
| UR-03 | 追溯取证不超过三次主要点击 |
## 背景说明
审计工作区按项目隔离读取;管理员可跨项目目录进入。事件按操作归组,
历史模式按 seq 还原,缺失事实明确标注不可恢复。'
log "seed: ingest corpus into $PID_MAIN"
cmd rdm.ingest "{\"projectId\":\"$PID_MAIN\",\"source\":\"01-用户需求与系统需求.md\",\"stage\":\"BD\",\"text\":$(python3 -c 'import json,sys; print(json.dumps(open("/dev/stdin").read()))' <<<"$SEED_MD")}" >/dev/null
log "seed done"
# ── 3. 验收 ─────────────────────────────────────────────
if [ "$SEED_ONLY" = 1 ]; then
log "seed-only mode: api=$API_BASE ui=next;cookie jar=$JAR;数据=$DATA"
log "preview: (cd modules/frontend && PLAYWRIGHT_API_TARGET=$API_BASE npx vite preview --port $UI_PORT)"
log "inspect: curl -b $JAR $API_BASE/api/commands/rdm.inspect.summary -X POST -H 'Content-Type: application/json' -d '{\"params\":{\"projectId\":\"$PID_MAIN\"}}'"
wait
exit 0
fi
log "start vite preview at http://127.0.0.1:$UI_PORT"
(cd "$DEV_ROOT/modules/frontend" && \
PLAYWRIGHT_API_TARGET="$API_BASE" npx vite preview --port "$UI_PORT" --strictPort \
>"$OUT/preview.log" 2>&1 &
echo $! > "$OUT/preview.pid")
UI_PID=$(cat "$OUT/preview.pid")
for i in $(seq 1 40); do
curl -sf -o /dev/null "http://127.0.0.1:$UI_PORT/" && break
sleep 0.25
[ "$i" = 40 ] && { tail -5 "$OUT/preview.log"; die "preview not ready"; }
done
log "playwright (install chromium if missing)"
(cd "$DEV_ROOT/modules/frontend" && npx playwright install chromium >/dev/null 2>&1 || true)
log "run specs"
cd "$DEV_ROOT/modules/frontend"
export PLAYWRIGHT_BASE_URL="http://127.0.0.1:$UI_PORT" E2E_OWNER="$OWNER" E2E_PASSWORD="$PASSWORD" \
E2E_PROJECT_MAIN="$PROJ_MAIN" E2E_PROJECT_ISOLATED="$PROJ_ISO"
set +e
npx playwright test --reporter=list >"$OUT/playwright.log" 2>&1
PW_EXIT=$?
set -e
cat "$OUT/playwright.log"
[ "$PW_EXIT" = 0 ] && log "PASS" || die "playwright exit=$PW_EXIT (log: $OUT/playwright.log, trace: $OUT/frontend/test-results/)"
log "done; artifacts in $OUT"