Agent验证技能:让AI像真实用户一样检查应用

Agent验证技能:让AI像真实用户一样检查应用 一个 Agent 会写代码但它能不能像用户一样亲自把页面点一遍确认按钮位置正确、文案显示完整、跳转流程符合预期在很长一段时间里这个问题的答案都是否定的。AI 编程助手能把需求变成代码却很难把代码变成“已经验证过可用的功能”。这里的缺口不是代码生成能力不足而是验证能力缺失。pstack 新增的创建与维护验证技能解决的就是这个缺口。简单说它把“验证应用”这件事从人工执行的测试流程变成了 Agent 可以随时调用的技能定义好验证场景配置好验证脚本Agent 在完成开发或修改后能像真实用户一样打开应用、输入内容、点击操作、读取页面反馈并给出明确的通过或失败结论。这篇文章不打算停留在功能简介上而会从 Agent 验证能力的定位说起分析验证技能与普通测试脚本、MCP 工具的区别然后带你走一遍创建与维护验证技能的完整流程。即使你暂时不使用 pstack这套思路也能帮你想清楚一个关键问题Agent 在生产应用代码之后到底应该如何证明自己的产出可用。1. 为什么 Agent 需要“像真实用户一样验证”过去我们评价一个 Agent 的能力默认标准是“能不能把需求变成代码”。代码生成、单元测试补全、代码审查建议这些都是 Agent 的典型能力。但真实项目里的问题往往发生在代码之外登录按钮在移动端被遮挡、某个接口返回成功但页面没有刷新、权限控制在前端被跳过、不同浏览器下布局错位。这些问题单元测试覆盖不到静态分析发现不了只有真正在浏览器里实际操作一遍才能暴露。这就是“真实用户验证”的价值不是验证函数的返回值而是验证用户能感知到的页面行为。一个会写代码的 Agent如果能继续完成“打开页面 → 输入数据 → 点击操作 → 观察页面反馈 → 判断是否符合预期”这一整套动作它才算是真正对产出负责。pstack 新增的验证技能正是把这一整套动作封装成 Agent 可以理解、调用、组合的能力模块。与传统的自动化测试脚本相比它有三个明显变化从“测试人员编写用例”变成“技能创建者定义场景Agent 自动执行”。从“固定脚本跑固定用例”变成“Agent 根据当前任务上下文选择合适的验证技能”。从“执行完给出日志”变成“执行完给出结构化结论哪里通过、哪里失败、为什么失败”。对于正在做 AI 应用开发、Agent 平台建设的团队来说这个方向值得关注。它意味着 Agent 的闭环能力正在补齐过去 Agent 只负责“造”现在开始负责“验”。2. 验证技能的核心概念与定位要理解验证技能先要分清几个容易混淆的概念Agent、Skill技能、MCP 工具、自动化测试脚本。2.1 Agent 与 Skill 的关系Agent 是一个具备理解任务、规划步骤、调用工具、反思结果的智能体。Skill 则是 Agent 可以复用的能力单元相当于把完成某一类目标的流程固化下来。Agent 在需要时加载并执行 Skill而不是每次从零开始规划。验证技能就是 Skill 的一个具体类型。它的目标是验证某个应用功能是否符合预期。它与生成类型 Skill 的区别在于生成 Skill 的产出是代码、文档或配置而验证 Skill 的产出是“验证结论”包括通过状态、失败信息、截图、关键页面数据等。2.2 验证技能与 MCP 工具的区别MCPModel Context Protocol是 Agent 与外部工具通信的协议。一个 MCP 工具通常对应一次原子操作比如“读取网页内容”“调用某个 API”“执行一段代码”。它解决的是 Agent 能不能调用外部能力的问题粒度较细由 Agent 在规划中临时组合。验证技能则是一个面向完整目标的流程编排至少包含技能元数据名称、描述、输入参数、输出格式。执行脚本具体验证步骤。判断逻辑什么情况算通过什么情况算失败。异常处理超时、元素不存在、网络波动时如何处理。可以这样理解MCP 工具是 Agent 的“手和眼睛”验证技能是 Agent 的“操作手册和检查表”。没有 SkillAgent 也可以临时调用工具去验证但每次都要重新规划产出的结果也可能不稳定。有了 Skill验证过程变得可复用、可维护、可评估。对比维度MCP 工具验证技能Skill定位原子能力调用面向目标的能力流程粒度单次操作多步骤编排执行方式Agent 临时调用按预定义流程执行可复用性工具级复用场景级复用典型例子读取 URL 内容登录流程验证技能2.3 验证技能不是“又一套自动化测试框架”熟悉 Playwright、Selenium、Cypress 的开发者可能会问验证技能和这些自动化测试框架有什么区别区别不在底层技术上而在使用方式上。自动化测试框架服务的对象是“测试用例”由测试人员在本地或 CI 中主动触发。验证技能服务的对象是“Agent”是 Agent 执行任务链路中的一个专业环节。Agent 发现应用有改动自主决定调用验证技能根据执行结果决定下一步是继续修改还是完成任务。技能内部可以用 Playwright、Selenium也可以调用 HTTP 接口做轻量冒烟关键是它要能被 Agent 理解、加载和结果消费。因此验证技能的设计重点不只是脚本本身还要考虑元数据与输出结构。Agent 拿到技能执行结果后要能明白“验证未通过因为首页未出现预期横幅”而不是面对一堆原始日志。3. 验证技能的设计思路与典型执行流程验证技能看起来是“写一段自动化脚本”但真正决定它好不好用的是设计思路。3.1 从验证场景出发而不是从脚本出发创建验证技能的第一步不是写代码而是定义验证场景。一个场景要回答几个问题验证的对象是什么是某个页面、某个流程还是某个权限逻辑用户在真实使用时会经过哪些步骤每一步的预期结果是什么如何判断结果是否成立比如“登录验证”场景真实用户的操作路径是打开登录页 → 输入用户名和密码 → 点击登录 → 进入首页 → 页面显示当前用户信息。预期的判断条件包括登录后 URL 是否跳转、首页是否渲染出用户信息、是否出现错误提示。3.2 验证技能的典型执行流程一个验证技能在 Agent 中的执行流程通常包括Agent 根据任务上下文加载匹配的验证技能。解析技能声明的输入参数并向执行体传递。执行体启动验证环境执行验证脚本。脚本按照真实用户的操作路径与应用页面交互。采集关键页面状态、截图、接口数据。根据判断条件生成结构化结果。Agent 读取结果并决定下一步行动。这个流程里有两个容易被忽略的环节第 5 步和第 6 步。只有采集到足够的证据验证结论才可信只有结构化输出Agent 才能消费结果。否则验证技能就退化成“跑了一遍脚本但没有结论”的半成品。4. 创建验证技能从场景设计到技能注册下面以一个“登录与首页模块验证”技能为例走一遍创建流程。整体流程分为四步定义技能声明、编写验证脚本、注册技能、试运行。4.1 第一步定义技能声明技能声明文件用于描述技能的名称、用途、输入参数和运行方式。以 YAML 为例# wallet/skills/login_page_verification/skill.yaml name: login_page_verification description: 验证登录流程和登录后首页的展示是否符合预期 version: 1.0.0 inputs: base_url: type: string required: true description: 被测应用地址 username: type: string required: true description: 测试账号 password: type: string required: true description: 测试密码由安全配置注入不写死在技能中 outputs: status: type: string description: pass 或 fail messages: type: array description: 每步验证结果明细 screenshot: type: string description: 验证失败或关键步骤的截图路径 run: command: python run_verification.py --base-url {{base_url}} --username {{username}} --password {{password}} workdir: ./login_verification技能声明文件是 Agent 理解技能的关键。名称和描述要写得足够清晰便于 Agent 在匹配技能时做出正确选择。输入参数要明确哪些必需、哪些可选。运行命令采用占位符方式技能调用时由 Agent 或平台注入实际值。4.2 第二步编写验证脚本脚本是技能的执行核心。这里使用 Playwright 的 Python 版作为底层实现因为它能真实驱动浏览器符合“像真实用户一样验证”的需求。注意技术栈不限于 Playwright关键是对真实页面状态的验证。# wallet/skills/login_page_verification/run_verification.py import argparse import json from playwright.sync_api import sync_playwright def parse_args(): parser argparse.ArgumentParser(description登录与首页模块验证) parser.add_argument(--base-url, requiredTrue, help被测应用地址) parser.add_argument(--username, requiredTrue, help测试账号) parser.add_argument(--password, requiredTrue, help测试密码) return parser.parse_args() def verify_login(base_url, username, password): result { status: fail, messages: [], screenshot: , } with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page(viewport{width: 1440, height: 900}) try: page.goto(base_url, wait_untilnetworkidle, timeout15000) result[messages].append({step: 打开应用, status: pass}) page.get_by_role(link, name登录).click() page.get_by_label(用户名).fill(username) page.get_by_label(密码).fill(password) page.get_by_role(button, name登录).click() page.wait_for_url(**/dashboard, timeout10000) result[messages].append({step: 登录并跳转首页, status: pass}) welcome_locator page.locator(.user-welcome) welcome_text welcome_locator.inner_text(timeout5000) if username in welcome_text: result[messages].append({step: 首页展示用户信息, status: pass}) else: result[messages].append({ step: 首页展示用户信息, status: fail, actual: welcome_text, }) page.screenshot(pathverification_result.png, full_pageTrue) result[screenshot] verification_result.png result[status] pass except Exception as e: page.screenshot(pathverification_failed.png, full_pageTrue) result[screenshot] verification_failed.png result[messages].append({step: 执行异常, status: fail, error: str(e)}) finally: browser.close() return result if __name__ __main__: args parse_args() output verify_login(args.base_url, args.username, args.password) print(json.dumps(output, ensure_asciiFalse, indent2))这段脚本的关键逻辑在于每一步都记录独立的验证结果失败时不直接中断而是先截图保存现场证据再统一输出结论最终以 JSON 格式打印方便 Agent 解析。4.3 第三步注册技能技能目录和脚本准备好之后需要在 pstack 平台中进行注册。注册动作的目的是让 Agent 能够发现并加载该技能。注册时需要确认三件事技能名称是否唯一。输入参数描述是否准确。运行命令是否可以在目标环境中正常执行。注册完成后通常还需要做一次试运行。试运行建议使用测试环境避免直接操作生产环境。如果技能运行过程中出现了环境依赖问题此时是排查的最佳时机。4.4 第四步试运行试运行可以用脚本本身的命令行来验证这样可以先排除 Agent 平台侧的干扰cd wallet/skills/login_page_verification python run_verification.py \ --base-url http://localhost:8080 \ --username demo \ --password demo123如果脚本能在命令行正常跑通并输出 JSON 结果再回到 pstack 平台通过 Agent 触发一次完整调用。5. 完整示例技能内部如何加载配置与处理超时一个真正可维护的验证技能不应该把所有的选择器、超时时间、重试次数都硬编码在代码里。这里给出一个更工程化的示例把页面元素选择器抽离到 JSON 配置并加入超时重试逻辑。5.1 选择器配置抽离页面元素会随前端版本迭代而变化。如果把选择器写在脚本里前端元素一调整脚本就要改源码。抽离到 JSON 配置文件后维护技能时可以只改配置不用改代码。# wallet/skills/login_page_verification/selectors.json { login_link: rolelink[name登录], username_input: aria-label用户名, password_input: aria-label密码, login_button: rolebutton[name登录], dashboard_url: **/dashboard, user_welcome: .user-welcome }脚本中加载配置并使用配置中的选择器来完成操作。# wallet/skills/login_page_verification/run_verification_with_config.py import argparse import json import time from pathlib import Path from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError BASE_DIR Path(__file__).resolve().parent def load_config(filename): with open(BASE_DIR / filename, r, encodingutf-8) as f: return json.load(f) def parse_args(): parser argparse.ArgumentParser(description登录与首页模块验证配置化版本) parser.add_argument(--base-url, requiredTrue) parser.add_argument(--username, requiredTrue) parser.add_argument(--password, requiredTrue) parser.add_argument(--config, defaultselectors.json) return parser.parse_args() def retry_on_timeout(func, retries2, interval2): last_exc None for i in range(1 retries): try: return func() except PlaywrightTimeoutError as e: last_exc e if i retries: time.sleep(interval) raise last_exc def verify_login(base_url, username, password, selectors): result {status: fail, messages: [], screenshot: } with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() try: page.goto(base_url, wait_untilnetworkidle, timeout15000) result[messages].append({step: 打开应用, status: pass}) retry_on_timeout(lambda: page.click(selectors[login_link])) page.fill(selectors[username_input], username) page.fill(selectors[password_input], password) page.click(selectors[login_button]) page.wait_for_url(selectors[dashboard_url], timeout10000) result[messages].append({step: 登录并跳转首页, status: pass}) welcome_text page.locator(selectors[user_welcome]).inner_text(timeout5000) if username in welcome_text: result[messages].append({step: 首页展示用户信息, status: pass}) else: result[messages].append({ step: 首页展示用户信息, status: fail, actual: welcome_text, }) page.screenshot(pathverification_result.png, full_pageTrue) result[screenshot] verification_result.png result[status] pass except Exception as e: page.screenshot(pathverification_failed.png, full_pageTrue) result[screenshot] verification_failed.png result[messages].append({step: 执行异常, status: fail, error: str(e)}) finally: browser.close() return result if __name__ __main__: args parse_args() selectors load_config(args.config) output verify_login(args.base_url, args.username, args.password, selectors) print(json.dumps(output, ensure_asciiFalse, indent2))这个示例展示了两个工程化手段选择器配置化页面变更时优先改 selectors.json。超时重试使用retry_on_timeout处理页面加载不稳定导致的偶发超时提升验证技能在真实网络环境下的稳定性。5.2 运行验证执行命令与前一版类似python run_verification_with_config.py \ --base-url http://localhost:8080 \ --username demo \ --password demo123成功情况下返回结果大致如下{ status: pass, messages: [ { step: 打开应用, status: pass }, { step: 登录并跳转首页, status: pass }, { step: 首页展示用户信息, status: pass } ], screenshot: verification_result.png }可以看到Agent 拿到这份结果后不需要再做额外的日志分析就能直接判断当前应用是否符合预期。6. 维护验证技能页面变了技能不能“废”验证技能最容易遇到的坑不是第一次创建而是后续维护。应用页面只要改版一次技能里的选择器就可能失效。如果没有一套维护机制验证技能很快会变成“总是失败”的摆设。6.1 选择器变更优先于脚本变更维护的第一原则是尽量让选择器可配置把页面变更的影响范围控制在配置层。当页面调整了按钮的文案、输入框的 name 属性、页面的 URL 路径时通常只需要更新 selectors.json 或相关参数声明不需要重新编写脚本。6.2 建立“技能版本”概念验证技能应当有明确的版本。页面选择器更新、判断逻辑调整、运行环境变化都应该升级技能版本。Agent 在选择技能时如果能够感知版本信息就可以避免线上 Agent 还在使用旧版脚本。版本管理可以复用 git 等方式cd wallet/skills/login_page_verification git add skill.yaml run_verification_with_config.py selectors.json git commit -m fix(verify): 更新登录按钮选择器适配新版登录页 git tag v1.1.0技能脚本和平台代码一样需要版本管理。记录变更原因尤其要记录“页面发生了什么变化技能因此做了什么适配”。6.3 记录失败现场当验证技能失败时最重要的事情不是立刻修复而是把失败现场保存下来。常见的失败现场包括失败截图。页面 HTML 快照。当前页面 URL。执行的最后一步操作。控制台日志中的关键报错。没有这些信息维护者很难判断是技能本身的问题还是应用真的没有通过验证。更危险的情况是技能脚本因为选择器失效而误报失败实际上应用功能完全正常。这种“假失败”会让 Agent 不断尝试修复不存在的问题反而干扰开发流程。6.4 定期演练与回归验证技能不能创建完就不管了。更稳妥的方式是在 CI/CD 流程中定期跑一遍技能或者在测试环境每次部署后触发一次验证。这样技能本身是否仍然有效会得到持续反馈。否则等 Agent 真正需要调用技能时才发现技能已损坏就失去了验证的意义。7. 运行结果与效果评估验证技能运行成功并不代表验证结论一定可靠。要评估一个验证技能是否真正有效可以从以下四个维度来判断。7.1 可信度验证脚本是否真正模拟了用户操作还是只检查了静态标签比如验证登录只检查“登录按钮存在”是不够的要检查点击后是否真的跳转、页面是否出现当前用户信息。只有验证到用户可感知的结果结论才可信。7.2 稳定性同一个验证技能在相同环境下连续运行多次结果是否一致如果脚本因为网络波动、元素渲染延迟而频繁出现不稳定的失败Agent 就无法信任技能的结论。超时重试、等待策略、环境隔离都是提升稳定性的手段。7.3 可解释性失败时是否清楚说明了“哪一步失败、预期是什么、实际观察到什么”可解释的结果不仅方便排查也能帮助 Agent 更好地理解应用当前的状态从而决定下一步操作。7.4 可维护成本页面每次变更后技能需要多久才能恢复可用如果每次都要重写脚本技能的可维护成本就太高了。选择器抽离、参数化输入、版本化维护都能降低这个成本。7.5 验证失败时的第一步排查当验证技能运行失败时不要急着改代码按下面的顺序排查查看失败截图判断页面当前实际状态。对比预期和实际结果确认是应用问题还是验证脚本问题。检查选择器配置是否仍需更新。检查测试环境账号是否有效。检查运行环境依赖是否正常。8. 常见问题与排查思路根据日常使用经验验证技能最常见的几类问题如下。问题现象可能原因排查方式解决方案登录页元素找不到页面改版选择器失效查看失败截图检查页面 DOM更新 selectors.json 中的选择器验证超时页面加载慢或接口响应慢查看网络请求和接口耗时增大超时时间增加重试机制验证结果不稳定存在异步渲染未等待元素出现增加显式等待检查页面状态使用 wait_for 替代固定 sleep测试账号不可用账号被锁定、密码过期查看认证日志使用专门的测试账号并周期维护脚本在本地通过在 Agent 环境失败环境依赖缺失浏览器版本不一致对比本地与 Agent 运行环境统一运行时依赖使用容器化环境Agent 无法选择技能技能描述不清晰检查技能元数据 description优化技能描述补全关键场景词这里需要特别强调验证技能如果要在生产环境执行应遵循最小权限原则。建议使用独立的测试账号不要在生产环境使用真实用户账号如果验证任务涉及数据变更优先使用测试数据任何涉及敏感信息的注入都应通过安全配置管理不能直接明文写在技能描述或脚本中。9. 最佳实践与生产环境建议验证技能从能跑到能稳定用于生产还需要补上几件配置和工程化的事情。9.1 技能粒度一个场景一个技能不要试图一个技能验证所有功能。技能粒度越细复用性越高也越容易维护。登录验证、列表页验证、表单提交验证、权限控制验证应该分别创建独立技能。如果发现多个技能存在大量重复步骤可以考虑抽取公共执行库但技能本身的入口要保持独立。9.2 命名与描述要面向“Agent 检索”技能描述不只是给人看的也是给 Agent 做匹配判断的重要依据。命名建议使用“行为 对象”的格式比如login_page_verification、order_list_verification。描述要包含触发场景例如“当 Agent 需要验证登录流程或登录后首页展示时使用”。描述越清晰Agent 选择技能就越准确误配概率越低。9.3 安全与权限边界验证技能可能携带账号、密码、认证信息等敏感参数。以下几点需要特别注意密码等敏感参数优先通过安全配置或密钥管理注入不要写死在技能目录中。验证环境与生产环境隔离生产环境验证必须经过审批。技能执行日志可能包含敏感信息要对日志做脱敏处理。涉及数据库写入、删除操作的验证使用事务回滚或测试数据方案。9.4 观察与可观测性技能执行过程中应该有完整的可观测手段。除了页面的截图还可以采集接口请求记录、控制台错误、执行耗时等数据。这些数据既用于 Agent 决策也用于技能维护者持续评估技能质量。9.5 与 CI/CD 协同验证技能不应该只在 Agent 被触发时运行还应该纳入持续集成的回归体系。比如每日凌晨在测试环境运行一次核心验证技能变更页面后触发相关技能验证这些机制能提前发现技能的失效问题降低对 Agent 在线任务的影响。9.6 关注 Agent 对验证结果的利用最后也是容易被忽略的一点验证技能产出结果后Agent 会不会正确消费结果如果 Agent 拿到失败结果后只会重试一轮不会根据失败原因调整方案那么验证技能的价值就大打折扣。在设计 Agent 工作流时应明确失败分支的处理逻辑比如“当验证失败时根据失败信息回到对应步骤修复然后再跑同一技能做回归”。10. 结论验证技能的本质是让 Agent 为结果负责pstack 新增的创建与维护验证技能表面上是增加了一个自动化验证能力本质上是改变了 Agent 的工作方式。过去Agent 的产出以代码提交为终点现在Agent 可以继续执行验证用真实用户视角检查应用得到一个明确的通过或失败结论。这种变化让 Agent 从“生成者”变成了“生成并负责验证者”。对于正在搭建 AI 应用开发流程、Agent 平台或自动化验证体系的团队建议从一个小场景开始选取一个高频、稳定、用户操作路径清晰的模块先创建第一个验证技能再逐步扩展。重点不在于脚本写得多么复杂而在于技能声明清晰、验证结果结构化和维护成本可控。验证技能真正沉淀下来的价值是可复用的验证经验。它把团队对某个应用的理解、对典型用户路径的梳理、对关键判断条件的定义固化成一个 Agent 可以直接调用的资产。当这类资产积累得足够多时Agent 应用开发的验证短板才会真正被补齐。