qa: HITL deny 那条红在 Playwright 协议错误而非断言——waitForResponse 的 predicate 里读 body 与导航赛跑,被测行为零执行
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 1h 7m
- Merged PRs (30d)
- 969
Description
## 结论
`copilotkit-v2-hitl.spec.ts:109`(deny 那条)本趟红在 **Playwright 协议错误**,不是断言不成立:
```
2) [chat-read] › copilotkit-v2-hitl.spec.ts:109:5 › deny:拒绝技能后任务调整继续,不变成 HITL_REJECTED 失败
Error: page.waitForResponse: response.json: Protocol error (Network.getResponseBody):
No data found for resource with given identifier
Response body is not available for a response that was navigated away from.
Read response.body() before triggering any navigation.
```
判定类型:**spec 竞态(夹具问题)**,产品行为本轮未被验证——
这条 spec 想断言的「拒绝技能后任务调整继续,不变成 HITL_REJECTED 失败」**一次都没跑到**。
按本仓「红 ≠ 跑过」纪律,它今天既不能算通过,也不能算发现了产品缺陷。
## 实测 SHA
`f5c9064ed4304160318968bb618110a4ca1188bb`(`origin/main`),run
[34253214380](https://github.com/boardx/workspacex/actions/runs/34253214380)。
`verify:chat-read` 121 条:112 passed / 3 failed / 6 skipped。
同文件另外四条(`:99` once、`:115` 刷新恢复、`:128` forever,及 `hitl-dialog-dismiss:31`)本趟全绿。
⚠ 在上一个基线 `9de57821e` 上,这条**三趟全绿**
(run 34238087140 / 34238069791 / 34238127530)。**单趟不足以判它是回归还是抖动**,我不下这个结论。
## 根因线索:predicate 里读 body,和导航赛跑
`copilotkit-v2-hitl.spec.ts:37-41` 的 `waitForResponse` **在 predicate 内部** `await response.json()`:
```ts
const pendingResponse = page.waitForResponse(async (response) => {
if (response.request().method() !== "GET" || !/\/agent-runs\/[^/?]+$/.test(...) || !response.ok()) return false;
const body = await response.json(); // ← predicate 里读 body
return body.status === "awaiting_tool_permission" && Boolean(body.pendingApproval?.permissionRequestId);
}, { timeout: 60_000 });
```
predicate 会对**每一个**匹配前置条件的响应求值,也就是对一串轮询响应逐个去拿 body。
只要其中任意一个的 body 还没读完、页面就发生了导航(或该资源被回收),
`Network.getResponseBody` 就抛出上面这条错——错误信息末尾那句
`Read response.body() before triggering any navigation` 说的正是这件事。
这类红的形状是**顺序相关**的:它取决于这条 spec 与同车道其它用例的相对时序,
所以「上个 SHA 三趟全绿、这个 SHA 一趟红」完全可能是同一个既有竞态在不同排布下暴露,
而不是这几个 commit 引入的东西。⇒ 这条也正落在 #3047 收窄之后剩下的那部分里。
## 建议方向(不由我实施)
- **A(推荐)**:predicate 只做**不读 body** 的廉价判断(method / URL / status),
拿到 response 之后再在 predicate 之外 `await response.json()` 做内容判定;
若需要按内容筛选,用 `expect.poll` 主动打权威读接口轮询,而不是靠嗅探被动响应。
- **B**:保留现状但把 body 读取包进 `try/catch` 并在失败时 `return false`——
能止血,但会把「读不到 body」和「内容不匹配」混为一谈,不如 A 干净。
改完按老规矩造反证(构造一次导航打断,确认新写法不再抛协议错、而该红的断言仍会红)。
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in copilotkit-v2-hitl.spec.ts:37-41 and inspect the deny case at line 109. Run verify:chat-read, then validate the response-waiting logic with a navigation interruption and confirm it no longer raises a Playwright protocol error while the intended deny assertion still detects failures.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- playwright, typescript
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100