From velocitai
Defines a three-tier strategy for verifying form save success: Toast detection, redirect detection, and data comparison. Includes macOS Meta+A fix for rich text editors.
How this skill is triggered — by the user, by Claude, or both
Slash command
/velocitai:save-verify-strategyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
---
macOS Chromium 中 Control+A 是 Emacs 快捷键(光标移到行首),不是"全选"。在 CKEditor / TinyMCE / Quill 等富文本编辑器中,Control+A 无法选中全部内容,导致旧内容残留。
❌ 反例:
def fill_rich_text(self, text: str):
self.click(self.EDITOR)
self.page.keyboard.press("Control+A") # macOS 上只移动光标到行首
self.page.keyboard.press("Backspace") # 只删一个字符,旧内容残留
self.page.keyboard.type(text) # 新内容追加在旧内容后面
✅ 正例:
def fill_rich_text(self, text: str):
self.click(self.EDITOR)
self.page.keyboard.press("Meta+A") # macOS Command+A = 全选
self.page.keyboard.press("Backspace") # 清空全部内容
self.page.keyboard.type(text) # 写入纯新内容
排查信号:填入的数据末尾多了旧内容残片(如 "2026-05-06 10:30:00123" 尾部多了 123)→ 全选快捷键无效。
| 保存后行为 | 验证方式 | 可靠性 | 适用场景 |
|---|---|---|---|
| 显示 Toast | is_visible(toast_locator) — 必须在 networkidle 后立即检测 | 低(2-3s 消失) | 有明确 Toast 的场景 |
| 页面重定向 | 检测编辑页特征元素消失 wait_for(state="hidden") | 中(持久状态) | 保存后自动跳转的场景 |
| 无 UI 反馈 | 写入标记数据 → 重新打开 → 读取对比 | 高(数据级验证) | 无 Toast、不确定是否重定向 |
原则:三级可组合使用。优先用持久状态变更,Toast 仅作辅助。
click_save() 方法中的 wait_for_timeout() 会消耗 Toast 存活时间。必须在 networkidle 后立即检测 Toast,不能先等待再检测。
❌ 反例:
def click_save(self):
self.click(self.SAVE_BTN)
self.page.wait_for_load_state("networkidle")
self.page.wait_for_timeout(3000) # Toast 在这 3s 内出现又消失了
# 调用方
assert page.is_visible(toast) # Toast 已消失,永远 False
✅ 正例:
def click_save(self):
self.click(self.SAVE_BTN)
self.page.wait_for_load_state("networkidle")
# 不加额外等待 — 让调用方立即检测 Toast
# 调用方
is_ok = page.is_visible(toast, timeout=10000) # 立即开始等 Toast
page.wait_for_timeout(3000) # 检测完再等页面稳定
保存后页面可能重定向离开编辑页。通过检测编辑页特征元素消失来确认保存成功:
def is_save_success(self) -> bool:
"""编辑页 header 消失 = 保存成功并已离开编辑页"""
try:
self.page.locator(self.EDIT_PAGE_HEADER).first.wait_for(
state="hidden", timeout=15000
)
return True
except Exception:
return False
保存后的重定向目的地可能因导航上下文不同而异(直接 URL 访问 vs SPA 内跳转 vs 新 tab 中操作)。不要断言重定向到某个特定页面。
❌ 反例:
# 假设保存后一定重定向到首页
redirected_home = HomePage(page)
assert redirected_home.is_page_loaded() # 实际可能跳到其他页面
✅ 正例:
# 只验证离开了编辑页,不假设去了哪里
assert edit_page.is_save_success() # 编辑页 header 消失 = 保存成功
保存后如果需要重新访问同一页面验证数据,不要尝试在重定向后的页面内导航(重定向目的地不确定,元素可能不存在)。关闭旧 tab,从原始 tab(状态稳定)重新打开新 tab 进入。
❌ 反例:
# 保存后尝试在重定向页面内导航回去
redirected_page = SomePage(new_page)
assert redirected_page.is_page_loaded() # 重定向目的地不确定 → 失败
redirected_page.click(breadcrumb) # 元素可能不存在
✅ 正例:
# 保存后关闭旧 tab
finally:
new_page.close()
# 原始 tab 状态稳定,从这里重新进入
assert original_home.is_page_loaded()
new_page_2 = original_home.open_target_in_new_tab(name)
try:
# 在新 tab 中导航到编辑页 → 验证数据
finally:
new_page_2.close()
当没有 Toast 或 UI 反馈时,用「写入标记 → 保存 → 重新打开 → 读取对比」作为最终验证闭环。
# ── 写入阶段 ──
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
edit_page.fill_description(timestamp)
write_test_data("标记键名", timestamp) # 持久化到配置文件
# ... 保存操作 ...
# ── 验证阶段(重新打开编辑页)──
saved = read_test_data().get("标记键名")
actual = edit_page.get_description_text()
allure.attach(saved, name="期望时间戳", ...) # 无论成败都记录到报告
allure.attach(actual, name="实际时间戳", ...)
assert actual == saved, (
f"数据不一致:期望='{saved}',实际='{actual}'"
)
关键要点:
allure.attach() 将期望值和实际值写入报告npx claudepluginhub danielsuo117/velocitai --plugin velocitaiVerifies UI changes visually for mobile (Maestro), web (Playwright), and macOS (Peekaboo). Includes availability probes, path selection, screenshot capture, and dev-server preflight.
Verifies frontend changes against spec acceptance criteria using Playwright MCP for browser automation. Automates spec intake, dev server/auth checks, and test runs.
Runs Playwright-driven visual checks, axe-core a11y audits, screenshot regression diffs, and E2E test scaffolds for UI-affecting changes.