166 行
5.5 KiB
Bash
166 行
5.5 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
CLIENT_DIR="$ROOT_DIR/client"
|
|
MODE="${1:-full}"
|
|
DEV_SERVER_PID=""
|
|
DEV_SERVER_LOG=""
|
|
PLAYWRIGHT_RESULT_FILE=""
|
|
PLAYWRIGHT_STDERR_LOG=""
|
|
|
|
usage() {
|
|
printf '%s\n' \
|
|
"Usage: bash scripts/qa/run-local-candidate.sh [full|focused|list]" \
|
|
"" \
|
|
" full 完整基础门禁 + 路由反例 + 本地 Vite 行为测试(默认)" \
|
|
" focused 路由反例 + 本地 Vite 行为测试,不代表完整候选通过" \
|
|
" list 只解析 Playwright 测试资产,不代表行为通过"
|
|
}
|
|
|
|
cleanup() {
|
|
local exit_code="$?"
|
|
if [[ -n "$DEV_SERVER_PID" ]] && kill -0 "$DEV_SERVER_PID" 2>/dev/null; then
|
|
kill "$DEV_SERVER_PID" 2>/dev/null || true
|
|
wait "$DEV_SERVER_PID" 2>/dev/null || true
|
|
fi
|
|
if [[ "$exit_code" -ne 0 ]]; then
|
|
local vite_log_lines=0
|
|
local playwright_log_lines=0
|
|
if [[ -n "$DEV_SERVER_LOG" && -f "$DEV_SERVER_LOG" ]]; then
|
|
vite_log_lines="$(wc -l < "$DEV_SERVER_LOG" | tr -d ' ')"
|
|
fi
|
|
if [[ -n "$PLAYWRIGHT_STDERR_LOG" && -f "$PLAYWRIGHT_STDERR_LOG" ]]; then
|
|
playwright_log_lines="$(wc -l < "$PLAYWRIGHT_STDERR_LOG" | tr -d ' ')"
|
|
fi
|
|
echo "[local-candidate][ERROR] safe_summary exit=${exit_code} vite_log_lines=${vite_log_lines} playwright_log_lines=${playwright_log_lines}" >&2
|
|
fi
|
|
[[ -z "$DEV_SERVER_LOG" ]] || rm -f "$DEV_SERVER_LOG"
|
|
[[ -z "$PLAYWRIGHT_RESULT_FILE" ]] || rm -f "$PLAYWRIGHT_RESULT_FILE"
|
|
[[ -z "$PLAYWRIGHT_STDERR_LOG" ]] || rm -f "$PLAYWRIGHT_STDERR_LOG"
|
|
return "$exit_code"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
case "$MODE" in
|
|
full|focused|list) ;;
|
|
help|-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
cd "$ROOT_DIR"
|
|
|
|
echo "=== [1/4] 路由 manifest 与反例测试 ==="
|
|
node --test \
|
|
scripts/qa/tests/check-minimal-profile.test.mjs \
|
|
scripts/qa/tests/check-flow-coverage.test.mjs \
|
|
scripts/qa/tests/validate-loopback-url.test.mjs \
|
|
scripts/qa/tests/verify-local-candidate-results.test.mjs
|
|
node scripts/qa/check-flow-coverage.mjs --local-candidate --json
|
|
|
|
if [[ "$MODE" == "full" ]]; then
|
|
echo "=== [2/4] 完整本地基础门禁 ==="
|
|
bash scripts/check-minimal.sh local-candidate
|
|
else
|
|
echo "=== [2/4] 完整本地基础门禁(focused/list 跳过) ==="
|
|
fi
|
|
|
|
mapfile -t CANDIDATE_SPECS < <(
|
|
node -e '
|
|
const manifest = require("./scripts/qa/route-manifest.json");
|
|
const expected = new Set(manifest.coreBundles ?? []);
|
|
const flows = manifest.localCandidateFlows ?? [];
|
|
const observed = new Set(flows.map((flow) => flow.bundle));
|
|
if (expected.size !== 10 || expected.size !== observed.size || [...expected].some((bundle) => !observed.has(bundle))) {
|
|
throw new Error("localCandidateFlows 未完整覆盖 coreBundles");
|
|
}
|
|
const witnessSpecs = flows.map((flow) => flow.witnessSpec);
|
|
const specs = [...new Set([...witnessSpecs, ...(manifest.acceptanceSpecs ?? [])])];
|
|
for (const spec of specs) {
|
|
if (typeof spec !== "string" || !spec.startsWith("client/tests/e2e/") || spec.includes("/manual/") || /live/i.test(spec)) {
|
|
throw new Error(`非法本地候选 spec: ${String(spec)}`);
|
|
}
|
|
console.log(spec.slice("client/".length));
|
|
}
|
|
'
|
|
)
|
|
|
|
if [[ "${#CANDIDATE_SPECS[@]}" -eq 0 ]]; then
|
|
echo "[local-candidate][ERROR] route-manifest 未登记 localCandidateFlows" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$MODE" == "list" ]]; then
|
|
echo "=== [3/4] Playwright 资产解析(不代表行为通过) ==="
|
|
(
|
|
cd "$CLIENT_DIR"
|
|
npx playwright test "${CANDIDATE_SPECS[@]}" --list
|
|
)
|
|
echo "=== [4/4] LIST_ONLY ==="
|
|
exit 0
|
|
fi
|
|
|
|
BASE_URL="${E2E_BASE_URL:-}"
|
|
if [[ -z "$BASE_URL" ]]; then
|
|
PORT="$(node scripts/qa/validate-loopback-url.mjs --port "${LOCAL_CANDIDATE_PORT:-5178}")"
|
|
BASE_URL="$(node scripts/qa/validate-loopback-url.mjs "http://127.0.0.1:${PORT}")"
|
|
DEV_SERVER_LOG="${TMPDIR:-/tmp}/hyb-oa-local-candidate-vite-${PORT}-$$.log"
|
|
(
|
|
cd "$CLIENT_DIR"
|
|
npm run dev -- --host 127.0.0.1 --port "$PORT" --strictPort
|
|
) >"$DEV_SERVER_LOG" 2>&1 &
|
|
DEV_SERVER_PID="$!"
|
|
|
|
VITE_READY=0
|
|
for _ in $(seq 1 60); do
|
|
if curl --fail --silent --show-error "$BASE_URL/login" >/dev/null 2>&1; then
|
|
VITE_READY=1
|
|
break
|
|
fi
|
|
if ! kill -0 "$DEV_SERVER_PID" 2>/dev/null; then
|
|
echo "[local-candidate][ERROR] 本地 Vite 启动失败" >&2
|
|
exit 1
|
|
fi
|
|
sleep 0.25
|
|
done
|
|
if [[ "$VITE_READY" -ne 1 ]]; then
|
|
echo "[local-candidate][ERROR] 本地 Vite 就绪超时" >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
BASE_URL="$(node scripts/qa/validate-loopback-url.mjs "$BASE_URL")"
|
|
fi
|
|
|
|
echo "=== [3/4] 本地合成浏览器行为 ==="
|
|
PLAYWRIGHT_RESULT_FILE="$(mktemp "${TMPDIR:-/tmp}/hyb-oa-local-candidate-results.XXXXXX.json")"
|
|
PLAYWRIGHT_STDERR_LOG="$(mktemp "${TMPDIR:-/tmp}/hyb-oa-local-candidate-playwright.XXXXXX.log")"
|
|
|
|
PLAYWRIGHT_STATUS=0
|
|
(
|
|
cd "$CLIENT_DIR"
|
|
E2E_MODE=local E2E_BASE_URL="$BASE_URL" \
|
|
npx playwright test "${CANDIDATE_SPECS[@]}" --workers=1 --reporter=json
|
|
) >"$PLAYWRIGHT_RESULT_FILE" 2>"$PLAYWRIGHT_STDERR_LOG" || PLAYWRIGHT_STATUS="$?"
|
|
|
|
RESULT_GATE_STATUS=0
|
|
node scripts/qa/verify-local-candidate-results.mjs \
|
|
--manifest scripts/qa/route-manifest.json \
|
|
--results "$PLAYWRIGHT_RESULT_FILE" || RESULT_GATE_STATUS="$?"
|
|
|
|
if [[ "$PLAYWRIGHT_STATUS" -ne 0 || "$RESULT_GATE_STATUS" -ne 0 ]]; then
|
|
echo "[local-candidate][ERROR] browser_exit=${PLAYWRIGHT_STATUS} witness_result_gate=${RESULT_GATE_STATUS}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$MODE" == "full" ]]; then
|
|
echo "=== [4/4] LOCAL_CANDIDATE_PASS ==="
|
|
else
|
|
echo "=== [4/4] FOCUSED_BROWSER_PASS ==="
|
|
fi
|