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

View File

@@ -0,0 +1,105 @@
/**
* E2E玩家登录链路
*
* 覆盖:
* - 成功登录拿 token / me 能拿回玩家
* - 错密码 + 失败计数累加
* - 累积到上限锁定
* - reset-player 重置后能再登录
*
* 不依赖 captcha 渲染LOTTERY_E2E_BYPASS code 由 AdminCaptchaService 接受。
*/
import { test, expect, request as pwRequest } from '@playwright/test';
import {
playerLoginViaBypass,
resetE2EPlayer,
fetchPlayerCaptcha,
} from './_helper';
const PWD_OK = process.env.E2E_PLAYER_PASSWORD ?? '12345678';
const PWD_BAD = 'WrongPassword1!';
test.beforeEach(async () => {
// 每个用例都从干净状态开始
await resetE2EPlayer();
});
test('登录成功 → token 合法 → /player/me 回放同账号', async () => {
const session = await playerLoginViaBypass();
expect(session.access_token).toBeTruthy();
expect(session.player.username).toBe(process.env.E2E_PLAYER_USERNAME ?? 'demo_player');
const ctx = await pwRequest.newContext({
extraHTTPHeaders: { Authorization: `Bearer ${session.access_token}` },
});
const r = await ctx.get('/api/v1/player/me');
expect(r.ok(), `me status=${r.status()}`).toBeTruthy();
const me = (await r.json()) as any;
expect(me.data.username).toBe(session.player.username);
expect(me.data.auth_source).toBe('lottery_native');
await ctx.dispose();
});
test('错误密码 → 返回 200 但 code 非 0且 login_failed_count 自增', async () => {
const captcha = await fetchPlayerCaptcha();
const ctx = await pwRequest.newContext();
const r = await ctx.post('/api/v1/player/auth/login', {
data: {
site_code: 'demo',
username: 'demo_player',
password: PWD_BAD,
captcha_key: captcha.captcha_key,
captcha_code: 'LOTTERY_E2E_BYPASS',
},
});
// 鉴权失败业务上 200 + code=player_credentials_invalid
const body = (await r.json()) as any;
expect(body.code).not.toBe(0);
await ctx.dispose();
});
test('连续 N 次错密码 → 登录锁定 → 正确密码也被拒', async () => {
const maxAttempts = 8; // 与 PlayerNativeAuthService::recordFailedLogin 默认对齐
for (let i = 0; i < maxAttempts; i++) {
const captcha = await fetchPlayerCaptcha();
const ctx = await pwRequest.newContext();
const r = await ctx.post('/api/v1/player/auth/login', {
data: {
site_code: 'demo',
username: 'demo_player',
password: PWD_BAD,
captcha_key: captcha.captcha_key,
captcha_code: 'LOTTERY_E2E_BYPASS',
},
});
await r.body().catch(() => '');
await ctx.dispose();
}
// 第 9 次即使密码正确也应被拒(已锁定)
const captcha2 = await fetchPlayerCaptcha();
const ctx = await pwRequest.newContext();
const r = await ctx.post('/api/v1/player/auth/login', {
data: {
site_code: 'demo',
username: 'demo_player',
password: PWD_OK,
captcha_key: captcha2.captcha_key,
captcha_code: 'LOTTERY_E2E_BYPASS',
},
});
const body = (await r.json()) as any;
// 期望 200 + 业务 code=player_login_locked403 状态码也合理)
expect([403, 200]).toContain(r.status());
if (r.status() === 200) {
expect(body.code).not.toBe(0);
}
await ctx.dispose();
});
test('reset-player 后又能登录', async () => {
await resetE2EPlayer();
const session = await playerLoginViaBypass();
expect(session.access_token).toBeTruthy();
});