ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

OpenFront 匹配系统集成测试指南:contained / E2E / 取消重排队三套 Harness 全解析

OpenFront 匹配系统集成测试指南:contained / E2E / 取消重排队三套 Harness 全解析 游戏开发后端【免费下载链接】OpenFrontIOOnline browser-based RTS game项目地址https://gitcode.com/gh_mirrors/op/OpenFrontIO点击查看免费下载导读OpenFront基于浏览器的开源 RTS 游戏的匹配系统由闭源 API worker 负责撮合客户端通过matchmaking-modal的 WebSocket 连接进入队列并在得到match-assignment后接入游戏服务器。tests/matchmaking/目录提供了三套专门驱动真实浏览器headless Chromium的集成测试 harness用来验证这条排队 → 分配 → 建局 → 入局 → 异常处理链路的 close-code/重连契约与取消重排队行为。读完本文你将掌握每套 harness 的适用场景、运行方式、前置条件、故障诊断方法以及它们与 客户端 Matchmaking 实现 和 CloseCodes 定义 之间的对应关系。背景为什么要为匹配系统单独写 harness普通单元测试npm test下的 vitest 用例无法覆盖匹配系统的两个关键事实匹配分配发生在闭源 API worker 侧客户端只通过 WebSocket 与 HTTP 轮询与其交互契约只能通过真实网络行为验证浏览器 WebSocket 的 close-code 语义如 1006、1008、1000、41xx只有在真实浏览器环境里才会原样出现jsdom 无法模拟。因此这三套 harness 全部驱动真实 headless 浏览器且明确不参与npm test由独立的 npm script 触发。相关脚本定义在 package.jsontest:matchmaking: node tests/matchmaking/contained.mjs, test:matchmaking:e2e: node tests/matchmaking/e2e.mjs, test:matchmaking:cancel: node tests/matchmaking/e2e-cancel.mjs,三套 harness 共用tests/matchmaking/util.mjs提供的waitFor带超时与轮询间隔的等待器、isUp探测端口是否在监听和makeCheckerPASS/FAIL 断言收集器失败即退出码 1三个工具函数。一、Contained harnessnpm run test:matchmaking设计目标tests/matchmaking/contained.mjs是一套不依赖闭源 API worker的集成测试。它把真实应用中的matchmaking-modal引导到一个进程内假匹配服务器tests/matchmaking/fakeServer.mjs基于ws包实现该服务器按文档化协议说话但浏览器侧仍是真实 WebSocket因此close-code 语义完全真实。运行前置npm run dev应用运行在 :9000见 package.json 中dev脚本的GAME_ENVdev组合不需要API worker额外前置按照 run-openfront 技能说明 先运行bash .claude/skills/run-openfront/setup.sh初始化 headless Chromium 环境宿主无浏览器、Playwright 尚不支持该系统版本setup 脚本会绕过这两点。覆盖场景与断言文档给出了核心契约表与contained.mjs中的实际断言一一对应场景期望的客户端行为connect 后加入队列发送{type:join, jwt}连接被突然中断部署/重启close code 1006重连 重新 join带退避1008Invalid session重连 重新 join携带新 tokenmatch-assignment记录 gameIdsocket 结束1000Replaced by newer connection显示提示消息不重试用户主动退出背出不重试、无提示Socket 重定向原理测试在页面里page.evaluate子类化window.WebSocket凡 URL 包含/matchmaking/join的连接都被改写指向 fake server 的wsUrl其余连接原样放行见 contained.mjs。同时挂上show-message事件收集器用于断言替换连接提示文案。假服务器的控制协议fake server 在随机端口上同时提供ws://127.0.0.1:port的 WebSocket 端点和http://127.0.0.1:port/control/*的控制 HTTP APIfakeServer.mjs测试通过它逐一触发各服务端行为控制端点行为/control/state返回历史 join 列表{jwt, mode, at}与当前排队 socket/control/killws.terminate()全部 socket——模拟部署/重启浏览器侧表现为 1006 无关闭帧/control/reject-next令下一次 join 收到1008 Invalid session/control/replace以1000 Replaced by newer connection关闭全部 socket——模拟更新连接抢占队列槽位/control/ranked-limit以4100 close_reason.ranked_limit_reached关闭/control/invalid-clan以4101 close_reason.invalid_clan关闭/control/clan-unverified以4102 close_reason.clan_verification_failed关闭/control/assign向所有 socket 广播{type:match-assignment, gameId}此外verifyClient在握手前模拟了真实 worker 的 HTTP 400 校验缺少instance_id或mode不是1v1/2v2的握手会被拒绝fakeServer.mjs。测试流程与真实客户端逻辑的对应脚本依次执行七个步骤resetAndConnect()后等待 join 到达——对应MatchmakingModal.connect()中onopen后2 秒延迟才发送{type:join, jwt}Matchmaking.ts该延迟是为了让用户能看到 connecting 状态、并让立即退出的用户不进入队列测试用joinCountReaches(1, 8000)的 8 秒超时容忍这 2 秒/control/kill触发重连重 join——对应 onclose 处理 中非 1008/41xx 关闭按服务器部署/重启处理走重连分支重连延迟为min(1000 * 2 ** attempts, 15000)的指数退避/control/reject-nextkill验证 1008getPlayToken()会刷新过期 token所以重 join 携带的是新 token/control/assign验证match-assignment后gameID被记录、socket 主动关闭客户端将intentionalClose置位后socket.close()见 Matchmaking.ts随后调用el.onClose()停止checkGame轮询/control/replace验证 1000isTerminalClose(1000)为真但客户端对CloseCode.Normal有专门分支——弹出matchmaking_modal.replaced提示并关闭不重试Matchmaking.ts测试等待 3.5 秒后确认 join 数量仍为 5用户主动onClose()验证不重试、无提示——onClose置intentionalClose true并关闭 socketMatchmaking.ts以2v2模式重连断言 join 消息携带mode2v2。二、E2E harnessnpm run test:matchmaking:e2e设计目标tests/matchmaking/e2e.mjs跑的是真实全链路浏览器玩家通过真实模态进入真实队列API worker 在localhost:8787匹配器撮合后dev 游戏服务器的/matchmaking/checkin长轮询收到分配并创建游戏。测试断言覆盖所有玩家收到同一个gameId游戏创建后每个玩家都派发join-lobby游戏携带该模式的配置allowedPublicIds白名单接纳每一位被匹配的玩家。运行前置npm run dev应用 游戏服务器在 :9000dev 模式下游戏服务器默认对 :8787 做 checkin 长轮询闭源 API worker 本地运行在 API 仓库执行wrangler dev端口 8787。模式与关键实现细节MM_MODE2v2 npm run test:matchmaking:e2e4 名玩家进入 2v2 队列测试还会搭上真实流程进入已开始的游戏断言局内队伍拆分为 2 vs 2且在每个客户端上完全一致确定性。每个玩家使用独立的浏览器 context独立 localStorage 独立身份并对每个页面注入rafThrottlerAF 节流至 2 秒一帧与glSpoofheadless 只有 SwiftShader而initGL.ts会因failIfMajorPerformanceCaveat与渲染器字符串拒绝软件 WebGL见 e2e.mjs——注释明确说明我们验证的是匹配/队伍而非渲染。游戏服务器在 dev 下默认是两个 workerw0/w1对应ServerEnv的 dev 集群映射worker 路径由 gameId 推导因此fetchGameInfo会依次探测两个 worker 的/api/game/gameIde2e.mjs。配置断言分模式进行1v1 要求gameConfig.gameMode Free For All且maxPlayers 22v2 要求gameMode Team、playerTeams 2、maxPlayers 4、rankedType 2v2且allowedPublicIds.length PLAYER_COUNTe2e.mjs。2v2 模式下测试进入游戏后通过每个客户端的build-menu元素读取GameView的 ground-truth 状态筛选HUMAN类型玩家读取每个玩家的clientID() : team()断言四人队号计数为2,2且各客户端序列化结果逐字一致e2e.mjs——这从数据上证明匹配结果在所有客户端确定性一致。三、Cancellation harnessnpm run test:matchmaking:cancel设计目标tests/matchmaking/e2e-cancel.mjs验证shorthanded 匹配局取消 自动重排队对应 PR #4762 引入的行为4 名玩家排 2v2其中一人no-show的/api/game/*/exists轮询被拦截因此它永远不会派发join-lobby、永远不会连上游戏服务器——模拟一个未及时连上游戏的客户端。断言的核心行为在15 秒开局截止时间到达时服务器取消该局而不是 3 人开局三名已连上的玩家收到kick_reason.match_cancelledtoast派发leave-lobby并自动重新排队模态回到 searching 状态no-show 玩家不受影响无 toast、保留过期的 assignment被取消的游戏从服务器剪除且不产生存档archive。实现细节no-show 通过page.route(**/api/game/*/exists, ...)的route.abort()实现e2e-cancel.mjs与 e2e 相同的rafThrottle、双 worker 探测、控制台收集与 dump模态的requeue()拒绝在已关闭的模态上重排队真实流程中模态在 prestart 前保持打开因此测试按Main的模态关闭循环同款方式先打开模态el.isModalOpen true再connect()e2e-cancel.mjs重排队完成的判定是四元组toasts.length 0 leaveLobby ! null gameID null connected truee2e-cancel.mjs即看到取消 toast、离开死掉的 lobby、gameID 被清空、且 2 秒连接延迟后重新 join 成功leave-lobby的cause必须是match-cancelled剪除验证等待 2 秒后fetchGameInfo(gameId) null并读取/tmp/dev.log确认服务器日志包含cancelling matchmade gamee2e-cancel.mjs。该行为对应客户端 MatchmakingModal.requeue()清除 game 轮询、重置connected/gameID/intentionalClose/limitReached/queueSize/reconnectAttempts后重新connect()若模态已关闭则返回false调用方据此得知没有重排队。四、失败时如何诊断三套 harness 都在finally块中关闭浏览器与 fake server失败时给出明确的定位线索ContainedjoinCountReaches的waitFor超时会指明等待的是第几个 join可据此判断是 2 秒 join 延迟、重连退避还是协议不匹配导致的问题E2E / Cancel任一步失败都会 dump全部玩家浏览器控制台每人最后 25 行。README 给出了两条黄金判据与 e2e.mjs 的提示一致close code 1008→ worker 拒绝了 play token检查 token 过期/刷新链路完全没有 assignment→ 通常是 worker 拒绝了游戏服务器的x-api-keycheckin检查 API worker 与游戏服务器间的鉴权配置或匹配器没有运行。Cancel 额外检查/tmp/dev.log中的cancelling matchmade game日志确保取消动作确实发生在服务器侧。五、客户端契约速览close code 与重连策略要理解三套 harness 为什么这样断言需要对照客户端的契约实现。核心规则集中在 Matchmaking.ts 的 onclose 与 CloseCodes.ts1000 Normal队列槽位被同账号更新连接如第二个标签页抢占 → 弹matchmaking_modal.replaced提示并关闭不重试1008 / 1011旧版服务仍以裸 reason 发送这两种关闭码客户端兼容处理legacyReason其中ranked_limit_reached/invalid_clan/clan_verification_failed分别走限流、无效公会、公会未验证分支其余按jwt 被拒处理——getPlayToken()刷新后重 join 携带新 token4100 RankedLimitReached免费排位次数用尽服务器会持续拒绝直到次日 UTC 或订阅因此不重连进入limitReached状态展示付费 upsellMatchmaking.ts4101 / 4102无效公会 / 公会验证失败关闭并提示41xx 之外的其他非预期关闭含 1006视为服务器部署/重启队列仅在内存中指数退避min(1000 * 2 ** attempts, 15000)重连重 joinisTerminalClose()1000、1002以及4000–4999全区间视为终态关闭CloseCodes.ts。另外还有一条与测试密切相关的自我保护由于 lobby 每 ~3 秒广播一次queue-size客户端设置15 秒 watchdog若长时间静默锁屏、断网导致连接假死即主动断开重连避免排队幽灵导致游戏缺人开局Matchmaking.ts。六、运行建议与注意事项按序准备环境先bash .claude/skills/run-openfront/setup.sh一次性初始化 headless 浏览器环境再npm run dev最后视目标启动 API worker仅 e2e / cancel 需要Contained 不需要闭源服务是日常回归匹配契约的首选E2E / Cancel 需要完整的 API 游戏服务器 浏览器栈适合在接近生产的本地环境验证这些 harness 是node脚本而非 vitest 用例运行方式为npm run test:matchmaking等三个独立命令不会在npm test中执行涉及局内状态断言如 2v2 队伍拆分时测试直接读取客户端GameView的 ground-truth 数据而非渲染像素因此 headless SwiftShader 环境足以支撑断言不需要真实 GPU若需深入客户端行为可继续阅读 Matchmaking.ts、CloseCodes.ts、SocketClose.ts 以及 run-openfront 驱动说明。赞分享游戏开发后端【免费下载链接】OpenFrontIOOnline browser-based RTS game项目地址https://gitcode.com/gh_mirrors/op/OpenFrontIO点击查看免费下载相关推荐bpmn-js 版本演进全解析从 CHANGELOG 读懂 BPMN 2.0 建模工具包的能力图谱与升级路径bpmn js 版本演进全解析从 CHANGELOG 读懂 BPMN 2.0 建模工具包的能力图谱与升级路径 本文以 bpmn js 官方 CHANGELOG前端UI组件Trigger.dev Webapp 测试体系全解析单元、Smoke E2E 与全量 Auth E2E 套件实战指南Trigger.dev Webapp 测试体系全解析单元、Smoke E2E 与全量 Auth E2E 套件实战指南 本指南基于 Trigger.dev 开源AI Agent后端任务调度开发工具可观测性AI 应用AutoBangumi E2E 集成测试指南从 Docker 编排到 Hermetic 全链路验证AutoBangumi E2E 集成测试指南从 Docker 编排到 Hermetic 全链路验证 AutoBangumi 的端到端E2E测试通过真实 D后端前端音视频上一篇Karmada 多调度组Multiple Scheduling Group深度解析从多组亲和到分级弹性调度的完整实现指南下一篇paascloud-master安全框架设计SecurityConfig配置与权限控制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表