- 新增 settlement-operations 合并列表 API,收付/调账接口改为标准分页 - actionable_only 流水在内存过滤后正确分页 - 已结账单流水不再展示补差/冲正快捷动作 - 坏账核销支持 idempotency_key 并写入 result_bill_id - 补充操作记录、流水与坏账幂等相关测试
lotterLaravel E2E 测试
端到端测试,真实 Postgres + Redis + Laravel + Reverb,与 Feature 测试(SQLite 内存库 + mock)解耦。
与 Feature 测试的边界
| 维度 | Feature | E2E(这套) |
|---|---|---|
| 数据库 | SQLite :memory: + RefreshDatabase(事务回滚) |
真 PG(docker compose 15432),事务落库 |
| Redis | array 驱动(不真连) |
真 Redis(16379),Lua/广播全真实 |
| 队列 | sync(同步执行) |
redis(异步,启 queue:work) |
| 鉴权 | actingAs 直接注入 |
真 HTTP 调 auth/login 拿 token |
| 广播 | Event::fake() 拦截 |
真走 Reverb(8080) |
| 入口 | app()->handle($request) |
真 php artisan serve(8000) |
| 时延 | 数百 ms | 数十秒(启服务、跑 migrate) |
| 跑哪 | 每次 push / PR | 上线前 + 重大改动后 |
业务逻辑交给 Feature;部署/启动/集成问题归 e2e。
目录结构
e2e/
├── docker-compose.yml # postgres + redis(仅 e2e 用)
├── .env.e2e # 模板 .env
├── package.json # playwright 依赖
├── playwright.config.ts
├── tests/ # 测试用例(API mode)
│ ├── fixtures.ts # 共享 HTTP 客户端 / login / 拿 draw
│ ├── api/
│ │ ├── _helper.spec.ts
│ │ ├── 01.health.spec.ts
│ │ ├── 02.player-auth.spec.ts
│ │ ├── 03.wallet-ticket.spec.ts
│ │ ├── 04.admin-auth.spec.ts
│ │ ├── 05.wallet-transfer.spec.ts # 转账 + 幂等 + 1001/1010
│ │ ├── 06.wallet-logs.spec.ts # 流水一致性 + 过滤 + 分页
│ │ ├── 07.admin-player.spec.ts # 创建/冻结/解冻玩家
│ │ ├── 08.draw-publish-settle.spec.ts # 开奖结算派彩(poll,无 skip)
│ │ ├── 09.credit-bet.spec.ts # 信用盘下注
│ │ ├── 10.agent-settlement.spec.ts # 代理账期关账
│ │ ├── 11.sso-mainsite.spec.ts # SSO + 主站 mock 异常 + happy path
│ │ ├── 12.broadcast.spec.ts # Reverb balance.update
│ │ ├── 13.credit-settlement-win.spec.ts # 信用盘中奖释额
│ │ ├── 14.settlement-payment.spec.ts # 账期 confirm + 收付
│ │ ├── 15.reconcile-job.spec.ts # pending_reconcile 扫描
│ │ ├── 16.agent-share-bill.spec.ts # 代理占成账单
│ │ ├── 17.settlement-partial-payment.spec.ts # 部分收付
│ │ ├── 18.settlement-permissions.spec.ts # 站点财务/代理权限
│ │ ├── 19.settlement-bad-debt-partial.spec.ts # 部分收付后坏账
│ │ ├── _helper.ts # 共享步骤
│ │ └── helpers/draw-settlement.ts # 结算流水线
│ └── ui/
│ ├── admin-login.spec.ts
│ ├── admin-settlement-center.spec.ts
│ ├── front-login-hall.spec.ts
│ └── front-place-bet.spec.ts
├── database/seeders/
│ └── E2EPlayerSeeder.php # 建可登录玩家(E2E\Seeders 命名空间)
├── routes/e2e.php # 仅 /api/v1/_e2e/* 路由
├── providers/
│ └── E2EServiceProvider.php # 仅在 LOTTERY_E2E=true 时挂载路由
├── scripts/
│ ├── run.sh # 一键起 stack + mock + UI + playwright
│ └── mock-wallet-server.mjs # 主站钱包 mock(5555)
└── README.md # 本文件
# E2E 控制器(仅路由被挂载才暴露,物理上位于 app/ 下便于 Laravel 自动加载)
app/Http/Controllers/Api/V1/E2E/
├── E2ECaptchaPeekController.php # captcha bypass 提示
├── E2EPlayerStateController.php # 玩家 reset / set-balance / unlock / inspect
├── E2EDrawController.php # close-now / finish-cooldown / tick
├── E2EInspectController.php # credit-ledger / wallet-txns / ticket-items
└── E2EProvisionController.php # credit-player / SSO mint / wallet mock 配置
# E2E 服务提供者注册入口(仅 E2E 时注册)
bootstrap/providers.php # 在末尾追加 E2EServiceProvider
# 生产代码最小入侵点
app/Services/AdminCaptchaService.php # 多了一个 `LOTTERY_E2E_BYPASS` 分支(env 关闭时不生效)
composer.json # autoload-dev 加 E2E\Seeders\, E2E\Providers\
前置依赖
- Docker(Docker Desktop / OrbStack / Colima 任一)
- Node.js 20+(跑 Playwright)
- PHP 8.3+、Composer
- 第一次跑:
npx playwright install chromium(自动;本仓库 e2e 用 API 模式不依赖浏览器壳,但安装包仍需)
一键跑(macOS / Linux)
./e2e/scripts/run.sh
会自动:
docker compose up -d(PG :15432 + Redis :16379)- 等 PG/Redis health
composer install(缺 vendor 时)- 复制
e2e/.env.e2e→.env,注入强随机LOTTERY_NATIVE_JWT_SECRET/REVERB_APP_SECRET php artisan key:generate(缺时)php artisan lottery:db-init --fresh⚠️ 重建lottery_e2e库(只作用于此库,绝不碰其他库)- 跑
E2EPlayerSeeder(建可登录玩家) - 起
php artisan serve(8000)、queue:work redis(默认队列)、reverb:start(8080) - UI 测试默认用本机 Google Chrome(
playwright.config.tschannel: 'chrome'),无需下载 Playwright Chromium npx playwright test(若要用自带 Chromium:PLAYWRIGHT_USE_BUNDLED_CHROMIUM=1 ./e2e/scripts/run.sh)
跑完按 Ctrl+C 自动停 serve/queue/reverb。docker compose down -v 自行决定(脚本不删 volume,下次跑快)。
单独跑(不重起 stack)
# 起 stack(不跑测试)
docker compose -f e2e/docker-compose.yml up -d
DB_DATABASE=lottery_e2e php artisan serve --port=8000
DB_DATABASE=lottery_e2e php artisan queue:work redis
DB_DATABASE=lottery_e2e php artisan reverb:start --port=8080
# 跑测试
cd e2e
PLAYWRIGHT_API_URL=http://127.0.0.1:8000 \
E2E_PLAYER_USERNAME=demo_player E2E_PLAYER_PASSWORD=12345678 \
npx playwright test --headed # 想要 UI 调试时
e2e 账号
| 角色 | 账号 | 密码 |
|---|---|---|
| 超管 | admin |
12345678 |
e2e 玩家账号
| 字段 | 值 |
|---|---|
site_code |
demo |
username |
demo_player |
password |
12345678 |
auth_source |
lottery_native |
funding_mode |
wallet |
| 初始余额 | 1,250,000 minor(NPR 125.00,由 DEV_SEED_WALLET_BALANCE_MINOR 改) |
覆盖矩阵(截至当前)
| 链路 | spec | 用例数 | 状态 |
|---|---|---|---|
| 健康/ping/captcha/公开接口 | 01.health | 4 | ✅ |
| 玩家登录 / 失败 / 锁定 | 02.player-auth | 4 | ✅ |
| 玩家钱包 + 下注 + 幂等 | 03.wallet-ticket | 3 | ✅ |
| 超管登录 / dashboard / 401 | 04.admin-auth | 3 | ✅ |
| 玩家 transfer-in/out + 1001/1010 | 05.wallet-transfer | 7 | ✅ |
| 钱包流水一致性 + 过滤 + 分页 | 06.wallet-logs | 4 | ✅ |
| 超管创建/冻结/解冻/查玩家 | 07.admin-player | 6 | ✅ |
| 开奖+结算+派彩 完整链路 | 08.draw-publish-settle | 1 | ✅ 确定性 poll |
| 信用盘下注占用授信 | 09.credit-bet | 1 | ✅ |
| 代理账期关账出账单 | 10.agent-settlement | 1 | ✅ |
| SSO JWT + 主站钱包异常 + happy path | 11.sso-mainsite | 4 | ✅ |
| Reverb balance.update | 12.broadcast | 1 | ✅ |
| 信用盘中奖释额 game_settlement_win | 13.credit-settlement-win | 1 | ✅ |
| 账期 confirm + 登记收付闭环 | 14.settlement-payment | 1 | ✅ |
| pending_reconcile → reconcile-jobs | 15.reconcile-job | 1 | ✅ |
| 代理占成账单 share_profit | 16.agent-share-bill | 1 | ✅ |
| 部分收付 partial_paid → settled | 17.settlement-partial-payment | 2 | ✅ |
| 站点财务收付 / 绑定代理禁坏账 | 18.settlement-permissions | 1 | ✅ |
| 部分收付后坏账核销 | 19.settlement-bad-debt-partial | 1 | ✅ |
| 管理端 UI 登录 | ui/admin-login | 1 | ✅ |
| 管理端 UI 结算中心 | ui/admin-settlement-center | 1 | ✅ |
| 玩家端 UI 登录进大厅 | ui/front-login-hall | 1 | ✅ |
| 玩家端 UI 下注提交 | ui/front-place-bet | 1 | ✅ |
合计 ~43 个用例覆盖 e2e 关键链路。
已知未覆盖(需外部依赖或重构成本高)
- 玩家提现链路:与 transfer-out 重叠度 90%,增量价值低
- 报表 / settings 实时生效:表单字段多,断言脆,价值中
跑特定 spec
cd e2e
npx playwright test 05.wallet-transfer.spec.ts --headed
关键安全设计
- 生产环境零侵入:
AdminCaptchaService::verify走env('LOTTERY_E2E')开关,生产.env留空 → bypass 分支 dead code。 - E2E 路由不挂载到生产:
E2EServiceProvider::boot在LOTTERY_E2E !== true时直接 return;/api/v1/_e2e/*在生产完全 404。 - 数据隔离:e2e 库名固定
lottery_e2e,与lottery主库端口不同(15432 vs 5432)。 - migrate:fresh 是库内操作:脚本注释里明确"只作用于此 docker 库",并对应 AGENTS.md 规定。
- JWT / Reverb 密钥运行时随机生成:避免与生产/主站共用 secret。
添加新用例
- 在
e2e/tests/api/新建XX.something.spec.ts - 复用
fixtures.ts的playerLogin/adminLogin/playerCtx/adminCtx - 任何要复用 e2e-only 路由的辅助,往
E2EServiceProvider挂的路由里加,绝不往生产路由加 - 跑:
cd e2e && npx playwright test 05.new.spec.ts
排障
- API 启动失败:
tail -n 100 e2e/logs/serve.log/queue.log/reverb.log - PG/Redis 起不来:
docker compose -f e2e/docker-compose.yml logs - 测试卡住:脚本的
cleanup钩子 Ctrl+C 会停 artisan/queue/reverb - player 登录 401:
curl -X POST http://127.0.0.1:8000/api/v1/_e2e/reset-player重置玩家
CI 集成
仓库已包含 .github/workflows/e2e.yml(push/PR 自动跑 API 项目;E2E_UI=0 不启前后端 dev server)。
本地跑全量(含 UI):
./e2e/scripts/run.sh
仅 API(与 CI 一致):
E2E_UI=0 ./e2e/scripts/run.sh
失败产物:e2e/artifacts/、e2e/logs/。