Airtest自动化测试第二篇:图像识别脚本编写——核心API详解与实战技巧
·
录制只是入门,真正用好Airtest,必须掌握核心API。本篇详解touch、swipe、text、wait、exists、assert等所有关键操作。
目录
一、Airtest图像识别原理
1.1 它是怎么"看见"按钮的
你的操作:截一张"登录按钮"的图片,保存为 login_btn.png
Airtest的流程:
① 截取当前手机屏幕
② 在当前屏幕中搜索 login_btn.png 的位置
③ 找到匹配位置 → 计算中心坐标 → 点击
④ 找不到 → 报错 ImageNotFoundError
1.2 关键概念:置信度(Threshold)
python
复制
# threshold 是图像匹配的"门槛值",范围0~1
# 默认值:0.7(70%相似即认为匹配)
touch(Template(r"btn.png", threshold=0.7)) # 默认,70%相似
touch(Template(r"btn.png", threshold=0.9)) # 严格,90%相似才匹配
touch(Template(r"btn.png", threshold=0.5)) # 宽松,50%相似就匹配
| threshold值 | 效果 | 适用场景 |
|---|---|---|
| 0.9 ~ 0.99 | 极严格,几乎一模一样才匹配 | 图标固定不变 |
| 0.7 ~ 0.8 | 默认推荐,稳定可靠 | 大多数场景 |
| 0.5 ~ 0.6 | 宽松,允许较大差异 | 动画、特效、多分辨率 |
| < 0.5 | 极宽松,容易误匹配 | 不建议使用 |
1.3 坐标系
python
复制
# Airtest使用相对坐标(比例坐标),而不是像素坐标
# 屏幕中心是 (0, 0)
# 左上角是 (-1, -1)(或接近)
# 右下角是 (1, 1)(或接近)
# 好处:同一脚本在不同分辨率的手机上都能运行!
touch((0, 0)) # 点击屏幕中心
touch((0.5, 0.5)) # 点击右下区域中心
touch((-0.5, 0)) # 点击左侧中间
二、操作类API
2.1 touch —— 点击
python
复制
from airtest.core.api import *
# 方式一:图像识别点击(最常用)
touch(Template(r"login_btn.png"))
# 方式二:坐标点击
touch((500, 800)) # 像素坐标
# 方式三:图片 + 坐标偏移
touch(Template(r"icon.png", target_pos=5))
# target_pos: 1=左上 2=上中 3=右上 4=左中 5=中心 6=右中 7=左下 8=下中 9=右下
# 方式四:连续点击
touch(Template(r"btn.png"), times=3) # 点击3次
touch(Template(r"btn.png"), duration=0.5) # 按下0.5秒(长按)
# 方式五:双指点击
pinch(in_or_out='in', center=None, percent=0.5) # 缩小
pinch(in_or_out='out', center=None, percent=0.5) # 放大
2.2 swipe —— 滑动
python
复制
# 从起点滑到终点
swipe((100, 1000), (100, 200)) # 从下往上滑(像素坐标)
# 图像识别起点,滑到指定坐标
swipe(Template(r"item.png"), vector=(0, -0.5)) # 从item位置向上滑
# 常用滑动方向(相对坐标)
swipe((0, 0.7), (0, 0.3)) # 上滑(浏览列表)
swipe((0, 0.3), (0, 0.7)) # 下滑(刷新)
swipe((0.8, 0.5), (0.2, 0.5)) # 左滑(翻页)
swipe((0.2, 0.5), (0.8, 0.5)) # 右滑(返回)
# 控制滑动速度
swipe((0, 0.7), (0, 0.3), duration=0.5) # 快速滑动
swipe((0, 0.7), (0, 0.3), duration=2.0) # 慢速滑动
# 连续滑动(分多步)
swipe((0, 0.7), (0, 0.5), duration=0.3)
swipe((0, 0.5), (0, 0.3), duration=0.3)
2.3 text —— 文本输入
python
复制
2.4 keyevent —— 按键事件
python
复制
# 常用按键
keyevent("HOME") # Home键
keyevent("BACK") # 返回键
keyevent("MENU") # 菜单键
keyevent("VOLUME_UP") # 音量+
keyevent("VOLUME_DOWN") # 音量-
keyevent("POWER") # 电源键
keyevent("ENTER") # 回车键
keyevent("DELETE") # 删除键
keyevent("SEARCH") # 搜索键
# 组合键
keyevent("HOME") # 回到桌面
sleep(0.5)
keyevent("RECENTAPPS") # 最近任务
2.5 snapshot —— 截图
python
复制
# 截取当前屏幕
snapshot(filename="当前屏幕.png")
# 截取指定区域
snapshot(filename="局部.png", rect=(100, 200, 300, 400))
# rect: (左上x, 左上y, 右下x, 右下y)
# 在报告中显示截图(调试用)
snapshot(msg="操作前")
touch(Template(r"btn.png"))
snapshot(msg="操作后")
三、等待与查找类API
3.1 sleep —— 固定等待
python
复制
sleep(1) # 等待1秒
sleep(0.5) # 等待0.5秒
sleep(3) # 等待3秒
# 使用场景:页面加载需要时间、动画播放需要时间
touch(Template(r"login_btn.png"))
sleep(2) # 等待登录后的页面加载
assert_exists(Template(r"home_icon.png"), "登录成功")
注意:sleep是固定等待,不够智能。网络慢时可能不够,网络快时浪费。尽量用wait代替。
3.2 wait —— 智能等待(推荐!)
python
复制
# 等待某个元素出现,默认超时20秒
wait(Template(r"loading_complete.png"))
# 指定超时时间
wait(Template(r"result.png"), timeout=30) # 最多等30秒
wait(Template(r"popup.png"), timeout=5) # 最多等5秒
# 等待 + 超时后执行操作
wait(Template(r"btn.png"), timeout=10, interval=0.5)
# timeout: 最长等待时间(秒)
# interval: 检测间隔(秒),每隔0.5秒检测一次
# wait 的工作原理:
# 每隔 interval 秒截一次屏 → 搜索目标图片 → 找到就返回 → 超时就报错
3.3 exists —— 判断是否存在
python
复制
# 判断元素是否存在,返回 True 或 False
if exists(Template(r"ad_popup.png")):
touch(Template(r"close_ad.png"))
print("关闭了广告弹窗")
else:
print("没有广告弹窗,直接继续")
# exists不会报错,找不到返回False
# 默认检测时间约3秒
3.4 wait vs exists vs sleep 对比
| API | 行为 | 找不到时 | 适用场景 |
|---|---|---|---|
sleep(N) | 固定等待N秒 | 不检查 | 已知固定耗时 |
wait(img) | 循环查找直到出现 | 抛异常 | 必须出现的元素 |
exists(img) | 查找一次返回结果 | 返回False | 可能不出现的元素 |
python
复制
# 最佳实践:组合使用
# 场景:登录后等待首页加载(必须出现)
touch(Template(r"login_btn.png"))
wait(Template(r"home_tab.png"), timeout=10) # 必须等到首页出现
# 场景:可能弹出更新提示(可能不出现)
if exists(Template(r"update_dialog.png")):
touch(Template(r"cancel_btn.png")) # 有弹窗就关掉
# 场景:固定动画时间
touch(Template(r"start_btn.png"))
sleep(2) # 等开场动画播放(动画时长固定,用sleep更合适)
3.5 循环等待(进阶技巧)
python
复制
# 场景:等待列表加载完成(不确定要等多久)
for i in range(10):
if exists(Template(r"list_item.png")):
break
sleep(1)
else:
raise Exception("列表10秒内未加载完成")
# 场景:等待加载动画消失
# 加载中的转圈消失 → 页面加载完成
for i in range(30): # 最多等30秒
if not exists(Template(r"loading_spinner.png")):
print("加载完成")
break
sleep(1)
四、断言类API
4.1 assert_exists —— 验证存在
python
复制
# 验证元素存在(最常用)
assert_exists(Template(r"success_msg.png"), "应该显示成功提示")
# 如果找不到,报告显示:断言失败,应该显示成功提示
4.2 assert_not_exists —— 验证不存在
python
复制
# 验证元素不存在
assert_not_exists(Template(r"error_msg.png"), "不应该显示错误提示")
# 验证广告已关闭
touch(Template(r"close_ad.png"))
sleep(1)
assert_not_exists(Template(r"ad_banner.png"), "广告应该已关闭")
4.3 assert_equal —— 验证相等
python
复制
# 比较两个值是否相等
actual = poco("result_text").get_text()
assert_equal(actual, "3", "计算结果应为3")
# 比较数字
assert_equal(len(poco("list_item")), 10, "列表应有10条数据")
4.4 断言实战组合
python
复制
# 完整的验证流程
# 1. 操作前验证初始状态
assert_exists(Template(r"login_page.png"), "应在登录页面")
# 2. 执行操作
touch(Template(r"username_input.png"))
text("admin")
touch(Template(r"password_input.png"))
text("123456")
touch(Template(r"login_btn.png"))
# 3. 等待结果
wait(Template(r"home_page.png"), timeout=10)
# 4. 操作后验证
assert_exists(Template(r"welcome_text.png"), "登录成功应显示欢迎语")
assert_not_exists(Template(r"login_page.png"), "不应停留在登录页")
五、辅助类API
5.1 start_app / stop_app
python
复制
# 启动APP
start_app("com.example.app") # Android包名
start_app("com.tencent.mm") # 启动微信
# 停止APP
stop_app("com.example.app")
# 重启APP(测试常用)
stop_app("com.example.app")
sleep(1)
start_app("com.example.app")
5.2 clear_app
python
复制
# 清除APP数据(恢复到初始状态)
clear_app("com.example.app")
# 等同于:设置 → 应用 → 清除数据
# 测试场景:每个用例执行前清除数据,保证独立性
5.3 install / uninstall
python
复制
# 安装APK
install(r"C:\app\release.apk")
# 卸载APP
uninstall("com.example.app")
5.4 wake / home
python
复制
# 唤醒屏幕
wake()
# 回到桌面
home()
# 组合:确保手机处于可用状态
def ensure_device_ready():
wake() # 唤醒屏幕
sleep(0.5)
home() # 回到桌面
sleep(0.5)
stop_app("com.example.app")
sleep(0.5)
start_app("com.example.app")
sleep(2)
六、Template详解:图像识别的灵魂
6.1 Template的完整参数
python
复制
Template(
filename=r"btn.png", # 图片文件名(必填)
threshold=0.7, # 置信度阈值(0~1,默认0.7)
target_pos=5, # 点击位置:1-9(九宫格),默认5(中心)
record_pos=(0, 0), # 录制时的相对坐标
resolution=(1080, 1920), # 录制时的屏幕分辨率
rgb=False # 是否开启彩色识别(默认灰度)
)
6.2 threshold 调优实战
python
复制
# 场景一:按钮在不同页面有轻微色差
# 默认0.7找不到 → 降低到0.6
touch(Template(r"btn.png", threshold=0.6))
# 场景二:多个相似图标容易误匹配
# 默认0.7误点 → 提高到0.85
touch(Template(r"specific_icon.png", threshold=0.85))
# 场景三:游戏中技能图标有发光特效
# 图像变化大 → 降低到0.5
touch(Template(r"skill_icon.png", threshold=0.5))
6.3 target_pos 九宫格定位
target_pos 参数:点击截图的哪个位置
1(左上) 2(上中) 3(右上)
4(左中) 5(中心) 6(右中)
7(左下) 8(下中) 9(右下)
示例:
touch(Template(r"card.png", target_pos=8))
# 点击卡片的底部中间(通常那里是按钮)
touch(Template(r"long_btn.png", target_pos=3))
# 点击长按钮的右上角
6.4 rgb彩色识别
python
复制
# 默认是灰度识别(把彩色图转灰度再匹配)
# 适合大多数场景
# 开启彩色识别(颜色是关键区分特征时)
touch(Template(r"red_btn.png", rgb=True))
# 适用于:彩色图标、状态指示灯、红绿按钮区分
# 注意:rgb=True 识别速度更慢,只在必要时使用
6.5 多分辨率适配
python
复制
# 方法一:使用record_pos和resolution(推荐)
# IDE录制时自动记录坐标和分辨率
touch(Template(r"btn.png", record_pos=(0.2, 0.5), resolution=(1080, 1920)))
# 在不同分辨率手机上,Airtest会自动换算坐标
# 方法二:使用相对坐标直接点击
touch((0.2, 0.5)) # 屏幕宽度的20%,高度的50%
七、截图技巧:写好脚本的关键
7.1 截图的黄金法则
✅ 好的截图:
1. 截取特征明显的区域(有文字、有独特图标)
2. 尽量小(只截按钮本身,不要背景)
3. 避开动态内容(时间、电量、信号)
4. 在目标设备上截(不要在模拟器上截图给真机用)
5. 统一分辨率(所有截图在同一设备上截)
❌ 坏的截图:
1. 截了整屏 → 太慢,容易受其他元素干扰
2. 包含时间/电量 → 每次都不一样,永远匹配不上
3. 在1080p手机上截,在720p手机上跑 → 可能不匹配
4. 截了半透明元素 → 背景不同导致匹配失败
5. 包含动画帧 → 每次截都不一样
7.2 截图大小与识别速度
python
复制
# 截图大小直接影响识别速度
# 整屏截图(1080×1920):~200ms
# 小图标截图(50×50):~10ms
# 优化原则:截得越小越好,但必须包含足够的特征
7.3 动态内容的处理
python
复制
# 问题:时间、电量、信号等会变化
# 解决:裁剪掉动态部分,只保留静态部分
# 问题:列表内容会滚动变化
# 解决:截取列表标题栏(固定不变),不截列表项
# 问题:按钮文字会变化(如"剩余3次")
# 解决:只截按钮背景/边框,不截文字部分
7.4 批量截图管理
# 建议的截图命名规范
login_btn.png # 登录按钮
login_btn_disabled.png # 登录按钮(禁用态)
home_tab.png # 首页Tab
home_tab_active.png # 首页Tab(选中态)
search_input.png # 搜索输入框
search_result_item.png # 搜索结果项
popup_close.png # 弹窗关闭按钮
error_toast.png # 错误提示Toast
八、实战:编写一个完整的测试脚本
8.1 需求
测试一个天气APP的基本功能:
- 启动APP,等待首页加载
- 点击搜索框,输入"北京"
- 验证搜索结果包含"北京"
- 点击"北京",查看详情
- 验证详情页显示温度和天气描述
8.2 完整脚本
python
复制
8.3 脚本优化要点
python
复制
九、常见问题与排错
9.1 图像识别失败
python
复制
# 问题1:TargetNotFoundError: 找不到图片
# 可能原因及解决:
# ① 截图和设备当前画面不匹配 → 重新截图
# ② 页面还没加载完就找了 → 加 wait 或 sleep
# ③ threshold太高 → 降低到0.6
# ④ 截图中包含动态内容 → 重新截静态部分
# ⑤ 分辨率不一致 → 在同设备上截图
# 调试技巧:打印当前屏幕
snapshot(filename="debug_current.png")
# 对比你的截图和当前屏幕,看哪里不同
9.2 点击位置不准
python
复制
# 问题:点击位置偏移
# 原因:截图范围太大,Airtest计算的中心点不在按钮上
# 解决1:缩小截图范围,只截按钮本身
# 解决2:使用 target_pos 微调点击位置
touch(Template(r"big_area.png", target_pos=8)) # 点下半部分
# 解决3:先定位再偏移点击
pos = wait(Template(r"icon.png"))
touch((pos[0], pos[1] + 50)) # 在图标下方50像素处点击
9.3 滑动不生效
python
复制
# 问题:swipe没反应
# 可能原因:起点和终点太近、duration太短
# 解决:增大滑动距离,增加duration
swipe((0, 0.8), (0, 0.2), duration=1.0) # 大幅滑动,持续1秒
# 连续滑动(对付惯性滚动)
for _ in range(3):
swipe((0, 0.7), (0, 0.3), duration=0.5)
sleep(0.5)
9.4 文本输入失败
python
复制
# 中文输入失败
# 解决:使用剪贴板方式
import pyperclip
pyperclip.copy("中文测试")
keyevent("PASTE") # 粘贴(部分设备支持)
# 或者使用ADB方式
text("test") # 英文一般没问题
9.5 脚本在不同设备上表现不一致
python
复制
# 原因:不同手机分辨率、DPI、字体不同
# 解决策略:
# ① 使用相对坐标而非像素坐标
# ② 同一脚本在不同设备上各准备一套截图
# ③ 使用Poco控件识别代替图像识别(更稳定)
# ④ 降低threshold增加容错
十、总结
API速查表
| 分类 | API | 说明 |
|---|---|---|
| 点击 | touch(img) | 图像识别点击 |
touch((x,y)) | 坐标点击 | |
| 滑动 | swipe(p1, p2) | 从p1滑到p2 |
swipe(p1, v) | 从p1按向量v滑动 | |
| 输入 | text("内容") | 输入文本 |
keyevent("HOME") | 按键事件 | |
| 等待 | sleep(N) | 固定等待N秒 |
wait(img, timeout=N) | 智能等待(推荐) | |
exists(img) | 判断是否存在 | |
| 断言 | assert_exists(img, msg) | 验证存在 |
assert_not_exists(img, msg) | 验证不存在 | |
| 应用 | start_app(pkg) | 启动APP |
stop_app(pkg) | 停止APP | |
clear_app(pkg) | 清除APP数据 | |
| 截图 | snapshot() | 截图保存到报告 |
图像识别核心口诀
截图要小不要大,特征明显好匹配。
阈值默认零点七,找不到就降一降。
动态内容要避开,时间电量不能截。
多设备跑用坐标,图像识别作备份。
wait比sleep更智能,exists判断不报错。
断言写在操作后,报告清晰好排查。
下一篇预告:Airtest自动化测试专题第三篇——「Poco控件识别:精准定位UI元素」,掌握Poco的控件树定位、层级选择、等待与断言,以及Airtest+Poco混合编程。
本文约5500字,是「Airtest自动化测试专题」第2篇。建议打开AirtestIDE,跟着示例敲一遍,印象最深。
更多推荐
所有评论(0)