关于 verify-login-logout
使用 Reticle 验证 Web 应用登录、登出及会话过期行为的 skill。它要求同时检查界面、路由和网络证据,并对无法验证的步骤报告未知或受阻。
主要功能
- 01验证登录请求与登录后状态
- 02登出后检查受保护页面访问
- 03模拟会话过期并检查处理结果
- 04区分通过、未知与受阻状态
- 05清除模拟并避免泄露凭据
适用场景
- 01检查登录是否获服务器接受
- 02验证登出后服务器拒绝旧会话
- 03测试会话过期时是否返回登录页
MCP 相关
依赖 Reticle 工具操作并检查运行中的 Web 应用,包括页面、路由、网络请求和网络模拟。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ reticlehq/ reticle --skill verify-login-logout安装后在 Claude Code 中输入/verify-login-logout确认已加载。
详细介绍
屏幕上显示已登录,不代表真的已登录
登录表单提交后显示仪表盘,只能证明页面发生了变化。它不能证明服务器接受了凭据,也不能证明退出登录会结束会话,或会话过期的用户看到的是登录页面,而不是空白屏幕和一连串 401。
Reticle 可以在运行中的应用里检查这三点。尚未安装?运行 RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init,然后使用 install-and-verify 技能。
开始之前
- 使用项目的测试账号。 从种子数据、测试夹具或
.env.example中获取凭据,或询问用户。绝不要输入真实用户的密码。 - 确认会话可以观测:
reticle_session({ action: "list" })。隐藏或受限流的标签页可能会接受点击,却不渲染任何内容。如果会话不健康,请将运行结果报告为受阻,而不是通过。 - 步骤 3 需要由 Reticle 控制的浏览器。 强制会话过期需要使用
reticle_network_mock,它适用于租用的标签页(reticle_run({ tool: "reticle_lease", args: { action: "acquire", url } }))或受驱动的标签页,但不适用于常驻 SDK。若没有这样的浏览器,请执行步骤 1 和 2,并将步骤 3 报告为未知。
1. 登录,并说明登录后应前往何处
使用 reticle_look({ action: "page", sessionId, mode: "interactive" }) 获取表单的 refs,用 reticle_act 填写字段,然后提交,并在点击前说明预期结果:
reticle_act_and_wait({ sessionId, ref: "<sign-in button>", action: "click", until: { kind: "allOf", predicates: [
{ kind: "net", urlContains: "/api/login", ok: true },
{ kind: "route", pathname: "/dashboard" },
{ kind: "element", query: { role: "button", name: "Account" } },
]}})
三项检查都要做,因为每一项单独来看都可能有误:客户端可能在服务器响应前就切换路由,用户菜单也可能从过期缓存中渲染。全程使用应用的真实来源、端点、落地路由和已登录标记:这里展示的只是示例。
reticle_run({ tool: "reticle_storage", sessionId, args: {} }) 会显示应用存储的内容。敏感键会被脱敏,httpOnly Cookie 按设计不会对页面可见,因此这些信息只能证明会话存放在哪里,不能作为判定依据。
2. 退出登录,然后尝试返回
reticle_act_and_wait({ sessionId, ref: "<sign-out control>", action: "click", until: { kind: "allOf", predicates: [
{ kind: "route", contains: "/login" },
{ kind: "element", query: { role: "button", name: "Account" }, absent: true },
]}})
进入登录页面只能证明 UI 发生了变化,不能证明会话已结束。直接导航到受保护页面,并使用应用的真实来源和路由,证明访问遭到拒绝:
reticle_navigate({ sessionId, url: "<the app's origin>/dashboard" })
reticle_assert({ sessionId, predicate: { kind: "allOf", predicates: [
{ kind: "route", contains: "/login" },
{ kind: "text", contains: "Sign in" },
{ kind: "net", urlContains: "/api/me", status: 401 },
]}})
net 子句才能证明会话已由服务器结束。客户端保护机制可能会根据已清除的标记跳转到 /login,而服务器仍接受旧会话;这样一来,路由和文本检查都会通过。页面用于确认当前登录用户的请求现在必须遭到拒绝。如果页面没有发出此类请求,请如实说明,并将服务器端验证结果报告为未知。
退出登录后仍能渲染受保护页面,是这项技能可能发现的最重要问题。即使其他检查都通过,也要报告这一发现。
3. 让会话过期,并验证应用会重新要求登录
再次登录(步骤 1)。然后让服务器将会话视为已过期,方法是让应用用于检查会话的调用失败:
reticle_run({ tool: "reticle_network_mock", sessionId, args: {
mocks: [{ urlContains: "/api/me", status: 401 }],
}})
重新加载受保护页面,并说明预期结果:
reticle_navigate({ sessionId, url: "<the app's origin>/dashboard" })
reticle_assert({ sessionId, timeout_ms: 5000, predicate: { kind: "allOf", predicates: [
{ kind: "net", urlContains: "/api/me", status: 401 },
{ kind: "route", contains: "/login" },
{ kind: "console", level: "error", absent: true },
]}})
net 子句能证明应用响应的是模拟的 401。如果应用从未调用模拟的端点,那么无论如何都会发生的跳转只是巧合,并非对过期会话的处理;因此,在模拟它实际调用的接口之前,结论应为未知。控制台子句也很重要:如果应用通过抛出未捕获错误并显示空白页面来“处理”会话过期,那就不算处理了。完成后,使用 { clear: true } 清除模拟。
如实报告
只有 verified: "yes" 才算通过。步骤 3 是替代测试:Reticle 无法删除 httpOnly Cookie 或修改存储,因此通过模拟 401 来强制触发过期。请说明你模拟了哪个端点。交还前清除所有模拟并退出登录;绝不要报告你看到的任何密码、令牌或 Cookie 值。
能力参考:curl https://docs.reticle.sh/capabilities.md。其他所有内容:curl https://docs.reticle.sh/llms.txt。