1. 项目概述为什么选择Airtest做跨平台UI自动化如果你正在为Android、iOS、Windows或者Web应用的UI自动化测试头疼尤其是需要在不同平台间切换或者团队里既有移动端又有PC端的测试需求那你很可能已经听说过或者正在寻找一个“万能”的解决方案。传统的方案往往是“组合拳”用Appium搞移动端用Selenium搞Web再用PyAutoGUI或者WinAppDriver对付Windows原生应用。每个框架都有自己的环境配置、脚本语法和运行依赖维护成本高学习曲线陡峭。我最初接触Airtest就是被它“跨平台”的旗号吸引的。当时手头一个项目需要同时验证一个App的Android版本、iOS版本以及对应的管理后台Web端。按老路子得准备三套脚本、三个运行环境光是协调执行和结果汇总就够喝一壶的。Airtest承诺用一套基于Python的脚本配合一个IDE就能搞定图像识别、控件识别覆盖上述所有平台。这听起来像是个“银弹”实际用下来虽然并非完美无缺但在特定场景下它确实极大地提升了效率降低了多平台自动化的门槛。简单来说Airtest是一个由网易开源的UI自动化测试框架。它的核心卖点有两个基于图像识别的跨平台和低代码/无代码的脚本生成。你不需要像Selenium那样去深入理解DOM结构或者XPath也不一定需要像Appium那样去获取每个控件的resource-id对于很多简单的点击、滑动、输入操作直接截个图Airtest就能帮你定位并操作。这对于测试那些难以获取控件信息的游戏、或者一些封装很深的原生应用、甚至是一些通过Citrix等虚拟化技术呈现的界面有奇效。它主要适合以下几类人测试工程师尤其是面对多平台Android, iOS, Windows, Web项目希望用统一工具降低学习成本和维护成本。开发人员需要快速为自己开发的应用编写一些冒烟测试或简单的功能回归脚本不想在测试框架配置上花费太多时间。对编程不太熟悉但需要自动化的同事Airtest IDE提供了录制和脚本回放功能可以像使用按键精灵一样生成脚本。当然它也不是万能的。对于追求极致稳定、需要深度操作控件、或者进行大规模数据驱动测试的复杂场景传统的基于控件的框架可能更合适。但Airtest在快速验证、兼容性测试、以及处理“图像类”界面时优势非常明显。接下来我就结合自己踩过的坑和总结的经验带你深入拆解这个框架。2. 核心架构与跨平台原理拆解要玩转Airtest不能只停留在“录制-回放”的层面理解它的架构和原理才能更好地判断它是否适合你的项目以及在出问题时如何排查。2.1 四层驱动模型一统江湖的野心Airtest的跨平台能力并非魔法而是通过一个分层的驱动模型来实现的。你可以把它想象成一个“翻译官”和“指挥官”的结合体。第一层Airtest核心脚本层。这是你直接打交道的部分就是用Python或Airtest IDE生成的.air脚本写的测试逻辑。这一层的API是统一的比如touch(图片)、swipe(起点, 终点)、text(输入内容)。你的脚本只关心“做什么”不关心“在哪做”和“怎么做”。第二层Airtest核心引擎层。这一层是框架的核心大脑。它接收脚本层的指令并进行解析。它的关键能力是图像识别。当你传入一张图片时引擎会使用OpenCV的模板匹配、特征点匹配等算法在当前设备屏幕上寻找匹配的区域。找到后它会计算出该区域的坐标。对于控件操作在Android和iOS上它会借助下一层的驱动来获取控件树并进行定位。第三层平台驱动层。这是跨平台的关键。Airtest抽象了不同平台的底层操作接口为每个平台实现了对应的“驱动”。Android: 基于poco和adb。poco负责控件识别和操作通过UIAutomator2/ATX-Agentadb负责屏幕截图、安装应用、传输文件等底层命令。iOS: 基于poco和tidevice或facebook-wda。poco同样负责控件操作通过WebDriverAgenttidevice是一个优秀的第三方工具用于替代Xcode的instruments实现与iOS设备的通信和截图。Windows: 主要基于pywinauto或WinAppDriver。Airtest封装了这些库用于识别和操作Windows窗口控件。图像识别在此平台同样有效。Web: 基于selenium。Airtest可以直接调用Selenium的WebDriver来操作浏览器同时也可以在其之上叠加图像识别。第四层设备与协议层。这就是具体的设备或程序了比如通过USB连接的手机、运行在本地或远端的浏览器窗口、一个桌面应用程序。驱动层通过特定的协议如ADB协议、WebDriver协议与它们通信。注意这个分层模型意味着当你写touch(Template(“button.png”))时Airtest引擎会先尝试用图像识别。如果同时开启了对应平台的poco驱动它也会尝试用控件定位。这种“图像为主控件为辅”的策略是Airtest实现跨平台且保持易用性的核心。2.2 图像识别 vs. 控件识别如何选择与混用这是使用Airtest必须理清的一个核心概念也直接决定了脚本的稳定性和维护成本。图像识别原理将你提供的截图模板图片与当前设备屏幕截图进行像素级匹配。优点真正跨平台只要屏幕上显示的样子一样不管它是Android按钮、iOS开关还是网页上的一个图标都能操作。无视底层实现对于游戏、Flash、虚拟机内界面、第三方控件库等难以通过常规手段获取控件信息的场景几乎是唯一选择。快速上手录制功能直接截取图片生成脚本无需分析UI层级。缺点稳定性受视觉变化影响UI改版、主题切换、分辨率变化、字体渲染差异、甚至光线阴影都可能导致匹配失败。执行效率较低需要不断截屏、进行图像处理比直接操作控件内存开销大、速度慢。难以处理动态内容对于列表、表格中位置不固定的元素纯图像识别很吃力。控件识别通过Poco原理通过访问应用的UI控件树类似于Web的DOM树通过属性如name、text、type来定位控件。优点稳定精确只要控件属性不变定位就非常稳定不受UI样式微调影响。执行效率高直接通过内存访问控件速度快。支持更丰富的操作可以获取控件文本、属性进行长按、拖动等复杂手势。缺点平台依赖和配置复杂需要为不同平台启动不同的代理服务如Android的UIAutomator2 iOS的WebDriverAgent。并非所有元素都可访问游戏、Canvas绘制的内容、系统级弹窗可能无法获取控件树。需要一定的学习成本要理解控件树的结构和定位语法。实操心得混合策略才是王道。在实际项目中我几乎从不单独使用某一种方式。我的策略是静态元素、图标、品牌Logo优先使用图像识别。比如应用的启动图标、导航栏的返回按钮、具有独特样式的确认弹窗。这些元素视觉标识强且不太频繁改动。动态内容、列表、输入框、文本坚决使用控件识别Poco。比如聊天列表、新闻Feed、用户名输入框。通过poco(text“登录”).click()远比截一张“登录”按钮的图片要稳定得多。作为容错和辅助在用Poco定位失败时例如某些定制化控件Poco抓不到可以尝试用图像识别作为备用方案。也可以在用图像识别进入某个界面后再切换到Poco进行精细操作。Airtest的强大之处就在于它允许你在同一份脚本中无缝切换这两种方式。auto_setup函数初始化时可以同时连接设备并初始化Poco后续脚本中即可随意调用touch(图片)或poco(“控件”).click()。3. 环境搭建与核心工具链详解“工欲善其事必先利其器”。Airtest的环境搭建比纯Appium或Selenium要简单一些但跨平台意味着你需要准备多个平台的“利器”。3.1 跨平台环境配置清单基础环境必须Python 3.7这是运行Airtest脚本的基础。建议使用Python 3.8或3.9兼容性最好。Airtest核心库pip install airtest。这个包包含了核心的图像识别引擎和跨平台API。Poco库pip install pocoui。这是用于控件识别的核心库。注意老教程里可能是poco现在官方推荐使用pocoui。Airtest IDE可选但强烈推荐这是一个图形化工具用于录制脚本、查看报告、连接设备。它不是必须的但对于开发、调试脚本来说效率提升巨大。可以从官网下载。平台特定环境AndroidADB确保Android Debug Bridge已安装并配置在系统PATH中。Airtest依赖它与手机通信。手机设置开启开发者选项和USB调试。如果是真机可能需要安装对应的手机驱动。Poco-Service运行控件识别需要。通常Airtest在第一次连接Android设备时会自动通过ADB在设备上安装com.netease.open.pocoservice这个APK。如果安装失败需要手动处理。iOStidevice这是目前连接iOS设备最推荐的工具。pip install tidevice。它用于启动WebDriverAgentWDA。WebDriverAgentWDA苹果官方的UI自动化驱动。需要Xcode编译并安装到iPhone上。这是iOS自动化最复杂的一步。tidevice可以简化WDA的启动过程。苹果开发者账号/证书用于签名WDA项目使其能在真机上运行。libimobiledevicemacOS/Linux用于获取设备信息。Windows通常只需安装Airtest和Poco库即可。对于较新的应用如UWP或基于某些新框架的应用可能需要确保pywinauto的backend设置正确uia是默认且推荐的后端。Web对应浏览器的WebDriver如ChromeDriver、GeckoDriver。需要下载并与浏览器版本匹配并放在系统PATH或脚本指定路径。Seleniumpip install selenium。Airtest的Web模块是对Selenium的封装。踩坑实录iOS环境是最大的拦路虎。我花了整整两天才搞定第一个iOS设备的连接。问题卡在WDA的编译和签名上。关键点使用tidevice wdaproxy命令启动WDA比直接用Xcode跑项目稳定得多。确保Xcode版本与iOS版本大致匹配。在Xcode中正确设置Team你的开发者账号和Bundle Identifier一个唯一的ID。手机上需要信任开发者证书。如果遇到“Unable to launch WebDriverAgent”错误多半是签名问题可以尝试用tidevice wdaproxy --bundle-id 你的BundleID指定。 建议为iOS自动化准备一台专用的Mac mini和测试机环境一旦配好就不要轻易升级系统或Xcode。3.2 Airtest IDE你的自动化瑞士军刀很多新手会忽略IDE直接硬撸代码。但我强烈建议无论你最终如何运行脚本在开发阶段都使用Airtest IDE。它不仅仅是录制工具。核心功能与使用技巧设备连接与管理IDE提供了一个统一的窗口连接Android、iOS、Windows应用和浏览器。连接状态一目了然比命令行方便太多。特别是它的“远程设备连接”功能可以连接同一网络下的设备摆脱USB线束缚。脚本录制与生成在设备窗**口点击录制按钮然后你的操作会被实时转换成代码。这是学习API和快速创建原型脚本的最佳方式。但切记录制的脚本尤其是纯图像识别需要大量优化后才能用于生产环境。Poco Inspector控件检视器这是神器连接设备后点击Poco辅助窗可以实时看到当前界面的控件树。你可以像使用浏览器开发者工具一样点击界面元素查看其属性并自动生成定位代码。这对于编写基于控件的脚本至关重要。图像编辑器双击脚本中的图片可以打开编辑器。你可以调整它的识别阈值threshold、是否灰度匹配rgb、以及指定匹配区域target_pos。比如一个按钮颜色会变但形状不变就可以尝试使用灰度匹配并提高阈值。实时调试与日志查看直接按F5在IDE中运行脚本可以实时看到执行步骤、图像识别结果和日志输出。执行失败时能立刻看到在哪一步、为什么失败如图片未找到、控件不存在。实操心得将IDE作为“侦察兵”和“调试器”而非“代码生成器”。我通常的流程是用IDE连接设备用Poco Inspector探查界面结构找到稳定的控件定位方式。对于必须用图像的地方用IDE截图并微调识别参数。然后在IDE里写好关键步骤的代码并运行调试。最后将调试好的.air脚本本质是一个文件夹包含图片和脚本或纯.py脚本放到CI/CD环境中用命令行去执行。IDE是强大的辅助但生产运行应该脱离IDE。4. 脚本编写实战从入门到生产级理解了原理配好了环境我们开始动手写脚本。这里我会按照一个完整的测试用例流程穿插讲解最佳实践和避坑指南。4.1 脚本结构设计与初始化一个健壮的Airtest脚本应该像一个小型项目一样有结构。# -*- encodingutf8 -*- __author__ “YourName” from airtest.core.api import * # 引入图像识别核心API如touch, swipe, exists from poco.drivers.android.uiautomation import AndroidUiautomationPoco # 引入Android Poco驱动 # from poco.drivers.ios.iospoco import IOSUiautomationPoco # iOS驱动 # from poco.drivers.windows.win_poco import WindowsPoco # Windows驱动 import logging import pytest # 如果使用pytest管理用例 import sys import os # 1. 初始化连接 def init_device(): # 方式一自动连接适用于IDE或已知设备序列号 # auto_setup(__file__, devices[“Android:///”]) # 连接默认USB安卓设备 # auto_setup(__file__, devices[“iOS:///127.0.0.1:8100]) # 连接本地WDA服务 # 方式二手动连接更灵活推荐用于生产脚本 # Android dev connect_device(“Android:///SERIALNO”) # 替换为你的设备序列号 # iOS (通过tidevice启动WDA后) # dev connect_device(“iOS:///127.0.0.1:8100”) # Windows (应用路径) # dev connect_device(“Windows:///123456”) # 窗口句柄或应用路径 # Web # dev connect_device(“Chrome://”) # 或指定浏览器和URL poco AndroidUiautomationPoco(devicedev, use_airtest_inputTrue, screenshot_each_actionFalse) return dev, poco # 2. 全局初始化setup class TestDemo: classmethod def setup_class(cls): cls.dev, cls.poco init_device() cls.dev. home() # 确保从桌面开始 # 关闭无关弹窗如权限申请 # stop_app(“com.example.package”) # start_app(“com.example.package”) logging.info(“测试类初始化完成。”) # 3. 测试用例示例 def test_login_with_wrong_password(self): “”“测试使用错误密码登录”“” try: # 用例步骤 self.poco(“com.example.app:id/username”).set_text(“testuser”) self.poco(“com.example.app:id/password”).set_text(“wrong”) # 混合使用图像识别点击一个风格独特的登录按钮 touch(Template(r”login_button.png”, threshold0.8, target_pos5)) # 断言检查错误提示是否出现 error_msg self.poco(textContains“密码错误”) assert error_msg.exists(), “未出现预期的错误提示” # 更佳的断言方式使用Airtest的assert_exists # assert_exists(Template(“error_toast.png”), “密码错误提示未显示”) except Exception as e: # 失败时截图 snapshot(msg“login_failed”) logging.error(f“登录测试失败: {e}”) raise classmethod def teardown_class(cls): # 清理环境如退出应用返回桌面 cls.dev. home() logging.info(“测试类清理完成。”) if __name__ ‘__main__’: # 本地调试运行 poco AndroidUiautomationPoco() # … 直接写测试步骤 …关键点解析auto_setupvsconnect_deviceauto_setup是IDE和快速原型用的它会自动根据脚本所在目录的airtest.ini文件连接设备。生产环境建议用connect_device显式连接参数清晰易于配置化管理如从配置文件读取设备URI。Poco初始化参数use_airtest_inputTrue让Poco使用Airtest的输入法更稳定screenshot_each_actionFalse可以关闭每个动作都截图的默认行为提升速度需要截图时再手动调用snapshot()。结构清晰使用测试框架如pytest,unittest组织用例有清晰的setup初始化和teardown清理步骤利于用例管理和持续集成。4.2 元素定位的十八般武艺与稳定性优化定位不稳定是UI自动化的头号杀手。Airtest给了我们多种武器要用对地方。1. 图像定位优化技巧threshold阈值默认0.7。匹配相似度范围0~1。值越高要求越像。如果UI有轻微变色或阴影可以适当降低到0.6-0.65。但过低会增加误匹配风险。rgb彩色识别默认False即使用灰度图像进行匹配。这能避免颜色变化的干扰。如果你的元素是靠颜色区分的如红色警告图标则需设为True。target_pos点击位置默认5即图片中心。你可以设置为1-9对应九宫格位置。例如一个长条按钮你可能想点击它的左端target_pos4。使用wait和exists不要直接touch先wait(Template(…), timeout10)等待元素出现或者用if exists(Template(…)):判断再进行操作增加脚本容错性。截取特征区域截图时不要截整个按钮而是截取按钮中最独特、最不容易变化的部分作为模板。比如一个有文字的按钮截取图标部分比截取带文字的部分更稳定文字内容、字体可能变。2. Poco控件定位进阶Poco支持多种定位器类似于Selenium。属性选择器poco(text“登录”),poco(type“android.widget.Button”),poco(name“btn_submit”)。相对选择器与层级# 通过父子关系定位 poco(“android:id/content”).offspring(type“ListView”).child(text“项目1”) # 通过兄弟关系定位 poco(text“用户名”).parent().offspring(type“EditText”)[0]正则表达式与模糊匹配poco(textMatches“^Log.*”),poco(nameContains“btn”)。等待机制poco(text“登录”).wait_for_appearance(timeout10)比单纯的sleep好得多。focus和scroll对于列表使用poco(“ListView”).focus([0.5, 0.8])可以滚动列表。poco.scroll()方法可以模拟滑动查找元素。3. 混合定位实战案例假设要测试一个购物App的搜索功能搜索框是原生控件但搜索结果的商品项是自定义视图Poco抓不到完整信息但商品图片是固定的。def test_search_product(self): # 步骤1使用Poco精准定位搜索框并输入 search_input self.poco(“com.shop.app:id/search_box”) search_input.set_text(“蓝牙耳机”) # 步骤2使用图像识别点击“搜索”按钮按钮样式独特 touch(Template(r”search_action.png”, threshold0.9)) # 步骤3等待结果加载 self.poco(“android:id/progress”).wait_for_disappearance() # 等待进度圈消失 # 步骤4混合验证 - 检查是否有结果标题控件 assert self.poco(text“搜索结果”).exists(), “未进入搜索结果页” # 步骤5对列表中的第一个商品用图像识别其图片是否存在用Poco获取其价格文本 if exists(Template(r”product_sample.png”, threshold0.8)): # 假设商品项是一个Layout其第二个子节点是价格 price_text self.poco(“android.widget.ListView”).child(type“RelativeLayout”)[0].child(type“TextView”)[1].get_text() logging.info(f“第一个商品价格: {price_text}”) assert “¥” in price_text else: snapshot(msg“no_product_found”) raise AssertionError(“未找到预期商品图片”)4.3 断言、报告与异常处理自动化测试的灵魂是验证。Airtest提供了多种断言方式并生成了直观的报告。断言策略assert_exists/assert_not_exists基于图像识别的断言。最常用。assert_equal/assert_not_equal通用断言。Poco控件断言通常结合Python自带的assert语句如assert poco(“元素”).exists()。多维度断言重要的验证点建议从多个角度断言。例如支付成功既要断言界面跳转到成功页图像也要断言订单状态文本更新控件还可以断言Toast提示出现图像或控件。报告生成Airtest在运行脚本后会自动生成一个HTML报告位于log目录下。报告里包含了每一步的操作截图、成功/失败状态、日志和性能数据如每一步的耗时。命令行生成报告airtest report test_case.air --log_root ./log --outfile ./report.html --lang zh脚本中生成报告可以在teardown中调用simple_report函数。from airtest.report.report import simple_report simple_report(__file__, logpathTrue, output‘report.html’)异常处理与鲁棒性脚本必须能处理各种意外如网络延迟、弹窗干扰、元素加载慢。def safe_click(element_image, timeout10, retry3): “”“安全点击带有重试机制”“” for i in range(retry): try: if exists(element_image): touch(element_image) return True else: sleep(2) # 等待一下再重试 except TargetNotFoundError: logging.warning(f“第{i1}次尝试点击 {element_image} 失败元素未找到”) if i retry - 1: snapshot(msgf“failed_to_click_{element_image}”) raise sleep(2) return False # 使用示例 safe_click(Template(“popup_close.png”)) # 尝试关闭可能出现的弹窗5. 生产环境部署与持续集成脚本在本地IDE里跑得欢不代表能在服务器上稳定运行。生产环境部署是另一个大坑。5.1 无头运行与设备管理无头运行HeadlessCI服务器通常没有图形界面。Airtest的部分操作尤其是图像识别需要屏幕截图。解决方案是Android使用安卓模拟器如Android Studio AVD并开启硬件加速如-gpu host或者使用云真机/设备农场如STF、OpenSTF、各大云测平台。它们通常提供ADB over TCP连接。iOS更复杂。通常需要一台始终开机的Mac作为构建节点连接着iPhone。使用tidevice通过网络管理设备。也可以考虑使用基于Xcode Simulator的自动化但模拟器无法覆盖所有真机特性。Windows/Web在服务器上安装对应的浏览器或应用并可能需要虚拟显示服务器如Xvfb on Linux来提供虚拟桌面环境。设备连接管理在CI脚本中不能写死设备序列号。应该通过ADB命令动态列出设备并选择adb devices | grep device$ | awk ‘{print $1}’ | head -n 1或者使用设备池管理工具从池中申请一个空闲设备。对于iOS确保WDA服务已通过tidevice wdaproxy在指定端口启动。5.2 集成到CI/CD流水线以Jenkins GitLab为例构建节点准备确保构建节点可以是Mac、Linux或Windows上安装了完整的Python环境、Airtest/Poco库、以及对应平台的驱动ADB、WebDriver、tidevice等。脚本仓库将Airtest脚本.air目录或.py文件和测试资源图片放入Git仓库。Jenkins Pipelinepipeline { agent any // 指定有设备环境的节点 stages { stage(‘Checkout’) { steps { git ‘…’ } } stage(‘Install Dependencies’) { steps { sh ‘pip install -r requirements.txt’ // requirements.txt包含airtest, pocoui, pytest等 } } stage(‘Prepare Device’) { steps { sh ‘adb connect 192.168.1.100:5555’ // 连接网络ADB设备 sh ‘tidevice wdaproxy -B YOUR_BUNDLE_ID --port 8100 ’ // 启动iOS WDA服务 } } stage(‘Run Tests’) { steps { // 使用pytest运行 sh ‘pytest test_suite.py --alluredir./allure-results’ // 或直接使用airtest命令行运行 // sh ‘airtest run test_case.air --device Android:///192.168.1.100:5555 --log ./log’ } } stage(‘Generate Report’) { steps { sh ‘airtest report ./log --outfile ./report.html’ // 同时可以集成Allure等更漂亮的报告 sh ‘allure generate ./allure-results -o ./allure-report’ } post { always { archiveArtifacts artifacts: ‘report.html, allure-report/**’, fingerprint: true } } } stage(‘Cleanup’) { steps { sh ‘adb disconnect 192.168.1.100:5555’ sh ‘pkill -f tidevice’ // 清理iOS代理进程 } } } }测试数据与配置将设备连接信息、应用包名、账号密码等敏感信息通过Jenkins的Credentials或环境变量注入不要硬编码在脚本中。5.3 性能监控与脚本维护UI自动化脚本是“活”的需要持续维护。执行速度监控每个用例的执行时间。如果图像识别过多导致变慢考虑优化如减少不必要的snapshot调整threshold或改用Poco。稳定性看板收集每次运行的通过率建立趋势图。通过率持续下降往往是应用UI发生较大变化的信号。图片模板管理当UI改版时需要更新图片模板。建议将图片资源按模块/版本管理。可以在脚本中加入版本检查逻辑如果检测到新版本UI自动使用另一套图片模板。Page Object模式对于中大型项目强烈建议使用Page Object设计模式。将每个页面的元素定位和常用操作封装成类业务脚本只调用这些页面对象的方法。这样当UI定位方式改变时只需修改对应的Page Object类而不需要修改所有测试脚本。# 示例LoginPage.py class LoginPage: def __init__(self, poco): self.poco poco self.username_input self.poco(“com.app:id/username”) self.password_input self.poco(“com.app:id/password”) self.login_button_image Template(“login_btn.png”) def login(self, username, password): self.username_input.set_text(username) self.password_input.set_text(password) touch(self.login_button_image) def get_error_message(self): return self.poco(textContains“错误”).get_text() # 在测试脚本中 from LoginPage import LoginPage def test_login(): login_page LoginPage(poco) login_page.login(“user”, “wrong”) assert “密码” in login_page.get_error_message()走到这一步你的Airtest自动化就不再是玩具而是一个可以持续为项目交付价值的工程化资产。它可能无法解决所有UI自动化问题但在跨平台、快速验证、处理“疑难杂症”界面方面它无疑是一把趁手的利器。记住没有最好的框架只有最合适的策略。将Airtest的图像识别与传统控件的稳定性结合起来灵活运用才能构建出真正 robust 的自动化测试体系。