mcpField
Q
quicke2e

quicke2e

github.com/dmoka测试与质量10574 次安装5 周前更新
代码仓库

关于 quicke2e

quicke2e 是基于 Playwright 的浏览器端到端测试命令行工作流,适合编写和运行 Web 应用测试。它将用例设计与浏览器执行分开,并由代码判定通过或失败。

主要功能

  1. 01发现页面、表单和链接
  2. 02编写浏览器流程规格
  3. 03使用 Playwright 运行流程
  4. 04检查断言是否过弱
  5. 05测试边界、拒绝与攻击场景

适用场景

  1. 01编写浏览器测试前梳理应用
  2. 02按角色验证登录后流程
  3. 03测试表单校验与业务规则
  4. 04检查对抗性输入的拒绝行为

安装

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

$npx skills add https://github.com/dmoka/quicke2e --skill quicke2e

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

详细介绍

quicke2e — 设计用例,让 Jev 驱动,让代码判定

分工。不要混淆:

  • **你(大模型)**设计用例并编写规范文件。离线执行一次。
  • Jev 为每份规范驱动浏览器。成本低、速度快,可重复多次。它只会选择工具提供的选项之一。
  • 代码判定通过或失败。绝不由你或 Jev 判定。

1. 映射应用

如果没有基础 URL,请先询问。然后:

npx quicke2e discover http://localhost:3000 --start / -o quicke2e.map.json
  • 在 localhost 上,默认执行完整爬取:会提交表单。请使用一次性数据库(用 --reset "<cmd>" 恢复种子数据)。
  • 对于其他主机:使用 --safe,除非用户确认数据可以丢弃(--i-own-this-data)。
  • 需要登录才能访问的页面:先保存会话(使用下面的登录流程),然后传入 --storage state.json。每种角色各爬取一次,另在未登录状态下爬取一次:已登录状态下爬取不会访问 /login 或 /join。
  • 阅读映射结果:页面、边、表单、字段、选项,以及每次表单提交后跳转到的位置。

需要登录才能访问的页面。 保存一次已登录的浏览器状态,然后将其用于爬取和每个流程:

// login.mjs: `TEST_EMAIL=... TEST_PASSWORD=... node login.mjs` writes auth.json (credentials from env, never in the spec)
import { chromium } from "playwright";
const b = await chromium.launch(); const p = await b.newPage();
await p.goto("http://localhost:3000/sign-in");
await p.getByLabel("Email").fill(process.env.TEST_EMAIL);
await p.getByLabel("Password").fill(process.env.TEST_PASSWORD);
await p.getByRole("button", { name: /sign in/i }).click();
await p.waitForURL((u) => !u.pathname.includes("sign-in"));
await p.context().storageState({ path: "auth.json" }); await b.close();
  • 使用该状态爬取:npx quicke2e discover http://localhost:3000 --storage auth.json。不传入该状态时,映射结果中只有未登录页面。
  • 在每个需要登录的流程中设置:storageState: "auth.json"。每种角色使用一个文件(customer.json、admin.json)。
  • 每次重置数据库后都要重新运行 login.mjs:重新植入种子数据会删除会话,过期的 auth.json 会让每次运行都跳转到登录页面。
  • 如果 Cookie 横幅或欢迎对话框挡住了登录表单,请先在 login.mjs 中接受或关闭它,再填写表单。保存的 auth.json 也会保存此同意状态,因此运行开始时不会出现横幅。
  • storageState 不会保存 sessionStorage。如果应用将会话或购物车保存在其中,请在流程内登录。
  • 如果 getByLabel("Password") 匹配到两个元素(其中一个是“显示密码”按钮),请使用 getByLabel("Password", { exact: true })。

2. 阅读源代码中的规则

映射结果会显示事物的位置,源代码则会说明必须满足什么条件。按以下顺序阅读: 领域/业务逻辑(定价、限制、时间窗口、权限、状态机)、校验架构、 API/路由处理程序、种子数据(可用作输入的真实姓名、代码、日期)、README/PRD。 将每条规则列为一行:rule — file:line

3. 发明测试用例

对于每个表单和每条规则,每次会话中都要主动编写四种类型的用例,无需他人要求:

  1. 正常路径 — 页面主要功能正常。始终放在第一位。
  2. 边界 — 规则的边界:最后一个有效日期、最大数量、达到限制的代码。
  3. 拒绝 — 规则必须拒绝:窗口已过期、已售罄、代码错误、活动已开始、用户没有相应角色。
  4. 攻击 — 恶意用户,见下文。

攻击:始终也要从攻击者的角度思考。 每次会话中,无需他人要求,也要发明攻击用例:从攻击者、混沌工程师和高级 QA 工程师的角度思考。在 QuickE2E 可执行的范围内,发明脚本化测试套件从未覆盖的用例:

  • 在真实字段中输入滥用值:负数、零和超大数量、超长字符串和 Unicode 字符串、脚本标签、类似 SQL 的字符串、代码中多余的空格和奇怪的大小写。
  • 滥用折扣码:再次使用已用完的代码、在第一个代码上再叠加第二个代码、使用大小写异常的过期或已禁用代码。
  • 通过 URL 访问他人的资源:start 是他人的订单,control 是用户自己的订单。
  • 在 start 中构造 URL:异常查询值(?qty=-3)、重复参数(?code=A&code=B)、其他格式的 ID(/orders/0x2)。
  • 已关闭的窗口:已经开始的活动、已售罄的演出。
  • 通过 URL 再次提交已完成的流程。
  • 必填字段留空或填入垃圾内容。

每个攻击用例都要断言两件事。应用会拒绝:用户看到的拒绝文本或错误状态要写入 expect。成功状态必须不存在:仅在成功时显示的文本(确认信息、折扣行、退款行)要写入 expectAbsent,绝不能使用页面加载时就存在的标签。仅断言拒绝文本还不够,因为应用可能显示错误信息,同时仍应用了折扣。当 expectAbsent 中的文本变为可见时,运行会以 ABSENT_SEEN 结果停止:应用接受了攻击。如果出现错误页面(500),运行也会失败,因为拒绝文本并未出现。

将每个攻击用例规范标记为 kind: "attack"。优先选择脚本化测试套件没有覆盖的用例。先检查现有测试,并指出它们跳过了哪些规则。

攻击用例的限制
  • 一个浏览器,一次执行一个操作:不进行同时双击、竞态或多标签页流程。
  • 不篡改请求头、Cookie 或请求正文,也不进行网络层攻击。在 start 中构造 URL 属于测试范围。
  • 这些测试应交由 API 属性测试或代码级对抗测试器处理。在报告中将它们列为该层的测试用例。
  • 仅攻击你拥有的应用:使用本地数据库的 localhost,或用户通过 --i-own-this-data 确认授权的主机。

4. 编写规范

一个文件 quicke2e.spec.mjs,导出一个数组:

export default [
  {
    name: "refund-refused-after-start",
    kind: "attack",
    start: "/orders",
    maxSteps: 14,
    inputs: { email: "[email protected]" },          // every typed value comes from here
    goal: "Open the order for the concert that already started and request a refund",
    expectUrl: "/orders/\\d+$",                        // THE ASSERTION
    expect: ["Refunds close when the event starts"],   // THE ASSERTION
    neverClick: ["Confirm refund*"],                   // a refusal test must not complete the action
    expectAbsent: ["Refunded €"],                      // THE ASSERTION: the success state stays absent
  },
  {
    name: "other-users-order",                         // a load attack: opening the URL IS the attack
    kind: "attack",
    start: "/orders/1",                                // someone else's order
    control: "/orders/281",                            // the user's own order: the assertion must be false here
    goal: "Open order 1",
    expect: ["Nothing here"],
    expectAbsent: ["Total paid"],
  },
];

如果页面需要已登录用户,请在每个测试规范中设置 storageState: "auth.json"(登录步骤见第 1 节)。使用已植入数据的演示账户。 断言规则——断言本身就是测试:

  • 运行会在第一个满足所有断言的快照处停止。断言最后一个操作之后的页面(提交后,即提交所到达的页面),否则运行会提前停止。
  • 只断言操作完成后为真的内容:之前不可能存在的 URL、之前不存在的文本,或通过 expectState: [{ role, name, value | checked | selected }] 检查控件状态。
  • expect 检查视力正常的用户能看到的文本。它会忽略表单控件(测试输入或选择的值不算结果)和隐藏文本。若要检查所选选项或字段的值,请使用 expectState。对于会消失的 Toast,请使用 expectSeen。
  • 绝不要断言页面上本来就有的标签、按钮文字或单词。
  • 删除操作:使用 expectGone: ["<the item's text>"](该文本可以出现在起始页面上,但结束时必须消失),并加上空状态或成功 Toast。expectAbsent 用于必须永远不出现的文本。
  • expect 不区分大小写。要测试应用是否会规范化大小写(例如以小写形式存储电子邮件),请使用 expectState 断言控件的值,或断言页面上只有该值可能出现的位置是否准确显示了存储值。
  • 仅包含空格的值(" ")会按原样输入。空字符串("")表示“让字段保持空白”。
  • 会创建数据的规格(注册、新建项目)每次运行都需要新值或重置:使用 --reset,并确保生成的值唯一(Date.now() 在每个 quicke2e 进程中只求值一次)。
  • 对于拒绝操作,请断言拒绝文本,并确认成功状态不存在:expectAbsent 只能填写成功时才会显示的文本。若 expectAbsent 中的文本已出现在起始页面上,check 会拒绝该断言(ABSENT_ON_START)。
  • 负载攻击(起始 URL 本身就是攻击)需要设置 control:一个会显示应用已接受攻击的页面。随后,check 会在控制页面上运行 WEAK_ASSERTION 检查,并要求每条 expectAbsent 文本都确实出现在控制页面上(成功标记必须真实存在)。运行会在起始页面上作出判断,绝不会点击:如果拒绝提示不在那里,结果就是 NOT_REFUSED。
  • 应用可能接受的值(例如名称字段中的脚本标签)不属于拒绝场景:应断言下一页面将该值显示为纯文本。
  • 在目标描述中写明攻击值和每个操作(例如“将票券上的姓名替换为仅包含空格的内容,然后付款”)。使用“给定的值”时,引擎不会将其输入字段(测量结果:MODEL_BLOCKED 2/2;明确写出该值后,通过率为 2/2)。
  • 每个拒绝场景都必须为成功操作设置 neverClick("Pay*"、"Place order*"、"Delete*")。如果应用错误地接受了输入,或浏览器先拦截了表单,运行就会失败,且不会购买、付款或删除。测量发现:未设置此项的拒绝场景实际提交了一笔订单。例外:如果应用在点击时才拒绝操作(“Place order”后有服务器端检查),该点击本身就是测试,因此不要将其列入;应改用一次性数据库。运行失败并显示 neverClick hid: "...",表示发生了这种情况。
  • 浏览器验证(min、max、required、type=email)会在应用收到提交之前拦截操作,而且提示气泡不是页面文本。针对这类验证编写拒绝场景前,请先在源代码中查看这些属性;这类规则通常应在更低层级进行测试。
  • 下拉菜单的值也通过 inputs 设置:键为标签中的词语,值为选项上显示的文本。
  • inputs 中的字段键必须与字段标签匹配(email 匹配“Email”)。标签包含相同子串的字段(“Email”和“Billing email”)会获得相同的值:请为这类字段指定不同的键,或避免这种场景。

如果页面显示了引擎不应看到的敏感数据(恢复码、已保存的卡片、个人数据),请在规格中声明:redact: [".backup-code", "#saved-cards", /recovery code \S+/i] (CSS 选择器和文本模式)。系统不会自动猜测。规格中的机密输入(密码、API 密钥、卡号、令牌等)始终会被隐藏。 规格之间的数据。 更改数据(例如订单、已取消的事件)的规格会改变下一个规格看到的内容。每次运行前使用 run --reset "<cmd>" 恢复种子数据(例如 --reset "npm run db:seed && node login.mjs"),或对规格排序以避免冲突。过期的 auth.json 会立即终止运行,并返回 AUTH_REQUIRED。

对话框、悬停、拖放。 系统会处理原生 confirm/prompt 对话框(prompt 会使用其消息中所提到键对应的规格值);设置 dialog: "dismiss" 可测试取消路径。当页面采用可识别的模式时,系统支持仅悬停时显示的内容和拖放操作;请用这些词描述目标(“将鼠标悬停在 user2 的头像上”、“将卡片拖到 Done 上”)。

5. 检查,然后运行

npx quicke2e check quicke2e.spec.mjs --base http://localhost:3000     # rejects WEAK_ASSERTION specs
npx quicke2e run quicke2e.spec.mjs --base http://localhost:3000 --map quicke2e.map.json --runs 3

WEAK_ASSERTION 表示该断言在起始页面上已经成立:请重写断言,切勿添加 --allow-weak 来消除提示。

先运行正常路径(将其单独放在一个文件中,或使用 --only)。正常路径失败时,请报告问题,并暂停该流程上的边界、拒绝和攻击用例,直到正常路径通过:流程出错时,攻击无法区分拒绝和故障。

6. 报告

对于每个失败的规格,请判断这是 APP 错误(违反了源代码中的规则),还是 SPEC/TOOL 问题(运行未到达目标位置)。首先根据原因作出判断:每个 FAIL 下方的行(或 --json 字段 assertions、heldBack、loop、emptyRequired)会说明哪个断言未成立、哪个提交因哪个字段而被拦截,以及哪个操作重复执行。然后读取跟踪记录(--trace runs/):每一步都会列出引擎收到的选项(offered)。结果:DONE_VERIFIED = 通过;ABSENT_SEEN = 应用显示了仅成功时才会出现的文本,说明它接受了攻击(记录字段 absentSeen);SERVER_ERROR = 页面返回 HTTP 5xx(记录字段 httpStatus):这是 APP 错误,请自行加载该 URL 以查看错误;MAX_STEPS/MODEL_BLOCKED/LOOP = 运行未到达目标位置;最终页面错误但成功文本可见 = 应用违反了规则。

  • 切勿修改规格以让其通过。 拒绝或攻击用例失败就是要报告的问题。
  • 对于每个 APP 错误,请提供规则(文件:行号)、规格名称、发生情况(结果、absentSeen、最终 URL)。建议能发现该问题的最低层级测试:针对领域规则的单元测试,或针对路由的 API 测试。
  • 失效的攻击(MAX_STEPS、MODEL_BLOCKED、NO_SPEC_VALUE)没有判定结果。请明确说明;不要将其算作通过或错误。
  • 先报告正常路径,再报告边界、拒绝和攻击结果。
  • 列出因上述限制而未运行的攻击,并注明应在哪一层进行测试。
  • 对于必须保持通过的流程,请根据一次通过的运行生成 Playwright 规格:--emit e2e/generated/。

评论

支持 Markdown