From fbd583fc65a81f1b7ba29d4cadb3a43c6f91102b Mon Sep 17 00:00:00 2001 From: weichen Date: Sun, 11 Oct 2026 16:59:03 +0000 Subject: [PATCH] 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. --- .gitignore | 3 + modules/frontend | 2 +- scripts/e2e.sh | 174 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100755 scripts/e2e.sh diff --git a/.gitignore b/.gitignore index d7dc4407..3a6b5aa7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ runtime/ # Mimosa 安全扫描插件的逐会话状态(hook-status/reports/ledger,机器自动重建) .mimosa/ + +# e2e.sh 运行工件(构建/种子/日志;scripts/e2e.sh 输出) +.tmp/ diff --git a/modules/frontend b/modules/frontend index ed9845f8..7acfef0a 160000 --- a/modules/frontend +++ b/modules/frontend @@ -1 +1 @@ -Subproject commit ed9845f8e43cb376eab7e223838015e2b62a5421 +Subproject commit 7acfef0a1f6d1b59f6fca5e544d05df364257802 diff --git a/scripts/e2e.sh b/scripts/e2e.sh new file mode 100755 index 00000000..573d186a --- /dev/null +++ b/scripts/e2e.sh @@ -0,0 +1,174 @@ +#!/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/;.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 → 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"