关于 verify-optimistic-update
这是一个用于 Reticle 的技能,通过延迟并失败保存请求,检查乐观更新是否回滚、错误提示是否出现,以及重试后的数据是否持久保存。适用于在 Reticle 可观测的浏览器会话中验证 Web 应用行为。
主要功能
- 01延迟并失败保存请求
- 02验证乐观界面状态
- 03检查回滚和失败提示
- 04重试并验证数据持久性
适用场景
- 01测试待办事项编辑失败
- 02确认保存失败后的错误提示
- 03检查重载后的已保存更改
MCP 相关
该技能使用 Reticle 浏览器会话工具操作运行中的应用并进行断言;输入未说明其 MCP 暴露方式。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ reticlehq/ reticle --skill verify-optimistic-update安装后在 Claude Code 中输入/verify-optimistic-update确认已加载。
详细介绍
乐观更新是一项服务器可以反悔的承诺
在服务器响应之前更新 UI,会让应用感觉更快。但这也意味着 UI 声称了一件服务器尚未认可的事。请求失败、UI 不得不撤回这一声称的失败路径,是没人会实际点通的路径,因为只要后端正常运行,这种情况就不会发生。
Reticle 可以让它在运行中的应用里发生。尚未安装?运行 RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init,然后使用 install-and-verify 技能。
开始前请先阅读:要让请求失败,需要使用 Reticle 控制的浏览器
reticle_network_mock 可在租用的标签页(reticle_run({ tool: "reticle_lease", args: { action: "acquire", url } }))或受控标签页中使用,但不能通过常驻 SDK 使用;后者会返回 { ok: false, reason: "no-cdp-provider" }。如果没有这样的标签页,请执行第 4 步(成功路径),并将回滚情况报告为未知。你绝不能只跑通顺利路径就报告回滚正常。
先确认会话可以观察页面:reticle_session({ action: "list" })。隐藏或受限流的标签页可能会接受点击,却不渲染任何内容。
1. 让保存缓慢失败
reticle_run({ tool: "reticle_network_mock", sessionId, args: {
mocks: [{ urlContains: "/api/todos", method: "PATCH", status: 500, delayMs: 1500 }],
}})
延迟能让乐观状态变得可观察。没有延迟的话,失败可能在任何内容显示之前就发生,你就无法区分“已回滚”和“从未更新”。
2. 执行操作,并证明乐观状态出现
使用 reticle_look({ action: "page", sessionId, mode: "interactive" }) 获取控件的 ref,然后在点击之前描述乐观状态:
reticle_act_and_wait({ sessionId, ref: "<rename or toggle>", action: "click", until:
{ kind: "text", contains: "Buy oat milk" }
})
这能证明 UI 在服务器响应之前就已更新。下一步要检查的正是这一声称是否被撤回。
3. 证明状态已回退,并说明原因
请求失败后,新值必须消失,旧值必须恢复,并且要告知用户。描述应用实际显示的失败消息,并将范围限定在其提示框内,因为页面上任何其他提示框都能满足不加限定的角色检查:
reticle_assert({ sessionId, timeout_ms: 5000, predicate: { kind: "allOf", predicates: [
{ kind: "net", urlContains: "/api/todos", method: "PATCH", status: 500 },
{ kind: "text", contains: "Buy oat milk", absent: true },
{ kind: "text", contains: "Buy milk" },
{ kind: "text", contains: "Could not save", scope: "[role=alert]" },
{ kind: "console", level: "error", absent: true },
]}})
如果应用将状态保存在已注册的存储中,也要检查该存储,因为 UI 可能在屏幕上回滚,而存储中仍保留乐观值:
reticle_assert({ sessionId, predicate: { kind: "state", path: "todos.0.title", equals: "Buy milk" } })
4. 移除模拟,并证明成功状态得以保留
清除模拟,然后从回滚后的状态再次进行相同的更改。重新渲染后,第 2 步的 ref 可能已过时;如果是重命名,还需要重新输入新值,因此请先读取页面:
reticle_run({ tool: "reticle_network_mock", sessionId, args: { clear: true } })
reticle_look({ action: "page", sessionId, mode: "interactive" })
重新执行编辑所需的操作(重命名时,用新值对字段执行 reticle_act),然后保存并描述结果:
reticle_act_and_wait({ sessionId, ref: "<save or toggle>", action: "click", until: { kind: "allOf", predicates: [
{ kind: "net", urlContains: "/api/todos", method: "PATCH", ok: true },
{ kind: "text", contains: "Buy oat milk" },
]}})
接着重新加载页面并证明服务器保留了更改,这才是“已保存”的含义。使用真正的重新加载并等待新页面,不要导航到当前已在访问的 URL:
reticle_navigate({ sessionId, reload: true })
它必须返回带有 sessionId 的 confirmed: true。在该会话上断言;它就是重新加载后的页面:
reticle_assert({ sessionId: "<sessionId from the reload>", predicate: { kind: "text", contains: "Buy oat milk" } })
如果无法确认重新加载,请将持久化情况报告为未知。
如实报告
只有 verified: "yes" 才算通过。在模拟条件下得出的结论只适用于模拟条件,因此请说明你让哪个端点失败,以及使用了什么状态码。交接前清除所有模拟,否则下一个会话遇到的后端会导致每次保存都失败。
能力参考:curl https://docs.reticle.sh/capabilities.md。其他所有内容:curl https://docs.reticle.sh/llms.txt。