test(dashboard): wait for saved answer hydration on first open - #5166
Conversation
The standalone answer report renders its heading statically while the saved answer arrives from fetchChatSession. Only the reload path waited for that hydration (#4646b43d4); the first navigation asserted the table immediately after the heading appeared, so a slow mount could fail the shared dashboard-acceptance lane with "The standalone answer lost content". Wait for the hydrated content before asserting it, matching the reload path. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
PR #5166 复审 — exact head ddca0e61599e3a182ec4f2a14bef1dcc2a082ded
动机
PR #5141 的 head 上 dashboard-acceptance 变红,报错为 The standalone answer lost content or executed model HTML(examples/personal-workspace-browser/answer-presentation.mjs:96)。这条 lane 是共享门禁:任何改动触发 core_tests 的 PR 都会跑到它,所以它属于主干级偶发红灯,而不是某个 PR 的改动面。这条 PR 把该红灯修成确定性通过,避免每个 PR 作者都要为同一次误判重跑或人工裁定 flake。
改动思路
AnswerReportPage 的 <h1>完整答复</h1> 是同步渲染的,而正文 .answer-report-content 只有在 state.kind === "ready" 之后才出现,取决于异步的 fetchChatSession(sessionId)。场景原本只等标题可见就立刻断言正文,因此"标题出现"与"正文已水合"之间没有顺序保证;在较慢的 runner 上,SPA mount 之后发起的那次读取可能晚于 networkidle 的静默窗口,断言先于响应返回。#4646b43d4 已经给 reload 路径补过同款等待,这里只是把首次导航这条被遗漏的路径补齐,让两条路径遵循同一条等待纪律。
具体改动
在首次导航到独立答复页、断言正文之前,增加一次有界的可见性等待:
await page.locator(".answer-report-content table").waitFor({ state: "visible", timeout: 15_000 });只改测试的等待顺序,不改产品渲染、不改断言强度:仍然是"表格恰好一个 + 含关键文本 + 无 script 元素 + window.pwned 未置位"四件事同时成立。同时可以排除另一种解释:markdown.tsx 与 answer-report-page.tsx 在 PR base 之后未改动,渲染器直接构造 React 节点、不使用 dangerouslySetInnerHTML,所以失败不可能来自模型 HTML 被当作 HTML 执行;失败分支只会是正文尚未水合。
对主干的风险
风险很低且方向为正向。改动只落在 examples/ 下的验收场景,没有产品代码、没有配置、没有持久化契约变化,回滚只需删掉这一行等待。唯一需要留意的是它把一次"可能侥幸通过"的断言变成"必须真水合"的断言,因此不会再出现内容真正丢失却因时序侥幸通过的情形。本地按 CI 的同一调用方式运行,18/18 场景通过,answer-presentation 单场景重复 8/8 通过。
我的整体评价
这是一个必要且最小的修复:它对准的是真实存在的水合竞态,采用了仓库内已有的、由维护者本人为 reload 路径确立的同一种写法,diff 只有三行并且可独立回滚。它同时保持了验证强度,没有用放宽超时或删除断言来换绿色。建议合入。
English verdict: APPROVE - ddca0e6 removes the first-open hydration race with the same bounded wait the reload path already uses; 18/18 browser scenarios pass locally under the CI invocation and no product behavior or assertion strength changed.
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
PR #5166 复审 — exact head 841c003465cee3d0d1a13d7291a840fed41601e8
动机
PR #5141 的 head 上 dashboard-acceptance 变红,报错为 The standalone answer lost content or executed model HTML(examples/personal-workspace-browser/answer-presentation.mjs:96)。这条 lane 是共享门禁:任何改动触发 core_tests 的 PR 都会跑到它,所以它属于主干级偶发红灯,而不是某个 PR 的改动面。这条 PR 把该红灯修成确定性通过,避免每个 PR 作者都要为同一次误判重跑或人工裁定 flake。
改动思路
AnswerReportPage 的 <h1>完整答复</h1> 是同步渲染的,而正文 .answer-report-content 只有在 state.kind === "ready" 之后才出现,取决于异步的 fetchChatSession(sessionId)。场景原本只等标题可见就立刻断言正文,因此"标题出现"与"正文已水合"之间没有顺序保证;在较慢的 runner 上,SPA mount 之后发起的那次读取可能晚于 networkidle 的静默窗口,断言先于响应返回。#4646b43d4 已经给 reload 路径补过同款等待,这里只是把首次导航这条被遗漏的路径补齐,让两条路径遵循同一条等待纪律。
具体改动
在首次导航到独立答复页、断言正文之前,增加一次有界的可见性等待:
await page.locator(".answer-report-content table").waitFor({ state: "visible", timeout: 15_000 });只改测试的等待顺序,不改产品渲染、不改断言强度:仍然是"表格恰好一个 + 含关键文本 + 无 script 元素 + window.pwned 未置位"四件事同时成立。同时可以排除另一种解释:markdown.tsx 与 answer-report-page.tsx 在 PR base 之后未改动,渲染器直接构造 React 节点、不使用 dangerouslySetInnerHTML,所以失败不可能来自模型 HTML 被当作 HTML 执行;失败分支只会是正文尚未水合。
对主干的风险
风险很低且方向为正向。改动只落在 examples/ 下的验收场景,没有产品代码、没有配置、没有持久化契约变化,回滚只需删掉这一行等待。唯一需要留意的是它把一次"可能侥幸通过"的断言变成"必须真水合"的断言,因此不会再出现内容真正丢失却因时序侥幸通过的情形。本地按 CI 的同一调用方式运行,18/18 场景通过,answer-presentation 单场景重复 8/8 通过。
我的整体评价
这是一个必要且最小的修复:它对准的是真实存在的水合竞态,采用了仓库内已有的、由维护者本人为 reload 路径确立的同一种写法,diff 只有三行并且可独立回滚。它同时保持了验证强度,没有用放宽超时或删除断言来换绿色。建议合入。
本次复审的 head 841c003465cee3d0d1a13d7291a840fed41601e8 相对上一版只并入了最新的 origin/main(56e04a336,把剩余 JSONL 读取器按 LF 分帧),与 base 的差异仍是单文件 +3/-0,被评审的改动没有变化;并分支后按 CI 的同一调用方式复跑,结果仍为 18/18 场景通过。
English verdict: APPROVE - 841c003 removes the first-open hydration race with the same bounded wait the reload path already uses; 18/18 browser scenarios pass locally under the CI invocation and no product behavior or assertion strength changed.
动机
dashboard-acceptance在 PR #5141 的 head(1a1e54329)上失败:该 lane 是共享门禁:任何触发
core_tests的 PR 都会跑到它,所以它是主干上的偶发红灯,而不属于某个 PR 的改动面。根因
AnswerReportPage的标题是静态渲染的:而正文
.answer-report-content只在state.kind === "ready"之后才出现,依赖于异步的fetchChatSession(sessionId)。场景只在标题出现后就立刻断言正文:于是"标题可见"与"正文已水合"之间没有顺序保证。在较慢的 runner 上
networkidle可能早于 SPA mount + fetch 完成,断言先于响应返回,就会报 "lost content"。#4646b43d4已经为 reload 路径补过同款等待,但首次导航这条路径漏了。改动
在首次导航后、断言前,等待已水合的正文,和 reload 路径保持一致:
只改测试的等待纪律,不改产品行为、断言内容或证据强度:仍然是"表格存在 + 含关键文本 + 无 script 元素 +
window.pwned未置位"。验证
node --check examples/personal-workspace-browser/answer-presentation.mjsLOOPX_PERSONAL_WORKSPACE_SCENARIO=answer-presentation node examples/personal-workspace-browser-smoke.mjs:通过(8/8 重复运行)apps/presentation/dashboard下运行):personal-workspace-browser-smoke (development): okmarkdown.tsx与answer-report-page.tsx在 PR base 之后未改动,且渲染器直接构造 React 节点、不使用dangerouslySetInnerHTML,因此失败不可能是模型 HTML 被执行;失败分支只会是正文未水合。