关于 quicke2e
quicke2e 是基于 Playwright 的浏览器端到端测试命令行工作流,适合编写和运行 Web 应用测试。它将用例设计与浏览器执行分开,并由代码判定通过或失败。
主要功能
- 01发现页面、表单和链接
- 02编写浏览器流程规格
- 03使用 Playwright 运行流程
- 04检查断言是否过弱
- 05测试边界、拒绝与攻击场景
适用场景
- 01编写浏览器测试前梳理应用
- 02按角色验证登录后流程
- 03测试表单校验与业务规则
- 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. 发明测试用例
对于每个表单和每条规则,每次会话中都要主动编写四种类型的用例,无需他人要求:
- 正常路径 — 页面主要功能正常。始终放在第一位。
- 边界 — 规则的边界:最后一个有效日期、最大数量、达到限制的代码。
- 拒绝 — 规则必须拒绝:窗口已过期、已售罄、代码错误、活动已开始、用户没有相应角色。
- 攻击 — 恶意用户,见下文。
攻击:始终也要从攻击者的角度思考。 每次会话中,无需他人要求,也要发明攻击用例:从攻击者、混沌工程师和高级 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/。