For AI agents: the complete documentation index is available at /next/zh/llms.txt, the full documentation bundle is available at /next/zh/llms-full.txt, and this page is available as Markdown at /next/zh/ai/agent-lynx/debugging.md.
AI logo
AI
  • 简体中文
  • 日志与运行时调试

    当页面检查发现结果不符合预期时,agent-lynx 可以将页面现象与日志、已加载的脚本和 JavaScript 实时值对应起来,确认代码执行到哪里、收到了什么运行时输入。

    从点击无响应追到运行时错误

    沿用页面检查中的 Tailwind CSS Settings 示例,这次点击 Ocean 后,主题没有切换。为了复现这个故障,我们在本地副本的点击回调中加入了对缺失配置的读取;原示例没有这处错误。

    下面的命令与输出采集自 Android 上的 Lynx Explorer。agent 已经连接应用并选定页面,$CLIENT_ID 是 list-clients 返回的目标应用 ID,本次页面的 session 为 2。

    示例对话6 次工具调用
    你

    点了 Ocean,但主题没有切换,帮我查一下原因。

    Agent

    思路:先找到 Ocean 按钮,启动错误日志监听,再点击一次复现问题。

    agent-lynx snapshot找到 Ocean 按钮
    调用
    # 读取当前页面的元素引用;这里节选主题区域,Ocean 的按钮容器是 @e15。
    agent-lynx snapshot --client "$CLIENT_ID" --session 2
    输出
    @e1 [scroll-view] @(207,450) {scrollable}
    └─ @e2 [view] @(207,460)
       ├─ @e3 [view] @(207,70)
       │  ├─ @e4 [text] "Settings" @(207,59)
       │  └─ @e5 [text] "Manage your account and preferences." @(207,86)
       ├─ @e6 [view] @(207,177)
       │  ├─ @e7 [view] @(207,153)
       │  │  ├─ @e8 [text] "Theme" @(207,143)
       │  │  └─ @e9 [text] "Switch design tokens at runtime via CSS variables." @(207,167)
       │  └─ @e10 [view] @(207,207)
       │     ├─ @e11 [view] @(91,206)
       │     │  └─ @e12 [text] "Dark" @(91,206)
       │     ├─ @e13 [view] @(207,206)
       │     │  └─ @e14 [text] "Light" @(208,206)
       │     └─ @e15 [view] @(324,206)
       │        └─ @e16 [text] "Ocean" @(323,206)
    agent-lynx tap @e15已复现点击
    调用
    # 在日志监听期间点击当前快照中的按钮,让同一次操作的错误进入采集窗口。
    agent-lynx tap @e15 --client "$CLIENT_ID" --session 2
    输出
    Tapped @e15 at (324,206). Re-run `agent-lynx snapshot` before using refs again.

    思路:后台 VM 抛出了读取 ocean 的 TypeError。调用栈指向 background.js,先找到这份运行中脚本的 ID。

    agent-lynx get-sources匹配到脚本 5
    调用
    # 列出当前后台 VM 的脚本。app-service.js 也在列表中;本次错误对应的是 .lynx/main/background.js。
    agent-lynx get-sources --client "$CLIENT_ID" --session 2
    输出
    [
      {
        "scriptId": "1",
        "url": "file://shared/lynx_core.js"
      },
      {
        "scriptId": "4",
        "url": "file://view3/app-service.js"
      },
      {
        "scriptId": "5",
        "url": "file://view3/.lynx/main/background.js"
      }
    ]
    agent-lynx cdp --method Debugger.getScriptSource找到出错的回调
    调用
    # 读取脚本 5 的真实代码,再用 jq 提取 scriptSource 的第 484–491 行,查看调用栈附近的点击回调。
    agent-lynx cdp --thread background --method Debugger.getScriptSource '{"scriptId":"5"}' --client "$CLIENT_ID" --session 2 | jq -r '.scriptSource | split("\n")[483:491][]'
    输出
            $0: themeNames.map((t)=>/*#__PURE__*/ (0,_lynx_js_react_jsx_dev_runtime__rspack_import_0.jsxDEV)(__snapshot_835da_abf5c_2, {
                    values: [
                        `flex-1 rounded-md py-2 items-center justify-center border ${currentTheme === t ? "bg-primary border-primary" : "bg-secondary border-border"}`,
                        ()=>setCurrentTheme(lynx.__globalProps.themeNames[t]),
                        `text-sm font-medium ${currentTheme === t ? "text-primary-foreground" : "text-secondary-foreground"}`
                    ],
                    $0: t[0].toUpperCase() + t.slice(1)
                }, t, false, {

    思路:回调在调用 setCurrentTheme 前先读取 lynx.__globalProps.themeNames[t]。检查当前后台 VM 中的 themeNames 是否存在。

    点击触发了事件回调,但回调在读取 lynx.__globalProps.themeNames[t] 时抛出了 TypeError。当前 themeNames 是 undefined,读取它的 ocean 属性失败,因此 setCurrentTheme 没有执行。

    这个示例已经通过遍历得到主题名 t,应恢复为 setCurrentTheme(t)。如果业务确实需要宿主配置,则需要先约定并传入该字段,再处理缺失时的行为。

    日志将“没有切换”定位到后台 VM 的异常,脚本源码说明了哪次属性读取失败,evaluate 则验证了当前值。脚本列表中可见 app-service.js,而本次调用栈指向 .lynx/main/background.js;应按实际返回的 URL 和脚本 ID 继续查找。

    get-console 订阅运行时日志,不保证保留历史记录。这段对话先启动 --watch,在监听期间点击按钮,并在收到两条错误后结束采集。一次短窗口没有输出,不能说明页面从未报错。

    从页面元素定位 JSX 和事件处理函数

    调用栈给出了运行中脚本的位置,UI source map 则能把页面上的元素对应到编写它的 JSX。继续查看 Ocean 按钮,就能把上一段中的错误回调落到本地源文件上。

    这段示例在支持 DOM.getOriginalNodeSourceInfo 的开发版客户端中运行同一份 bundle,构建时启用了 UI source map。$CLIENT_ID 指向这个客户端,本次页面的 session 为 1。

    示例对话3 次工具调用
    你

    Ocean 按钮对应哪段 JSX?把它的点击处理函数也找出来。

    Agent

    思路:重新读取页面快照,选中 Ocean 的按钮容器,再把这个元素映射到源码。

    agent-lynx snapshot找到 Ocean 按钮
    调用
    # 刷新当前页面的引用;这里节选主题区域,使用包含 Ocean 文本的 @e15 容器。
    agent-lynx snapshot --client "$CLIENT_ID" --session 1
    输出
    @e1 [scroll-view] @(207,450) {scrollable}
    └─ @e2 [view] @(207,460)
       ├─ @e3 [view] @(207,70)
       │  ├─ @e4 [text] "Settings" @(207,59)
       │  └─ @e5 [text] "Manage your account and preferences." @(207,86)
       ├─ @e6 [view] @(207,177)
       │  ├─ @e7 [view] @(207,153)
       │  │  ├─ @e8 [text] "Theme" @(207,143)
       │  │  └─ @e9 [text] "Switch design tokens at runtime via CSS variables." @(207,167)
       │  └─ @e10 [view] @(207,207)
       │     ├─ @e11 [view] @(91,206)
       │     │  └─ @e12 [text] "Dark" @(91,206)
       │     ├─ @e13 [view] @(207,206)
       │     │  └─ @e14 [text] "Light" @(208,206)
       │     └─ @e15 [view] @(324,206)
       │        └─ @e16 [text] "Ocean" @(323,206)

    思路:映射指向 App.tsx 第 62 行的 view。读取附近的 JSX,查看这个节点绑定的事件处理函数。

    Read · examples/tailwindcss/src/App.tsx找到 bindtap
    调用
    // 读取本地示例副本中的第 61–80 行;第 69 行是本次复现加入的出错回调。
    Read examples/tailwindcss/src/App.tsx:61–80
    输出
        61	            {themeNames.map((t) => (
        62	              <view
        63	                key={t}
        64	                className={`flex-1 rounded-md py-2 items-center justify-center border ${
        65	                  currentTheme === t
        66	                    ? "bg-primary border-primary"
        67	                    : "bg-secondary border-border"
        68	                }`}
        69	                bindtap={() => setCurrentTheme(lynx.__globalProps.themeNames[t])}
        70	              >
        71	                <text
        72	                  className={`text-sm font-medium ${
        73	                    currentTheme === t
        74	                      ? "text-primary-foreground"
        75	                      : "text-secondary-foreground"
        76	                  }`}
        77	                >
        78	                  {t[0].toUpperCase() + t.slice(1)}
        79	                </text>
        80	              </view>

    Ocean 按钮对应 examples/tailwindcss/src/App.tsx 第 62 行、第 15 列的 view,它由 themeNames.map 创建。第 69 行的 bindtap 绑定了点击处理函数,正是前面读取缺失配置的那段代码。

    get-source-info 返回元素的源码位置,agent 再读取对应文件,才能看到事件绑定和业务逻辑。mapped 表示映射成功;运行时生成的包装节点或缺少元数据的页面可能无法得到源码位置。

    在同一个目标上串联证据

    同一个 session 可以包含 background 和 main 两个 VM。示例日志标为 background,因此后续的脚本请求和表达式求值也指向后台 VM。get-sources 列出的是后台 VM 中的脚本。

    脚本 ID 和远程对象 ID 都属于对应的 client、session 和 VM。例如,日志中包含 objectId 时,可以通过 cdp 的 Runtime.getProperties 在同一目标上读取对象属性。页面重载或切换后,应重新获取日志和脚本 ID 再继续排查。

    日志与运行时调试能力

    能力支持的操作与用途
    读取与跟踪日志get-console 读取日志级别、VM 线程和可用的调用栈;watch 模式可以在复现问题时持续跟踪新日志。
    发现运行中的脚本get-sources 列出后台 VM 中的脚本 URL 和 ID;通过 cdp 的 Debugger.getScriptSource 读取脚本的运行时代码。
    定位元素源码页面提供 UI source-map 元数据时,get-source-info 可以将新快照中的元素引用映射到源码位置。
    检查 JavaScript 实时值evaluate 在后台或主线程 VM 中执行表达式,可读取实时值、等待 Promise,或返回远程对象供进一步检查。
    打开图形化检查器inspect 输出检查器 URL,在图形界面中检查选定的 client 和 session。
    发送页面协议请求cdp 调用 Lynx CDP 实现支持的方法,包括读取脚本源码、检查运行时对象等。
    发送宿主协议请求app 向选定的 client 发送 App 协议请求,可用方法和参数取决于宿主应用。

    日志过滤、输出格式、VM 选择和表达式求值参数见 CLI 参考。需要继续检查组件 props、state 和层级关系时,参见 ReactLynx 组件。

    除非另有说明,本项目采用知识共享署名 4.0 国际许可协议进行许可,代码示例采用 Apache License 2.0 许可协议进行许可。