mcpField
V
verify-form-validation

verify-form-validation

github.com/reticlehq测试与质量1.3k62 次安装今天更新
代码仓库

关于 verify-form-validation

这是用于 Reticle 的技能,可在运行中的应用里测试表单验证。它检查应用级错误、真实禁用状态、无效提交是否发送请求,以及修正值后的恢复情况。

主要功能

  1. 01区分应用错误与浏览器原生验证
  2. 02检查提交控件的实际禁用状态
  3. 03等待页面稳定后再检查请求数
  4. 04验证有效输入会清除错误并启用提交
  5. 05按受测字段和值报告结果

适用场景

  1. 01测试无效字段值及其错误提示
  2. 02发现仅用 CSS 呈现禁用状态的提交控件
  3. 03检查无效提交是否未发送匹配请求
  4. 04验证修正后提交控件重新可用

MCP 相关

此技能使用 Reticle 工具与运行中的应用交互并断言行为;输入未说明它提供 MCP 服务。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/reticlehq/reticle --skill verify-form-validation

安装后在 Claude Code 中输入/verify-form-validation确认已加载。

详细介绍

屏幕上看起来正常的表单,提交时却被拒绝

验证代码只写一次,在浏览器里用一个明显无效的值匆匆看一眼,此后就再也不测试了。最终上线的 bug,往往不是你试过的那个值,而是你没试过的情况:字段失去焦点前提交按钮一直未禁用、按钮只是通过 CSS 显示为禁用却仍然可以点击,或者检查尚未完成,处理程序就已发出请求。

Reticle 可以驱动真实表单,并一次检查这三种故障情况。还没设置?请按照 reticle 技能操作:它会安装 Reticle,在 init 修改应用文件前询问用户,并验证一个工作流。

开始前请先阅读:浏览器自身的验证可能会掩盖应用的问题

required、pattern、type="email" 等属性会让浏览器在约束验证提示气泡处拦截操作,应用的 JS 根本没有运行。如果你断言的是浏览器原生提示,就验证了浏览器,而不是你应该测试的代码。请确认你检查的错误来自应用自己的元素(testid、角色或渲染出的字符串),而不是 :invalid 伪状态,然后再相信测试结果。如果表单只依赖原生验证,背后没有应用层检查,那就是发现的问题:如实说明,不要用一个碰巧能通过的谓词来掩盖。

触发拒绝

和任何 Reticle 驱动操作一样,先说明操作的后果:

reticle_look({ action: "page", sessionId, mode: "interactive" })   // get refs for the field and submit control

reticle_act_and_wait({ sessionId, ref, action: "fill", args: { value: "<invalid>" }, until: { kind: "allOf", predicates: [
  { kind: "element", query: { testid: "field-error" } },
  { kind: "element", query: { role: "button", name: "Submit" }, state: "disabled" },
]}})

如果检查是在失去焦点时运行,而不是在按键时运行,请在断言前单独执行一步,将焦点移到其他控件:对提交按钮或下一个字段调用 reticle_act({ sessionId, ref: otherRef, action: "focus" }),因为只有聚焦到其他控件才会触发你需要的失焦事件。如果检查有防抖,则不要等待固定时长。使用 reticle_run({ tool: "reticle_clock", args: { sessionId, advanceMs } }) 精确推进到防抖窗口之后,方法与 test-error-states 中相同。固定休眠在你的机器上可能通过,但在 CI 中会偶发失败。

证明没有发出请求

按钮看起来已禁用,不等于请求没有发出。不要只相信你刚才断言过的 disabled 状态。试着照常提交,以便发现仅靠 CSS 禁用、实际仍可点击的控件,而不是让它蒙混过关:

reticle_act({ sessionId, ref: submitRef, action: "click" })

然后断言请求没有发出。点击后立即检查 count: 0,本身证明不了什么:请求可能只是_还没_发出。给应用留出真正尝试发出请求的时间,再相信请求不存在;先等待应用稳定,再检查计数,并在一次调用中完成:

reticle_assert({ sessionId, since, timeout_ms: 3000, predicate: { kind: "allOf", predicates: [
  { kind: "settled" },
  { kind: "net", method: "POST", urlContains: "/api/...", count: 0 },
]}})

timeout_ms 的作用是让检查等待,而不是只读取一次快照:reticle_assert 默认使用 timeout_ms: 0,也就是只在调用时检查一次。因此,如果不设置该参数,点击后只会检查一次 settled,无法证明应用确实处于空闲状态。设置 timeout_ms: 3000 后,调用会持续轮询,最长等待三秒,直到页面确实安静下来。只有在页面稳定后,count: 0 的结果才有意义。since 应使用点击操作自身的结果,而不是之前填写字段的结果,这样范围才仅包括提交尝试之后的请求。

清除错误

更正输入值,并确认拒绝是有条件的,而不是永久性的:

reticle_act_and_wait({ sessionId, ref, action: "fill", args: { value: "<valid>" }, until: { kind: "allOf", predicates: [
  { kind: "element", query: { testid: "field-error" }, absent: true },
  { kind: "element", query: { role: "button", name: "Submit" }, state: "enabled" },
]}})

表单一旦拒绝过一次就再也不会重新启用,这是披着第一个 bug 外衣的第二个 bug。

断言内容

只有 verified: "yes" 才算通过。"no" 是真实的问题,"unknown" 表示 Reticle 无法判断,需要更好的捕获结果,而 "no-fault" 表示没有证伪任何内容,但也没有明确确认任何内容。这三种结果都不能作为表单会拒绝无效输入的证据。

  1. 错误提示来自应用本身,而不是浏览器的原生提示气泡。
  2. 提交控件的 disabled 状态确实生效,而不是仅通过 opacity/cursor 样式伪装成不可用。这与 design-system-compliance 所区分的“看起来已禁用”和“实际已禁用”相同。
  3. 没有触发任何匹配请求,这是经过计数得出的,而不是根据“没有抛出错误”推断的。
  4. 拒绝行为是有条件的:输入有效值后,错误会消失,提交按钮会重新启用。

如实说明

这里的判定针对的是这个字段和这个无效值,而不是整个表单。必填项检查无法说明另一个字段是否通过了格式检查。如果你只操作了一个输入框,请指出是哪一个,并说明表单其余部分尚未验证,不要让单个通过结果被误解为“表单会验证”。


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

评论

支持 Markdown