乐于分享
好东西不私藏

用一点APP Agent的能力,让我们的自动化测试进步一点点

用一点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

核心作用:

  • 依赖引入:导入zhipuai SDK用于调用智谱大模型,导入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 key    def _try_parse_json_object(self, input):        """JSON清理和格式化工具:修复大模型返回的不规范JSON数据"""        result = None        try:            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("```")]        # 尝试修复并解析JSON        try:            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, result    def 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. 调用智谱大模型API        if 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 = element        abs_x = int(rel_x / 1000 * width)        abs_y = int(rel_y / 1000 * height)        return abs_x, abs_y    elif element_len == 4:        # 4元素:先计算目标元素的中心点相对坐标,再转换为绝对坐标        left_top_x, left_top_y, right_bottom_x, right_bottom_y = element        rel_center_x = (left_top_x + right_bottom_x) / 2        rel_center_y = (left_top_y + right_bottom_y) / 2        abs_center_x = int(rel_center_x / 1000 * width)        abs_center_y = int(rel_center_y / 1000 * height)        return abs_center_x, abs_center_y    else:        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 运行前准备

  1. 环境安装:安装所需依赖包        pip install airtest zhipuai json-repair

  2. 设备连接:确保Android手机开启开发者模式和USB调试,通过USB连接电脑,执行adb devices能看到设备列表。

  3. API key申请:登录智谱AI开放平台(https://open.bigmodel.cn/),申请API key,并填入GLMApi.pyself.client = ZhipuAI(api_key="")的引号内。

  4. 触发小键盘:在手机上打开任意APP的输入框(如微信聊天输入框),触发输入法小键盘显示。

4.2 运行步骤与结果

  1. 运行GLMApi.py脚本;

  2. 脚本会自动完成以下操作:

    1. 连接手机并生成当前界面截图(保存到Snapshot/current/Android目录);

    2. 将截图和“帮我收起小键盘”指令发送给GLM-4.6v-flash大模型;

    3. 大模型识别出小键盘的收起按钮,返回类似“do(action=”Tap”, element=[800,900])”的响应;

    4. 脚本提取坐标(800,900),转换为当前手机的绝对像素坐标(如手机分辨率1080×2400时,转换为(864, 2160));

    5. Airtest根据绝对坐标执行点击操作,成功收起小键盘。

  3. 跨端验证:在华为、小米、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 True    return 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开放平台免费申请;代码仅围绕跨端兼容的核心痛点实现,实际落地时可根据团队的测试需求进行进一步优化。

关注+评论 可以私信我领取完整示例代码

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 用一点APP Agent的能力,让我们的自动化测试进步一点点

评论 抢沙发

8 + 4 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮