AI Agent驱动APP自动化测试:从自然语言需求到智能执行
1. 项目概述:当AI成为你的专属测试工程师
最近在搞APP测试的朋友,应该都体会过那种重复、枯燥但又必须保证覆盖率的痛苦。点这里、点那里,录脚本、跑用例、看日志、报Bug……一套流程下来,人累得够呛,效率还上不去。我干了这么多年移动端测试,对这种“人肉测试机”的状态深恶痛绝。直到前段时间,我结合手头几个热门的AI工具和自动化框架,折腾出了一个东西——我把它叫做“APP自动化测试Skill”。
这玩意儿不是什么全新的测试框架,而是一个基于现有成熟工具(比如Appium、Playwright)和AI大模型(如Claude Code、Cursor)构建的“智能测试代理”。它的核心思路是:把测试用例的自然语言描述、甚至是模糊的业务需求,直接交给AI去理解和执行,让AI来充当那个不知疲倦、且具备一定“思考”能力的测试执行工程师。简单说,就是你想测什么,用大白话告诉它,它就能自己去写脚本、跑起来、然后给你一份带截图和问题分析的测试报告。
听起来是不是有点“AI替你打工”那味儿了?这背后其实是“AI Agent”(智能体)概念在测试领域的一次落地尝试。我结合了网络热词里提到的Claude Code Skill、AI编程、自动化测试框架等元素,目标是让测试人员,尤其是那些对编码不那么熟悉的朋友,也能轻松驾驭复杂的自动化测试。无论是测四大银行虚拟仿真app这样的金融应用,还是鸿蒙app开发小项目,甚至是应对自动化测试面试题里提到的各种场景,这个Skill都试图提供一种更智能的解决方案。
2. 核心设计思路:从“写脚本”到“说需求”的范式转变
传统的自动化测试,无论是用Appium、Selenium还是Playwright,核心流程都是:人工分析需求 -> 人工编写测试脚本 -> 配置环境执行 -> 人工分析结果。这个过程中,“编写测试脚本”是最大的门槛和耗时点,它要求测试人员具备良好的编程能力和对测试框架的深入理解。
我这个“APP自动化测试Skill”的设计,就是要打破这个模式。它的核心思路是引入一个“AI翻译层”和“决策执行层”。
2.1 架构拆解:三层智能测试引擎
整个Skill的架构可以粗略分为三层:
自然语言理解与规划层:这是大脑。我主要集成了类似
Claude或通义灵码这类具备强大代码理解和生成能力的AI。当你输入“测试一下用户登录功能,用错误密码试试,再检查登录成功后的首页元素”这样的指令时,这一层负责将模糊的自然语言需求,拆解成具体的、可执行的测试步骤序列。它会理解“用户登录”对应哪个页面、“错误密码”是哪个测试数据、“首页元素”具体指哪些控件。脚本生成与适配层:这是双手。根据规划层输出的步骤序列,以及你预先配置好的项目信息(如APP包名、首页Activity、控件定位策略等),这一层会自动生成可执行的测试代码。这里没有重新发明轮子,而是基于成熟的
Python + Playwright for Android/iOS或Appium框架来生成脚本。它的智能体现在能根据上下文,选择最合适的定位方式(如ID、XPath、文本),并生成健壮的等待和断言逻辑。执行调度与反馈层:这是双腿。负责调用真实的设备或模拟器,执行生成的脚本,并监控执行过程。关键的是,它不止是跑完拉倒,还会在关键步骤自动截图,收集日志(Logcat、Console)。当测试失败时,它能将错误信息、上下文截图再次反馈给AI理解层,让AI尝试分析失败原因(是脚本问题、环境问题还是真正的Bug),甚至尝试自我修复脚本或给出明确的排查建议。
注意:这里完全避开了任何涉及网络代理工具的内容。所有AI交互均基于合规的、可公开访问的API(如OpenAI API、国内大模型API)或本地化部署的大模型来完成,确保整个流程在安全、合规的环境下进行。
2.2 为什么选择Playwright + AI这个组合?
在热词里看到了Appium、Selenium、Playwright,甚至还有2026年跨平台自动化测试工具这样的未来概念。我选择以Playwright为主要执行底座,是经过一番考量的。
- 跨端能力:Playwright不仅支持Web,对移动端(Android、iOS)的原生应用和混合应用测试支持也越来越成熟和稳定。一套语法几乎能通吃,这符合“多终端统一测试”的趋势。
- 稳定性与智能等待:Playwright内置的自动等待机制减少了大量编写
time.sleep的麻烦,让AI生成的脚本更稳定,不容易因为网络或渲染延迟而失败。 - 丰富的录制与调试工具:Playwright Test提供了强大的录制、追踪(Trace)和调试功能。当AI生成的脚本出现问题时,我可以利用这些工具快速定位,并把问题场景(Trace文件)作为上下文反馈给AI,让它学习修正。
而AI部分,我没有局限于某一个模型。实践中,我采用了一种“分工协作”的思路:用Claude或GPT-4这类长文本、强推理模型做需求分析和步骤规划;用Cursor或通义灵码这类深度集成IDE、更懂项目上下文的工具来做具体的代码生成和补全。这比单纯依赖一个模型效果要好得多。
3. 实操搭建:手把手构建你的AI测试Skill
光说不练假把式。下面我就以测试一个简单的安卓计算器APP为例,拆解如何从零搭建这个Skill的核心部分。我们会用到Python、Playwright for Android、以及OpenAI的API(作为AI大脑示例)。
3.1 环境准备与基础框架搭建
首先,你需要一个Python环境(建议3.8+),以及一台可供调试的安卓设备或模拟器。
# 1. 创建项目目录并初始化虚拟环境 mkdir ai-test-skill && cd ai-test-skill python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate # 2. 安装核心依赖 pip install playwright openai pytest # 安装Playwright的浏览器和Android驱动 playwright install android接下来,初始化一个基础的测试项目结构。我建议使用pytest作为测试运行器,它比unittest更灵活,报告也更美观。
# 项目结构大致如下 ai-test-skill/ ├── requirements.txt ├── conftest.py # pytest全局配置,设备初始化等 ├── ai_core.py # AI交互的核心模块 ├── skill_engine.py # 技能引擎,协调AI规划与脚本执行 ├── tests/ │ ├── __init__.py │ └── test_calculator_ai.py # 由AI生成的测试用例示例 └── artifacts/ # 存放测试报告、截图、日志conftest.py里,我们配置好Playwright连接的安卓设备:
# conftest.py import pytest from playwright.sync_api import Browser, Page, sync_playwright @pytest.fixture(scope="session") def android_device(): """连接到安卓设备或模拟器""" playwright = sync_playwright().start() # 这里假设你已通过`playwright install android`安装了驱动,且设备已通过adb连接 # 更稳妥的方式是使用设备序列号 device = playwright.devices['Pixel 5'] # 或使用具体的设备描述 browser = playwright.chromium.launch() context = browser.new_context(**device) page = context.new_page() yield page context.close() browser.close() playwright.stop()3.2 AI核心模块:让机器听懂人话
ai_core.py是这个Skill的大脑。它负责与AI大模型对话,将测试需求转化为结构化指令。
# ai_core.py import openai import json import os from typing import List, Dict, Any class AITestPlanner: def __init__(self, api_key: str, model: str = "gpt-4"): openai.api_key = api_key # 注意:实际使用请从环境变量读取,避免硬编码 self.model = model # 定义系统角色,让AI扮演一个资深的测试自动化专家 self.system_prompt = """你是一个资深的移动应用测试自动化专家。你的任务是将用户用自然语言描述的测试需求,转化为详细、可执行、符合Playwright for Android语法的测试步骤序列。 输出必须是一个严格的JSON数组,每个元素是一个步骤对象。步骤对象包含以下字段: - `action`: 操作类型,如 `launch_app`, `tap`, `input_text`, `assert_text`, `swipe`, `back`, `screenshot`等。 - `target`: 操作目标描述,用于后续生成定位器。如“登录按钮”、“用户名输入框”。 - `value`: 可选,操作需要的值。如输入文本、预期的断言文本。 - `comment`: 该步骤的注释或说明。 请根据Android应用常见交互进行合理推断。如果需求模糊,请基于常见应用逻辑进行补充。""" def plan_test_steps(self, requirement: str, app_info: Dict[str, Any]) -> List[Dict]: """将测试需求转化为步骤计划""" user_prompt = f""" 测试需求:{requirement} 应用信息:{app_info} 请生成测试步骤序列。 """ try: response = openai.ChatCompletion.create( model=self.model, messages=[ {"role": "system", "content": self.system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.2, # 低温度,保证输出稳定性 ) content = response.choices[0].message.content # 清理可能出现的markdown代码块标记 if content.startswith('```json'): content = content[7:] if content.endswith('```'): content = content[:-3] steps = json.loads(content.strip()) return steps except json.JSONDecodeError as e: print(f"AI返回的JSON解析失败: {e}") print(f"原始返回内容: {content}") # 可以在这里加入重试或降级逻辑 return [] except Exception as e: print(f"调用AI API失败: {e}") return [] def generate_playwright_code(self, steps: List[Dict], page_var_name: str = "page") -> str: """将步骤计划转化为Playwright Python代码""" code_lines = ["# 由AI生成的测试脚本", "import pytest", ""] for i, step in enumerate(steps): action = step.get('action') target = step.get('target', '') value = step.get('value', '') comment = step.get('comment', '') code_lines.append(f" # 步骤{i+1}: {comment}") if action == 'launch_app': # 这里需要你事先在conftest中配置好app启动,或者使用`page.context.new_page()`方式 # 简化处理,假设app已启动 code_lines.append(f' # 应用已在fixture中启动') elif action == 'tap': # 这是一个难点:如何将`target`描述转化为定位器? # 初级方案:假设我们有一个映射表,或让AI生成更具体的定位提示(如“包含文本‘登录’的按钮”) # 这里我们生成一个通用模板,后续需要“定位器解析器”来完善 code_lines.append(f' {page_var_name}.locator(\"text={target}\").first.click() # 可能需要调整定位策略') elif action == 'input_text': code_lines.append(f' {page_var_name}.locator(\"text={target}\").first.fill(\"{value}\")') elif action == 'assert_text': code_lines.append(f' assert {page_var_name}.locator(\"text={value}\").first.is_visible()') elif action == 'screenshot': code_lines.append(f' {page_var_name}.screenshot(path=f\"artifacts/step_{i+1}.png\")') code_lines.append("") # 空行分隔 # 将代码片段包装成一个pytest测试函数 function_code = f"\ndef test_ai_generated({page_var_name}):\n" + "\n".join(f" {line}" if line.strip() and not line.startswith('#') else line for line in code_lines) return function_code这个AITestPlanner类做了两件事:plan_test_steps把自然语言需求变成结构化的步骤列表;generate_playwright_code则把这个列表转换成初步的Python代码。注意,这里的代码生成是“粗糙”的,因为它无法精确知道控件的唯一定位器。这是当前AI在测试领域落地的核心挑战之一。为了解决它,我引入了下一个关键模块。
3.3 技能引擎与定位器智能解析
skill_engine.py是协调中心,它还有一个重要职责:结合运行时上下文,优化AI生成的定位器。
# skill_engine.py import subprocess import tempfile import os from ai_core import AITestPlanner from playwright.sync_api import Page class TestSkillEngine: def __init__(self, ai_planner: AITestPlanner, app_package: str, app_activity: str = None): self.ai_planner = ai_planner self.app_package = app_package self.app_activity = app_activity self.locator_strategy_priority = ['text', 'role', 'id', 'xpath'] # 定位策略优先级 def execute_requirement(self, requirement: str, page: Page): """核心执行流程:需求 -> AI规划 -> 代码生成与优化 -> 执行""" print(f"处理需求: {requirement}") # 1. AI规划步骤 app_info = {"package": self.app_package, "activity": self.app_activity} steps = self.ai_planner.plan_test_steps(requirement, app_info) if not steps: print("AI规划失败,退出。") return print(f"AI生成{len(steps)}个步骤。") # 2. 动态优化步骤中的定位器(关键!) optimized_steps = self._optimize_locators(steps, page) # 3. 生成最终可执行代码(或直接解释执行) # 这里演示直接解释执行优化后的步骤,更灵活 self._interpret_and_execute_steps(optimized_steps, page) def _optimize_locators(self, steps: List[Dict], page: Page) -> List[Dict]: """尝试优化步骤中的定位器描述。 这是一个简化版。理想情况下,可以: a) 利用Playwright的`page.locator(selector).all()`获取所有匹配元素,结合AI描述选择最可能的一个。 b) 使用OCR识别屏幕文本,辅助定位。 c) 维护一个页面元素映射库(Page Object Model)。 """ optimized = [] for step in steps: opt_step = step.copy() target_desc = step.get('target', '') # 如果target是明确的文本,我们可以直接使用 if target_desc and '按钮' in target_desc: # 尝试提取按钮文本,例如从“登录按钮”提取“登录” possible_text = target_desc.replace('按钮', '').strip() # 检查页面上是否存在这个文本 if page.locator(f'text={possible_text}').count() > 0: opt_step['_resolved_locator'] = f'text={possible_text}' else: # 如果找不到,回退到原始描述,执行时可能需要更复杂的逻辑 opt_step['_resolved_locator'] = target_desc else: opt_step['_resolved_locator'] = target_desc optimized.append(opt_step) return optimized def _interpret_and_execute_steps(self, steps: List[Dict], page: Page): """解释并执行优化后的步骤序列""" for i, step in enumerate(steps): action = step.get('action') locator = step.get('_resolved_locator', step.get('target', '')) value = step.get('value', '') print(f"执行步骤{i+1}: {action} -> {locator}") try: if action == 'tap' or action == 'click': # 使用优化后的定位器 page.locator(locator).first.click() elif action == 'input_text': page.locator(locator).first.fill(value) elif action == 'assert_text': assert page.locator(f'text={value}').first.is_visible(), f"未找到文本: {value}" elif action == 'screenshot': page.screenshot(path=f"artifacts/step_{i+1}_{action}.png") # ... 其他action处理 page.wait_for_timeout(500) # 步骤间短暂等待,观察效果 except Exception as e: print(f"步骤{i+1}执行失败: {e}") page.screenshot(path=f"artifacts/error_step_{i+1}.png") # 这里可以加入失败处理逻辑,比如尝试备用定位器,或者记录错误后继续 # 对于关键步骤,可以选择break break这个引擎的关键在于_optimize_locators方法。它试图在运行时将AI生成的模糊描述(如“登录按钮”)与当前页面上的实际元素进行匹配。这是一个持续优化的过程,你可以通过集成OCR、维护控件资源ID白名单、甚至让AI学习分析UI层级结构(通过page.locator('*').all_inner_texts()获取)来不断增强它的解析能力。
3.4 编写你的第一个AI驱动测试用例
现在,我们把所有部分组合起来。在tests/test_calculator_ai.py中:
# tests/test_calculator_ai.py import sys import os sys.path.append(os.path.dirname(os.path.dirname(__file__))) from skill_engine import TestSkillEngine from ai_core import AITestPlanner def test_ai_calculator(android_device): # 1. 启动被测应用(这里以系统计算器为例,包名可能不同) # 更优的方式是通过`android_device.context.new_page()`连接到一个已启动的app # 这里简化处理,假设页面已经指向了计算器APP page = android_device # 你可以通过ADB命令启动特定App # subprocess.run(['adb', 'shell', 'am', 'start', '-n', 'com.android.calculator2/.Calculator']) # 2. 初始化AI规划器和技能引擎 # 注意:请将YOUR_OPENAI_API_KEY替换为你的实际API Key,或从环境变量读取 api_key = os.getenv("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY") planner = AITestPlanner(api_key=api_key, model="gpt-3.5-turbo") # 先用3.5测试,成本低 engine = TestSkillEngine(ai_planner=planner, app_package='com.android.calculator2') # 3. 定义你的测试需求(用自然语言!) test_requirement = """ 测试安卓计算器应用的基本运算功能。 1. 先点击清除按钮(如果有的话),确保计算器归零。 2. 输入数字 12。 3. 点击加法按钮 '+'。 4. 输入数字 34。 5. 点击等号按钮 '='。 6. 验证结果区域是否显示正确的结果 46。 7. 在点击等号后截图保存。 """ # 4. 让AI技能引擎去执行! engine.execute_requirement(test_requirement, page) # 5. 断言可以放在引擎内部,也可以在这里补充。 # 因为我们在需求中已经包含了验证步骤,引擎会执行assert。 # 这里可以添加一些最终的全局断言 # assert page.locator("text=46").is_visible()运行这个测试:
pytest tests/test_calculator_ai.py -v如果一切顺利,你会看到AI规划出的步骤被逐个执行,控制台有日志输出,并且在artifacts文件夹下会生成每一步的截图。这就是“AI替你打工”的雏形:你只需要用中文描述想测什么,剩下的脚本生成、定位适配、执行和验证,都由这个Skill来尝试完成。
4. 避坑指南与效能提升实战
理想很丰满,但第一次跑通后,你肯定会遇到各种问题。下面是我在开发这个Skill过程中踩过的坑和总结的优化技巧。
4.1 定位器解析:从“玄学”到“科学”
问题:AI生成的“登录按钮”,在代码里写成page.locator("text=登录按钮"),但实际APP里这个按钮的文本可能就是“登录”,或者它根本没有文本,只有一个图标。直接执行必然失败。
解决方案:建立多级回退的定位器解析策略。
- 首选精确匹配:尝试用AI描述中的核心词(如“登录”)进行文本定位。
- 角色与属性回退:如果文本定位失败,尝试通过角色(
role=button)结合其他属性(如aria-label,content-desc)来定位。这需要你获取APP的UI层级信息。可以通过page.locator('*').all()遍历并打印元素的属性来构建知识库。 - 坐标点击(最后手段):对于某些极度动态或难以定位的元素,在特定屏幕分辨率下,可以记录相对坐标。但这种方法非常脆弱,不推荐作为主要方案。
- 让AI学习你的APP:在系统提示词(
system_prompt)里,加入你APP特有的控件描述。例如:“该应用的登录按钮的resource-id是com.example.app:id/btn_login,或者其文本是‘Sign In’。” AI在生成步骤时,会倾向于使用这些已知信息。
我在skill_engine.py的_optimize_locators方法里实现了简单的文本提取和匹配,但这只是起点。一个更高级的实现是,在第一次成功执行某个流程后,将(页面状态描述, 目标描述, 成功定位器)作为样本保存下来,形成一个不断增长的定位器映射库,供后续相似需求直接使用或供AI参考。
4.2 处理动态内容与异步加载
问题:AI生成的脚本是线性的,click()之后立刻assert,但页面可能还在加载,导致断言失败。
解决方案:在AI生成代码或引擎解释执行时,强制注入等待逻辑。
- 隐式等待:Playwright本身有自动等待,但对于网络请求或复杂动画,可能需要更长时间。可以在引擎执行每个
click、fill操作后,添加一个针对下一个预期元素出现的显式等待。# 在执行`click`后,不是直接执行下一步,而是: page.locator(locator).first.click() # 等待下一个操作的目标元素出现,或者等待一个网络请求完成 page.wait_for_selector(next_locator, state="visible", timeout=10000) - 让AI感知“等待”:在给AI的系统提示词中明确说明:“在可能引起页面跳转或内容刷新的操作(如点击提交按钮、跳转标签页)后,应添加等待页面稳定或特定元素出现的步骤。” AI在规划时就会主动插入
sleep或wait_for_selector步骤。
4.3 测试数据管理与场景扩展
问题:测试需求里说“用错误密码试试”,AI知道要输入错误密码,但“错误密码”具体是什么?是固定的“wrong”,还是需要从数据池里随机取一个?
解决方案:将测试数据与测试逻辑分离,并让AI学会调用数据池。
- 建立测试数据文件:创建一个
test_data.json或data_pool.py,定义如invalid_passwords = ["", "123", "wrongpassword", "admin123"]。 - 增强AI提示词:告诉AI:“当需要测试数据时,请使用
data_pool.get_invalid_password()这样的函数调用来表示,而不是硬编码具体值。” - 引擎后处理:在
_interpret_and_execute_steps阶段,检测到步骤的value字段包含特定的函数调用标记(如{{get_invalid_password}}),就将其替换为从数据池中取出的真实值。
这样,你的一句“用5个不同的无效用户名和密码组合测试登录”,AI就能规划出循环结构,并调用数据池,生成覆盖更广的测试。
4.4 报告生成与失败分析智能化
问题:测试失败了,是脚本问题、环境问题还是真的Bug?只看日志和截图,判断起来费时费力。
解决方案:让Skill不仅会执行,还要会初步分析。
- 丰富执行上下文:在每次测试执行时,除了截图,额外捕获:
- 当前页面源码(
page.content())或可访问的UI树结构。 - 控制台日志(
page.on('console', ...))。 - 网络请求列表(如果权限允许)。
- 当前页面源码(
- 失败时调用AI诊断:当
assert失败或发生异常时,将错误信息、最后一张截图、以及之前的页面状态描述,打包发送给AI(可以换用一个更擅长分析的模型,如GPT-4)。- 提示词示例:“以下是一个自动化测试失败的场景。错误信息是
AssertionError: Element not found。这是失败前的屏幕截图描述[截图描述]。这是测试试图执行的操作:点击‘提交’按钮。请分析可能的原因,并按可能性排序:A. 定位器不准确;B. 页面未加载完成;C. 元素被遮挡;D. 应用出现了Bug(如按钮不可点击)。请给出下一步排查建议。”
- 提示词示例:“以下是一个自动化测试失败的场景。错误信息是
- 自动生成诊断报告:将AI的分析结果,连同原始的执行日志和截图,整合成一份HTML报告。这份报告不仅能告诉你“失败了”,还能给出“可能是什么原因”和“接下来该看哪里”的建议,极大提升排查效率。
5. 进阶构想:从Skill到自主测试Agent
目前这个Skill还处于“你指挥,它执行”的阶段。结合热词中的AI Agent概念,我们可以展望它的进化形态:
目标驱动测试:你不再需要描述步骤,只需要给出一个业务目标,如“确保用户从注册到支付下单的全流程畅通”。Agent自己会去探索APP,识别出关键路径(注册、登录、浏览商品、加入购物车、支付),并自动生成覆盖这些路径的测试用例集,甚至尝试探索边界情况(如支付时断网)。
视觉理解强化:集成
pytesseract或EasyOCR等OCR库,以及轻量级的图像识别模型,让Agent能真正“看到”屏幕,理解那些无法通过无障碍服务获取的UI元素(如自定义图形按钮、验证码等)。这能从根本上解决动态定位的难题。自学习与自适应:Agent在测试过程中,会记录哪些定位器是稳定的,哪些是脆弱的。对于脆弱的定位器,它会尝试寻找更稳定的替代方案(如用相对坐标组合其他属性),并将这个经验更新到自己的知识库中。下次遇到类似页面,它会优先使用更优方案。
与开发流程集成:这个Skill可以作为一个
GitHub Action或GitLab CI/CD的环节。每当开发提交新代码,Agent自动拉取最新的APK/IPA,运行核心场景的自动化测试,并将智能分析后的报告直接评论在Merge Request上。这实现了真正的“AI测试左移”。
实现这些进阶功能需要更多的工程工作和算法集成,但核心思想不变:将测试人员从重复、低价值的脚本编写和执行中解放出来,让他们专注于更高级的测试设计、质量分析和风险评估。让AI去处理那些它擅长的模式识别、重复劳动和初步分析,让人去做需要创造力和深度思考的工作。这才是“AI替你打工”的真正意义。
这条路还很长,我分享的这个Skill只是一个起点。它可能还不完美,运行中会遇到各种奇怪的失败,但每一次调试和优化,都是让测试工作变得更智能、更高效的一步。如果你也在做类似的事情,或者有更好的想法,欢迎一起交流。毕竟,让机器干机器该干的活,我们才能去做更有意思的事。
