mcpField
V
verify-pagination

verify-pagination

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

关于 verify-pagination

这是一个用于 Reticle 的技能,可根据运行中 Web 应用发出的请求测试分页和无限滚动列表。它比较不同页面的行,并检查列表末尾是否停止加载。

主要功能

  1. 01记录所有可见行
  2. 02检查页面切换或追加
  3. 03检测重复行
  4. 04验证列表末尾行为

适用场景

  1. 01测试数字分页
  2. 02检查“加载更多”列表
  3. 03验证无限滚动边界

MCP 相关

该技能使用 Reticle 浏览器会话工具操作运行中的应用并进行断言;输入未说明其 MCP 暴露方式。

安装

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

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

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

详细介绍

列表的问题会在第二页暴露

第一页能正常渲染,只能证明第一次请求有效。到了第二页,偏移量可能会差一位、游标可能被重复使用、相同的行可能再次出现,或者追加操作会替换已有内容。到了最后一页,问题则可能是「下一页」按钮仍然会发起请求,去获取并不存在的行。

Reticle 可以在运行中的应用里,根据页面实际发出的请求检查上述所有问题。尚未安装?运行 RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init,然后使用 install-and-verify 技能。

确认会话能够观察页面

reticle_session({ action: "list" })

隐藏或受限流的标签页可能会接受点击,却不渲染任何内容;而通过交叉观察器加载的无限滚动列表,可能根本不会触发加载。如果会话状态不正常,请换用正常的上下文,例如租用一个标签页(reticle_run({ tool: "reticle_lease", args: { action: "acquire", url } }))。否则,应将本次运行报告为受阻,而不是通过。

1. 记录第一页

在操作之前记下屏幕上显示的内容,否则就无法与下一页进行比较:

reticle_look({ action: "find", sessionId, by: "role", value: "row" })

使用实际代表这些行的元素(row、listitem、article 或 testid)。记录每一行的可见文本,而不是抽样:第一页中间重复一行,与顶部重复一行同样是 bug。如果结果提示内容已截断,请将查找范围限定在列表内(scope: "<the list's ref>"),直到结果不再截断;否则,说明哪些行无法查看。

2. 加载下一页,并先说明预期结果

先说明点击后必须发生什么,而不是事后再说。对于数字分页或上一页/下一页分页,页面内容会被替换,因此新请求返回后,第一页的行必须消失:

reticle_act_and_wait({ sessionId, ref: "<next control>", action: "click", until: { kind: "allOf", predicates: [
  { kind: "net", urlContains: "/api/items", ok: true },
  { kind: "text", contains: "<first row of page 1>", absent: true },
  { kind: "text", contains: "<last row of page 1>", absent: true },
]}})

对于**「加载更多」或无限滚动**,页面内容会追加,因此必须保留第一页的内容,并且请求必须已经返回。使用实际触发加载的操作:点击「加载更多」按钮时使用 click;滚动时加载的列表则对列表末尾使用 scrollIntoView。

reticle_act_and_wait({ sessionId, ref: "<load more button>", action: "click", until: { kind: "allOf", predicates: [
  { kind: "net", urlContains: "/api/items", ok: true },
  { kind: "text", contains: "<first row of page 1>" },
]}})

如果列表在客户端分页(所有行都在一个响应中返回,页面只是从中截取部分内容),就不会发出请求,而 net 子句会一直等待一个永远不会到来的请求。移除它,保留文本子句,并在报告中说明未验证服务端分页。

urlContains 必须指定列表自身的端点。任何请求都能满足的谓词无法证明这个列表的行为。

3. 确认行都是新的,且没有重复

再次读取这些行:

reticle_look({ action: "find", sessionId, by: "role", value: "row" })

将文本与第 1 步记录的内容进行比较:

  • 分页替换:第 2 页中的任何一行都不能与第 1 页的行相同。列表中任何位置出现重叠,都意味着偏移量或游标没有前移。
  • 追加:第 1 页的每一行都应出现一次,且新行应跟在它们后面。第 1 页的某行现在出现两次,意味着批次被追加了两次,或数据被重新获取。

这项比较是你读取后得出的证据,因此要据此给出结论。选取一行仅出现在新页面中的内容,并断言它确实出现:

reticle_assert({ sessionId, predicate: { kind: "text", contains: "<a row only on the new page>" } })

如果列表内容重叠,就说明你找到了 bug。报告哪些行重复出现,不要报告通过。

4. 确认列表末尾处理正确

前往最后一页(或继续滚动,直到列表不再增长),并确认相关控件已关闭:

reticle_assert({ sessionId, predicate: { kind: "anyOf", predicates: [
  { kind: "element", query: { role: "button", name: "Next" }, state: "disabled" },
  { kind: "element", query: { role: "button", name: "Next" }, absent: true },
]}})

对于无限滚动,再次滚动到末尾,然后等页面安静下来再确认没有发出请求。否则,延迟触发的观察器可能会在读取到零之后才发出请求:

reticle_act({ sessionId, ref: "<the list end>", action: "scrollIntoView" })
reticle_assert({ sessionId, timeout_ms: 5000, predicate: { kind: "allOf", predicates: [
  { kind: "settled", quietMs: 1500 },
  { kind: "net", urlContains: "/api/items", count: 0 },
]}})

即使没有渲染任何新内容,列表在末尾之后仍不断请求下一页,也是一项真实的问题。

诚实

只有 verified: "yes" 才表示通过。对于客户端列表,yes 仅涵盖你所断言的行,与服务端无关:请明确说明这一点。说明你断言的是哪个端点,并将任何无法访问的步骤报告为 未知,而非通过。


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

评论

支持 Markdown