# 真 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-17 `4e486f60`),不再「走 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 实证,逐个血泪) 1. root 运行必须 `--no-sandbox`; 2. **`--no-zygote` 必加**(否则 `Failed global descriptor lookup` → 渲染进程崩 → 空 DOM); 3. WS 握手 403 → 启动加 `--remote-allow-origins=*`,或 websocket-client 用 `suppress_origin=True`; 4. Chrome 111+ 建 target 用 **`PUT /json/new?url`**(GET 不行); 5. **ssh 直启 chrome 必死**(继承 ssh 会话 fd,断连即杀,nohup/setsid 救不全)——正解=整个流程写成 mini 上的脚本,脚本内 `exec >/tmp/walk_out.txt 2>&1` 重定向自身输出,`nohup bash run_walk.sh &` 启动即返回,远端 until-loop 轮询输出文件; 6. 单文件传 mini:`cat local | ssh mini-desktop 'cat >/tmp/x'`; 7. 轮询禁 `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(如免短信登录端点 / `test1` token),再用 CDP 在**目标 origin 下**注入 localStorage 后再导航: ```python evaljs("localStorage.setItem('ACCESS_TOKEN', '')") # key 名以前端约定为准(查 utils/auth);有的还需 refreshToken/tenantId nav("http://localhost:4173/<受保护路由>") # 注入后再导航,路由守卫读 token 即放行 ``` 坑:① **必须在同 origin 已开的 target 里 setItem**(localStorage 按 origin 隔离,先 `nav` 到 `:4173` 任一页建立 origin 再 set,空白页 set 不生效);② key 名/附加字段以前端真实约定为准(错名=守卫仍判未登录弹回登录页);③ 仅供**只读走查**,真链路验收(创建→生成→发布)仍要走真登录路径。 ## 4. admin 走查(审核台,链路②) - 接 staging:`game-admin/.env.local` 两行——`VITE_BASE_URL=http://: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 行为: 1. `npm run build`(vue-tsc -b 类型 + vite build)= 类型/编译门; 2. mock 调度 smoke:`npx tsx` 直 import `src/mock/index.ts` 的 `dispatch(method,url,body)`(DOM-free)断言 create/poll/校验/锁——**不起服务器**验 mock 逻辑; 3. **mock-over-vite HTTP 快走查**:`vite preview --port 4173`(VITE_API_BASE 空 → mock=ON,`configurePreviewServer` 也挂中间件)后 `curl` 打 `/app-api//*`——验**整条 HTTP 路径**(前端 axios 会走的同一条:vite 中间件→mock handler→契约信封 `{code,data,msg}`);异步任务链轮询到终态(status=2 + 回填 versionId)验「无定时器时间式推进」收敛。 4. 三层全绿后,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 喂给宿主引擎包路: 1. **源** = 仓内已提交的九门探针 bundle(`game-runtime/games/_wg1-gen/_adapter-probe/bundle.iife.js`,__GameBundle.bootGameHost 入口)。 2. **打包** = 脚本(`game-studio/scripts/gen-preview-manifest.mjs`)把 bundle 包成 GamePackage(`engineBundle` 内嵌)写 `public/mock-manifests/9001.json`(gitignore,体积大);打印 **OUTER_CHECKSUM = sha256(被服务 json 原文)**。 3. **接线** = `mock/runtime.ts` 对该 versionId 回填 `manifestUrl='/mock-manifests/9001.json'` + `checksum=`。宿主 `GamePlayer.resolvePackage`→`fetchAndVerifyManifest`(校 sha256)→ 引擎包路 `inject.ts` 独立内联 `