Airtest入门及多设备管理总结 airtest环境配置
itomcoil 2024-12-23 11:08 31 浏览
Airtest是一款基于图像识别和poco控件识别的UI自动化测试工具,用于游戏和App测试,也广泛应用于设备群控,其特性和功能不亚于appium和atx等自动化框架。
说起Airtest就不得不提AirtestIDE,一个强大的GUI工具,它整合了Airtest和Poco两大框架,内置adb工具、Poco-inspector、设备录屏、脚本编辑器、ui截图等,也正是由于它集成许多了强大的工具,使得自动化测试变得更为方便,极大的提升了自动化测试效率,并且得到广泛的使用。
1. 简单入门
1.1 准备
- 从官网下载并安装AirtestIDE。
- 准备一台移动设备,确保USB调试功能处于开启状态,也可使用模拟器代替。
1.2 启动AirtestIDE
打开AirtestIDE,会启动两个程序,一个是打印操作日志的控制台程序,如下:
一个是AirtestIDE的UI界面,如下:
1.3 连接设备
连接的时候要确保设备在线,通常需要点击刷新ADB来查看更新设备及设备状态,然后双击需要连接的设备即可连接,如果连接的设备是模拟器,需注意如下:
- 确保模拟器与Airtest中的adb版本一致,否则无法连接,命令行中使用adb version即可查看adb版本,Airtest中的adb在Install_path\airtest\core\android\static\adb\windows目录下面。
- 确保勾选Javacap方式②连接,避免连接后出现黑屏。
1.4 UI定位
在Poco辅助窗选择Android①并且使能Poco inspector②,然后将鼠标放到控件上面即可显示控件的UI名称③,也可在左侧双击UI名称将其写到脚本编辑窗中④。
1.5 脚本编辑
在脚本编辑窗编写操作脚本⑤,比如使用百度搜索去搜索Airtest关键词,输入关键字后点击百度一下控件即可完成搜索。
1.6 运行
运行脚本,并在Log查看窗查看运行日志⑥。以上操作只是简单入门,更多操作可参考官方文档。
2. 多线程中使用Airtest
当项目中需要群控设备时,就会使用多进程或者多线程的方式来调度Airtest,并将Airtest和Poco框架集成到项目中,以纯Python代码的方式来使用Airtest,不过仍需Airtest IDE作为辅助工具帮助完成UI控件的定位,下面给大家分享一下使用Airtest控制多台设备的方法以及存在的问题。
2.1 安装
纯python环境中使用Airtest,需在项目环境中安装Airtest和Poco两个模块,如下: pip install -U airtest pocoui
2.2 多设备连接
每台设备都需要单独绑定一个Poco对象,Poco对象就是一个以apk的形式安装在设备内部的一个名为com.netease.open.pocoservice的服务(以下统称pocoservice),这个服务可用于打印设备UI树以及模拟点击等,多设备连接的示例代码如下:
from airtest.core.api import *
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
# 过滤日志
air_logger = logging.getLogger("airtest")
air_logger.setLevel(logging.ERROR)
auto_setup(__file__)
dev1 = connect_device("Android:///127.0.0.1:21503")
dev2 = connect_device("Android:///127.0.0.1:21503")
dev3 = connect_device("Android:///127.0.0.1:21503")
poco1 = AndroidUiautomationPoco(device=dev1)
poco2 = AndroidUiautomationPoco(device=dev2)
poco3 = AndroidUiautomationPoco(device=dev3)
2.3 Poco管理
上面这个写法确实保证了每台设备都单独绑定了一个Poco对象,但是上面这种形式不利于Poco对象的管理,比如检测每个Poco的存活状态。
因此需要一个容器去管理并创建Poco对象,这里套用源码里面一种方法作为参考,它使用单例模式去管理Poco的创建并将其存为字典,这样既保证了每台设备都有一个单独的Poco,也方便通过设备串号去获取Poco对象,源码如下:
class AndroidUiautomationHelper(object):
_nuis = {}
@classmethod
def get_instance(cls, device):
"""
This is only a slot to store and get already initialized poco instance rather than initializing again. You can
simply pass the ``current device instance`` provided by ``airtest`` to get the AndroidUiautomationPoco instance.
If no such AndroidUiautomationPoco instance, a new instance will be created and stored.
Args:
device (:py:obj:`airtest.core.device.Device`): more details refer to ``airtest doc``
Returns:
poco instance
"""
if cls._nuis.get(device) is None:
cls._nuis[device] = AndroidUiautomationPoco(device)
return cls._nuis[device]
AndroidUiautomationPoco在初始化的时候,内部维护了一个线程KeepRunningInstrumentationThread监控pocoservice,监控pocoservice的状态防止异常退出。
class KeepRunningInstrumentationThread(threading.Thread):
"""Keep pocoservice running"""
def __init__(self, poco, port_to_ping):
super(KeepRunningInstrumentationThread, self).__init__()
self._stop_event = threading.Event()
self.poco = poco
self.port_to_ping = port_to_ping
self.daemon = True
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def run(self):
while not self.stopped():
if getattr(self.poco, "_instrument_proc", None) is not None:
stdout, stderr = self.poco._instrument_proc.communicate()
print('[pocoservice.apk] stdout: {}'.format(stdout))
print('[pocoservice.apk] stderr: {}'.format(stderr))
if not self.stopped():
self.poco._start_instrument(self.port_to_ping) # 尝试重启
time.sleep(1)
这里存在的问题是,一旦pocoservice出了问题(不稳定),由于KeepRunningInstrumentationThread的存在,pocoservice就会重启,但是由于pocoservice服务崩溃后,有时是无法重启的,就会循环抛出raise RuntimeError("unable to launch AndroidUiautomationPoco")的异常,导致此设备无法正常运行,一般情况下,我们需要单独处理它,具体如下:
处理Airtest抛出的异常并确保pocoservice服务重启,一般情况下,需要重新安装pocoservice,即重新初始化。但是如何才能检测Poco异常,并且捕获此异常呢?这里在介绍一种方式,在管理Poco时,使用定时任务的方法去检测Poco的状况,然后将异常Poco移除,等待其下次连接。
2.4 设备异常处理
一般情况下,设备异常主要表现为AdbError、DeviceConnectionError,引起这类异常的原因多种多样,因为Airtest控制设备的核心就是通过adb shell命令去操作,只要执行adb shell命令,都有可能出现这类错误,你可以这样想,Airtest中任何动作都是在执行adb shell命令,为确保项目能长期稳定运行,就要特别注意处理此类异常。
- 第一个问题
Airtest的adb shell命令函数通过封装subprocess.Popen来实现,并且使用communicate接收stdout和stderr,这种方式启动一个非阻塞的子进程是没有问题的,但是当使用shell命令去启动一个阻塞式的子进程时就会卡住,一直等待子进程结束或者主进程退出才能退出,而有时候我们不希望被子进程卡住,所以需单独封装一个不阻塞的adb shell函数,保证程序不会被卡住,这种情况下为确保进程启动成功,需自定义函数去检测该进程存在,如下:
def rshell_nowait(self, command, proc_name):
"""
调用远程设备的shell命令并立刻返回, 并杀死当前进程。
:param command: shell命令
:param proc_name: 命令启动的进程名, 用于停止进程
:return: 成功:启动进程的pid, 失败:None
"""
if hasattr(self, "device"):
base_cmd_str = f"{self.device.adb.adb_path} -s {self.device.serialno} shell "
cmd_str = base_cmd_str + command
for _ in range(3):
proc = subprocess.Popen(cmd_str)
proc.kill() # 此进程立即关闭,不会影响远程设备开启的子进程
pid = self.get_rpid(proc_name)
if pid:
return pid
def get_rpid(self, proc_name):
"""
使用ps查询远程设备上proc_name对应的pid
:param proc_name: 进程名
:return: 成功:进程pid, 失败:None
"""
if hasattr(self, "device"):
cmd = f'{self.device.adb.adb_path} -s {self.device.serialno} shell ps | findstr {proc_name}'
res = list(filter(lambda x: len(x) > 0, os.popen(cmd).read().split(' ')))
return res[1] if res else None
注意:通过subprocess.Popen打开的进程记得使用完成后及时关闭,防止出现Too many open files的错误。
- 第二个问题
Airtest中初始化ADB也是会经常报错,这直接导致设备连接失败,但是Airtest并没有直接捕获此类错误,所以我们需要在上层处理该错误并增加重试机制,如下面这样,也封装成装饰器或者使用retrying.retry。
def check_device(serialno, retries=3):
for _ in range(retries)
try:
adb = ADB(serialno)
adb.wait_for_device(timeout=timeout)
devices = [item[0] for item in adb.devices(state='device')]
return serialno in devices
except Exception as err:
pass
一般情况下使用try except来捕可能的异常,这里推荐使用funcy,funcy是一款堪称瑞士军刀的Python库,其中有一个函数silent就是用来装饰可能引起异常的函数,silent源码如下,它实现了一个名为ignore的装饰器来处理异常。当然funcy也封装许多python日常工作中常用的工具,感兴趣的话可以看看funcy的源码。
def silent(func):
"""忽略错误的调用"""
return ignore(Exception)(func)
def ignore(errors, default=None):
errors = _ensure_exceptable(errors)
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except errors as e:
return default
return wrapper
return decorator
def _ensure_exceptable(errors):
is_exception = isinstance(errors, type) and issubclass(errors, BaseException)
return errors if is_exception else tuple(errors)
#参考使用方法
import json
str1 = '{a: 1, 'b':2}'
json_str = silent(json.loads)(str1)
- 第三个问题
Airtest执行命令时会调用G.DEVICE获取当前设备(使用Poco对象底层会使用G.DEVICE而非自身初始化时传入的device对象),所以在多线程情况下,本该由这台设备执行的命令可能被切换另外一台设备执行从而导致一系列错误。解决办法就是维护一个队列,保证是主线程在执行Airtest的操作,并在使用Airtest的地方设置G.DEVICE确保G.DEVICE等于Poco的device。
3.结语
Airtest在稳定性、多设备控制尤其是多线程中存在很多坑。最好多看源码加深对Airtest的理解,然后再基于Airtest框架做一些高级的定制化扩展功能。
我们是行者AI,我们在“AI+游戏”中不断前行。
快来【公众号 | xingzhe_ai】,和我们讨论更多技术问题吧!
相关推荐
- selenium(WEB自动化工具)
-
定义解释Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7,8,9,10,11),MozillaF...
- 开发利器丨如何使用ELK设计微服务中的日志收集方案?
-
【摘要】微服务各个组件的相关实践会涉及到工具,本文将会介绍微服务日常开发的一些利器,这些工具帮助我们构建更加健壮的微服务系统,并帮助排查解决微服务系统中的问题与性能瓶颈等。我们将重点介绍微服务架构中...
- 高并发系统设计:应对每秒数万QPS的架构策略
-
当面试官问及"如何应对每秒几万QPS(QueriesPerSecond)"时,大概率是想知道你对高并发系统设计的理解有多少。本文将深入探讨从基础设施到应用层面的解决方案。01、理解...
- 2025 年每个 JavaScript 开发者都应该了解的功能
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.Iteratorhelpers开发者...
- JavaScript Array 对象
-
Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...
- Gemini 2.5编程全球霸榜,谷歌重回AI王座,神秘模型曝光,奥特曼迎战
-
刚刚,Gemini2.5Pro编程登顶,6美元性价比碾压Claude3.7Sonnet。不仅如此,谷歌还暗藏着更强的编程模型Dragontail,这次是要彻底翻盘了。谷歌,彻底打了一场漂亮的翻...
- 动力节点最新JavaScript教程(高级篇),深入学习JavaScript
-
JavaScript是一种运行在浏览器中的解释型编程语言,它的解释器被称为JavaScript引擎,是浏览器的一部分,JavaScript广泛用于浏览器客户端编程,通常JavaScript脚本是通过嵌...
- 一文看懂Kiro,其 Spec工作流秒杀Cursor,可移植至Claude Code
-
当Cursor的“即兴编程”开始拖累项目质量,AWS新晋IDEKiro以Spec工作流打出“先规范后编码”的系统工程思维:需求-设计-任务三件套一次生成,文档与代码同步落地,复杂项目不...
- 「晚安·好梦」努力只能及格,拼命才能优秀
-
欢迎光临,浏览之前点击上面的音乐放松一下心情吧!喜欢的话给小编一个关注呀!Effortscanonlypass,anddesperatelycanbeexcellent.努力只能及格...
- JavaScript 中 some 与 every 方法的区别是什么?
-
大家好,很高兴又见面了,我是姜茶的编程笔记,我们一起学习前端相关领域技术,共同进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力在JavaScript中,Array.protot...
- 10个高效的Python爬虫框架,你用过几个?
-
小型爬虫需求,requests库+bs4库就能解决;大型爬虫数据,尤其涉及异步抓取、内容管理及后续扩展等功能时,就需要用到爬虫框架了。下面介绍了10个爬虫框架,大家可以学习使用!1.Scrapysc...
- 12个高效的Python爬虫框架,你用过几个?
-
实现爬虫技术的编程环境有很多种,Java、Python、C++等都可以用来爬虫。但很多人选择Python来写爬虫,为什么呢?因为Python确实很适合做爬虫,丰富的第三方库十分强大,简单几行代码便可实...
- pip3 install pyspider报错问题解决
-
运行如下命令报错:>>>pip3installpyspider观察上面的报错问题,需要安装pycurl。是到这个网址:http://www.lfd.uci.edu/~gohlke...
- PySpider框架的使用
-
PysiderPysider是一个国人用Python编写的、带有强大的WebUI的网络爬虫系统,它支持多种数据库、任务监控、项目管理、结果查看、URL去重等强大的功能。安装pip3inst...
- 「机器学习」神经网络的激活函数、并通过python实现激活函数
-
神经网络的激活函数、并通过python实现whatis激活函数感知机的网络结构如下:左图中,偏置b没有被画出来,如果要表示出b,可以像右图那样做。用数学式来表示感知机:上面这个数学式子可以被改写:...
- 一周热门
- 最近发表
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)