mcpField
A
awt

awt

github.com/ksgisang测试与质量153 次安装2 周前更新
代码仓库

关于 awt

AWT 是面向 AI 编码工具的网页 UI 和端到端测试技能,通过真实浏览器执行测试并反馈结果。它要求先扫描页面、展示场景并取得用户批准,再运行测试;适合需要验证网页流程或排查回归问题的开发者。

主要功能

  1. 01扫描页面交互元素
  2. 02生成并执行 YAML 测试场景
  3. 03真实浏览器点击、输入与截图
  4. 04报告失败原因和未验证步骤
  5. 05运行前要求用户批准

适用场景

  1. 01验证登录等网页端到端流程
  2. 02代码变更后进行回归测试
  3. 03根据截图排查测试失败
  4. 04导出测试报告供交接

安装

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

$npx skills add https://github.com/ksgisang/awt-skill --skill awt

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
cli-reference.md
config-reference.md
scenario-schema.md
templates/
config-template.yaml
scenario-template.yaml

详细介绍

AWT — AI 编码工具的眼睛和双手

AWT(AI Watch Tester)让你的 AI 编码工具能够查看和操作 Web 应用。你的 AI 负责设计测试策略;AWT 使用真实浏览器执行测试——点击、输入、截屏并反馈结果。

何时使用此技能

  • 用户想要测试 Web 应用(端到端、QA、回归)
  • 用户说“测试一下”“检查是否正常”“验证登录”
  • 用户需要在代码变更后检测 Bug
  • 用户想要自动化回归测试
  • 端到端测试失败,用户想要从源代码中查找根本原因

何时不要使用

  • 单元测试或仅测试 API(AWT 用于 UI/端到端测试)
  • 性能/负载测试(使用 k6、Artillery)
  • 原生移动应用测试(仅支持 Web 和桌面)

⛔ 重要规则 — 开始前务必阅读

  1. 绝不要使用 -y 或 --auto-approve — 任何测试运行前,用户都必须批准。
  2. 绝不要使用 aat devqa — 它会在没有用户确认的情况下运行整个流程。始终使用下方的 4 步工作流。
  3. 运行测试前,绝不要不先向用户展示测试场景并取得明确批准(例如“진행해”“go ahead”“yes”“run it”)。
  4. 未经用户许可,绝不要自动修复代码或场景 — 始终报告失败,并询问用户如何处理。
  5. 绝不要设置 headless: true — 用户必须能看到浏览器。
  6. 绝不要猜测元素名称 — 始终先扫描,并使用 scan_result.json 中的实际数据。
  7. 绝不要启动 aat loop --approval-mode auto — 它会将 AI 生成的更改写入 用户的文件,并不断重新测试,直到测试套件全部通过,其间没有人检查这些更改。 如果用户要求无人值守的修复循环,请告知他们 --approval-mode branch 会执行相同操作,但会将每项更改提交到一次性 git 分支 (git branch -D aat/fix-NNN 可撤销这些更改),并让用户自行启动。 --approval-mode manual(默认值)会显示修复方案,但即使用户批准也不会写入任何内容 — 不要描述成已应用任何修复。

★ 必须遵循的 4 步工作流 ★

用户要求测试任何内容时,你都必须按顺序遵循以下 4 步。不要跳过任何步骤,也不要合并步骤。

步骤 1:扫描 — 分析目标网站
aat scan --url <URL>

扫描后,读取 .aat/scan_result.json,并向用户提供摘要:

"I scanned http://localhost:3000 and found 83 interactive elements:
 - 5 input fields (email, password, search, ...)
 - 12 buttons (Login, Sign Up, Submit, ...)
 - 15 links (Home, About, Dashboard, ...)
 
 Should I create a test scenario based on these elements?"

⏸️ 等待用户回复后,再继续步骤 2。

步骤 2:生成并展示 — 创建场景并展示

使用 scan_result.json 中的确切数据编写 YAML 场景。然后向用户展示完整场景:

"Here is the test scenario I created (5 steps):

 1. 🌐 Navigate to http://localhost:3000
 2. 🖱  Type '[email protected]' into email field
 3. 🖱  Type 'password123' into password field  
 4. 🖱  Click 'Login' button (critical)
 5. ✅ Verify URL contains '/dashboard'

 Should I run this test? Or would you like me to modify anything?"

⏸️ 等待用户批准后,再继续步骤 3。

  • 如果用户说“修改 X” → 编辑场景并再次展示
  • 如果用户说“go ahead” / “진행해” / “yes” → 继续步骤 3
  • 如果用户说“cancel” → 完全停止
步骤 3:执行 — 运行已批准的场景
aat run --skill-mode --fast scenarios/<scenario>.yaml

监控输出。如果任何步骤失败:

  1. 立即停止 — 不要继续下一个场景
  2. 从 AWT 输出中读取失败详情
  3. 如果 === AWT SKILL DEVQA === 块中有 ACTUAL_CAUSE,请先读取 ACTUAL_CAUSE — 见下文
  4. 如果同一块中提供了屏幕截图,请读取该截图
  5. 向用户报告:
"Test failed at Step 3 (Click Login button):
 - Error: Element 'Login' not found in DOM
 - Possible cause: The button text might be 'Sign In' instead of 'Login'
 - Screenshot: .aat/screenshots/step_003_fail.png
 
 Should I:
 (a) Fix the scenario (change 'Login' to 'Sign In') and re-test?
 (b) Fix the source code instead?
 (c) Skip this step and continue?"
ERROR 与 ACTUAL_CAUSE——不要混淆

发生严重故障时,该区块可能包含两行:

ERROR: Dashboard must load after login
ACTUAL_CAUSE: Bounced to the login page: https://app.example.com/login?next=/dashboard

ERROR 是场景作者对本应发生什么的描述。它是在运行前写下的标签,不是调查结果。ACTUAL_CAUSE 才是浏览器实际执行的操作。请根据 ACTUAL_CAUSE 进行诊断;把 ERROR 当作原因,会得出完全错误的诊断(此处会认为“仪表板坏了”,但实际情况是会话从未建立)。

如果没有 ACTUAL_CAUSE,ERROR 就已经包含真实原因。

LOOKED_FOR 指明步骤要查找的选择器、文本或图像——这是判断“页面有问题”还是“场景指向了一个已不存在的内容”所需的关键信息。当 PAGE_TITLE 恰好就是该字符串时,它旁边会出现一行 PAGE_TITLE_NOTE。请仔细阅读:<title> 是标签页元数据,不是页面文本,因此无法满足文本断言。如果没有这一行,该区块看起来就会自相矛盾,而显而易见的结论——“文本明明就在这里,所以 AWT 出问题了”——其实是错的。

STATUS: WARNINGS——步骤已运行,但没有改变任何内容

退出代码为 3,并带有一个 === AWT SKILL VERIFY === 区块,其中列出了每个发出警告的步骤。点击操作已执行且未引发错误,但没有任何像素发生变化,这几乎总是意味着它点在了目标旁边,而不是目标上。**不要将此类运行报告为已通过。**查看该警告步骤的屏幕截图,检查指定的目标,并告知用户你的发现。忽略警告,就会让一次未命中的点击在三步之后演变成“产品坏了”。

find_and_type 警告表示文本没有输入到任何地方:字段是 <select>、处于 readonly 或 disabled 状态,或者之后仍然为空。消息会说明具体情况以及应改用什么方式——<select> 需要使用 action: select_option 和 target.selector,而不是输入文本。请修正场景,不要原样重新运行。

它还可能提示文本输入到了错误的字段,这通常出现在长表单中。有两种情况,都会指出涉及的字段:

  • “'…' 被输入到 input#suggest,而不是该步骤指定的 input#name”——文本落到了其他位置。AWT 会先将文本目标解析为唯一一个可见字段:该字段的 <label>、包裹字段的标签或 aria-label 与该文本匹配;之后才会尝试匹配占位符子串,因此对于带标签的表单,文本会输入到页面指示的位置;如果没有标签说明应该使用哪个输入框,或者输入框被遮挡,则仍可能触发此警告。
  • “……而第 9 步已为目标 '비밀번호' 填写过该字段”——两个步骤指定了不同内容,却写入了同一个输入框,因此其中一个步骤没有获得自己的字段。

两种情况的修复方法相同:为这些步骤指定 target.selector。不要原样重新运行,也不要将该步骤报告为已通过——在产生此警告的那次运行中,这样的三个步骤都被计为通过,但表单最终显示了 “이름을 입력해주세요.” AWT 选择候选项时也会记录歧义,因此该警告前通常会有一行同时列出两者。

⏸️ 修复或重新运行前,请等待用户指示。

第 4 步:报告 — 总结结果

所有步骤均通过后,向用户报告:

"✅ Test complete: 5/5 steps passed (37.8 seconds)
 
 All navigation links and login flow are working correctly."

绝不要报告包含 AWT 未计数步骤的步骤总数。 当场景中途失败时,断点之后的步骤应报告为 UNVERIFIED,而不是 PASSED,因为这些步骤是在场景从未到达的屏幕上运行的。应按以下方式报告:

"❌ Test failed at step 6 of 24.

 Step 6 could not enter the school name — the field is a <select>, so the
 value never went in and no account was created.
 The 18 steps after it are UNVERIFIED: they ran against the login screen,
 so whatever they checked, they did not check it on the signed-up state."

此处说“24 个步骤中有 22 个通过”,正是这项状态要避免的错误。如果场景应在该步骤停止,而不是继续运行,请为该步骤设置 critical: true。

如果用户想保存报告或转交给其他人,请在运行时添加 --report pdf。它会生成 reports/<scenario id>/report.pdf,列出每个步骤,并在文件中嵌入失败和警告步骤的屏幕截图,同时打印文件路径。不要默认添加此参数:大多数运行结果都在终端中查看,没人要求的报告只会变成无人打开的文件。

如果用户想查看成功步骤(例如用于交接或记录一次顺利运行),请添加 --report-screenshots all。默认情况下,只有失败和警告步骤会附带屏幕截图,因此运行一切正常时,报告中完全没有图片。

“这是从什么时候开始失败的?”应从 .aat/runs/ 查找答案,而不是从 .aat/last_run.json。 指针文件每次运行都会被覆盖,因此它始终只描述最新一次运行。每次运行也会存入 .aat/runs/<timestamp>-<nnn>_<scenarios>.json,其中包含状态统计,最多保留最新的 50 个文件。查看当前文件之前的两三个文件,即可指出某个步骤首次变红的是哪次运行;没有这些文件,“以前是能用的”就只能由用户断言。


自动设置

which aat || (pip install aat-devqa && playwright install chromium)

场景 YAML 参考

根据 scan_result.json 编写场景

读取 .aat/scan_result.json,并使用其中的确切元素数据编写 YAML:

id: "SC-001"
name: "Login Flow"
steps:
  - step: 1
    action: navigate
    value: "http://localhost:3000/login"
    description: "Go to login page"

  # Use EXACT coordinates/selectors from scan_result.json
  - step: 2
    action: find_and_type
    target:
      selector: "#email"           # from scan_result.json
      text: "Email"
    value: "[email protected]"
    description: "Enter email"

  - step: 3
    action: find_and_click
    target:
      text: "Login"                # from scan_result.json
    region: main                   # exclude nav panel
    critical: true                 # stop if login fails
    description: "Click login button"

  - step: 4
    action: assert_url
    value: "/dashboard"
    on_fail: stop
    message: "Login failed — not redirected to dashboard"
    description: "Verify redirect to dashboard"

规则:

  • 有扫描数据时,使用其中的 selector(最可靠)
  • 将 text 用作备用方案(基于 OCR)
  • 所有点击都使用 region: main(避免导航面板中的误报)
  • 将登录/身份验证步骤标记为 critical: true
  • 在会触发导航的点击后添加 assert_url
操作(27 种)
类别操作
导航navigate、go_back、refresh
查找 + 鼠标find_and_click、find_and_double_click、find_and_right_click
查找 + 键盘find_and_type、find_and_clear
直接操作click_at、type_text、press_key、key_combo
断言assert、assert_text、assert_screen_changed、assert_url
会话save_session、load_session
输入upload_file、select_option
控制流if_visible、include
查询find、get_text
实用工具wait、screenshot、scroll

每种操作所需的内容请参阅 references/scenario-schema.md。

关键步骤选项
- action: find_and_click
  target:
    text: "Submit"               # OCR text
    selector: "#submit-btn"      # CSS selector (highest priority)
  region: main                   # top/bottom/left/right/center/main/full
  critical: true                 # stop test on failure
  on_fail: stop                  # same as critical
  method: auto                   # auto/semantics/template/ocr/vision
  match_index: 0                 # 0=first match, -1=last
  change_threshold: 0.05         # for critical auto-verification
  learn: false                   # do not remember this position (see below)
  description: "Click submit"

learn: false — 目标位置会随内容变化时使用。 AWT 会记住找到目标时的位置;如果后续运行中的步骤没有提供 selector,就会复用该位置。这对登录字段等固定元素很有帮助,但对任何会随内容移动的元素都容易失效:例如高度随文本变化的模态框中的按钮、绘制在问题图片上的选项覆盖层,以及列表中的行。应将这类步骤标记为 learn: false。编写 selector 不能替代此设置——selector 的优先级高于已记住的位置,但名称不同的目标仍会以新名称被记住。

文本断言:默认匹配子串,仅在需要时精确匹配

assert_text 和 assert_type: text_visible 都是在询问“页面中是否包含 这段文字?”。它们会匹配子串,因此即使正确的词被杂项内容包裹,也会通过。一个测验应用泄露了模板,本该显示 질량,却渲染成了 \(\text{질량}\),但每次 assert_text: 질량 都通过了——这个词确实在页面中,任何查看屏幕的人都能明显发现缺陷,而所有针对它的断言都看不出来。

如果检查的重点是某项内容的呈现方式,而不仅仅是它是否存在,请使用 text_equals 并指定选择器:

- step: 3
  action: assert
  description: "the question shows the formula, not its LaTeX source"
  assert_type: text_equals
  value: "질량"
  target:
    selector: "#question"

它会将元素的全部可见文本与 value 比较,因此泄露内容会导致检查失败。需要了解两点:

  • 选择器是其生效的关键。 不指定选择器时,它会与整个可见页面进行比较,这就要求页面中不能有其他内容。这种形式是为旧场景保留的;请勿以此方式编写新场景。
  • text_visible 会有意忽略选择器。 它的用途就是匹配整个页面中的子串。需要缩小匹配范围时,请使用 text_equals。

assert_text 不接受隐藏文本。display:none 文本虽然存在于 DOM 中,但过去仅凭这一点就足以通过——因此,代码已不再显示的通知仍然满足了用户本应能看到的断言。现在,指定未渲染元素的选择器会失败,并提示 "in the DOM but not visible";不指定选择器时,则要求匹配项已渲染。若隐藏项前方还有一个可见副本(例如,过时的模板位于实时视图上方),检查仍会通过。

如果你的场景在这里失败了,应将其理解为:断言终于开始回答它原本要问的问题。

assert_url(登录/导航验证)
- action: assert_url
  value: "/dashboard"
  on_fail: stop
  message: "Login failed"
  description: "Verify redirect"
访问控制测试:expect_login_redirect

AWT 通常会将“最终进入了登录页面”视为必须停止的情况,因为对大多数场景来说,这意味着会话已失效。访问控制测试则希望结果相反:被重定向到 /login 就是通过。请为这些步骤添加标记:

- step: 2
  action: navigate
  value: "{{url}}/admin"
  expect_login_redirect: true     # landing on /login here is expected
  description: "Open an admin page while signed out"

- step: 3
  action: assert_url
  value: "/login"
  expect_login_redirect: true
  description: "Verify the guest was sent to the login page"

请按步骤设置。场景级别也有同一个字段,但它会关闭文件中所有步骤的检测,因此仅当整个场景都与访问控制有关时才使用它。省略该字段则会保持默认行为:未声明预期登录重定向的步骤遇到这种情况时,仍会立即停止。

会话复用
# Save after login
- action: save_session
  name: "my_app_login"

# Load in next run (24h expiry)
- action: load_session
  name: "my_app_login"
  max_age_min: 15              # fail here if the saved session is older

日志会说明加载的会话已存放多久(“21 分钟前保存”)。max_age_min 会在加载步骤直接失败:会结束闲置会话的服务会在保存的会话文件显得过时时,就早早将其终止;如果没有此上限,这会在几步之后才表现为登录失败,让所有人白白去输入凭据。

区域参数
区域范围使用时机
full整个屏幕(默认)通用场景
main右侧 80%点击时务必使用(避开导航面板)
top顶部 30%页眉元素
bottom底部 30%页脚、浮动按钮
center中央 60%x60%模态对话框
折叠区域下方的内容

AWT 截取的每张屏幕截图都是当前视口,因此页面更下方的目标不会出现在画面中。当视觉查找在首屏失败时,AWT 现在会逐屏向下滚动并再次查找,并报告实际找到匹配项的画面。

这适用于读取像素的路径——匹配器链、根据已存图片进行的自修复,以及 text_visible OCR 回退。DOM 查找无需此功能:CSS 选择器和 inner_text 本来就能查看整个文档。

  • 不要只为将目标显示在视口中而添加 scroll 步骤。 现在系统会自动完成这件事,而且手动调校的滚动距离在不同视口下会失效。只有当滚动本身就是测试内容时(延迟加载、无限列表、粘性页眉的行为),才添加 scroll。
  • 如果查找不到内容,滚动位置会恢复原状,因此失败的步骤不会导致后续步骤面对意料之外的页面位置。
  • OCR 回退的查找距离比匹配器链短,因为每个未命中的画面都要完整运行一次 Tesseract。对于页面很下方、只能通过像素识别的文字,请先断言靠近顶部的内容。

Flutter CanvasKit 支持

AWT 会自动检测 Flutter CanvasKit 并启用 Semantics:

  1. 在 navigate 后等待应用启动——CanvasKit 只有在 WASM 加载完成后才会创建标记;navigate 用时为 0.4s,而标记在 2.6s 后才出现
  2. 点击 flt-semantics-placeholder(重试 3 次,每次 3s)
  3. 等待稍后加载的 Web 字体及其引发的重绘
  4. 读取 flt-semantics[aria-label] 获取元素坐标
  5. 如果 Semantics 不可用,则回退到 OCR

不要为 Flutter 添加手动调校的 wait 步骤。 上面的步骤 1 和 3 就是这些等待原本要解决的问题。移除所有 wait 的场景在真实 CanvasKit 应用中也能通过;在一台机器上调校好的 wait 在另一台机器上可能失效。只有当应用自身需要执行启动工作(如获取数据)时才添加 wait,不要用于等待 Flutter。

Flutter 上的匹配优先级: CSS 选择器 → Flutter Semantics → Playwright 文本 → OCR → Vision AI

Flutter 专属规则:

  • 使用带有 target.text 的 find_and_click / find_and_type——应使用这条路径。target.text 必须与 Semantics 标签匹配;该标签通常是字段的浮动标签(例如 이메일),而不是提示文字
  • 避免使用 CSS 选择器和手写坐标——Flutter 不会生成标准 DOM 元素,而且任何重新排版都会导致坐标失效
  • 当文字不在 DOM 中时,text_visible 会通过 OCR 读取画布;对于非拉丁文字,请配置 matching.ocr_languages(默认值为 eng+kor)。它会向下滚动几个屏幕进行查找(参见 折叠区域下方的内容),因此不要添加 scroll 步骤来查找画布上的文字
  • 如果 OCR 识别到导航文字,请使用 region: main
  • 在 type_text 上使用 verify: true(画布输入可能不会渲染)
  • 点击后添加 assert_screen_changed
  • 使用 method: semantics 强制执行 Semantics 查找

源代码根因分析

测试失败时,请追查源代码:

  1. 阅读测试输出——失败的步骤、错误信息和 URL
  2. 搜索代码库——查找预期文字、URL 路由和组件
  3. 阅读组件代码——找出它为何没有渲染、重定向或响应
  4. 向用户提出修复方案——展示具体差异并征求批准
  5. 用户批准后——应用修复、重新构建、重新扫描并重新测试

**核心原则:**绝不自动应用修复。始终展示差异并征求批准。


CLI 命令

命令描述
aat scan --url URL扫描页面,将元素收集到 scan_result.json
aat run --skill-mode PATH以面向 AI 的结构化输出执行
aat run --skill-mode --fast PATH以快速、优先使用 DOM 的模式执行(Next.js、React 等)——即使选择器失效,也会根据已存储的图片进行修复
aat run --debug PATH执行并输出 OCR 候选项调试日志
aat run --report pdf PATH执行并为每个场景生成 PDF 报告(内嵌失败截图)
aat doctor检查环境
aat setup配置 AI 和视觉提供商
aat validate PATH验证 YAML 场景
aat cost查看 AI API 费用
aat learned list显示已记住的坐标、已学习的模式和已存储的元素图片
aat learn reset "NAME"忘记为一个目标记住的坐标(对所有目标使用 --all)
aat learned clear --templates忘记已存储的元素图片(对单个站点使用 --host HOST)
重要标志
--skill-mode    Structured output for AI assistants
--fast          DOM matching, skipping OCR and Vision AI (fastest for standard web
                apps). A broken selector still gets one deterministic attempt from
                the picture an earlier run banked for that element.
--debug         Show OCR candidates and matcher details
--strict        Treat skipped steps as failures
--learn         Record healed steps for pattern learning
--no-learn      Neither use nor update remembered coordinates in this run
--slow-mo N     Slow down actions by N ms
--report FMT    Write a report per scenario: pdf | markdown (omit for none)
--report-screenshots WHICH   Steps the PDF illustrates: failures | all | none
                             ('all' pictures the steps that worked)
⛔ 禁用标志(切勿使用)
-y / --auto-approve    Bypasses user approval — NEVER USE

最佳实践

  1. 编写场景前务必先扫描——切勿猜测元素名称
  2. 在所有 find_and_click 中使用 region: main——避免导航面板误报
  3. 将身份验证步骤标记为 critical: true——登录失败后继续测试毫无意义
  4. 在表单提交后添加 assert_url——确认已完成导航
  5. 使用 save_session/load_session——重复运行时跳过登录
  6. 失败时查看截图——SCREENSHOT 路径指向真实的 PNG 文件
  7. 将会移动的目标标记为 learn: false——模态框按钮、图片上的选项浮层、列表行:它们的位置会随内容变化,因此记住位置会导致下次运行时点击空白处
  8. 每次重试只修复一处——只更改失败的步骤
  9. 修复前务必询问用户——切勿自动修改代码或场景

AI 提供商(适用于视觉 AI 第 3 层)

提供商视觉能力费用设置
Gemini Flash是免费aat setup → Gemini
Claude是中等aat setup → Claude
GPT-4o是较高aat setup → OpenAI

视觉 AI 为可选项——第 1 层(模板)和第 2 层(OCR)免费,且无需 API 密钥即可使用。

参考文件

  • references/scenario-schema.md — 完整的 YAML 架构
  • references/cli-reference.md — 完整的 CLI 参考
  • references/config-reference.md — 所有配置选项
  • templates/scenario-template.yaml — 空白场景模板
  • templates/config-template.yaml — 默认配置模板

评论

支持 Markdown