用一点APP Agent的能力,让我们的自动化测试进步一点点
关注+评论 可以私信我领取完整示例代码
在移动APP自动化测试领域,“跨端兼容”始终是中小企业测试团队绕不开的痛点。随着大模型技术的爆发,APP Agent凭借其强大的环境感知和自主决策能力,为解决这一问题提供了新的思路。最近开源的Open-AutoGLM便是其中的典型代表,它基于大模型构建的APP自动化交互框架,让“AI自主完成APP操作”成为可能。但对于资源有限的中小企业而言,直接本地部署完整的AI Agent成本高、门槛高,投入回报率往往不尽如人意。
那么,我们是否可以换个思路?不追求完整的AI Agent部署,而是从其中提取核心能力,赋能我们现有的自动化测试框架?基于这个想法,我尝试从Open-AutoGLM中提炼出“大模型识别目标转化为坐标”的能力,结合团队正在使用的Airtest纯视觉方案,有效突破了跨端兼容性的瓶颈。本文将详细拆解这一实现过程,分享如何用一点点APP Agent的能力,让自动化测试框架实现质的提升。
话不多说 先上效果展示
一、先认识下Open-AutoGLM
Open-AutoGLM是近期开源的一款基于大语言模型(LLM)的移动端APP自动化交互框架,核心目标是实现“AI驱动的自主APP操作”。它区别于传统自动化框架的核心优势在于:具备视觉感知、自然语言理解和自主决策能力。
传统自动化测试框架(如Airtest、Appium)需要测试人员预先定义好操作坐标或元素属性,而Open-AutoGLM可以通过图像识别+自然语言指令,自主理解当前APP界面状态,规划操作步骤,并生成对应的交互指令。例如,你只需下达“打开XXAPP并登录”的自然语言指令,框架就能自主完成启动APP、识别登录按钮、输入账号密码、点击登录等一系列操作,无需手动编写繁琐的定位脚本。
这种“端到端自主交互”的能力,正是APP Agent的核心价值。但对于中小企业测试团队来说,要将其本地部署并落地应用,面临着诸多挑战:
-
算力成本高:完整的APP Agent需要大模型提供算力支持,本地部署大模型需要高性能GPU,硬件投入动辄数万元,对中小企业来说压力较大;
-
技术门槛高:部署过程涉及大模型微调、环境配置、框架适配等多个环节,需要专业的算法和开发人员,普通测试团队难以驾驭;
-
维护成本高:APP Agent需要持续适配不同APP版本、不同设备型号的界面变化,后续维护需要投入大量人力;
-
投入回报率低:中小企业的测试需求多集中在核心业务流程的自动化验证,完整的APP Agent能力存在“过度冗余”,投入大量资源后,实际提升的测试效率有限。
二、换个思路:提取核心能力,赋能现有框架
既然完整部署APP Agent不现实,那我们不妨“取其精华”——从APP Agent的核心能力中,提取出能解决当前测试痛点的部分,与现有框架结合。
我们团队当前使用的是Airtest的纯视觉方案,通过图像识别和OCR文字识别实现自动化操作。这种方案的优势是上手快、无需依赖APP源码,但短板也很明显:跨端兼容性差。同一操作脚本,在华为手机上能正常运行,在小米、OPPO手机上可能因为界面元素位置偏移、图标样式差异,导致图像识别失败,需要针对不同机型单独维护脚本,测试成本极高。
而APP Agent的核心能力之一——“基于视觉理解的目标定位”,恰好能解决这个问题。它不依赖预先定义的图像模板,而是通过大模型对当前界面进行视觉分析,识别出目标元素(如按钮、输入框),并生成对应的坐标。如果能将这个能力提取出来,提供给Airtest框架,让Airtest根据大模型生成的坐标执行操作,就能有效突破跨端兼容性的瓶颈。
基于这个思路,我设计了一套实现方案:以Airtest为基础执行框架,集成智谱GLM-4.6v-flash大模型(通过在线API调用,避免本地部署大模型的成本),让大模型负责“识别界面目标并生成坐标”,Airtest负责“根据坐标执行具体操作”。下面结合代码,详细拆解这一方案的实现逻辑。
三、核心代码思路解析
本次实现的核心代码分为两个模块:config.py(配置模块)和GLMApi.py(核心能力模块)。前者负责路径管理和平台判断,后者负责实现“截图-大模型识别-坐标提取-坐标转换”的完整流程。下面分别拆解关键代码逻辑。
3.1 配置模块:config.py
该模块的核心作用是:统一管理测试过程中的各类路径(如日志路径、截图路径),并提供当前连接设备的平台判断能力,为后续的跨端适配打下基础。
关键代码解析:
# -*- coding: gbk -*-from airtest.core.api import *from airtest.aircv import *from airtest.core.android.android import Androidfrom airtest.core.ios.ios import IOS# 基础路径配置:获取当前项目根目录prj_path = os.getcwd()# 各类路径定义:统一管理,便于后续维护log_path = os.path.join(prj_path, 'log') # 测试log保存路径log_file_path = os.path.join(prj_path, 'log', 'log.txt') # log.txt保存路径report_path = os.path.join(prj_path, 'report') # 测试报告保存路径test_case_path = os.path.join(prj_path, 'testCases') # 测试用例保存路径# 截图路径:按平台(Android/IOS)分类保存,便于后续针对性处理target_android_path = os.path.join(prj_path, 'Snapshot', 'target', 'Android')target_ios_path = os.path.join(prj_path, 'Snapshot', 'target', 'IOS')current_android_path = os.path.join(prj_path, 'Snapshot', 'current', 'Android')current_ios_path = os.path.join(prj_path, 'Snapshot', 'current', 'IOS')# 平台判断函数:获取当前连接设备的操作系统(Android/IOS)def get_current_platform():"""返回当前连接的手机操作系统。:return: Android或IOS"""device = G.DEVICE # Airtest全局设备对象,代表当前连接的设备if isinstance(device, Android):return "Android"elif isinstance(device, IOS):return "IOS"else:raise ValueError("unrecognized platform")核心亮点:
-
路径规范化:通过
os.path.join拼接路径,确保在不同操作系统(Windows、Mac)下都能正常运行;按“目标截图”和“当前截图”分类,按平台(Android/IOS)细分,便于后续图像管理和处理。 -
平台自适应:通过
get_current_platform函数,基于Airtest的全局设备对象G.DEVICE判断当前连接的设备类型,为后续“按平台保存截图”“按平台适配操作”提供依据。
3.2 核心能力模块:GLMApi.py
该模块是整个方案的核心,集成了“截图生成、大模型调用、坐标提取、坐标转换”四大核心功能,实现了“从界面视觉识别到可执行坐标”的完整链路。下面分模块解析关键代码。
3.2.1 基础依赖与全局配置
# -*- coding: gbk -*-import reimport astimport jsonimport base64from airtest.core.android.adb import ADBfrom json_repair import repair_jsonfrom zhipuai import ZhipuAI # 智谱AI SDK,用于调用GLM-4.6v-flash模型from datetime import datetimefrom config import * # 导入配置模块的路径和平台判断函数# 完整截图函数:根据当前平台保存截图def complete_snapshot(img_name):"""手机完整截图,根据手机操作系统保存在对应路径:param img_name:图片名(不要有中文):return:保存路径"""platform = get_current_platform() # 获取当前平台# 按平台选择截图保存路径if platform == "Android":pic_path = os.path.join(current_android_path, img_name)elif platform == "IOS":pic_path = os.path.join(current_ios_path, img_name)else:pic_path = os.path.join(current_android_path, img_name) # 默认Android路径G.DEVICE.snapshot(pic_path) # 调用Airtest的截图接口生成截图return pic_path核心作用:
-
依赖引入:导入
zhipuaiSDK用于调用智谱大模型,导入base64用于图像编码(大模型API要求图像以base64格式传输),导入json_repair用于修复大模型返回的不规范JSON数据。 -
统一截图入口:
complete_snapshot函数结合配置模块的get_current_platform,实现了“按平台自动保存截图”的功能,后续所有截图操作都通过该函数执行,确保路径统一。
3.2.2 大模型封装:GLMModel类
该类封装了智谱GLM-4.6v-flash模型的调用逻辑,核心负责“将截图(base64格式)+ 文本指令发送给大模型,获取模型返回的操作指令”。
class GLMModel:def __init__(self):self.model_name = "glm-4.6v-flash" # 调用的智谱大模型版本self.client = ZhipuAI(api_key="") # 初始化智谱客户端,需填入自己申请的API keydef _try_parse_json_object(self, input):"""JSON清理和格式化工具:修复大模型返回的不规范JSON数据"""result = Nonetry:result = json.loads(input)except json.JSONDecodeError:print("Warning: Error decoding faulty json, attempting repair")if result:return input, result# 提取JSON字符串(去除多余描述)_pattern = r"\{(.*)\}"_match = re.search(_pattern, input)input = "{" + _match.group(1) + "}" if _match else input# 清理JSON字符串中的特殊字符input = (input.replace("{{", "{").replace("}}", "}").replace('"[{', "[{").replace('}]"', "}]").replace("\\", " ").replace("\\n", " ").replace("\n", " ").replace("\r", "").strip())# 去除Markdown代码块标记if input.startswith("```"):input = input[len("```"):]if input.startswith("```json"):input = input[len("```json"):]if input.endswith("```"):input = input[: len(input) - len("```")]# 尝试修复并解析JSONtry:result = json.loads(input)except json.JSONDecodeError:json_info = str(repair_json(json_str=input, return_objects=False))try:result = json.loads(json_info)except json.JSONDecodeError:print("error loading json, json=%s", input)return json_info, {}return input, resultdef process_image(self, img_path, text_input, web_search=False):"""核心方法:处理图像和文字输入,调用大模型获取响应:param img_path: 图像文件路径:param text_input: 文本指令(如“收起小键盘”):param web_search: 是否开启联网搜索(此处无需开启):return: 大模型返回的处理结果"""# 1. 读取图像并转换为base64格式(大模型API要求)with open(img_path, 'rb') as img_file:img_base = base64.b64encode(img_file.read()).decode('utf-8')# 2. 构造请求参数:系统提示词(已在全局定义)+ 图像 + 文本指令messages = [{"role": "system", "content": SYSTEM_PROMPT},{"role": "user","content": [{"type": "image_url","image_url": {"url": img_base} # 传入base64编码的图像},{"type": "text", "text": text_input} # 传入文本指令]}]# 3. 调用智谱大模型APIif web_search:# 开启联网搜索(此处无需,仅保留扩展能力)tools = [{"type": "web_search", "web_search": {"enable": True}}]response = self.client.chat.completions.create(model=self.model_name,messages=messages,temperature=0.1, # 温度值越低,输出越稳定tools=tools)else:response = self.client.chat.completions.create(model=self.model_name,messages=messages,temperature=0.1)# 4. 记录日志并返回结果log(response, snapshot=True)content = response.choices[0].message.content.strip()return content核心亮点与注意事项:
-
API key配置:在
__init__方法中,self.client = ZhipuAI(api_key="")需要填入自己申请的智谱API key(申请地址:https://open.bigmodel.cn/),否则无法调用大模型。 -
图像编码处理:大模型API无法直接接收图像文件,因此需要通过
base64.b64encode将图像转换为base64编码的字符串,再传入请求参数。 -
JSON修复机制:大模型返回的结果可能存在格式不规范(如多余的描述文字、JSON语法错误),
_try_parse_json_object方法通过正则提取、特殊字符清理、json_repair修复等步骤,确保能正确解析JSON数据。 -
温度值控制:设置
temperature=0.1,降低输出的随机性,确保大模型返回的操作指令更稳定、可重复。
3.2.3 坐标处理:从识别结果到可执行坐标
大模型返回的坐标是相对坐标(基于0-1000的归一化坐标),而Airtest执行点击操作需要绝对像素坐标(如手机分辨率为1080×2400时,坐标范围是(0,0)到(1080,2400))。因此需要通过两个函数实现“相对坐标提取”和“绝对坐标转换”。
def _convert_relative_to_absolute(element):"""转换相对坐标(0-1000)为绝对像素坐标- 2元素:(相对x, 相对y) → 直接转换- 4元素:(左上x, 左上y, 右下x, 右下y) → 先算中心点再转换"""# 获取当前设备的屏幕分辨率(Airtest提供的接口)width, height = device().get_current_resolution()element_len = len(element)if element_len == 2:# 2元素:直接将相对坐标转换为绝对坐标rel_x, rel_y = elementabs_x = int(rel_x / 1000 * width)abs_y = int(rel_y / 1000 * height)return abs_x, abs_yelif element_len == 4:# 4元素:先计算目标元素的中心点相对坐标,再转换为绝对坐标left_top_x, left_top_y, right_bottom_x, right_bottom_y = elementrel_center_x = (left_top_x + right_bottom_x) / 2rel_center_y = (left_top_y + right_bottom_y) / 2abs_center_x = int(rel_center_x / 1000 * width)abs_center_y = int(rel_center_y / 1000 * height)return abs_center_x, abs_center_yelse:print(f"错误:element长度为{element_len},仅支持2个或4个元素")return ()def extract_element(response):"""从大模型返回的响应中提取element坐标(正则匹配)"""# 正则表达式:匹配 element=[...] 格式的内容pattern = r'element=\[([^\]]+)\]'match = re.search(pattern, response)if match:# 提取坐标字符串并转换为整数列表element_str = match.group(1)element_list = [int(item.strip()) for item in element_str.split(',')]return tuple(element_list)return ()核心逻辑:
-
坐标提取:
extract_element通过正则表达式r'element=\[([^\]]+)\]',从大模型返回的响应中提取坐标信息(如从“do(action=”Tap”, element=[100,200])”中提取出(100,200))。 -
坐标转换:
-
首先通过
device().get_current_resolution()获取当前设备的屏幕分辨率(如1080×2400); -
对于2元素坐标(直接表示目标点的相对坐标),通过“相对坐标/1000 × 屏幕分辨率”转换为绝对坐标;
-
对于4元素坐标(表示目标元素的矩形区域),先计算矩形中心点的相对坐标,再转换为绝对坐标,确保点击位置在元素中心,提高操作成功率。
3.2.4 示例入口:收起小键盘的自动化实现
下面是完整的示例代码,实现“调用大模型识别小键盘收起按钮,生成坐标并执行点击”的功能,解决跨端兼容问题。
if __name__ == '__main__':# 1. 连接设备:获取当前连接的Android设备列表dev_list = []for android in ADB().devices():dev_list.append((android[0], "android"))devices = []for dev in dev_list:if dev[1] == "android":# 构造Airtest设备连接字符串(指定截图和点击方式)devices.append("Android:///" + dev[0] + "?cap_method=javacap&touch_method=adb")# 2. 初始化Airtest环境auto_setup(__file__, logdir=log_path, devices=devices, project_root=prj_path)# 3. 定义测试指令:收起小键盘text_input = '帮我收起小键盘'# 4. 初始化大模型实例glm = GLMModel()# 5. 核心流程:截图 → 大模型识别 → 坐标提取 → 坐标转换 → 执行点击# 5.1 生成当前界面截图(命名为ai_temp_test.png)img_path = complete_snapshot('ai_temp_test.png')# 5.2 调用大模型处理图像和指令,获取响应result = glm.process_image(img_path, text_input)# 5.3 提取坐标_pos_x_y = extract_element(result)if _pos_x_y:# 5.4 转换为绝对坐标x, y = _convert_relative_to_absolute(_pos_x_y)print(f'换算后的坐标为:{x,y}')# 5.5 执行点击操作(Airtest的touch接口)touch((x, y))四、运行示例:跨端兼容的“收起小键盘”操作
“收起小键盘”是移动APP自动化测试中典型的跨端兼容痛点场景。不同品牌手机的输入法小键盘,其“收起按钮”的位置、样式差异极大:华为手机的收起按钮在右下角,小米手机可能在左上角,OPPO手机的按钮图标样式不同。传统Airtest纯视觉方案需要为每个品牌手机单独制作“收起按钮”的图像模板,维护成本极高。而通过本文的方案,只需一条自然语言指令,就能让大模型自动识别不同手机上的收起按钮,生成对应的坐标并执行点击,完美解决跨端兼容问题。
4.1 运行前准备
环境安装:安装所需依赖包
pip install airtest zhipuai json-repair设备连接:确保Android手机开启开发者模式和USB调试,通过USB连接电脑,执行
adb devices能看到设备列表。API key申请:登录智谱AI开放平台(https://open.bigmodel.cn/),申请API key,并填入
GLMApi.py中self.client = ZhipuAI(api_key="")的引号内。触发小键盘:在手机上打开任意APP的输入框(如微信聊天输入框),触发输入法小键盘显示。
4.2 运行步骤与结果
运行
GLMApi.py脚本;脚本会自动完成以下操作:
连接手机并生成当前界面截图(保存到
Snapshot/current/Android目录);将截图和“帮我收起小键盘”指令发送给GLM-4.6v-flash大模型;
大模型识别出小键盘的收起按钮,返回类似“do(action=”Tap”, element=[800,900])”的响应;
脚本提取坐标(800,900),转换为当前手机的绝对像素坐标(如手机分辨率1080×2400时,转换为(864, 2160));
Airtest根据绝对坐标执行点击操作,成功收起小键盘。
跨端验证:在华为、小米、OPPO等不同品牌手机上重复上述步骤,脚本均能正常运行,无需修改任何代码。
4.3 核心优势体现
与传统Airtest纯视觉方案相比,该方案的优势的在跨端场景下尤为明显:
|
对比维度 |
传统Airtest纯视觉方案 |
大模型+Airtest方案 |
|---|---|---|
|
跨端兼容性 |
差,需为不同机型制作图像模板 |
好,大模型自动识别不同机型的目标元素 |
|
维护成本 |
高,新增机型需重新制作和维护模板 |
低,无需维护模板,仅需统一指令 |
|
适配效率 |
低,新机型适配需要手动调试 |
高,自动适配不同机型,无需手动干预 |
五、后续扩展方向
本文实现的“大模型识别目标转坐标”能力,只是APP Agent核心能力的一个缩影。基于这个基础,我们还可以从以下几个方向扩展,进一步提升自动化测试框架的能力:
5.1 扩展更多测试场景
除了“收起小键盘”,还可以将该能力应用到更多跨端兼容痛点场景:
-
弹窗关闭:不同机型的弹窗关闭按钮位置、样式不同,通过大模型识别弹窗关闭按钮并生成坐标;
-
输入框定位:识别不同界面的输入框,生成点击坐标,确保输入操作的跨端兼容;
-
状态识别+操作:如识别“登录按钮是否可点击”“开关是否开启”,并执行对应的操作(点击登录、切换开关)。
5.2 优化模型调用与性能
当前方案使用在线大模型API,存在调用延迟和API成本的问题,可以从以下方面优化:
-
缓存机制:对相同界面、相同指令的大模型响应进行缓存,避免重复调用,降低成本并提升效率;
-
图像压缩:在发送图像到大模型前,对图像进行压缩(如降低分辨率、压缩质量),减少传输数据量,降低延迟;
-
错误重试:增加大模型调用失败的重试机制(如网络波动导致的调用失败),提升脚本稳定性;
-
开源模型本地化部署:如果团队有一定的算力资源,可以尝试部署开源的视觉大模型(如LLaVA、Qwen-VL),彻底摆脱对在线API的依赖,降低长期成本。
5.3 与Airtest生态深度集成
将当前能力封装为Airtest的自定义接口(如ai_touch(text_input)),实现与Airtest原有脚本的无缝衔接:
# 封装后的自定义接口def ai_touch(text_input):img_path = complete_snapshot('ai_temp.png')glm = GLMModel()result = glm.process_image(img_path, text_input)_pos_x_y = extract_element(result)if _pos_x_y:x, y = _convert_relative_to_absolute(_pos_x_y)touch((x, y))return Truereturn False# 原有Airtest脚本中直接调用auto_setup(__file__)start_app("com.tencent.mm") # 打开微信ai_touch("点击搜索框") # 调用AI识别并点击搜索框type("测试") # 输入文本ai_touch("收起小键盘") # 调用AI识别并收起小键盘这样,测试人员无需学习新的框架,就能在原有Airtest脚本中直接使用AI能力,降低学习和迁移成本。
5.4 增加操作验证与异常处理
为提升脚本的稳定性,可增加“操作结果验证”和“异常处理”机制:
-
操作验证:执行点击操作后,再次截图并发送给大模型,验证操作是否成功(如“小键盘是否已收起”);
-
异常处理:如果大模型未识别到目标元素,或点击操作未生效,脚本自动执行重试(如重新截图识别)或降级操作(如调用传统图像识别)。
对于中小企业测试团队而言,完整部署APP Agent并非最优解,“提取核心能力赋能现有框架”才是更务实、更高性价比的选择。本文通过“大模型识别目标转坐标”的核心思路,将APP Agent的视觉理解能力与Airtest的执行能力相结合,有效解决了纯视觉方案的跨端兼容痛点。
这种“小步快跑”的思路,不仅降低了AI技术在自动化测试中的应用门槛,也让测试团队能以极低的成本享受到大模型技术带来的红利。后续,我们还可以继续挖掘APP Agent的其他核心能力(如自然语言驱动的用例生成、界面异常识别),持续迭代优化现有框架,让自动化测试能力不断进步。
最后再次提醒:本文方案中使用的智谱GLM-4.6v-flash大模型需要申请API key,大家可前往智谱AI开放平台免费申请;代码仅围绕跨端兼容的核心痛点实现,实际落地时可根据团队的测试需求进行进一步优化。
关注+评论 可以私信我领取完整示例代码
夜雨聆风
