Airtest UI自动化测试框架:基于图像识别的跨平台解决方案

发布时间:2026/8/8 9:04:42
Airtest UI自动化测试框架:基于图像识别的跨平台解决方案
1. 项目概述为什么我们需要Airtest这样的UI自动化测试框架在移动互联网和游戏行业高速发展的今天应用的迭代速度越来越快版本发布周期从月缩短到周甚至到天。每一次版本更新无论是功能新增还是简单的热修复都意味着测试团队需要面对海量的回归测试用例。如果全靠人工手动点击、滑动、验证不仅效率低下、成本高昂而且重复劳动极易导致测试人员疲劳从而产生疏漏。这就是UI自动化测试的价值所在——让机器代替人去执行那些重复、枯燥的界面操作。然而传统的UI自动化测试框架无论是Appium、Selenium还是UIAutomator都面临一个核心挑战对UI控件树的强依赖。它们需要获取应用的源代码或布局信息通过ID、XPath等方式定位元素。这对于原生应用或许可行但对于游戏、或大量使用自定义控件、动态渲染的App如某些电商、视频应用控件树要么难以获取要么结构极其复杂且不稳定。一个UI的微小改动就可能导致整个自动化脚本的定位失效维护成本陡增。Airtest的出现正是为了解决这个痛点。它由网易游戏团队开源其核心思路非常巧妙基于图像识别技术来驱动UI自动化。简单来说它不关心你的按钮在代码里叫什么只关心它在屏幕上“长什么样”。你只需要截取屏幕上需要点击的按钮图片Airtest就能在运行时找到它并执行点击。这种“所见即所得”的方式极大地降低了对被测应用内部结构的依赖特别适合游戏、H5应用以及那些控件树不标准的场景。我最初接触Airtest是在一个Unity3D手游的测试项目中。游戏界面元素大多是纹理贴图传统的基于控件的自动化工具几乎无从下手。Airtest让我们通过截图就能完成复杂的战斗流程自动化测试效率提升了数倍。如今它已经发展成一个包含Airtest图像识别、Poco控件识别和AirtestIDE集成开发环境的完整项目生态支持Android、iOS、Windows和Web成为了跨平台UI自动化测试的一把利器。2. 核心架构与组件深度解析Airtest Project并非一个单一工具而是一个由多个组件构成的解决方案。理解每个组件的定位和协作关系是高效使用它的前提。2.1 Airtest框架基于图像识别的“火眼金睛”Airtest框架是整个生态的基石。它的核心能力是跨平台的图像识别与屏幕操作。工作原理Airtest采用了一种名为模板匹配的算法。当你截取一张图片例如一个“开始游戏”按钮作为模板时Airtest会在运行时实时捕获设备屏幕并在屏幕图像中搜索与模板最相似的区域。匹配成功后它会计算出该区域在屏幕上的坐标并模拟点击、滑动等操作。关键技术点解析特征匹配算法早期版本主要使用OpenCV的matchTemplate函数通过计算归一化相关系数来匹配。优点是速度快但对缩放、旋转、亮度变化敏感。新版本引入了基于特征点如SIFT、ORB的匹配算法作为补充提升了在复杂场景下的鲁棒性。多分辨率适配这是图像识别的一个巨大挑战。在1080P屏幕上录制的脚本如何在2K或720P的设备上运行Airtest的解决方案是基于屏幕分辨率进行坐标和图像的自动缩放。它会获取当前设备的屏幕分辨率并与录制脚本时的基准分辨率进行比较按比例缩放所有操作坐标和模板图像的匹配阈值。断言与报告除了操作测试离不开验证。Airtest提供了assert_exists()函数用于断言某个图像元素存在于屏幕上。更强大的是其内置的报告生成功能每一步操作成功或失败都会附带截图最终生成一个直观的HTML报告清晰地展示测试步骤和结果。实操心得图像识别的成功率高度依赖于模板图片的质量。录制时尽量选择特征明显、背景简洁的区域进行截图。避免选择大面积纯色或周期性纹理的区域作为模板这容易导致误匹配。2.2 Poco框架控件识别的“精确制导”虽然图像识别通用性强但在某些场景下效率不高例如需要获取一个列表项的具体文本内容或者操作一个频繁变化的动态元素。这时就需要Poco框架出场。Poco是一个基于UI控件树识别的框架。它通过注入一个poco-sdk到被测应用中可以实时获取当前界面的完整控件层级结构类似于Android的UIAutomator Viewer或iOS的Instruments。你可以像使用jQuery选择器一样通过属性如text登录、namebtn_submit来精确定位控件。Poco的核心优势精准定位直接操作控件对象不受图像缩放、渲染风格变化的影响。获取属性可以轻松读取控件的文本、坐标、尺寸等属性用于更复杂的断言逻辑。高效稳定对于结构稳定的原生或游戏UI如Unity的UGUI脚本的稳定性和执行速度远高于图像识别。Airtest与Poco的关系它们不是二选一而是互补的“黄金搭档”。在实际项目中我通常采用“Poco为主Airtest为辅”的策略。所有能通过Poco定位的标准控件都用Poco操作稳定高效。对于那些无法通过控件树获取的定制化元素、游戏特效、验证码图片等则用Airtest的图像识别来补充。两者在同一个脚本中可以无缝混合使用。2.3 AirtestIDE一站式可视化开发利器对于初学者甚至是有经验的工程师AirtestIDE都是极大的生产力工具。它是一个跨平台的桌面应用集成了脚本录制、编辑、运行和报告查看的所有功能。核心功能亮点设备连接与管理图形化界面连接Android/iOS真机、模拟器、Windows窗口甚至浏览器。“所见即所得”的脚本录制图像录制在设备屏幕上框选区域自动生成touch(图片)语句。Poco录制在Poco辅助窗中点击控件自动生成poco(“控件”).click()语句。代码自动补全编写代码时IDE会提示Airtest和Poco的所有API。脚本运行与实时调试可以单步运行、设置断点实时查看变量和屏幕状态。报告生成一键生成包含详细步骤和截图的HTML测试报告。注意事项AirtestIDE生成的脚本是.air格式它实际上是一个文件夹里面包含了Python脚本*.py和所有用到的图片资源。你可以直接在IDE中运行.air项目也可以使用命令行airtest run来运行。对于集成到CI/CD流水线通常使用后者。2.4 企业级扩展DeviceFarm与Airlab对于需要大规模、并发执行自动化测试的企业Airtest Project提供了商业解决方案。DeviceFarm一个设备集群管理方案。你可以将公司闲置的测试手机集中管理形成一个“设备云”。测试脚本可以自动分发到这些设备上并发执行极大提高了设备利用率和测试效率。Airlab一个云测试平台。它提供了测试用例管理、任务调度、报告聚合等更上层的服务适合构建企业内部的自动化测试中台。对于个人开发者或小团队专注于开源部分的Airtest、Poco和AirtestIDE已经完全足够。3. 从零开始手把手搭建Airtest自动化测试环境理论说得再多不如动手实践。下面我将以测试一个Android App为例带你完成从环境搭建到第一个脚本运行的全过程。3.1 环境准备与安装你需要准备以下三样东西一台电脑Windows、macOS或Linux均可。一部Android手机用于真机测试推荐。或者使用Android模拟器如夜神、雷电。被测应用程序一个APK文件。步骤一安装PythonAirtest基于Python因此需要先安装Python。建议安装Python 3.7-3.9版本兼容性最好。从Python官网下载安装包安装时务必勾选“Add Python to PATH”。步骤二安装Airtest核心库打开命令行CMD或Terminal使用pip进行安装。建议创建一个虚拟环境。# 安装Airtest框架 pip install airtest # 安装Poco框架。如果需要测试Android原生应用安装这个 pip install pocoui # 如果需要测试Unity游戏则安装这个 # pip install pocounity # 安装AirtestIDE可选但强烈推荐 # 从Airtest官网下载对应操作系统的安装包直接安装即可。步骤三连接Android设备手机开启“开发者选项”和“USB调试”模式。用USB线连接电脑和手机。在命令行输入adb devices。如果看到设备序列号后面显示device则表示连接成功。如果显示unauthorized需要在手机上点击确认“允许USB调试”。如果列表为空可能需要安装手机对应的USB驱动。3.2 使用AirtestIDE录制第一个脚本打开AirtestIDE你会看到界面主要分为四个区域设备连接窗口、脚本编辑区、Poco辅助窗和Log查看器。连接设备点击顶部工具栏的Android图标选择你的设备。连接成功后设备屏幕会投射到IDE中。录制一个点击操作在设备投屏画面上找到你想点击的元素比如App的图标。点击IDE左侧的touch按钮然后在设备投屏上框选那个图标。IDE会自动在脚本编辑区生成一行代码touch(Template(r“图片路径.png”))同时截图会保存在.air项目的根目录下。录制一个滑动操作点击swipe按钮先在起点按下鼠标拖动到终点释放IDE会生成swipe(v1, v2)语句。使用Poco录制点击“Poco辅助窗”按钮选择对应的模式如Android。等待初始化后你可以看到当前界面的控件树。点击控件树中的某个节点IDE会自动生成类似poco(“com.example:id/btn_login”).click()的代码。运行脚本点击顶部的运行按钮IDE就会自动执行你刚刚录制的脚本并在Log窗口看到执行过程。3.3 编写第一个完整的测试用例录制功能很方便但要写出健壮、可维护的脚本还需要手动编写代码。下面是一个模拟登录功能的简单示例# -*- encodingutf8 -*- __author__ “YourName” from airtest.core.api import * # 引入Airtest核心API from poco.drivers.android.uiautomation import AndroidUiautomationPoco # 引入Poco的Android驱动 # 初始化设备如果IDE已连接可以直接用auto_setup这里演示代码连接 # connect_device(“Android:///”) # 初始化Poco poco AndroidUiautomationPoco(use_airtest_inputTrue, screenshot_each_actionFalse) # 启动被测App start_app(“com.example.app”) # 用例1测试登录功能 # 步骤1点击账号输入框输入用户名 poco(“com.example.app:id/et_username”).click() text(“testuser”) # Airtest的文本输入函数全局输入 # 步骤2点击密码输入框输入密码 poco(“com.example.app:id/et_password”).click() text(“123456”, enterFalse) # enterFalse表示不按回车 # 步骤3点击登录按钮这里演示用图像识别作为备用方案 # 如果Poco定位失败尝试用图像识别点击登录按钮 login_button poco(“com.example.app:id/btn_login”) if login_button.exists(): login_button.click() else: # 备用方案通过图像识别点击 touch(Template(r“login_button.png”)) # 步骤4断言登录成功 # 假设登录成功后会出现一个“欢迎testuser”的文本元素 assert_exists(Template(r“welcome_message.png”), “登录成功提示未出现”) # 或者使用Poco断言 # assert_equal(poco(“com.example.app:id/tv_welcome”).get_text(), “欢迎testuser”) # 步骤5退出App stop_app(“com.example.app”)这个脚本展示了混合使用Poco和Airtest的方式以及基本的流程控制和断言。4. 核心API与脚本编写实战技巧掌握了基础操作后我们来深入看看Airtest和Poco中那些最常用、最核心的API以及如何组织更复杂的测试逻辑。4.1 Airtest核心API详解Airtest的API设计非常直观主要围绕设备操作和图像识别。设备操作APItouch(pos)点击。pos可以是坐标(x, y)也可以是Template图片对象。swipe(v1, v2, duration0.5)滑动。从v1滑到v2duration控制滑动时长。text(“input_string”, enterTrue)文本输入。enter决定是否在输入后按回车。keyevent(“KEYCODE_HOME”)按键事件如HOME、BACK、ENTER。snapshot(filenameNone)截取屏幕截图可用于手动断言或报告。wait(Template(...), timeout10, interval1)等待某个图片出现超时时间timeout秒轮询间隔interval秒。图像识别APIexists(Template(...))判断图片是否存在返回坐标或False。assert_exists(Template(...), msg“”)断言图片存在是测试中断言的利器。Template对象这是图像识别的核心。创建时可以指定匹配阈值threshold默认0.8和是否启用RGB匹配rgb。# 创建一个模板并提高匹配精度 target Template(r“button.png”, threshold0.9, rgbTrue) if exists(target): touch(target)实操心得threshold参数非常关键。默认0.8在大多数情况下够用但对于背景复杂或元素较小的图片可能需要提高到0.9甚至0.95来避免误匹配。反之如果元素颜色、亮度可能变化可以适当降低阈值到0.7。这是一个需要根据实际情况调整的经验值。4.2 Poco核心API与控件定位策略Poco的API更像一个前端测试框架核心是选择器和控件操作。控件定位选择器 Poco提供了多种定位方式可以组合使用形成强大的定位链。# 1. 通过属性名定位最常用 poco(“com.example:id/btn_ok”) poco(text“确定”) poco(type“android.widget.Button”) # 2. 通过相对关系定位 poco(“android:id/content”).child(“android.widget.LinearLayout”).offspring(text“登录”) # 3. 通过索引定位当有多个相同属性元素时 poco(“android.widget.Button”)[0] # 第一个按钮 poco(“android.widget.Button”)[1].click() # 点击第二个按钮 # 4. 等待元素出现非常重要 btn poco(“确定”).wait(10) # 等待最多10秒 if btn: btn.click()控件操作API.click()点击。.set_text(“text”)设置文本用于输入框。.get_text()获取文本内容。.attr(‘visible’)获取属性如是否可见、坐标、尺寸等。.swipe(‘direction’)向某个方向滑动控件。.long_click()长按。编写可维护的Poco脚本技巧使用Page Object模式将每个页面封装成一个类页面的元素定位器作为类的属性操作作为类的方法。这能极大提高代码的可读性和可维护性。class LoginPage: def __init__(self, poco): self.poco poco self.username_input poco(“com.example:id/et_username”) self.password_input poco(“com.example:id/et_password”) self.login_button poco(text“登录”) def login(self, username, password): self.username_input.set_text(username) self.password_input.set_text(password) self.login_button.click()善用等待UI操作后页面加载或元素出现需要时间。务必使用wait()方法而不是sleep()硬等待。异常处理与重试网络波动或设备卡顿可能导致单次操作失败。对于关键操作可以封装一个带重试机制的点击函数。def click_with_retry(poco_element, retry_times3): for i in range(retry_times): try: if poco_element.exists(): poco_element.click() return True except Exception as e: print(f“第{i1}次点击失败: {e}”) time.sleep(1) return False4.3 测试报告生成与定制Airtest内置的报告功能是其一大亮点。默认情况下运行脚本后会生成一个简洁的HTML报告。生成报告在AirtestIDE中运行会自动在脚本所在目录生成log.html。使用命令行运行airtest run test.air --log log/然后在log目录下找到log.html。报告内容解读 报告会按时间线展示每个测试步骤包括步骤描述执行的什么操作如touch,assert。结果成功绿色或失败红色。截图每一步执行前后的屏幕截图对于调试失败用例至关重要。耗时每一步的执行时间。自定义报告 你可以通过修改airtest/report/report.py模板或使用--plugins参数加载自定义插件来美化报告或者集成到自己的测试平台中。更常见的做法是在CI/CD流水线中运行Airtest脚本然后将生成的HTML报告作为构件存档供团队查看。5. 进阶实战复杂场景与性能优化当脚本规模变大、测试场景变复杂时我们会遇到新的挑战。下面分享一些处理复杂场景和进行性能优化的经验。5.1 处理动态元素与等待策略场景一个列表在加载更多数据你需要滑动到底部直到“没有更多数据”的提示出现。解决方案使用循环结合等待和断言。def scroll_to_bottom(poco): while True: # 尝试查找“没有更多”的提示 end_marker poco(text“没有更多内容”) if end_marker.exists(): print(“已滑动到底部”) break # 如果没找到则执行一次滑动 # 假设列表是一个可滑动的控件其name为‘list_view’ poco(“android.widget.ListView”).swipe(‘up’) # 向上滑动 # 滑动后等待一小段时间让新内容加载 sleep(2) # 安全措施避免无限循环设置最大滑动次数 # 可以在函数外部加一个计数器场景等待一个网络请求完成后的弹窗出现但弹窗出现时间不确定。解决方案使用Airtest的wait函数并设置合理的超时时间和轮询间隔。# 等待一个表示加载完成的“完成”图片出现最多等20秒每0.5秒检查一次 result wait(Template(r“loading_finished.png”), timeout20, interval0.5) if result: # 进行后续操作 else: raise AssertionError(“加载超时未检测到完成标志”)5.2 多设备并行测试在兼容性测试中经常需要在多台不同型号、分辨率的设备上运行同一套脚本。Airtest可以通过多进程实现简单的并行。基本思路使用Python的multiprocessing库为每台设备启动一个独立的进程执行测试任务。import multiprocessing from airtest.core.api import connect_device, auto_setup def run_test_on_device(device_url): # 每个进程独立连接设备 connect_device(device_url) # 这里可以写你的测试脚本主函数 # run_my_test_case() if __name__ ‘__main__’: devices [“Android:///emulator-5554”, “Android:///ABCDEFG123456”] pool multiprocessing.Pool(processeslen(devices)) pool.map(run_test_on_device, devices) pool.close() pool.join()对于更复杂的企业级需求就需要用到前文提到的DeviceFarm或自己搭建STFSmartphone Test Farm等设备集群管理方案。5.3 脚本稳定性优化与常见陷阱图像识别失败原因屏幕亮度变化、UI主题切换、动态背景、元素轻微位移。对策使用更独特的模板截取包含图标部分文字的区域比纯图标更好。调整threshold和rgb参数。启用scale_max参数进行多尺度匹配应对分辨率差异。如果元素位置相对固定可以改用坐标点击touch((x, y))但这是下策维护性差。Poco定位失败原因控件ID或文本动态生成、页面未加载完成、跨进程访问限制如WebView。对策使用更稳定的定位属性优先使用resource-id其次是text避免使用index。增加显式等待在操作前务必确保控件已加载。对于WebView需要先切换上下文poco.webview()才能定位其中的元素。脚本执行速度慢原因图像识别本身是计算密集型操作sleep使用过多报告截图过于频繁。优化减少不必要的截图在Airtest脚本中ST.SAVE_IMAGE False可以关闭每一步的截图但错误步骤仍会截图。用Poco代替Airtest凡是能用Poco稳定定位的就不要用图像识别。优化等待逻辑用智能等待wait替代固定时长等待sleep。并行与批处理如上文所述对无依赖的用例进行并行执行。6. 集成与落地将Airtest融入CI/CD流水线自动化测试只有集成到持续集成/持续部署CI/CD流水线中才能发挥最大价值实现“无人值守”的回归测试。6.1 基于命令行的脚本执行在CI服务器如Jenkins、GitLab CI上无法使用AirtestIDE的图形界面必须通过命令行执行。核心命令# 基本运行命令 airtest run /path/to/your/test.air --device Android:///手机设备号 --log /path/to/log/ # 更详细的命令示例 airtest run “D:\tests\login_test.air” \ --device Android://127.0.0.1:5037/emulator-5554 \ --log “./reports/login_test_log” \ --recording # 录制执行视频可选参数说明run: 运行指定.air脚本或.py脚本。--device: 指定运行设备。格式为平台://主机IP:端口/序列号。如果本地只有一个设备可以简写为Android:///。--log: 指定日志和报告的输出目录。--recording: 录制整个执行过程的视频对于调试复杂问题非常有帮助。6.2 与Jenkins集成示例以下是一个简单的Jenkins Pipeline脚本示例用于在代码合并后自动触发Airtest回归测试pipeline { agent any stages { stage(‘Checkout’) { steps { git ‘https://your-git-repo.com/your-project.git’ } } stage(‘Environment Setup’) { steps { sh ‘pip install airtest pocoui -i https://pypi.douban.com/simple/’ // 假设测试脚本在项目根目录的 ‘airtest_scripts’ 文件夹下 } } stage(‘Run Airtest on Multiple Devices’) { parallel { stage(‘Test on Device A’) { steps { sh ‘airtest run ./airtest_scripts/smoke_test.air --device Android:///device_sn_A --log ./reports/device_A’ } } stage(‘Test on Device B’) { steps { sh ‘airtest run ./airtest_scripts/smoke_test.air --device Android:///device_sn_B --log ./reports/device_B’ } } } } stage(‘Archive Report’) { steps { // 将测试报告归档供后续查看 archiveArtifacts artifacts: ‘reports/**/*.html’, fingerprint: true // 也可以使用插件如HTML Publisher plugin来发布报告 publishHTML(target: [ reportDir: ‘reports/device_A’, reportFiles: ‘log.html’, reportName: ‘Airtest Report Device A’ ]) } } stage(‘Fail on Test Failure’) { steps { // 解析测试结果如果失败则让Pipeline失败 script { // 这里需要自己写逻辑解析airtest的运行退出码或log内容 // 一个简单的方法是检查log目录中是否有错误截图标记 def hasError fileExists ‘reports/device_A/failed’ if (hasError) { error(‘Airtest 测试用例执行失败’) } } } } } }6.3 测试结果管理与通知报告聚合当并行测试多台设备后会产生多份报告。可以编写一个脚本将这些独立的HTML报告汇总成一个总览报告展示各设备的通过率、失败用例等。失败通知在Pipeline的post阶段可以根据测试结果通过邮件、钉钉、企业微信等Webhook接口将失败用例的简要信息和报告链接发送给相关责任人。历史趋势将每次运行的通过率、用例数等关键指标记录到数据库如InfluxDB然后通过Grafana等工具进行可视化可以清晰看到测试健康度的变化趋势。将Airtest集成到CI/CD中意味着每一次代码提交都能得到快速的自动化反馈将UI层面的缺陷拦截在开发阶段真正为产品质量保驾护航。这个过程需要测试开发工程师与运维、开发同学紧密合作共同搭建稳定高效的自动化测试流水线。