
1. 项目概述为什么选择Airtest做Web端UI自动化最近在团队里做技术选型讨论Web端UI自动化框架时我提了Airtest不少同事的第一反应是“Airtest那不是专门做游戏和App自动化的吗” 这其实是个挺普遍的误解。确实Airtest凭借其基于图像识别的特性在移动端和游戏测试领域名声大噪但它的能力远不止于此。我之所以在这次Web项目中力推Airtest核心原因就一个它用一种近乎“所见即所得”的方式大幅降低了UI自动化的编写和维护门槛尤其适合那些页面元素动态变化频繁、或者传统基于DOM定位如Selenium容易失效的场景。想象一下这个场景你要测试一个使用了大量JavaScript动态渲染前端框架比如React、Vue的现代Web应用。一个按钮它的id、class可能每次构建都会变或者根本就是随机生成的。用Selenium写脚本光是为了稳定地定位到这个按钮你可能就得写一长串复杂的XPath或CSS选择器还得加上各种显式等待WebDriverWait脚本又长又脆前端稍微改点样式或结构脚本就挂了维护成本直线上升。而Airtest的思路是我不管你的代码怎么变只要这个按钮在屏幕上看起来还是那个样子有特定的文字、颜色、形状我就能通过截图找到它并点击。这种基于视觉的自动化恰好能补上传统基于DOM定位方法的短板。当然这并不意味着我们要完全抛弃Selenium。在实际的“airtest做web端UI自动化实战”中我们采用的是一种混合模式Airtest IDE负责提供强大的图像识别、录屏、报告生成能力而浏览器驱动和基础DOM操作则交给它背后集成的Selenium通过selenium.webdriver。你可以理解为Airtest为Selenium穿上了一件更智能、更易用的“外衣”。对于测试同学或者刚开始接触自动化的开发者来说不用再死磕复杂的元素定位语法通过“点点点”的录制和简单的图像断言就能快速上手对于有经验的工程师依然可以调用底层的Selenium API进行更精细的控制。接下来我就结合一个从零开始的实战项目拆解如何用Airtest高效、稳定地开展Web UI自动化。2. 环境搭建与核心工具链解析工欲善其事必先利其器。用Airtest做Web自动化环境的正确搭建是第一步也是最容易踩坑的一步。很多人失败在起点就是因为浏览器、驱动、Airtest版本没匹配好。2.1 核心组件安装与版本协同首先你需要安装以下三个核心组件并确保它们的版本相互兼容Python环境建议使用Python 3.7-3.9版本这是目前Airtest和主流Selenium库最稳定的支持范围。可以使用pyenv或conda创建独立的虚拟环境避免包冲突。Airtest框架通过pip安装核心库。这里有个关键点我们不仅需要airtest还需要专门用于Web的airtest-selenium库。pip install airtest pip install airtest-selenium安装完成后可以运行airtest --version检查。我当前使用的稳定版本是1.2.14。浏览器与WebDriver这是最大的兼容性雷区。你必须确保浏览器版本、WebDriver驱动版本、Selenium库版本三者匹配。浏览器以Chrome为例建议使用官方稳定版如Chrome 115。避免使用太新或太旧的版本。WebDriver前往 ChromeDriver官网 或使用webdriver-manager推荐自动管理。手动下载时务必选择与你的Chrome主版本号完全一致的驱动。Selenium库airtest-selenium会依赖特定版本的selenium通常pip安装时会自动解决。如果出现问题可以尝试固定版本如pip install selenium4.10.0。注意强烈推荐使用webdriver-manager这个神器来管理驱动。它会在运行时自动检测浏览器版本并下载匹配的驱动彻底解决版本匹配的噩梦。安装命令pip install webdriver-manager。2.2 Airtest IDE不仅仅是录制工具很多新手以为Airtest IDE只是个脚本录制器那就太小看它了。它是我们整个自动化开发流程的集成作战中心。从官网下载并安装Airtest IDE后你会发现它主要提供三大功能块对Web自动化都至关重要设备连接与窗口管理启动IDE后你可以连接各种设备对于Web测试我们选择“Windows”窗口。你可以将打开的浏览器窗口直接“嵌入”到IDE中实现脚本编辑和实时画面反馈同屏操作效率极高。Poco辅助窗与元素查看虽然我们主打图像识别但Airtest也集成了Poco框架可以辅助查看Web页面的DOM结构。在IDE中启用Poco辅助窗后你可以像使用浏览器开发者工具一样悬停查看页面元素的属性这对于编写基于属性的断言或辅助定位非常有帮助。脚本录制与生成这是最直观的功能。点击录制按钮你在浏览器中的操作点击、输入、滑动会被自动转换成Airtest语句。但切记不要完全依赖录制。录制的代码通常比较“笨”包含大量绝对坐标点击。我们需要将其作为初稿然后手动优化为更稳定的图像识别或属性定位语句。2.3 项目初始化与第一个脚本环境准备好后我们开始创建第一个项目。在Airtest IDE中新建一个项目它会自动生成一个.air的目录结构。我们在这个目录下创建一个Python脚本文件比如test_web_login.air。一个最基础的、使用airtest-selenium的脚本骨架如下# -*- encodingutf8 -*- __author__ YourName from airtest.core.api import * # 引入Airtest核心API如touch, exists, assert_equal from selenium import webdriver from selenium.webdriver.common.by import By from airtest_selenium.proxy import WebChrome # 关键使用Airtest封装的WebDriver # 1. 创建浏览器实例使用webdriver-manager自动管理驱动 from webdriver_manager.chrome import ChromeDriverManager driver WebChrome(executable_pathChromeDriverManager().install()) # 2. 访问目标网址 driver.get(https://www.your-test-site.com) sleep(2) # 简单等待页面加载实际项目中应用显式等待替代 # 3. 使用Airtest的图像识别功能定位并点击登录按钮 # 假设你有一张名为“login_button.png”的登录按钮截图放在当前.air目录下 if exists(Template(rlogin_button.png)): touch(Template(rlogin_button.png)) print(成功点击登录按钮) else: print(未找到登录按钮图像) driver.quit() raise AssertionError(登录按钮未出现) # 4. 也可以混合使用Selenium原生定位方式进行输入 # 定位到用户名输入框假设它有稳定的id username_input driver.find_element(By.ID, username) username_input.clear() username_input.send_keys(test_user) # 5. 关闭浏览器 driver.quit()这个简单的例子展示了混合模式的精髓第3步用Airtest的图像识别应对可能变化的UI组件第4步用Selenium的原生方法处理有稳定属性的表单输入。接下来我们将深入拆解这两种定位方式的实战技巧。3. 核心定位策略图像识别与DOM定位的混合艺术纯图像识别和纯DOM定位在复杂的Web自动化中都有其局限性。将两者结合并针对不同场景选用最佳策略是保证脚本稳定性的关键。3.1 图像识别定位制作高鲁棒性的截图模板图像识别是Airtest的招牌但用不好就会导致脚本极其脆弱。制作一个“好”的截图模板Template需要技巧截图范围要“恰到好处”不要截取整个按钮或元素也不要截取得太小。最佳实践是截取该元素最具辨识度的核心特征区域。例如一个带图标的按钮就截取“图标部分文字”一个输入框就截取其左侧的标签文字。这样可以避免因元素尺寸微调、背景变化而导致匹配失败。利用threshold和rgb参数Template函数支持threshold匹配阈值和rgb是否彩色匹配参数。# 调整匹配阈值为0.8默认0.7提高匹配严格度 login_btn Template(r“login_btn.png” threshold0.8, rgbTrue) if exists(login_btn): touch(login_btn)threshold默认0.7值越高匹配要求越严格。对于背景复杂或容易混淆的元素可以调到0.8或0.85。rgb默认为False灰度匹配。如果元素依靠颜色区分如红色警告按钮务必设为True进行彩色匹配。启用record_pos和resolution进行相对定位高级在Airtest IDE中截图时注意底部参数。record_pos记录了该截图在录制时屏幕上的相对位置resolution记录了录制时的屏幕分辨率。当脚本在不同分辨率的机器上运行时Airtest会利用这些信息进行自适应查找提高跨设备兼容性。一般保持默认即可。实操心得对于频繁使用的关键图像模板如主导航栏图标不要直接在脚本里用Template(r“xxx.png”)。建议在脚本开头统一定义为一个变量如NAV_HOME Template(r“img/nav_home.png”)。这样一是方便管理二是如果需要整体调整阈值只需修改一处。3.2 DOM定位当Selenium遇上Airtestairtest-selenium的WebChrome对象完全兼容Selenium WebDriver的API。这意味着所有你熟悉的find_element(By.XXX, “value”)方法都可以直接使用。但在Airtest的上下文中我们有一些更好的实践优先使用稳定属性idnameclass namecss selectorxpath。id通常是唯一且最稳定的。尽量避免使用包含索引位置或复杂结构的XPath它们在前端重构时极易失效。与Airtest的等待机制结合Airtest提供了wait函数可以方便地等待一个图像元素出现。对于DOM元素我们可以结合Selenium的WebDriverWait但更优雅的方式是使用Airtest封装的airtest_selenium中的等待方法或者自己封装一个通用等待函数。from airtest.core.api import wait from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 方法1使用Airtest的wait等待图像 wait(Template(r“loading_complete.png”), timeout30) # 方法2使用Selenium的WebDriverWait等待DOM元素 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, “dynamic_content”)) )使用Poco for Web进行辅助定位Airtest IDE内置的Poco辅助窗支持Web。它可以解析页面提供类似于name、text等属性的选择器。虽然Poco在Web上的稳定性不如在原生App中但作为一种辅助定位和查看页面结构的手段非常有用。特别是对于某些难以用传统方式定位的复杂组件可以尝试用Poco的poco(“web”).child(“div”).child(text“提交”)这类方式。3.3 混合定位实战案例登录流程假设我们要自动化一个登录流程登录按钮是动态生成的图片按钮而用户名和密码输入框有稳定的id。from airtest.core.api import * from airtest_selenium.proxy import WebChrome from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys # 初始化驱动 driver WebChrome() driver.implicitly_wait(10) # 设置隐式等待 # 访问登录页 driver.get(“https://example.com/login”) sleep(2) # 1. 使用Selenium定位输入框稳定 username driver.find_element(By.ID, “login-username”) password driver.find_element(By.ID, “login-password”) username.send_keys(“my_account”) password.send_keys(“my_password”) # 2. 使用Airtest图像识别点击动态登录按钮 # 假设这个按钮是张图片每次加载略有不同 login_button_template Template(r“tpl_login_button.png”, threshold0.8) try: # 等待登录按钮出现 wait(login_button_template, timeout10) # 点击找到的图像位置 touch(login_button_template) print(“通过图像识别点击登录按钮成功”) except TargetNotFoundError: print(“登录按钮图像未找到尝试备用方案按回车键”) password.send_keys(Keys.ENTER) # 备用方案 # 3. 混合断言既检查页面跳转后的图像如用户头像也检查DOM中的欢迎文本 assert_exists(Template(r“tpl_user_avatar.png”), “登录后用户头像未显示”) welcome_text driver.find_element(By.CLASS_NAME, “welcome-msg”).text assert “欢迎回来” in welcome_text, f“欢迎文本不符实际内容{welcome_text}” driver.quit()这个案例展示了如何根据元素的不同特性灵活选择最合适的定位方式并设计备用方案从而构建出抗干扰能力更强的自动化脚本。4. 常用操作封装与断言机制编写可维护的自动化脚本不能只是一连串的find_element和touch。良好的封装和清晰的断言是提升脚本质量的核心。4.1 封装常用页面操作将常用的操作封装成函数或类方法可以极大减少代码重复提高可读性和维护性。class WebBasePage: def __init__(self, driver): self.driver driver self.timeout 20 def find_element_by_image(self, tpl_path, timeoutNone): 通过图像查找元素返回坐标未找到则返回None timeout timeout or self.timeout try: pos wait(Template(tpl_path), timeouttimeout) return pos except TargetNotFoundError: print(f“未找到图像元素{tpl_path}”) return None def click_by_image(self, tpl_path, timeoutNone): 通过图像点击元素 pos self.find_element_by_image(tpl_path, timeout) if pos: touch(pos) return True return False def input_text_by_id(self, element_id, text): 通过ID定位并输入文本 element self.driver.find_element(By.ID, element_id) element.clear() element.send_keys(text) def switch_to_new_window(self, original_window): 切换到新打开的窗口 for window_handle in self.driver.window_handles: if window_handle ! original_window: self.driver.switch_to.window(window_handle) break # 使用封装后的类 class LoginPage(WebBasePage): LOGIN_BTN_TPL “tpl/login_button.png” USERNAME_INPUT_ID “username” PASSWORD_INPUT_ID “password” def login(self, username, password): self.input_text_by_id(self.USERNAME_INPUT_ID, username) self.input_text_by_id(self.PASSWORD_INPUT_ID, password) success self.click_by_image(self.LOGIN_BTN_TPL) assert success, “登录按钮点击失败” print(“登录操作执行完毕”) # 在脚本中调用 driver WebChrome() login_page LoginPage(driver) login_page.login(“test”, “123456”)4.2 强大的断言机制自动化测试没有断言就等于没有灵魂。Airtest提供了多种断言方式图像存在性断言assert_exists是最常用的。# 断言某个图像如成功提示图标必须存在 assert_exists(Template(r“tpl_success_toast.png”), “操作成功后提示未出现”)图像不存在断言用于断言某些元素如错误弹窗、加载动画应该消失。# 断言加载动画必须消失 assert_not_exists(Template(r“tpl_loading.png”), “页面加载超时加载动画仍在”)数值/文本断言结合Selenium获取DOM文本或属性使用Python标准的assert。# 获取页面标题进行断言 page_title driver.title assert page_title “我的主页”, f“页面标题不正确当前为{page_title}” # 获取元素文本进行断言 balance_element driver.find_element(By.CLASS_NAME, “balance”) actual_balance balance_element.text # 可能需要对文本进行清理如去除货币符号 expected_balance “100.00” assert expected_balance in actual_balance, f“余额显示错误预期包含‘{expected_balance}’实际为‘{actual_balance}’”自定义截图断言在关键检查点手动截图并附加到测试报告中。from airtest.core.api import snapshot # 在某个操作后截图并附加描述信息 snapshot(msg“检查订单提交后的页面状态”) # 这个截图会自动出现在最终的HTML测试报告中## 5. 测试报告生成与脚本组织 Airtest一个非常强大的功能就是其内置的、可视化的HTML测试报告。它能清晰地展示每一步的操作、截图、成功与否的状态对于排查问题非常有帮助。 ### 5.1 生成并查看测试报告 使用airtest.report接口可以非常简单地在脚本末尾生成报告。 python # 在脚本文件末尾所有测试逻辑之后添加 from airtest.report.report import simple_report # 指定脚本文件的路径通常是__file__以及输出日志和报告的路径 simple_report(__file__, logpathTrue, outputr“./log/log.html”)运行脚本后会在当前目录的log文件夹下生成一个log.html文件。用浏览器打开这个文件你会看到一个时间线式的报告每一步操作都有记录包括touch、assert、swipe等。每一步都配有屏幕截图直观地看到操作时的界面状态。成功与失败一目了然失败的步骤会用红色高亮并显示错误信息。可以折叠/展开查看详情方便快速浏览或深入排查。5.2 脚本组织与数据驱动对于大型项目我们不能把所有用例都写在一个.py或.air文件里。需要良好的组织架构Page Object Model (POM) 设计模式如前文所示将每个页面封装成一个类如LoginPage,HomePage,OrderPage页面的元素定位符和操作作为类的方法。测试脚本则成为一系列页面对象操作的串联清晰易懂。用例与逻辑分离测试用例脚本只关心业务流“做什么”具体的页面操作细节“怎么做”封装在Page Object中。数据驱动将测试数据如用户名、密码、搜索关键词从脚本中剥离出来存放在外部文件如JSON、YAML、Excel、CSV中。使用pytest或unittest的参数化功能来驱动测试。import pytest import json # 从JSON文件加载测试数据 with open(“test_data/login_data.json”) as f: test_cases json.load(f) pytest.mark.parametrize(“case”, test_cases) def test_login(driver, case): login_page LoginPage(driver) login_page.login(case[“username”], case[“password”]) # 根据case中的“expected”字段进行不同的断言 if case[“expected”] “success”: assert_exists(Template(r“tpl_welcome.png”)) else: assert_exists(Template(r“tpl_error_msg.png”))使用测试框架集成虽然Airtest可以独立运行但集成到pytest或unittest框架中能获得更好的用例管理、夹具fixture支持和并行执行能力。airtest-selenium的WebChrome驱动可以很方便地作为pytest的一个fixture在用例开始时创建结束时退出。6. 常见问题排查与性能优化实录在实际项目中你会遇到各种各样的问题。这里记录了几个最典型的问题和我的解决方案。6.1 图像识别失败问题排查表问题现象可能原因排查步骤与解决方案TargetNotFoundError频繁出现1. 截图模板不准确或变化。2. 屏幕分辨率/缩放比例不同。3. 页面加载未完成。4. 阈值(threshold)设置不合理。1.重新截图确保截图是当前页面的最新状态且特征区域明显。2.检查分辨率在运行脚本的机器上确认浏览器窗口大小和缩放比例与录制时相近。可以考虑使用auto_setup接口或调整脚本中的屏幕参数。3.增加等待在操作前加入sleep或wait确保元素完全渲染。优先使用wait(Template(...))。4.调整阈值尝试降低threshold如0.6以提高容错或提高它以匹配更精确的图案。使用Airtest IDE的图像识别结果预览功能辅助调试。点击位置偏移1. 录制和运行环境分辨率不一致。2. 使用了绝对坐标录制。1.使用相对定位确保截图时利用了record_pos参数或使用Poco等其他定位方式辅助。2.优化截图截取元素中心区域避免边缘。可以尝试在touch语句前加入target_pos参数指定点击截图区域的中心点如target_pos5表示中心。在循环中识别越来越慢Airtest的图像识别在未找到目标时会遍历全屏耗时较长。设置搜索区域如果知道元素可能出现的大致区域使用Template的region参数限定搜索范围可以极大提升识别速度。tpl Template(“button.png”, region(x, y, width, height))6.2 脚本执行速度优化减少全局搜索如上所述尽可能为Template指定region参数将搜索范围从整个屏幕缩小到一个特定区域。善用等待减少sleep无脑的sleep(5)是性能杀手。多用wait函数它会在找到元素后立即返回而不是死等固定时间。对于DOM元素使用Selenium的WebDriverWait。并行与批量执行对于大量独立用例考虑使用pytest-xdist等插件进行并行测试。Airtest脚本本身是Python代码可以很好地融入这些框架。关闭不必要的浏览器特性在初始化WebDriver时添加选项提升执行速度。from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_argument(“--headless”) # 无头模式不打开GUI速度最快 chrome_options.add_argument(“--disable-gpu”) # 禁用GPU加速 chrome_options.add_argument(“--no-sandbox”) chrome_options.add_argument(“--disable-dev-shm-usage”) chrome_options.add_argument(“--window-size1920,1080”) # 固定窗口大小保证一致性 # 注意无头模式下图像识别依然工作但部分依赖渲染的样式可能不同需测试。 driver WebChrome(optionschrome_options)6.3 稳定性提升技巧引入重试机制对于网络波动或瞬时加载慢导致的操作失败可以封装一个带重试的操作函数。from airtest.core.error import TargetNotFoundError import time def retry_click_by_image(tpl, retries3, interval2): for i in range(retries): try: pos wait(tpl, timeout10) touch(pos) return True except TargetNotFoundError: if i retries - 1: print(f“第{i1}次点击失败{interval}秒后重试...”) time.sleep(interval) else: print(f“重试{retries}次后仍失败”) return False清理浏览器状态用例之间做好隔离避免缓存、Cookie影响。可以在setUp和tearDown中清理。def setup_function(): driver.delete_all_cookies() driver.get(“about:blank”) # 跳转到空白页清空上下文 def teardown_function(): driver.quit()日志与报告分析养成查看Airtest HTML报告的习惯。失败用例的报告会高亮错误步骤和当时的屏幕截图这是定位问题最快的方式。结合Python的logging模块输出更详细的调试信息。经过这样一套从环境搭建、核心定位、操作封装、报告生成到问题排查的完整流程你会发现用Airtest做Web UI自动化并非要替代Selenium而是为其赋能。它用图像识别解决了UI自动化中最令人头疼的“元素定位不稳定”问题同时又保留了Selenium全部的能力和生态。对于需要快速交付、页面变动频繁或者测试人员代码能力较弱的团队来说这种混合模式无疑提供了一条高性价比的实践路径。我个人的体会是从“纯代码”转向“视觉辅助代码”的思维一开始可能需要适应但一旦掌握在应对复杂多变的Web前端时那种游刃有余的感觉会让你觉得之前的投入都是值得的。最后一个小建议将你的图像模板文件也纳入版本控制如Git并建立清晰的命名和目录规范这对团队协作和长期维护至关重要。