# 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) ```bash ./e2e/scripts/run.sh ``` 会自动: 1. `docker compose up -d`(PG :15432 + Redis :16379) 2. 等 PG/Redis health 3. `composer install`(缺 vendor 时) 4. 复制 `e2e/.env.e2e` → `.env`,注入强随机 `LOTTERY_NATIVE_JWT_SECRET` / `REVERB_APP_SECRET` 5. `php artisan key:generate`(缺时) 6. **`php artisan lottery:db-init --fresh`** ⚠️ 重建 `lottery_e2e` 库(**只**作用于此库,绝不碰其他库) 7. 跑 `E2EPlayerSeeder`(建可登录玩家) 8. 起 `php artisan serve`(8000)、`queue:work redis`(默认队列)、`reverb:start`(8080) 9. UI 测试默认用本机 **Google Chrome**(`playwright.config.ts` `channel: 'chrome'`),**无需**下载 Playwright Chromium 10. `npx playwright test`(若要用自带 Chromium:`PLAYWRIGHT_USE_BUNDLED_CHROMIUM=1 ./e2e/scripts/run.sh`) 跑完按 Ctrl+C 自动停 serve/queue/reverb。`docker compose down -v` 自行决定(脚本不删 volume,下次跑快)。 ## 单独跑(不重起 stack) ```bash # 起 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 ```bash 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。 ## 添加新用例 1. 在 `e2e/tests/api/` 新建 `XX.something.spec.ts` 2. 复用 `fixtures.ts` 的 `playerLogin` / `adminLogin` / `playerCtx` / `adminCtx` 3. 任何要复用 e2e-only 路由的辅助,往 `E2EServiceProvider` 挂的路由里加,**绝不**往生产路由加 4. 跑:`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): ```bash ./e2e/scripts/run.sh ``` 仅 API(与 CI 一致): ```bash E2E_UI=0 ./e2e/scripts/run.sh ``` 失败产物:`e2e/artifacts/`、`e2e/logs/`。