- play/index.html: 插件体验展示页,插件注册表驱动,新增插件只需加一条配置 - play/blueprints/: WPMind + 空白中文环境两个预设 - Justfile: pw-test 集成到 test-plugin 完整验收流程 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
363 lines
14 KiB
Makefile
363 lines
14 KiB
Makefile
# WordPress 插件自动化验收测试
|
||
# 用法: just <任务名> [参数]
|
||
# 查看所有任务: just --list
|
||
|
||
set dotenv-load
|
||
set shell := ["bash", "-cu"]
|
||
|
||
# 变量
|
||
site := env("WP_SITE", "http://localhost:9400")
|
||
plugin := env("WP_PLUGIN", "")
|
||
ssh_target := env("SSH_TARGET", "")
|
||
debug_log := env("WP_DEBUG_LOG_PATH", "/var/www/html/wp-content/debug.log")
|
||
pg_ver := env("PLAYGROUND_VERSION", "3.0.52")
|
||
pg_wp := env("PLAYGROUND_WP", "6.8")
|
||
pg_php := env("PLAYGROUND_PHP", "8.4")
|
||
chrome := env("CHROME_PATH", home_directory() / ".cache/ms-playwright/chromium-1208/chrome-linux/chrome")
|
||
date := `date +%Y-%m-%d`
|
||
results := home_directory() / "test-results" / date / plugin
|
||
blueprints := home_directory() / "blueprints"
|
||
|
||
# ─── Playground 环境管理 ───────────────────────────
|
||
|
||
# 启动干净 WordPress Playground
|
||
playground:
|
||
npx @wp-playground/cli@{{pg_ver}} server --port=9400 --login
|
||
|
||
# 启动 Playground + 加载 Blueprint
|
||
playground-bp name:
|
||
npx @wp-playground/cli@{{pg_ver}} server --port=9400 --login \
|
||
--blueprint={{blueprints}}/{{name}}.json
|
||
|
||
# 启动 Playground + 挂载本地插件目录
|
||
playground-dev path:
|
||
npx @wp-playground/cli@{{pg_ver}} server --port=9400 --login \
|
||
--mount={{path}}:/wordpress/wp-content/plugins/
|
||
|
||
# 中文基础环境
|
||
playground-zh:
|
||
npx @wp-playground/cli@{{pg_ver}} server --port=9400 --login \
|
||
--blueprint={{blueprints}}/zh-cn-base.json
|
||
|
||
# 执行 Blueprint(不启动 server,CI 用)
|
||
run-bp name:
|
||
npx @wp-playground/cli@{{pg_ver}} run-blueprint \
|
||
--blueprint={{blueprints}}/{{name}}.json
|
||
|
||
# 打包快照
|
||
snapshot name:
|
||
mkdir -p {{results}}
|
||
npx @wp-playground/cli@{{pg_ver}} build-snapshot \
|
||
--blueprint={{blueprints}}/{{name}}.json \
|
||
--outfile={{results}}/{{name}}-snapshot.zip
|
||
|
||
# ─── 完整验收流程 ──────────────────────────────────
|
||
|
||
# 一键拉取 + 验收(从 Forgejo 拉最新 release → 启动 Playground → 跑验收 → 导入趋势)
|
||
fetch-and-test repo name="":
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
PLUGIN_NAME="${2:-$(basename "{{repo}}")}"
|
||
echo "=== 拉取 {{repo}} 最新 release ==="
|
||
bash scripts/fetch-release.sh "{{repo}}"
|
||
ZIP=$(ls -t ~/下载/${PLUGIN_NAME}-*.zip 2>/dev/null | head -1)
|
||
[ -z "$ZIP" ] && { echo "未找到 zip 文件"; exit 1; }
|
||
echo "=== 准备 Playground ==="
|
||
# 停掉旧的 Playground 和 HTTP server
|
||
pkill -f "wp-playground" 2>/dev/null || true
|
||
pkill -f "python3 -m http.server 8888" 2>/dev/null || true
|
||
sleep 2
|
||
# 启动 HTTP server 服务 zip
|
||
cp "$ZIP" /tmp/${PLUGIN_NAME}.zip
|
||
cd /tmp && nohup python3 -m http.server 8888 > /dev/null 2>&1 &
|
||
HTTP_PID=$!
|
||
sleep 1
|
||
# 生成临时 Blueprint
|
||
cat > /tmp/${PLUGIN_NAME}-blueprint.json << BPEOF
|
||
{
|
||
"\$schema": "https://playground.wordpress.net/blueprint-schema.json",
|
||
"landingPage": "/wp-admin/plugins.php",
|
||
"preferredVersions": { "wp": "{{pg_wp}}", "php": "{{pg_php}}" },
|
||
"steps": [
|
||
{ "step": "setSiteOptions", "options": { "blogname": "验收测试站", "WPLANG": "zh_CN", "timezone_string": "Asia/Shanghai", "date_format": "Y-m-d", "time_format": "H:i" } },
|
||
{ "step": "login", "username": "admin", "password": "password" },
|
||
{ "step": "installPlugin", "pluginData": { "resource": "url", "url": "http://127.0.0.1:8888/${PLUGIN_NAME}.zip" } }
|
||
]
|
||
}
|
||
BPEOF
|
||
# 启动 Playground
|
||
nohup npx @wp-playground/cli@{{pg_ver}} server --port=9400 --login --blueprint=/tmp/${PLUGIN_NAME}-blueprint.json > /tmp/playground.log 2>&1 &
|
||
PG_PID=$!
|
||
echo "等待 Playground 启动..."
|
||
for i in $(seq 1 30); do
|
||
curl -s http://localhost:9400/ -o /dev/null && break
|
||
sleep 1
|
||
done
|
||
echo "=== 开始验收 ==="
|
||
just test-plugin "$PLUGIN_NAME"
|
||
# 导入趋势数据库
|
||
RESULTS="$HOME/test-results/$(date +%Y-%m-%d)/${PLUGIN_NAME}"
|
||
if [ -f "$RESULTS/verdict.json" ]; then
|
||
node scripts/trend-tracker.js import "$RESULTS/verdict.json" 2>/dev/null && echo "[trend] 已导入趋势数据库" || true
|
||
fi
|
||
# 清理
|
||
kill $HTTP_PID 2>/dev/null || true
|
||
echo "=== 全流程完成 ==="
|
||
|
||
# 完整验收(CLI + Playwright + 视觉回归 + 报告)
|
||
test-plugin name:
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
export WP_PLUGIN="{{name}}"
|
||
RESULTS="$HOME/test-results/{{date}}/{{name}}"
|
||
echo "=== 验收插件: {{name}} ==="
|
||
echo "目标: {{site}}"
|
||
echo "结果: $RESULTS"
|
||
just setup-dirs "{{name}}"
|
||
# CLI 工具扫描
|
||
just a11y-scan "{{name}}" || true
|
||
just lighthouse-scan "{{name}}" || true
|
||
just link-check "{{name}}" || true
|
||
just api-scan "{{name}}" || true
|
||
just html-validate-page "{{name}}" "{{site}}" || true
|
||
if [ -n "{{ssh_target}}" ]; then
|
||
just server-errors "{{name}}" || true
|
||
fi
|
||
# Playwright 自动化
|
||
node scripts/playwright/screenshots.js "{{name}}" "{{site}}" "$RESULTS/screenshots" || true
|
||
node scripts/playwright/security-scan.js "{{site}}" "$RESULTS/security" || true
|
||
# 等待 Chromium 进程释放,避免与 BackstopJS 资源竞争
|
||
sleep 2
|
||
pkill -f "chromium.*--headless" 2>/dev/null || true
|
||
sleep 1
|
||
# 视觉回归(自动选择基线)
|
||
bash scripts/backstop-baseline.sh auto "{{name}}" "unknown" 2>/dev/null || true
|
||
just visual-test || true
|
||
# i18n 验收
|
||
just i18n-test "{{name}}" || true
|
||
# PHPCS 静态分析(如果有源码)
|
||
PLUGIN_SRC="/tmp/wpmind-review/wpmind-src/{{name}}"
|
||
if [ -d "$PLUGIN_SRC" ]; then
|
||
bash scripts/phpcs-scan.sh "$PLUGIN_SRC" "$RESULTS/phpcs" || true
|
||
fi
|
||
# Playwright 功能回归测试
|
||
export TEST_OUTPUT="$RESULTS/playwright"
|
||
mkdir -p "$TEST_OUTPUT"
|
||
npx playwright test --config=playwright.config.js 2>&1 | tee "$TEST_OUTPUT/output.log" || true
|
||
# 生成汇总报告
|
||
node scripts/generate-report.js "{{name}}" "$RESULTS"
|
||
echo "=== 验收完成: $RESULTS/report.md ==="
|
||
|
||
# ─── 测试任务 ──────────────────────────────────────
|
||
|
||
# 创建结果目录
|
||
setup-dirs name:
|
||
mkdir -p ~/test-results/{{date}}/{{name}}/{screenshots,a11y,performance,security,api,i18n,diff}
|
||
|
||
# 无障碍扫描(pa11y + axe 双引擎)
|
||
a11y-scan name:
|
||
#!/usr/bin/env bash
|
||
set -uo pipefail
|
||
DIR="$HOME/test-results/{{date}}/{{name}}/a11y"
|
||
mkdir -p "$DIR"
|
||
echo "[pa11y+axe] 扫描 {{site}} ..."
|
||
pa11y "{{site}}" --config ~/pa11y.json --reporter json > "$DIR/pa11y-htmlcs.json" 2>&1 || true
|
||
PA11Y_ISSUES=$(jq 'length' "$DIR/pa11y-htmlcs.json" 2>/dev/null || echo "?")
|
||
echo "[pa11y/htmlcs] 发现 $PA11Y_ISSUES 个问题"
|
||
pa11y "{{site}}" --config ~/pa11y.json --runner axe --reporter json > "$DIR/pa11y-axe.json" 2>&1 || true
|
||
AXE_ISSUES=$(jq 'length' "$DIR/pa11y-axe.json" 2>/dev/null || echo "?")
|
||
echo "[pa11y/axe] 发现 $AXE_ISSUES 个问题"
|
||
echo "[a11y] 结果: $DIR/"
|
||
|
||
# Lighthouse 性能/SEO 扫描
|
||
lighthouse-scan name:
|
||
#!/usr/bin/env bash
|
||
set -uo pipefail
|
||
DIR="$HOME/test-results/{{date}}/{{name}}/performance"
|
||
mkdir -p "$DIR"
|
||
echo "[lighthouse] 扫描 {{site}} ..."
|
||
CHROME_PATH="{{chrome}}" lighthouse "{{site}}" \
|
||
--output=json --output=html \
|
||
--output-path="$DIR/lighthouse" \
|
||
--chrome-flags="--headless --no-sandbox --disable-setuid-sandbox" \
|
||
--quiet 2>&1 || true
|
||
if [ -f "$DIR/lighthouse.report.json" ]; then
|
||
jq -r '.categories | to_entries[] | " \(.value.title): \(.value.score * 100 | floor)"' "$DIR/lighthouse.report.json" 2>/dev/null || true
|
||
fi
|
||
|
||
# 全站死链检测
|
||
link-check name:
|
||
#!/usr/bin/env bash
|
||
set -uo pipefail
|
||
DIR="$HOME/test-results/{{date}}/{{name}}"
|
||
mkdir -p "$DIR"
|
||
echo "[linkchecker] 检测 {{site}} ..."
|
||
linkchecker "{{site}}" --output=csv > "$DIR/link-check.csv" 2>&1 || true
|
||
BROKEN=$(grep -c "^[^#].*error" "$DIR/link-check.csv" 2>/dev/null || echo "0")
|
||
echo "[linkchecker] 发现 $BROKEN 个断链"
|
||
|
||
# REST API 端点扫描
|
||
api-scan name:
|
||
#!/usr/bin/env bash
|
||
set -uo pipefail
|
||
DIR="$HOME/test-results/{{date}}/{{name}}/api"
|
||
mkdir -p "$DIR"
|
||
echo "[api] 扫描 {{site}}/wp-json/ ..."
|
||
curl -sL -c /tmp/wp-just-cookies.txt -b /tmp/wp-just-cookies.txt "{{site}}/wp-json/" > "$DIR/routes.json" 2>&1 || true
|
||
ROUTES=$(jq '.routes | length' "$DIR/routes.json" 2>/dev/null || echo "?")
|
||
echo "[api] 发现 $ROUTES 个路由"
|
||
|
||
# HTML 验证(单页)
|
||
html-validate-page name url:
|
||
#!/usr/bin/env bash
|
||
set -uo pipefail
|
||
DIR="$HOME/test-results/{{date}}/{{name}}"
|
||
mkdir -p "$DIR"
|
||
echo "[html-validate] 验证 {{url}} ..."
|
||
curl -sL -c /tmp/wp-just-cookies.txt -b /tmp/wp-just-cookies.txt "{{url}}" > /tmp/validate-page.html 2>/dev/null
|
||
html-validate /tmp/validate-page.html > "$DIR/html-validate.txt" 2>&1 || true
|
||
ERRORS=$(grep -c "error" "$DIR/html-validate.txt" 2>/dev/null || echo "0")
|
||
echo "[html-validate] 发现 $ERRORS 个问题"
|
||
|
||
# 服务端错误检查(需要 SSH 访问)
|
||
server-errors name:
|
||
#!/usr/bin/env bash
|
||
set -uo pipefail
|
||
DIR="$HOME/test-results/{{date}}/{{name}}"
|
||
mkdir -p "$DIR"
|
||
if [ -z "{{ssh_target}}" ]; then
|
||
echo "[server] 跳过: 未配置 SSH_TARGET(Playground 模式无需此步)"
|
||
exit 0
|
||
fi
|
||
echo "[server] 拉取 debug.log ..."
|
||
ssh "{{ssh_target}}" "cat {{debug_log}}" > "$DIR/debug.log" 2>&1 || true
|
||
FATALS=$(grep -c "Fatal" "$DIR/debug.log" 2>/dev/null || echo "0")
|
||
WARNINGS=$(grep -c "Warning" "$DIR/debug.log" 2>/dev/null || echo "0")
|
||
echo "[server] Fatal: $FATALS, Warning: $WARNINGS"
|
||
|
||
# ─── 视觉回归 ─────────────────────────────────────
|
||
|
||
# 建立视觉基线
|
||
visual-baseline:
|
||
backstop reference --config=backstop.json
|
||
|
||
# 视觉回归对比
|
||
visual-test:
|
||
backstop test --config=backstop.json
|
||
|
||
# 基线版本管理
|
||
baseline-save plugin version:
|
||
bash scripts/backstop-baseline.sh save {{plugin}} {{version}}
|
||
|
||
baseline-use plugin version:
|
||
bash scripts/backstop-baseline.sh use {{plugin}} {{version}}
|
||
|
||
baseline-auto plugin version:
|
||
bash scripts/backstop-baseline.sh auto {{plugin}} {{version}}
|
||
|
||
baseline-list plugin="":
|
||
bash scripts/backstop-baseline.sh list {{plugin}}
|
||
|
||
# PHPCS 静态分析
|
||
phpcs-scan name src-dir:
|
||
bash scripts/phpcs-scan.sh {{src-dir}} "$HOME/test-results/{{date}}/{{name}}/phpcs"
|
||
|
||
# Playwright 功能回归测试
|
||
pw-test name:
|
||
#!/usr/bin/env bash
|
||
export TEST_OUTPUT="$HOME/test-results/{{date}}/{{name}}/playwright"
|
||
mkdir -p "$TEST_OUTPUT"
|
||
npx playwright test --config=playwright.config.js 2>&1 | tee "$TEST_OUTPUT/output.log"
|
||
echo "[pw-test] 结果: $TEST_OUTPUT"
|
||
|
||
# 截图对比(两个目录)
|
||
screenshot-diff name actual expected:
|
||
#!/usr/bin/env bash
|
||
DIR="$HOME/test-results/{{date}}/{{name}}/diff"
|
||
mkdir -p "$DIR"
|
||
reg-cli "{{actual}}" "{{expected}}" "$DIR" --report "$DIR/report.html"
|
||
echo "[diff] 报告: $DIR/report.html"
|
||
|
||
# ─── Playwright 交互流程 ──────────────────────────
|
||
|
||
# 多分辨率截图(4 视口 × 4 页面)
|
||
screenshots name:
|
||
node scripts/playwright/screenshots.js "{{name}}" "{{site}}"
|
||
|
||
# 插件安装验收(上传→激活→截图→检查菜单)
|
||
plugin-install zip:
|
||
node scripts/playwright/plugin-install.js "{{zip}}" "{{site}}"
|
||
|
||
# 设置页面验收(字段收集→保存→刷新验证)
|
||
settings-test path="/wp-admin/options-general.php":
|
||
node scripts/playwright/settings-test.js "{{path}}" "{{site}}"
|
||
|
||
# 基础安全扫描(XSS/CSRF/SQLi/信息泄露)
|
||
security-scan name="default":
|
||
node scripts/playwright/security-scan.js "{{site}}"
|
||
|
||
# i18n 验收(溢出 + 中英截图 + 覆盖率 + 日期格式)
|
||
i18n-test name zip="":
|
||
#!/usr/bin/env bash
|
||
set -uo pipefail
|
||
STARTED_EN=false
|
||
RESULTS="$HOME/test-results/{{date}}/{{name}}/i18n"
|
||
mkdir -p "$RESULTS"
|
||
# Auto-start en_US Playground if not running
|
||
if ! ss -tlnp | grep -q ':9401'; then
|
||
echo "[i18n] 启动 en_US Playground..."
|
||
npx @wp-playground/cli@{{pg_ver}} server --port=9401 --login \
|
||
--blueprint={{blueprints}}/en-us-base.json &>/dev/null &
|
||
STARTED_EN=true
|
||
sleep 8
|
||
fi
|
||
cleanup() { if [ "$STARTED_EN" = "true" ]; then pkill -f 'port=9401' 2>/dev/null || true; fi; }
|
||
trap cleanup EXIT
|
||
EN_URL="http://localhost:9401"
|
||
if ! ss -tlnp | grep -q ':9401'; then
|
||
echo "[i18n] en_US 启动失败,仅测试 zh_CN"
|
||
EN_URL=""
|
||
fi
|
||
ZIP_ARG=""
|
||
if [ -n "{{zip}}" ] && [ -f "{{zip}}" ]; then ZIP_ARG="{{zip}}"; fi
|
||
node scripts/playwright/i18n-test.js "{{name}}" "{{site}}" "$EN_URL" "$RESULTS" "$ZIP_ARG"
|
||
|
||
# 启动英文 Playground(i18n 对比用)
|
||
playground-en:
|
||
npx @wp-playground/cli@{{pg_ver}} server --port=9401 --login \
|
||
--blueprint={{blueprints}}/en-us-base.json
|
||
|
||
# ─── 数据库管理(远程站点) ─────────────────────────
|
||
|
||
# 数据库快照
|
||
db-snapshot:
|
||
#!/usr/bin/env bash
|
||
if [ -z "{{ssh_target}}" ]; then echo "需要 SSH_TARGET"; exit 1; fi
|
||
ssh "{{ssh_target}}" "wp db export ~/backups/pre-test-$(date +%Y%m%d-%H%M).sql"
|
||
echo "[db] 快照已保存"
|
||
|
||
# 数据库回滚
|
||
db-rollback file:
|
||
#!/usr/bin/env bash
|
||
if [ -z "{{ssh_target}}" ]; then echo "需要 SSH_TARGET"; exit 1; fi
|
||
ssh "{{ssh_target}}" "wp db import {{file}}"
|
||
echo "[db] 已回滚到 {{file}}"
|
||
|
||
# ─── 工具 ─────────────────────────────────────────
|
||
|
||
# 监控 NAS staging 目录,自动跑验收
|
||
watch-staging:
|
||
scripts/staging-watcher.sh
|
||
|
||
# 检查一次 staging(适合 cron)
|
||
check-staging:
|
||
scripts/staging-watcher.sh --once
|
||
|
||
# 列出所有测试结果
|
||
results:
|
||
@ls -la ~/test-results/{{date}}/ 2>/dev/null || echo "今天没有测试结果"
|
||
|
||
# 清理旧结果(保留最近 7 天)
|
||
clean-results:
|
||
find ~/test-results/ -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;
|
||
echo "已清理 7 天前的测试结果"
|