Files
PMon Dev 24746795bf ci(a11y): 管理台 axe 42 格扫描入库并接入护栏(RUN_AXE 可选)
把交付目录的管理台扫描迁入源仓库 tests/admin-axe-audit.mjs(自包含:Node 内置 http 静态服务 + 浏览器自动探测 + CDP 注入 axe-core),矩阵与口径零放宽(3 视口 × 2 皮肤 × 6 页 + E-Ink 6 格 = 42 格,禁用规则 0 条,incomplete 不记 PASS)。run-guards.sh 增第 6 套件,默认不跑以保住 13s 快跑路径,RUN_AXE=1 才执行,未设时汇总表显式标 SKIPPED (RUN_AXE not set) 并打印原因与启用命令。workflow 支持 workflow_dispatch 输入与仓库变量 IW_RUN_AXE 两种启用方式。结果 JSON 与截图只写 /tmp,不入库。本地实跑:默认 PASS 5 / SKIPPED 1 / 13s / exit 0;RUN_AXE=1 后 PASS 6 / 0 / 60s / exit 0(管理台 42/42 格 violations=0、incomplete=12 均为 mobile-375 几何遮挡)。
2026-11-08 23:39:18 +08:00

164 lines
9.1 KiB
Bash
Executable File
Raw Permalink 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
# 墨观 InkWatch V4.0 · 设计系统双向护栏统一入口
#
# cd <repo> && bash tests/run-guards.sh
#
# 串行跑 6 个套件(第 6 个默认不跑,见 RUN_AXE);任一非零退出则整体失败。
# 无论中途是否有失败,都跑完全部套件并在末尾打印汇总表,便于一次看清全貌
#(不「遇错即停」)。
#
# 套件顺序:
# 1. web-token-guard.js 源仓库令牌层轻量守卫(本目录新增)
# 2. phase3b-static-guard.js 管理台深色落地静态护栏
# 3. dockerfile-coverage-test.js 镜像静态资源 COPY 覆盖
# 4. schema-consistency-test.js 指标字段三层一致性
# 5. client/scripts/test.sh 全量 Dart(含 token_parity_test.dart 双向护栏)
# 6. admin-axe-audit.mjs 管理台 6 页 axe-core 42 格矩阵(默认不跑)
#
# 环境变量:
# PHASE3B_PREVIEW=<path> preview.html 路径;存在则 phase3b 启用 7 处 hash
# 比对(87 PASS)。未设时脚本自动探测本地交付目录
# 候选;CI 上不存在即走「不带 preview」分支并打印
# SKIP 原因(85 PASS / 0 FAIL + 1 SKIP,属预期)。
# SKIP_FLUTTER_TESTS=1 跳过全量 Dart。默认必须跑;仅 Flutter 不可用时
# 显式降级,汇总表标 SKIPPED (explicit),绝不静默。
# FLUTTER_BIN=<path> 透传给 client/scripts/test.sh 指定 flutter 可执行文件
# (CI runner 上 flutter 不在 $HOME/development/flutter)。
# RUN_AXE=1 启用第 6 套件「管理台 axe 42 格矩阵」。默认不跑
# (保护约 14s 的本地快跑体验);未设时汇总表显式标
# SKIPPED (RUN_AXE not set) 并打印跳过原因,绝不静默。
# 前置:本机有 Edge/Chrome/Chromium + 能装 axe-core
# (/tmp/iw-admin-axe),见 tests/README.md。
#
# 本地复现无需 Gitea Actions;Gitea Actions 启用后由
# .gitea/workflows/design-system-guard.yml 调用同一入口。
set -u
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT" || exit 1
TMP="$(mktemp -d "${TMPDIR:-/tmp}/inkwatch-guards.XXXXXX")"
trap 'rm -rf "$TMP"' EXIT
# 结果行:名称|状态|退出码|耗时|指标
RESULTS=()
OVERALL=0
START=$SECONDS
add_result() { RESULTS+=("$1|$2|$3|$4|$5"); }
# 从套件日志里尽力提取一行短指标(best-effort,提取不到记 -)
metric_of() {
local name="$1" log="$2" line=""
case "$name" in
*web-token-guard*|*phase3b*)
line="$(grep -Eo 'PASS [0-9]+ +FAIL [0-9]+' "$log" | tail -1)" ;;
*dockerfile*|*schema*)
line="$(grep -Eo '测试通过: [0-9]+ +失败: [0-9]+' "$log" | tail -1)" ;;
*Dart*)
line="$(grep -E 'All tests passed' "$log" | tail -1 | sed -E 's/^[[:space:]]+//' | cut -c1-44)" ;;
*admin-axe*)
local grid viol inc
grid="$(grep -Eo '矩阵:[0-9]+/[0-9]+ 格' "$log" | tail -1)"
viol="$(grep -Eo 'violations 合计 [0-9]+ 条' "$log" | tail -1 | grep -Eo '[0-9]+')"
inc="$(grep -Eo 'incomplete 合计 [0-9]+ 条' "$log" | tail -1 | grep -Eo '[0-9]+')"
[ -n "$grid" ] && line="$grid viol=${viol:--} inc=${inc:--}" ;;
esac
printf '%s' "${line:--}"
}
run_suite() {
local name="$1"; shift
local log="$TMP/${#RESULTS[@]}.log"
local t0=$SECONDS rc=0
printf '\n════════════════════════════════════════════════════════\n'
printf ' ▶ %s\n' "$name"
printf '════════════════════════════════════════════════════════\n'
"$@" 2>&1 | tee "$log"
rc=${PIPESTATUS[0]}
local dur=$((SECONDS - t0))
local metric
metric="$(metric_of "$name" "$log")"
local status="PASS"
if [ "$rc" -ne 0 ]; then status="FAIL"; OVERALL=1; fi
add_result "$name" "$status" "$rc" "${dur}s" "$metric"
printf ' ── %s:%s(退出码 %s,耗时 %ss,指标 %s)\n' \
"$name" "$status" "$rc" "$dur" "$metric"
}
# ── 1. 源仓库 Web 令牌层轻量守卫 ──────────────────────────────
run_suite "web-token-guard.js" node tests/web-token-guard.js
# ── 2. phase3b 静态护栏(条件性带 PHASE3B_PREVIEW)────────────
# 交付目录 preview.html 候选;CI 上通常都不存在,自动降级为「不带」分支。
PREVIEW="${PHASE3B_PREVIEW:-}"
if [ -z "$PREVIEW" ]; then
for c in \
"$ROOT/../inkwatch-design-system/preview.html" \
"$HOME/WorkBuddy/2026-11-08-14-54-48/inkwatch-design-system/preview.html"; do
if [ -f "$c" ]; then PREVIEW="$c"; break; fi
done
fi
if [ -n "$PREVIEW" ] && [ -f "$PREVIEW" ]; then
printf ' ℹ PHASE3B_PREVIEW = %s\n' "$PREVIEW"
printf ' phase3b 启用 7 处首屏脚本 hash 比对(预期 87 PASS / 0 FAIL)。\n'
run_suite "phase3b-static-guard.js" \
env "PHASE3B_PREVIEW=$PREVIEW" node tests/phase3b-static-guard.js
else
printf ' ℹ SKIP 原因:未找到交付目录 preview.html,PHASE3B_PREVIEW 不设置。\n'
printf ' phase3b 走「不带 preview」分支(85 PASS / 0 FAIL + 1 SKIP,属预期)。\n'
printf ' 如需启用:PHASE3B_PREVIEW=<path> bash tests/run-guards.sh\n'
run_suite "phase3b-static-guard.js" node tests/phase3b-static-guard.js
fi
# ── 3. Dockerfile 静态资源覆盖 ────────────────────────────────
run_suite "dockerfile-coverage-test.js" node tests/dockerfile-coverage-test.js
# ── 4. 指标字段三层一致性 ─────────────────────────────────────
run_suite "schema-consistency-test.js" node tests/schema-consistency-test.js
# ── 5. 全量 Dart(默认必须跑)─────────────────────────────────
if [ "${SKIP_FLUTTER_TESTS:-0}" = "1" ]; then
printf '\n ⚠ SKIPPED (explicit):SKIP_FLUTTER_TESTS=1,按要求跳过全量 Dart 测试。\n'
printf ' 默认(不设该变量)必须跑 client/scripts/test.sh;此跳过会在汇总表显式标注。\n'
add_result "client/scripts/test.sh (Dart)" "SKIPPED (explicit)" "-" "0s" "SKIPPED (explicit)"
else
run_suite "client/scripts/test.sh (Dart)" bash -c 'cd client && bash scripts/test.sh'
fi
# ── 6. 管理台 axe-core 42 格矩阵(默认不跑)───────────────────
# 理由:需要本机浏览器 + 联网装 axe-core,单跑约 45s;默认路径保持约 14s 快跑。
# RUN_AXE=1 才执行;未设时显式标 SKIPPED,绝不静默跳过。
if [ "${RUN_AXE:-0}" = "1" ]; then
printf '\n ℹ RUN_AXE=1:启用管理台 axe 42 格矩阵(前置:浏览器 + axe-core,见 tests/README.md)。\n'
run_suite "admin-axe-audit.mjs" node tests/admin-axe-audit.mjs
else
printf '\n ⚠ SKIPPED (RUN_AXE not set):未启用管理台 axe 42 格矩阵。\n'
printf ' 原因:该套件需本机 Chromium/Edge + 联网装 axe-core,单跑约 45s;\n'
printf ' 默认快跑路径(约 14s)不含它。启用:RUN_AXE=1 bash tests/run-guards.sh\n'
add_result "admin-axe-audit.mjs" "SKIPPED (RUN_AXE not set)" "-" "0s" "SKIPPED (RUN_AXE not set)"
fi
# ── 汇总表 ───────────────────────────────────────────────────
TOTAL_DUR=$((SECONDS - START))
printf '\n'
printf '══════════════════════════ 护栏汇总 ══════════════════════════\n'
printf ' %-34s %-18s %-8s %-6s %s\n' "套件" "状态" "退出码" "耗时" "指标"
printf ' ────────────────────────────────────────────────────────────────────────────\n'
npass=0; nfail=0; nskip=0
for r in "${RESULTS[@]}"; do
IFS='|' read -r name status rc dur metric <<< "$r"
printf ' %-34s %-18s %-8s %-6s %s\n' "$name" "$status" "$rc" "$dur" "$metric"
case "$status" in
PASS) npass=$((npass + 1)) ;;
FAIL) nfail=$((nfail + 1)) ;;
*) nskip=$((nskip + 1)) ;;
esac
done
printf ' ────────────────────────────────────────────────────────────────────────────\n'
printf ' 合计:%d 套件 PASS %d / FAIL %d / SKIPPED %d | 总耗时 %ss | 整体退出码 %d\n' \
"${#RESULTS[@]}" "$npass" "$nfail" "$nskip" "$TOTAL_DUR" "$OVERALL"
printf '══════════════════════════════════════════════════════════════\n'
exit "$OVERALL"