feat: 添加E2E测试环境配置支持
Some checks failed
lotterLaravel CI / test (push) Has been cancelled

添加LOTTERY_E2E环境变量来控制E2E测试相关功能,
包括绕过验证码、登录限制和钱包API URL验证,
同时更新composer.json以包含E2E专用的提供者和服务。
This commit is contained in:
2026-06-18 14:42:22 +08:00
parent 6b2ea39ea1
commit 6ec9634704
45 changed files with 3702 additions and 6 deletions

253
e2e/scripts/run.sh Executable file
View File

@@ -0,0 +1,253 @@
#!/usr/bin/env bash
# e2e 一键跑通:
# 1. 启 docker composepostgres + redis
# 2. 复制 .env.e2e → .env注入强随机 JWT 密钥
# 3. migrate --seed跑 LocalDemoSeederadmin/12345678 + demo_player
# 4. php artisan serve 起应用
# 5. 启动 queue:work异步开奖/广播)
# 6. 启动 reverb:start
# 7. npx playwright install chromium首次
# 8. npx playwright test
# 9. 失败时打 docker logs + Laravel 日志;结束时清理 compose
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"
E2E_DIR="$ROOT_DIR/e2e"
COMPOSE_FILE="$E2E_DIR/docker-compose.yml"
ENV_FILE="$ROOT_DIR/.env"
ENV_E2E="$E2E_DIR/.env.e2e"
ARTIFACT_DIR="$E2E_DIR/artifacts"
LOG_DIR="$E2E_DIR/logs"
mkdir -p "$ARTIFACT_DIR" "$LOG_DIR"
API_URL="http://127.0.0.1:8000"
PUBLIC_URL="$API_URL/api/v1"
PIDS=()
cleanup() {
local code=$?
trap - INT TERM EXIT
echo
echo "==> Cleaning up e2e processes (code=$code)…"
for pid in "${PIDS[@]:-}"; do
[[ -n "$pid" ]] && kill "$pid" 2>/dev/null || true
done
# docker compose 留给用户决定是否 down避免误删 pg volume
exit "$code"
}
trap cleanup INT TERM EXIT
require() {
command -v "$1" >/dev/null 2>&1 || { echo "Missing: $1"; exit 2; }
}
require docker
require php
require node
require npx
echo "==> [1/8] docker compose up -d"
if ! docker image inspect postgres:16-alpine >/dev/null 2>&1 \
|| ! docker image inspect redis:7-alpine >/dev/null 2>&1; then
echo " pulling postgres/redis images (docker-credential-desktop workaround)"
mkdir -p /tmp/docker-e2e-nocreds
printf '%s\n' '{"auths":{}}' > /tmp/docker-e2e-nocreds/config.json
docker --config /tmp/docker-e2e-nocreds pull postgres:16-alpine
docker --config /tmp/docker-e2e-nocreds pull redis:7-alpine
fi
if ! docker info >/dev/null 2>&1; then
cat <<'EOF' >&2
!! Docker daemon 未运行。
启动 Docker Desktop / OrbStack / Colima 后重试。
macOS Docker Desktop: 打开 Docker.app
macOS OrbStack: open -a OrbStack
macOS Colima: colima start
或者不使用 docker直接本机起 PG端口 15432+ Redis端口 16379
跳过这一步,只跑 [4/8] 起的 .env + 后面步骤。
EOF
exit 5
fi
docker compose -f "$COMPOSE_FILE" up -d
echo "==> [2/8] wait for pg/redis health"
for i in {1..30}; do
if docker compose -f "$COMPOSE_FILE" ps --format json | grep -q '"Health":"healthy"' \
|| docker compose -f "$COMPOSE_FILE" ps | grep -E "(healthy|running)" >/dev/null; then
pg_ok=$(docker compose -f "$COMPOSE_FILE" exec -T postgres pg_isready -U lottery -d lottery_e2e 2>/dev/null || true)
redis_ok=$(docker compose -f "$COMPOSE_FILE" exec -T redis redis-cli ping 2>/dev/null || true)
if [[ "$pg_ok" == *"accepting connections"* && "$redis_ok" == "PONG" ]]; then
echo " pg/redis healthy"
break
fi
fi
sleep 1
if [[ $i -eq 30 ]]; then
echo "!! pg/redis not healthy after 30s; dumping compose logs"
docker compose -f "$COMPOSE_FILE" logs --no-color
exit 3
fi
done
echo "==> [3/8] install composer deps if missing"
if [[ ! -d vendor ]]; then
composer install --no-interaction --prefer-dist
fi
echo "==> [4/8] prepare .env"
if [[ ! -f "$ENV_FILE" ]]; then
cp "$ENV_E2E" "$ENV_FILE"
fi
# 同步关键 e2e 变量(不覆盖已存在的 APP_KEY避免触发额外 key:generate 流程)
python3 - <<PY
import os, re
src = "$ENV_E2E"
dst = "$ENV_FILE"
with open(src) as f: new = f.read()
with open(dst) as f: cur = f.read()
# 用 e2e 模板值覆盖,仅保留 APP_KEY
for line in new.splitlines():
if not line or line.lstrip().startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
if k.strip() == "APP_KEY":
continue
pattern = re.compile(rf"^{re.escape(k.strip())}=.*$", re.M)
if pattern.search(cur):
cur = pattern.sub(f"{k.strip()}={v}", cur)
else:
cur += ("\n" if not cur.endswith("\n") else "") + line
with open(dst, "w") as f: f.write(cur)
PY
# 强制覆盖关键 secret避免与主站/生产共用
php -r '
$env = "'"$ENV_FILE"'";
$c = file_get_contents($env);
$c = preg_replace("/^LOTTERY_NATIVE_JWT_SECRET=.*$/m", "LOTTERY_NATIVE_JWT_SECRET=" . bin2hex(random_bytes(32)), $c);
$c = preg_replace("/^REVERB_APP_SECRET=.*$/m", "REVERB_APP_SECRET=" . bin2hex(random_bytes(16)), $c);
file_put_contents($env, $c);
'
if ! grep -q "^APP_KEY=base64:" "$ENV_FILE"; then
php artisan key:generate --force
fi
echo "==> [5/8] lottery:db-init --fresh (e2e environment only)"
# 注意AGENTS.md 要求 migrate:fresh 须用户确认;本脚本为 e2e 自动化专用,仅作用于
# docker compose 内的 lottery_e2e 库,绝不触及生产。
# composer dump-autoload 让 E2EPlayerSeeder位于 e2e/database/seeders被发现。
composer dump-autoload --quiet
# 用统一入口migrate:fresh + FoundationSeeder + admin-auth-sync + LocalDemoSeeder
# lottery:db-init --fresh 内部已对 migrate 传 --force无需外层加。
DB_DATABASE=lottery_e2e php artisan lottery:db-init --fresh
# e2e 专用 seeder建可登录玩家带 password_hash。LocalDemoSeeder 不会建可登录玩家
DB_DATABASE=lottery_e2e php artisan db:seed --class='E2E\Seeders\E2EPlayerSeeder' --force
echo "==> [6/10] boot backend processes"
php artisan config:clear >/dev/null
export LOTTERY_NATIVE_JWT_SECRET="$(grep '^LOTTERY_NATIVE_JWT_SECRET=' "$ENV_FILE" | cut -d= -f2-)"
if [[ -z "${LOTTERY_NATIVE_JWT_SECRET}" ]]; then
LOTTERY_NATIVE_JWT_SECRET="$(openssl rand -hex 32)"
if grep -q '^LOTTERY_NATIVE_JWT_SECRET=' "$ENV_FILE"; then
perl -i -pe "s/^LOTTERY_NATIVE_JWT_SECRET=.*/LOTTERY_NATIVE_JWT_SECRET=${LOTTERY_NATIVE_JWT_SECRET}/" "$ENV_FILE"
else
echo "LOTTERY_NATIVE_JWT_SECRET=${LOTTERY_NATIVE_JWT_SECRET}" >>"$ENV_FILE"
fi
export LOTTERY_NATIVE_JWT_SECRET
fi
export LOTTERY_E2E=true
php artisan serve --host=127.0.0.1 --port=8000 >"$LOG_DIR/serve.log" 2>&1 &
PIDS+=($!)
php artisan queue:work redis --queue=broadcasts:countdown,broadcasts,default --tries=3 --timeout=120 >"$LOG_DIR/queue.log" 2>&1 &
PIDS+=($!)
php artisan reverb:start --host=127.0.0.1 --hostname=127.0.0.1 --port=8080 >"$LOG_DIR/reverb.log" 2>&1 &
PIDS+=($!)
node "$E2E_DIR/scripts/mock-wallet-server.mjs" >"$LOG_DIR/mock-wallet.log" 2>&1 &
PIDS+=($!)
ADMIN_DIR="$ROOT_DIR/../lotteryadmin"
FRONT_DIR="$ROOT_DIR/../lotteryfront"
if [[ "${E2E_UI:-1}" == "1" && -d "$ADMIN_DIR" ]]; then
echo "==> [7/10] start lotteryadmin (3801)"
(cd "$ADMIN_DIR" && LOTTERY_API_UPSTREAM="$API_URL" ALLOWED_DEV_ORIGINS=127.0.0.1 npm run dev) >"$LOG_DIR/admin-ui.log" 2>&1 &
PIDS+=($!)
fi
if [[ "${E2E_UI:-1}" == "1" && -d "$FRONT_DIR" ]]; then
echo "==> [7/10] start lotteryfront (3800)"
(cd "$FRONT_DIR" && LOTTERY_API_UPSTREAM="$API_URL" NEXT_PUBLIC_PLAYER_SITE_CODE=demo ALLOWED_DEV_ORIGINS=127.0.0.1 npm run dev) >"$LOG_DIR/front-ui.log" 2>&1 &
PIDS+=($!)
fi
echo "==> [8/10] wait for API healthy"
for i in {1..30}; do
if curl -fsS "$PUBLIC_URL/health" >/dev/null 2>&1; then
echo " API up"
break
fi
sleep 1
if [[ $i -eq 30 ]]; then
echo "!! API not healthy; logs:"
tail -n 50 "$LOG_DIR"/*.log
exit 4
fi
done
if [[ "${E2E_UI:-1}" == "1" ]]; then
echo "==> [9/10] wait for admin/front UI"
for i in {1..60}; do
admin_ok=false
front_ok=false
curl -fsS "http://localhost:3801/admin/login" >/dev/null 2>&1 && admin_ok=true
curl -fsS "http://localhost:3800/login" >/dev/null 2>&1 && front_ok=true
if [[ "$admin_ok" == true && "$front_ok" == true ]]; then
echo " UI up"
break
fi
sleep 2
if [[ $i -eq 60 ]]; then
echo "!! UI not ready; see admin-ui.log / front-ui.log"
fi
done
fi
echo "==> [10/10] playwright test"
cd "$E2E_DIR"
if [[ ! -d node_modules ]]; then
npm install
fi
if [[ ! -d node_modules/@playwright/test/.local-browsers ]]; then
npx playwright install chromium
fi
PLAYWRIGHT_JWT_SECRET=$(grep '^LOTTERY_NATIVE_JWT_SECRET=' "$ENV_FILE" | cut -d= -f2-) \
PLAYWRIGHT_API_URL="$API_URL" \
PLAYWRIGHT_ADMIN_URL="http://localhost:3801" \
PLAYWRIGHT_FRONT_URL="http://localhost:3800" \
REVERB_APP_KEY=$(grep '^REVERB_APP_KEY=' "$ENV_FILE" | cut -d= -f2-) \
REVERB_HOST=127.0.0.1 \
REVERB_PORT=8080 \
E2E_MOCK_WALLET_PORT=5555 \
E2E_ADMIN_USERNAME=admin \
E2E_ADMIN_PASSWORD=12345678 \
E2E_PLAYER_USERNAME=$(grep '^E2E_PLAYER_USERNAME=' "$ENV_E2E" | cut -d= -f2-) \
E2E_PLAYER_PASSWORD=$(grep '^E2E_PLAYER_PASSWORD=' "$ENV_E2E" | cut -d= -f2-) \
npx playwright test "$@"
rc=$?
if [[ $rc -ne 0 ]]; then
echo "==> Playwright failed; saving artifacts"
cp -R "$E2E_DIR/test-results" "$ARTIFACT_DIR/test-results-$(date +%s)" 2>/dev/null || true
cp -R "$E2E_DIR/playwright-report" "$ARTIFACT_DIR/playwright-report-$(date +%s)" 2>/dev/null || true
tail -n 200 "$LOG_DIR"/*.log > "$ARTIFACT_DIR/backend-logs-$(date +%s).log" || true
fi
exit "$rc"