沉淀 E1↔E6 合龙技法:engineBundle(__GameBundle)经静态 public/ manifest(脚本生成,gitignore)+ runtime mock 回填 manifestUrl+checksum → 宿主 fetchAndVerifyManifest → 引擎包路真玩,绕开 T1b 替换中自研壳 demo 兜底的 load_timeout。记 3 坑(静态路径非 /app-api、checksum 校原文、srcdoc allow-same-origin origin)+ CDP 验法。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.4 KiB
真 UI 走查配方(CDP on mini-desktop)
为什么必须真 UI 走查:编排器/批跑/curl 全绿 ≠ 真人 UI 能用。编排器旁路曾掩盖 2 个真后端发布缺陷(
currentVersionId生成后未回写 /getZones空桩),真 UI 走查是逮这类缺陷的唯一手段(HJ-FE-WALK-001)。链路①(创作→生成→预览)与链路②(发布→审核→入 feed)均用本配方闭合。 工具已入仓docs/agent-specs/2026-06-09-agent-loop-v1/orchestrator/:player_cdp.py(游戏试玩 CDP,含 BrowserPool)/admin_walk.py(admin 最小 CDP 客户端:new_target/send/evaljs/nav/shot)/run_walk.sh(chrome 生命周期)/probe_bridge_channel.py(宿主↔iframe 双边信道探针)。
1. 大原则
- chrome 一律在 mini-desktop 本地起(6c6g 连续 exit 144,该路线已作废);被测前端/后端同机,全 localhost。
- 浏览器内访问前端优先
http://localhost:4173(安全上下文,省心)。经 Tailscale IP/明文 http 现也可用:宿主 manifest 校验的crypto.subtle在非安全上下文缺失,已补纯 JS SHA-256 兜底(2026-06-174e486f60),不再「走 IP 永远 demo 兜底」。 - 红线(此坑血泪):上条「IP→demo 兜底」曾被当成「走查改用 localhost 规避」记着,却没意识到创始人/真实用户正是经 IP 访问 → 线上游戏一直「加载失败」(demo 退化后 W-CLEAN 占位发 game_error)。教训:走查环境 localhost = 安全上下文,会系统性掩盖「上下文依赖缺陷」(
crypto.subtle、isSecureContext门控特性等);用户可见 UI/e2e 走查必须另跑一遍真实访问 origin(IP/明文 http),不能只在 localhost 取证。当走查需要一个 workaround 才能避开某失败时,先问「真实用户会不会也撞上同一失败」。 - chrome 起后必先
curl -s localhost:9222/json/version验活再用。
2. CDP 七坑(Chrome 146 实证,逐个血泪)
- root 运行必须
--no-sandbox; --no-zygote必加(否则Failed global descriptor lookup→ 渲染进程崩 → 空 DOM);- WS 握手 403 → 启动加
--remote-allow-origins=*,或 websocket-client 用suppress_origin=True; - Chrome 111+ 建 target 用
PUT /json/new?url(GET 不行); - ssh 直启 chrome 必死(继承 ssh 会话 fd,断连即杀,nohup/setsid 救不全)——正解=整个流程写成 mini 上的脚本,脚本内
exec >/tmp/walk_out.txt 2>&1重定向自身输出,nohup bash run_walk.sh &启动即返回,远端 until-loop 轮询输出文件; - 单文件传 mini:
cat local | ssh mini-desktop 'cat >/tmp/x'; - 轮询禁
sleep N && cmd(会被拦),用远端for i in ...; do grep -q DONE file && break; sleep 2; done。
3. studio 走查(创作/试玩/发布链路)
- 入口
:4173;受控 textarea 填值 = 原型 setter + dispatch input 事件(触发 Vue v-model,直接赋值无效); - 点按钮用精确文案匹配防子串误中(
includes('满意')会误中「不满意,重做」); - 截图
cat管道拉回本地用 Read 查看;游戏试玩复用player_cdp.play()(批跑必须continue_on_demo=False)。 - localStorage 注入 token 过
requiresAuth走查(免登录直达受保护页只读走查)——2026-06-16 Lane E G0 实证:只想只读核实一个meta.requiresAuth的页面(如创作页),不必走完整登录表单。先后端拿一枚有效 token(如免短信登录端点 /test1token),再用 CDP 在目标 origin 下注入 localStorage 后再导航:
坑:① 必须在同 origin 已开的 target 里 setItem(localStorage 按 origin 隔离,先evaljs("localStorage.setItem('ACCESS_TOKEN', '<token>')") # key 名以前端约定为准(查 utils/auth);有的还需 refreshToken/tenantId nav("http://localhost:4173/<受保护路由>") # 注入后再导航,路由守卫读 token 即放行nav到:4173任一页建立 origin 再 set,空白页 set 不生效);② key 名/附加字段以前端真实约定为准(错名=守卫仍判未登录弹回登录页);③ 仅供只读走查,真链路验收(创建→生成→发布)仍要走真登录路径。
4. admin 走查(审核台,链路②)
- 接 staging:
game-admin/.env.local两行——VITE_BASE_URL=http://<staging>:48080+VITE_APP_WANXIANG_MOCK=false(不关全是假数据,最易漏);chrome 与 admin 同机时 BASE 可 localhost。 - 登录:租户「芋道源码」/ admin / admin123(表单预填,点登录即过,无验证码);
- 审核台:静态路由
/wanxiang/review(登录后侧栏「造梦运营→项目审核」恒在);「通过」=POST /admin-api/project/review {decision:1}→ 同事务 publish; - 验收锚点:UI 队列数变化 + DB 双实证(
game_project.status=4+game_feed_rank新行)。
5.5 mock HTTP 快走查(lili-mac,CDP 前置层,省心逮 80% 接线缺陷)
CDP 全走查重(chrome 生命周期 + 受控输入 + 截图)。前端接新契约(如 U6 studio.ts)先做一层 HTTP 快走查逮「mock 没注册 / vite 中间件没拦 / 字段不贴契约」这类接线缺陷,再上 CDP 验 UI 行为:
npm run build(vue-tsc -b 类型 + vite build)= 类型/编译门;- mock 调度 smoke:
npx tsx直 importsrc/mock/index.ts的dispatch(method,url,body)(DOM-free)断言 create/poll/校验/锁——不起服务器验 mock 逻辑; - mock-over-vite HTTP 快走查:
vite preview --port 4173(VITE_API_BASE 空 → mock=ON,configurePreviewServer也挂中间件)后curl打/app-api/<mod>/*——验整条 HTTP 路径(前端 axios 会走的同一条:vite 中间件→mock handler→契约信封{code,data,msg});异步任务链轮询到终态(status=2 + 回填 versionId)验「无定时器时间式推进」收敛。 - 三层全绿后,UI 行为(Vue 响应式/导航/点击)+ 真实 origin 上下文依赖才上 §3 CDP(权威仍在 mini-desktop 真 origin,localhost 系统性掩盖上下文缺陷见 §1 红线)。
- 禁裸
sleep轮询(harness 拦):用计数for循环 + curl 自身耗时,或 Monitor until-loop。
- 禁裸
口径:HTTP 快走查 = lili-mac 本机省心层(逮接线/契约漂移);权威 UI e2e 仍 = mini-desktop CDP 真 origin。前者绿不等于后者免做(上下文依赖缺陷只在真 origin 现形)。
5.6 让本地 mock 预览渲染【真适配器游戏】(闭合 create→预览可玩闭环)
本地 /create/preview/:versionId 默认走「runtime mock 无 manifestUrl → demo 兜底」,而 demo 兜底用的自研壳正被 T1b(LittleJS+Runner v2)替换 → 本地 load_timeout 玩不了。要让本地预览渲染【真生成物】(E1 适配器产物),把 engineBundle 经静态 manifest 喂给宿主引擎包路:
- 源 = 仓内已提交的九门探针 bundle(
game-runtime/games/_wg1-gen/_adapter-probe/bundle.iife.js,__GameBundle.bootGameHost 入口)。 - 打包 = 脚本(
game-studio/scripts/gen-preview-manifest.mjs)把 bundle 包成 GamePackage(engineBundle内嵌)写public/mock-manifests/9001.json(gitignore,体积大);打印 OUTER_CHECKSUM = sha256(被服务 json 原文)。 - 接线 =
mock/runtime.ts对该 versionId 回填manifestUrl='/mock-manifests/9001.json'+checksum=<OUTER_CHECKSUM>。宿主GamePlayer.resolvePackage→fetchAndVerifyManifest(校 sha256)→ 引擎包路inject.ts独立内联<script>暴露window.__GameBundle→bootGameHost({canvas,seed})→ 真玩 + 发game_loaded(不再超时)。 - 坑:① manifestUrl 必须是 vite 原样服务的静态路径(
public/),不能用/app-api/*(会被 mock 中间件套{code,data,msg}信封,破坏 raw fetch);② checksum 校的是被服务 json 原文的 sha256(非 bundle 自身、非 manifest 内层 checksum),换源 bundle 须重跑脚本同步 runtime.ts 常量;③ srcdocallow-same-origin→ 文档 origin=宿主 origin(非 'null'),已在 bridge 白名单。 - 验:CDP 走查预览页状态
加载失败(load_timeout)→可试玩、.preview-errtip=null、截图见游戏渲染(实证:tap-target Score:0/3 + 3 目标)。
5. 信道探针(回归排查常备)
宿主↔iframe postMessage 断裂用 probe_bridge_channel.py 取证:四锚点 + 判别实验(文档代际/iframe 重建监视/存活代理注入),v-for ref 数组案(host→game 全断)即由它锁凶。修复类波次收口前跑一轮防回归。