AirtestProject与pytest融合:支付宝小程序UI自动化测试实战

发布时间:2026/6/26 18:25:42
AirtestProject与pytest融合:支付宝小程序UI自动化测试实战 1. 项目概述与核心价值最近在搞支付宝小程序的测试UI回归这块真是让人头疼。每次发版前测试同学都要把核心链路手动点一遍费时费力不说还容易漏测。为了解决这个问题我花了不少时间研究最终敲定了一套基于AirtestProject和pytest的UI自动化测试方案。这套方案跑下来不仅把核心功能的回归时间从几小时压缩到了几分钟还把测试用例的管理和执行流程给规范化了。今天就来详细拆解一下我是怎么把这两个看似不搭界的工具揉在一起玩转支付宝小程序UI自动化的。简单来说AirtestProject负责“看得见”的部分——模拟人手去操作手机屏幕点击、滑动、输入文字而pytest则负责“看不见”的部分——组织测试用例、管理测试数据、生成漂亮的测试报告。支付宝小程序作为一个运行在特定容器支付宝客户端内的应用其UI自动化有它的特殊性比如页面元素识别、环境依赖等都需要特别处理。这套组合拳正好能覆盖从脚本编写、调试到批量执行、报告分析的全流程对于追求测试效率和质量的团队来说是个非常实用的选择。2. 技术选型与框架设计思路2.1 为什么是AirtestProject pytest市面上UI自动化框架不少像Appium、Selenium等名声在外。但针对支付宝小程序我选择AirtestProject主要有几个考量。首先Airtest基于图像识别和Poco控件识别双引擎对于小程序这种混合渲染部分原生组件部分WebView的界面适应性更强。有时候纯靠控件定位如xpath在小程序里会失效但图像识别可以作为有效的补充和兜底方案。其次Airtest的IDE对新手极其友好录制回放、脚本编辑、实时调试一体大大降低了脚本开发的门槛。最后它底层基于Python这为我们后续集成pytest提供了天然的语言基础。而选择pytest则是看中了它强大的测试管理和执行能力。pytest的fixture机制可以优雅地管理测试前置如启动支付宝、进入指定小程序和后置操作如清理数据、退出应用。它的参数化功能能轻松实现一套脚本覆盖多组测试数据。更重要的是pytest拥有丰富的插件生态比如pytest-html可以生成详尽的HTML报告pytest-xdist支持分布式并行执行这对于提升测试套件的执行效率至关重要。将Airtest的“操作能力”与pytest的“组织能力”结合就构成了一个既灵活又强大的自动化测试框架。2.2 整体框架架构设计我们的框架核心是分层设计目的是让脚本更易维护、更易复用。主要分为以下几层设备连接与驱动层这一层负责与物理手机或模拟器建立连接。我们使用Airtest的connect_device函数连接设备并封装一个统一的设备操作对象供上层调用。页面对象模型层这是核心层。我们将支付宝小程序的每一个页面抽象成一个Page类。这个类里封装了该页面的所有元素定位方式如图像模板、Poco选择器和在这个页面上可以进行的操作如点击按钮、输入文本、滑动列表。这样做的好处是当页面UI发生变化时我们只需要修改对应的Page类而不用到处修改测试脚本。测试用例层基于pytest编写测试用例函数。每个函数都是一个完整的测试场景例如“测试登录功能”。在用例函数中我们调用Page对象的方法来组合操作步骤并使用pytest的assert语句进行结果断言。测试数据层将测试数据如用户名、密码、商品ID从脚本中剥离出来存储在独立的文件如JSON、YAML或Excel中。通过pytest的参数化功能将数据注入到测试用例中。执行与报告层利用pytest的命令行工具或配置文件来组织测试套件的执行并通过插件生成可视化测试报告。此外我们还会设计一个conftest.py文件用来存放全局的pytest fixture例如初始化Airtest驱动、登录通用用户等确保每个测试用例都在一个干净、一致的环境中开始执行。注意支付宝小程序对自动化测试工具的反检测机制在不断加强。纯图像识别方式相对更“低调”但效率较低控件识别效率高但可能触发风控。在实际项目中往往需要根据具体页面特点混合使用两种方式并适当加入随机延迟模拟真人操作。3. 环境搭建与核心工具详解3.1 基础环境准备工欲善其事必先利其器。首先需要准备好Python环境、AirtestIDE以及测试用的支付宝环境。Python环境建议使用Python 3.7及以上版本。通过pip安装核心依赖包pip install airtest pip install pocoui # Poco控件识别库 pip install pytest pip install pytest-html # 用于生成HTML报告 pip install pytest-xdist # 可选用于并行测试AirtestIDE虽然脚本最终在命令行运行但AirtestIDE在开发调试阶段不可或缺。从官网下载安装后它主要提供三个功能一是设备连接和屏幕实时投屏二是脚本录制与回放可以快速生成操作代码三是图像编辑器方便截取和编辑用于识别的图片模板。支付宝测试环境强烈建议使用独立的支付宝测试账号并在手机上安装支付宝的测试包或开发者版本。正式环境的支付宝可能对自动化操作有更严格的限制。同时需要将待测的小程序部署到测试环境并确保你有该小程序的开发者或测试权限以便能正常访问。3.2 连接设备与初始化驱动连接真机是最稳定的方式。确保手机开启USB调试模式并通过USB线连接电脑。在AirtestIDE中你可以看到设备列表点击连接即可。在代码中连接设备的典型方式如下from airtest.core.api import * # 连接Android设备参数为设备序列号可通过adb devices获取 auto_setup(__file__, devices[Android://127.0.0.1:5037/你的设备序列号]) # 或者使用connect_device函数 dev connect_device(Android://127.0.0.1:5037/你的设备序列号)连接成功后dev对象就是我们对设备进行所有操作的入口。接下来需要初始化Poco对象用于控件识别。因为支付宝小程序本质上是混合应用我们需要使用Uiautomation模式来识别其原生组件部分。from poco.drivers.android.uiautomation import AndroidUiautomationPoco poco AndroidUiautomationPoco(use_airtest_inputTrue, screenshot_each_actionFalse)这里有两个关键参数use_airtest_inputTrue让Poco使用Airtest的输入法兼容性更好screenshot_each_actionFalse关闭每个动作都截图的功能可以提升执行速度调试时可设为True。3.3 封装关键操作与等待机制在UI自动化中等待是门艺术。页面加载、网络请求、动画效果都可能让元素无法立即出现。直接使用time.sleep是低效且不稳定的。我们需要封装智能的等待方法。显式等待等待某个特定条件成立比如元素出现、元素可点击。我们可以结合Airtest的exists图像和Poco的wait_for_appearance控件来封装。def wait_for_element(image_templateNone, poco_selectorNone, timeout30): 等待元素出现 :param image_template: 图像模板路径 :param poco_selector: Poco选择器如poco(text登录) :param timeout: 超时时间 :return: 找到的元素对象或True/False if image_template: return exists(Template(image_template), timeouttimeout) elif poco_selector: try: poco_selector.wait_for_appearance(timeouttimeout) return poco_selector except: return None else: raise ValueError(必须提供图像模板或Poco选择器)全局操作封装对于一些常用操作如安全地点击、输入也需要封装加入重试和异常处理逻辑。def safe_click(element, retry3): 安全点击尝试多次 for i in range(retry): try: element.click() sleep(1) # 点击后等待一下 return True except Exception as e: print(f第{i1}次点击失败: {e}) sleep(2) return False4. Page Object模型设计与实现4.1 PO模型的核心思想Page ObjectPO模型是UI自动化的最佳实践之一。它的核心思想是将页面对象和测试逻辑分离。每一个小程序页面如首页、商品详情页、购物车页、我的页面都对应一个Python类。这个类中不包含任何断言逻辑只包含两样东西元素定位器以属性的形式定义这个页面上所有需要操作或查看的UI元素在哪里。页面操作方法以函数的形式定义在这个页面上可以执行哪些操作如点击登录按钮、在搜索框输入关键词。这样做最大的好处是可维护性。当小程序UI改版某个按钮的位置或图片变了你只需要去对应的Page类里修改那一个元素的定位方式所有用到这个按钮的测试用例都无需改动。4.2 支付宝小程序首页Page类示例让我们以支付宝小程序典型的首页为例看看如何实现一个Page类。假设首页有搜索框、轮播图、几个功能入口图标。class HomePage: def __init__(self, poco): self.poco poco # 使用图像识别定位搜索框图标因为可能是一个自定义图标 self.search_icon_img Template(r../images/home_search_icon.png) # 使用Poco定位搜索输入框假设它是原生输入框 self.search_input poco(android.widget.EditText).offspring(search_input) # 使用Poco定位“我的”入口通过文本 self.my_tab poco(text我的) # 轮播图可以用图像或控件定位这里假设用图像定位第一张图 self.banner_img Template(r../images/home_banner_1.png) def enter_search(self, keyword): 进入搜索页面并输入关键词 # 等待并点击搜索图标 if exists(self.search_icon_img): touch(self.search_icon_img) else: # 备用方案如果图像识别失败尝试用控件定位 self.search_input.click() sleep(1) # 等待搜索页面加载 # 在搜索框输入文字 self.search_input.set_text(keyword) # 模拟键盘回车 keyevent(ENTER) return SearchResultPage(self.poco) # 返回搜索结果页对象 def goto_my_page(self): 跳转到‘我的’页面 self.my_tab.click() sleep(2) # 等待页面跳转 return MyPage(self.poco) # 返回‘我的’页面对象 def is_banner_displayed(self): 检查轮播图是否正常显示 return exists(self.banner_img, timeout5)在这个类中__init__方法初始化了页面上关键元素的定位方式。enter_search和goto_my_page是页面操作方法它们内部封装了具体的操作步骤点击、输入、等待并且方法最后返回了下一个页面的对象。这形成了一个清晰的页面流测试脚本的阅读逻辑就和用户操作流程一致了。4.3 复杂交互与列表处理支付宝小程序中常有滑动列表、长按、多手势等复杂交互。Airtest和Poco提供了相应的API。处理滑动列表例如在商品列表页滚动加载更多。def scroll_to_load_more(self, max_swipes5): 向下滑动直到‘没有更多’出现或达到最大滑动次数 no_more_text self.poco(text没有更多了) for i in range(max_swipes): if no_more_text.exists(): break # 从屏幕中部向上滑动模拟下拉 swipe((self.width/2, self.height*0.7), (self.width/2, self.height*0.3), duration0.5) sleep(2) # 等待数据加载处理浮层和弹窗支付宝小程序有很多系统弹窗如授权、支付密码。这些弹窗往往是全局的可以单独封装一个CommonPopup类来处理在所有Page类中调用。class CommonPopup: def __init__(self, poco): self.poco poco def handle_authorize(self, acceptTrue): 处理授权弹窗 auth_title self.poco(textMatches.*获取你的.*信息.*) if auth_title.exists(): if accept: self.poco(text同意).click() else: self.poco(text拒绝).click() return True return False5. 基于pytest的测试用例组织与执行5.1 使用Fixture管理测试生命周期pytest的Fixture是管理测试依赖和生命周期的神器。我们将设备的初始化、应用的启动、用户的登录等操作封装成Fixture放在conftest.py文件中供所有测试模块使用。# conftest.py import pytest from airtest.core.api import * from poco.drivers.android.uiautomation import AndroidUiautomationPoco pytest.fixture(scopesession) def device_setup(): 会话级Fixture整个测试会话只执行一次连接设备 dev connect_device(Android://127.0.0.1:5037/设备序列号) poco AndroidUiautomationPoco(use_airtest_inputTrue) yield dev, poco # 将设备对象和poco对象提供给测试用例 # 测试会话结束后可以在这里执行清理如断开连接通常不需要 print(测试会话结束) pytest.fixture(scopefunction) def alipay_mini_program(device_setup): 函数级Fixture每个测试用例执行前确保回到支付宝小程序首页 dev, poco device_setup # 1. 启动支付宝 start_app(com.eg.android.AlipayGphone) sleep(5) # 2. 处理可能的启动广告、升级弹窗调用CommonPopup popup CommonPopup(poco) popup.handle_update_popup() # 3. 进入指定小程序这里假设小程序已在‘我的小程序’列表 poco(text我的).click() poco(text小程序).click() poco(text你的小程序名).click() sleep(5) # 等待小程序加载 # 4. 返回首页Page对象供测试用例使用 from pages.home_page import HomePage yield HomePage(poco) # 5. 测试用例结束后可以关闭小程序可选 # keyevent(BACK) 多次直到退出小程序这样在测试用例中我们只需要将alipay_mini_program作为参数传入就能获得一个已经处于小程序首页的Page对象无需在每个用例里重复写启动逻辑。5.2 编写结构化测试用例有了Fixture和Page对象编写测试用例就变得非常清晰和简单。每个测试函数应该专注于一个具体的业务场景。# test_search.py import pytest class TestSearchFunction: 测试搜索功能 pytest.mark.parametrize(keyword, expected_count, [(手机, 10), (不存在的商品xyz, 0)]) def test_search_by_keyword(self, alipay_mini_program, keyword, expected_count): 测试输入关键词搜索商品 :param alipay_mini_program: Fixture提供的首页对象 :param keyword: 搜索关键词 :param expected_count: 期望搜到的商品数量至少 # 1. 从首页进入搜索 search_result_page alipay_mini_program.enter_search(keyword) # 2. 断言搜索结果页面应该成功打开 assert search_result_page.is_page_loaded(), 搜索结果页加载失败 # 3. 获取实际商品数量假设通过商品卡片数量判断 actual_count search_result_page.get_product_count() # 4. 进行断言 if expected_count 0: assert actual_count expected_count, f期望至少{expected_count}个结果实际只有{actual_count}个 else: assert actual_count expected_count, f期望无结果实际找到{actual_count}个结果 def test_search_history(self, alipay_mini_program): 测试搜索历史记录功能 # 先搜索一次 alipay_mini_program.enter_search(测试历史) # 返回首页再次点击搜索框 alipay_mini_program.back_to_home() alipay_mini_program.click_search_box() # 断言搜索历史列表中包含“测试历史” assert alipay_mini_program.is_search_history_exist(测试历史), 搜索历史未正确记录用例使用了pytest.mark.parametrize装饰器来实现数据驱动测试用一套逻辑测试多组数据。断言语句清晰明了直接反映了业务期望。5.3 测试执行与报告生成脚本写好了如何执行并看到结果pytest命令行工具非常强大。基本执行在项目根目录下执行pytest命令会自动发现并运行所有以test_开头的文件。指定运行pytest test_search.py::TestSearchFunction只运行搜索功能的测试类。并行执行安装pytest-xdist后使用pytest -n auto可以自动根据CPU核心数并行运行测试极大缩短执行时间。生成HTML报告这是展示测试结果的关键。使用pytest-html插件。pytest --htmlreport.html --self-contained-html这条命令会生成一个独立的report.html文件里面包含了测试通过率、每个用例的执行详情、失败用例的错误日志和截图需要额外配置截图钩子。报告可以直接在浏览器中打开方便团队查看和归档。配置截图钩子为了让HTML报告在用例失败时自动附上当时的屏幕截图需要在conftest.py中增加处理逻辑。pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: # 用例失败时截图并附加到报告中 snapshot_path f./screenshots/failure_{item.name}_{datetime.now().strftime(%Y%m%d_%H%M%S)}.png snapshot(filenamesnapshot_path) # Airtest截图 if hasattr(report, extra): report.extra.append(pytest_html.extras.image(snapshot_path))这样每次用例失败报告里就会多一张问题发生时的屏幕截图对于排查UI问题或者脚本定位问题有极大帮助。6. 实战难点与高级技巧6.1 元素定位的稳定性策略支付宝小程序的UI变化相对频繁且部分元素是动态生成的这给元素定位带来了巨大挑战。不能把定位方式写死必须采用多层次的稳定性策略。1. 混合定位策略对于关键元素准备图像识别和控件识别两套方案。优先使用控件定位因为更快更准失败后自动降级到图像识别。def click_secure_button(self, poco_selector, image_template, desc按钮): 安全点击按钮尝试多种定位方式 # 方案1: Poco控件定位 if poco_selector.exists(): poco_selector.click() return True # 方案2: 图像识别定位 elif exists(image_template): touch(image_template) return True else: # 方案3: 文本匹配兜底 all_texts self.poco(textMatchesf.*{desc}.*) if all_texts.exists(): all_texts.click() return True raise ElementNotFoundError(f未找到{desc}元素)2. 相对定位与模糊匹配不要依赖绝对的位置或完整的文本。使用Poco的offspring、child、parent等关系进行相对定位。使用正则表达式进行文本模糊匹配textMatches”.*登录.*”。3. 动态ID处理小程序中很多元素的resource-id或name是动态生成的每次运行都不同。对于这类元素应通过其相邻的、具有稳定特征的兄弟节点或父节点来定位。6.2 状态同步与异步等待小程序中大量操作是异步的如网络请求、动画。脚本执行速度远快于界面响应速度必须进行有效的状态同步。1. 基于内容的等待不要盲目sleep而是等待某个特定内容出现或消失。例如提交订单后等待“支付成功”的提示出现或者等待“加载中”的旋转图标消失。def wait_for_success_toast(self, timeout10): 等待成功Toast出现并消失 success_toast self.poco(textMatches.*成功.*) success_toast.wait_for_appearance(timeout) success_toast.wait_for_disappearance(timeout) # 等待它消失确保流程走完2. 轮询检查对于某些无法直接通过UI元素判断的操作可以采用轮询检查。例如检查订单是否创建成功可以间隔几秒去“我的订单”页面查看一次最多尝试N次。3. 设置合理的超时时间不同的操作超时时间应不同。网络请求可以长一些15-30秒本地页面跳转可以短一些5-10秒。超时时间太短容易导致误报失败太长则降低测试效率。6.3 测试数据管理与数据驱动测试数据的管理是另一个核心。我们将测试数据与脚本分离。1. 使用YAML/JSON文件管理创建一个test_data目录里面按功能模块存放数据文件。# test_data/search_data.yaml search_cases: - keyword: 苹果 expected_min_count: 5 category: 水果 - keyword: 华为 expected_min_count: 3 category: 手机 - keyword: asdfghjkl expected_min_count: 0 category: 无效关键词2. 在Fixture中加载数据可以在Fixture中读取这些数据文件并通过参数化传递给测试用例。import yaml import pytest def load_search_data(): with open(./test_data/search_data.yaml, r, encodingutf-8) as f: data yaml.safe_load(f) return data[search_cases] pytest.fixture(paramsload_search_data()) def search_case(request): 参数化Fixture每次提供一个测试用例数据 return request.param # 在测试用例中使用 def test_search_with_data(alipay_mini_program, search_case): result_page alipay_mini_program.enter_search(search_case[keyword]) assert result_page.get_product_count() search_case[expected_min_count]3. 数据清理对于创建了数据的测试如下单、发布内容一定要在用例执行后清理避免影响后续测试。这可以在Fixture的teardown部分完成或者通过调用小程序的测试接口进行数据清理。6.4 集成到CI/CD流水线自动化测试只有集成到持续集成/持续部署CI/CD流程中才能最大化其价值。通常的做法是准备专用测试机在CI服务器如Jenkins、GitLab Runner上连接一台或多台固定的测试手机或使用云测平台的真机集群。编写执行脚本创建一个Shell脚本如run_tests.sh里面包含环境检查、安装依赖、启动服务、执行pytest命令、收集报告等步骤。配置CI任务在Jenkins或GitLab CI中配置一个Pipeline任务监听代码仓库的特定分支如develop、release/*。当有代码推送或合并请求时自动触发该任务。任务步骤拉取代码获取最新的自动化测试脚本和被测小程序代码。安装环境安装Python、Airtest、项目依赖等。部署测试包将小程序的最新测试包部署到测试环境。执行测试运行run_tests.sh执行UI自动化测试套件。归档结果将生成的HTML测试报告、失败截图等文件归档供后续查看。结果通知根据测试结果通过/失败通过邮件、钉钉、企业微信等工具通知相关人员。失败重试与稳定性UI测试天生不稳定偶尔失败可能是环境波动所致。可以在CI配置中设置失败重试机制如pytest的--reruns参数只有重试多次仍失败才判定为真正失败。7. 常见问题排查与优化实录7.1 元素找不到或操作失败这是最常见的问题其排查思路可以形成一个固定流程确认设备连接与屏幕状态首先检查手机是否锁屏、是否停留在支付宝首页。可以手动点亮屏幕并回到支付宝。使用AirtestIDE辅助定位在IDE中重新连接设备使用Poco辅助窗查看当前页面的UI树结构确认你使用的定位语句是否能唯一匹配到目标元素。很多时候元素name或text发生了变化。检查等待时间在操作前增加一个显式等待确保元素已经加载出来。使用wait_for_appearance比sleep更可靠。尝试图像识别兜底如果控件定位一直失败考虑使用AirtestIDE截取目标元素的图片改用图像识别。查看异常日志仔细阅读pytest输出的错误堆栈信息Airtest和Poco的异常信息通常会给出线索比如Target matching timed out等待超时或Invalid operation on invalid node节点无效。7.2 脚本运行速度慢UI自动化脚本执行慢是通病但可以通过以下手段优化减少不必要的截图将Poco初始化时的screenshot_each_action设为False在非调试阶段关闭Airtest的全局截图。优化等待策略用智能等待等元素出现/消失替代固定的sleep。将超时时间设置在一个合理的下限。并行执行利用pytest-xdist并行运行多个测试用例。前提是测试用例之间没有依赖且有多台测试设备。用例设计优化避免每个用例都从“启动支付宝-进入小程序”开始。可以将登录等前置操作放到session级别的Fixture中一次登录多个用例复用。对于不依赖登录状态的用例可以设计为免登录执行。7.3 支付宝环境风控与限制支付宝对自动化操作比较敏感可能会触发风控表现为滑块验证、操作频繁提示、甚至账号短暂限制。使用测试专用账号绝对不要使用生产环境账号或重要账号进行自动化测试。模拟真人操作在关键操作之间加入随机、合理的延迟如sleep(random.uniform(1, 3))避免脚本以机器般的固定频率高速操作。控制执行频率不要短时间内高频次运行全量测试套件。可以将用例分批次、分时段执行。准备应对方案在脚本中增加对风控弹窗的识别和处理逻辑。一旦检测到滑块或验证码可以记录日志并标记测试跳过而不是让脚本卡死。7.4 测试报告与失败分析生成的HTML报告是分析问题的入口。对于失败的用例要按以下步骤分析查看错误摘要报告顶部会列出所有失败用例的名称和错误类型。查看详细日志点击失败用例展开Captured log call和Extra部分。这里会有pytest打印的日志和附加的失败截图。分析截图这是最直观的。对比失败时的屏幕截图和预期状态看是页面没加载出来还是元素位置不对或者是出现了意外的弹窗。复现问题根据错误信息尝试在AirtestIDE中手动执行失败的步骤看是否能稳定复现。如果能复现就是脚本或环境问题如果不能复现可能是之前的偶发性环境问题考虑加入重试机制。这套基于AirtestProject和pytest的支付宝小程序UI自动化方案从最初的探索到现在的稳定运行中间踩过了不少坑。最大的体会是UI自动化不是一劳永逸的它需要随着产品的迭代而持续维护。建立良好的Page Object模型和用例结构是降低维护成本的关键。同时不要追求100%的自动化覆盖率优先将那些稳定、核心、重复执行率高的冒烟测试和回归测试用例自动化才能获得最高的投入产出比。在实际项目中将这套自动化脚本与监控系统结合定期在夜间执行第二天早上就能看到一份清晰的回归测试报告能为团队的快速迭代提供坚实的质量保障。