test-error-states

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

The error state has never run

从未触发过的错误状态

Every app has a
catch
block nobody has executed and an empty state nobody has seen. They are written from imagination, shipped untested, and discovered by a user on a bad day.
Reticle can force those conditions in the running app. Not installed?
RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init
, then the
install-and-verify
skill.
每个应用都存在从未被执行过的
catch
代码块,以及从未有人见过的空状态。这些逻辑仅凭想象编写,未经测试就上线,最终会在用户倒霉的那天暴露问题。
Reticle可以在运行中的应用里触发这些场景。还未安装?执行
RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init
,然后使用
install-and-verify
技能。

Read this before you start: network mocking needs a driven or leased browser

开始前须知:网络模拟需要受控或租赁的浏览器

reticle_network_mock
intercepts requests in a browser Reticle owns, and the always-on SDK cannot do it. A connected tab with neither
reticle drive
nor a lease still returns
{ ok: false, reason: "no-cdp-provider" }
.
Your route is a leased Playwright tab (
reticle_lease acquire
) or
RETICLE_CDP_URL
pointed at a Chrome started with remote debugging:
bash
undefined
reticle_network_mock
会在Reticle控制的浏览器中拦截请求,而始终在线的SDK无法实现这一功能。没有
reticle drive
或租赁连接的标签页会返回
{ ok: false, reason: "no-cdp-provider" }
你的路由需要是租赁的Playwright标签页(
reticle_lease acquire
),或者将
RETICLE_CDP_URL
指向开启远程调试的Chrome:
bash
undefined

macOS — the user runs this once, in their own Chrome

macOS — the user runs this once, in their own Chrome

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
RETICLE_CDP_URL=http://localhost:9222 npx @reticlehq/server@latest mcp

If you have neither a lease nor a driven browser, **say so in one line and offer the clock half of this skill anyway**: `reticle_clock` needs none of it. What you may not do is drive the happy path, watch it pass, and report that error handling works.
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
RETICLE_CDP_URL=http://localhost:9222 npx @reticlehq/server@latest mcp

如果既没有租赁浏览器也没有受控浏览器,**请用一句话说明,并仍可使用本技能的时钟控制功能**:`reticle_clock`无需依赖这些条件。绝对不能只走正常流程、看到测试通过就断言错误处理逻辑有效。

Force a failure

触发失败场景

reticle_run({ tool: "reticle_network_mock", sessionId, args: {
  mocks: [{ urlContains: "/api/deploys", status: 500 }],
}})
Then drive the flow and name the recovery you expect before you act:
reticle_act_and_wait({ sessionId, ref, action: "click", until: { kind: "allOf", predicates: [
  { kind: "element", query: { testid: "error-banner" } },
  { kind: "console", level: "error", absent: true },
]}})
Note the second predicate. A UI that "handles" an error by logging an uncaught exception has not handled it. Clear mocks with
{ clear: true }
when you are done, or every later check runs against a lie.
Worth forcing, in rough order of how often they are broken:
500
, a
4xx
with a real error body, an empty
200
(
[]
, the empty state), a malformed payload, and a request that never resolves (the spinner that spins forever).
reticle_run({ tool: "reticle_network_mock", sessionId, args: {
  mocks: [{ urlContains: "/api/deploys", status: 500 }],
}})
然后驱动流程,并在操作前明确预期的恢复行为
reticle_act_and_wait({ sessionId, ref, action: "click", until: { kind: "allOf", predicates: [
  { kind: "element", query: { testid: "error-banner" } },
  { kind: "console", level: "error", absent: true },
]}})
注意第二个断言条件。如果UI通过记录未捕获异常来“处理”错误,那并不算真正的处理。完成测试后用
{ clear: true }
清除模拟,否则后续所有检查都会基于错误的环境。
按故障出现频率从高到低,值得触发的场景包括:
500
错误、带真实错误体的
4xx
错误、返回空数组
[]
200
响应(空状态)、格式错误的负载,以及永远无法解析的请求(无限加载的 spinner)。

Skip time instead of sleeping

跳过时间而非等待

reticle_clock({ sessionId, freeze: true })
reticle_clock({ sessionId, advanceMs: 5000 })
reticle_clock({ sessionId, reset: true })
Toasts that auto-dismiss, debounced search, polling, session timeouts, retry backoff. All of these are normally verified by sleeping, which is slow and flaky in equal measure. A timing assertion is a statement about the machine, so it passes on your laptop and fails in CI.
Freeze, advance by exactly the interval, assert the consequence. Same result on a fast laptop and a loaded runner. Always
reset
when you finish
, or a frozen clock silently breaks everything that runs after you.
reticle_clock({ sessionId, freeze: true })
reticle_clock({ sessionId, advanceMs: 5000 })
reticle_clock({ sessionId, reset: true })
自动消失的提示框、防抖搜索、轮询、会话超时、重试退避。这些功能通常通过等待来验证,但这种方式既缓慢又不稳定。时间断言是针对机器的逻辑,在你的笔记本上能通过,在CI环境中可能失败。
冻结时钟、精确推进指定时长、断言结果。无论是在高性能笔记本还是负载较高的运行器上,结果都一致。测试结束后务必执行
reset
,否则冻结的时钟会悄无声息地破坏后续所有操作。

What to assert

需要验证的内容

The recovery, not the absence of a crash:
  1. The error is shown to the user: a specific element, not just "the page did not blank".
  2. The app stayed usable: retry works, the form still has its input, navigation is not stuck.
  3. State is honest:
    reticle_state
    shows the failure, not a half-applied optimistic update. A UI that rolled back visually while the store kept the optimistic value is the classic bug here, and only the store read finds it.
  4. No uncaught error in the console.
要验证恢复行为,而非仅验证未崩溃:
  1. 错误已展示给用户:要有明确的元素,不能只看“页面没空白”。
  2. 应用仍可正常使用:重试功能有效、表单仍保留输入内容、导航未卡住。
  3. 状态真实可靠
    reticle_state
    显示失败状态,而非半应用的乐观更新。这类经典Bug表现为UI已回滚但存储仍保留乐观值,只有读取存储才能发现。
  4. 控制台无未捕获错误

Honesty

注意事项

Mocking changes the app's world, so a verdict taken under a mock is a statement about the mocked condition and nothing else. Say which mock was active when you report a pass, and clear every mock and reset the clock before handing back. An audit that leaves a
500
pinned on
/api/deploys
breaks the next person's session and looks like a real outage.

Capability reference:
curl https://docs.reticle.sh/capabilities.md
. Everything else:
curl https://docs.reticle.sh/llms.txt
.
模拟会改变应用的运行环境,因此基于模拟得出的结论仅适用于该模拟场景。报告测试通过时需说明使用了哪种模拟,并且在结束后要清理所有模拟并重置时钟。如果测试后仍保留针对
/api/deploys
500
错误模拟,会破坏下一位使用者的会话,看起来像是真实的故障。

能力参考:
curl https://docs.reticle.sh/capabilities.md
。其他内容:
curl https://docs.reticle.sh/llms.txt