Pycharm实战!Airtest和Pytest结合,打造轻量化游戏测试!
一、前言
Airtest 是网易出品的跨平台 UI 自动化测试框架,主打图像识别和 Poco 元素定位,适配游戏、Android、iOS、Windows 等多端;Pytest 是 Python 生态最主流的测试框架,支持用例管理、参数化、夹具、报告生成等能力。但众所周知,Airtest IDE虽然赋予了强大的能力,但所需资源很大,也不够灵活,因此,在进阶下,我们脱离Airtest IDE ,使用Pycahrm来实现脚本编写。
将两者结合,既能发挥 Airtest 的多端 UI 操作能力,又能利用 Pytest 的测试工程化能力,打造高效的自动化测试体系。
以下是每章摘要:
二、环境准备:给大家准备项目搭建时所需的环境和工具
三、设备连接:给大家介绍设备连接方式,包括Android、ios、windows平台和真机以及模拟机
四、
二、环境准备
在开始之前,请大家确保以下环境已经准备好(大家无需担心,我均为大家准备好了):
| 所需工具 | 版本 | |
| airtest IDE | >= 1.2.13 | 图1 |
| adb | 图2 | |
| pycahrm | >=2023 | 图3 |
如果大家不想去自行选择,也可直接从我的网盘中拿,都给大家整理好了(注意,我的是windows10/11,如果和我的不一样,比如说是mac,就得自行去给出的链接中下载了)
通过网盘分享的知识:device链接: https://pan.baidu.com/s/5MUFr2oAZvX-Z8ogbP-fX0w
图1:

图2:

图3:
下载过程也非常简答粗暴,大家一路下一步就可以了。
注意,python环境的话大家有两种方式,一种是虚拟环境,一种是本地环境,如果是本地环境的话,建议大家配置一下环境变量,同时adb驱动也需要配置环境变量,都非常简单:
只需要在win搜索框搜索编辑系统环境变量,逐步选 -> 环境变量 -> 系统变量(PATH) -> 新建,把你下载的两个安装包中bin目录和platfoem-tools目录的两个路径加入进去就可以了,如下图:
![]()
至此,环境安装就完成了。
三、设备连接
airtest支持多平台多设备接入,连接方式多种多样,这里为了便于大家理解,我使用Airtest IDE给大家演示,同时会穿插对应的脚本接入方式。
如果大家想要更深入的了解,可以参考
(1)Android平台
Android平台可分为模拟器连接,真机连接(真机连接分为有线连接和无线连接)
模拟器连接(以MuMu模拟器为例,其他模拟操作均类似):
1. 首先我们登录模拟器后进入主页,进入设置:
2.接着,进入关于手机选项,找到版本号进行连续点击,会弹出进入开发者模式

3.回到设置主页面,进入系统选项

4. 会发现出现一个开发者选项,点击进入

5.进入开发者选项后,打开USB调试功能

6.打开airtest IDE,无需登录(登录会有些问题,不影响我们使用功能),在右侧设备窗中点击远程设备连接,默认连接即可(ide 会默认第一次连接到的设备号,不同模拟器有不同的开放端口,这个大家在使用对应的模拟器时一搜就有了),这里MoMo是7555

对应在pycahrm中代码连接(不理解可以先跳过,我在后面会给大家例程):
from airtest.core.api import connect_device
#Android://<adb主机>:<adb端口>/<设备序列号>?附加参数
device_uri = "Android://127.0.0.1:5037/127.0.0.1:7555?cap_method=JAVACAP"
connect_device(device_uri)
真机连接:
真机连接使用USB的话非常简单,只需要使用USB连接电脑和手机即可(注意使用数据线)
插入后注意开启手机的开发者模和USB调试,之后刷新ADB即可直接连接
对应代码(单机连接,就是只有一个手机设备连接到咱们的电脑)
from airtest.core.api import connect_device
connect_device("Android:///") #单机连接
真机连接(远程连接/多设备连接)
首先我们需要在手机上打开开发者模式(这里涉及到隐私,博主就不展示了),操作跟模拟器上差不多,接着我们需要在手机上找到开发者选项,找到远程调试功能,这是安卓11+版本特有的功能,打开后回到电脑设备,打开cmd输入以下命令:
adb pair xxxxx #你手机上显示的ip地址和端口
接着会显示让你输入配对码,手机会自动弹出,输入后会显示如下信息
这个时候我们查看手机上的信息,会显示一个调试地址和端口,我们按照以下格式输入到airtest IDE即可
Android://127.0.0.1:5037/真机序列号
或者
Android:///真机序列号
同样,多设备连接我们只需要在这些步骤之前加上如下命令,在填写即可
adb devices #会显示已连接的设备信息
对应的脚本实现:
from airtest.core.api import connect_device
serial = "abc1234567890abc"
device_uri = f"Android://127.0.0.1:5037/{serial}"
connect_device(device_uri)
#远程连接同理
connect_device("Android://127.0.0.1:5037/设备号")
#如果嫌麻烦,只需要单设备接入即可
connect_device("Android:///")
ios连接
ios连接这里只需要:ios:///设备号即可(注意,ios的话需要开发者凭证,没有的话可以用ID)
Windows连接
连接windows窗口:
(1) 连接一个窗口句柄为123456的Windows窗口: Windows:///123456(就是它的名字)

(2) 正则表达式:Windows:///正则表达式 (这里很少使用,可以忽略)
(3) 连接桌面:Windows:///(使用这个可以直接连接到桌面)
四、pycharm集成airtest和pytest
(1)环境搭建,要在pycharm中使用airtest和pytest我们首先需要进行相关依赖的下载,在pycharm终端中进行下载
(2)driver脚本的编写,用于设备连接
from urllib.parse import urlencode
from airtest.core.api import connect_device
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
from poco.drivers.ios import iosPoco
from poco.drivers.windows import WindowsPoco
class DeviceDriver:
@staticmethod
def build_uri(platform : str, device_id : str, params : dict = None) -> str:
"""
根据官方 connect_device 规范构造URI
返回示例:
Android:"Android:///12.0.0.1:5555"
Android远程:"Android://127.0.0.1:5037/设备地址"
ios:"iOS:///http://localhost:8100/?mjpeg_port=9100&udid=00008030-00123456"
Windows: "Windows:///?title_re=记事本&foreground=False"
:param platform: 平台(Android, iOS, Windows)
:param device_id: 设备id
:param params: 附加参数
:return:
"""
platform_lower = platform.lower()
params = params or {}
if platform_lower == 'android':
uri = f"Android:///{device_id}"
if params:
uri += "?" + urlencode(params)
return uri
elif platform_lower == 'ios':
# iOS 需要 WebDriverAgent 地址,默认 http://localhost:8100
wda_url = params.pop("wda_url", "http://localhost:8100")
# 将 device_id 作为 udid 参数(如果未单独指定)
if device_id and "udid" not in params and "uuid" not in params:
params["udid"] = device_id
uri = f"iOS:///{wda_url}"
if params:
uri += "?" + urlencode(params)
return uri
elif platform_lower == 'windows':
uri = "Windows:///"
query = {}
if device_id:
#判断是否为窗口句柄(纯数字)
if device_id.isdigit():
query["handle"] = device_id
else:
query["title_re"] = device_id
query.update(params)
if query:
uri += "?" + urlencode(query)
return uri
else:
raise ValueError(f"不支持此参数:{platform}")
@staticmethod
def connect(platform : str, device_id : str, params : dict = None):
"""连接设备并返回poco,airtest的实例"""
uri = DeviceDriver.build_uri(platform, device_id, params)
print(f"连接设备:{uri}")
dev = connect_device(uri)
#根据平台初始化poco
platform_lower = platform.lower()
if platform_lower == 'android':
poco = AndroidUiautomationPoco(use_airtest_input=True)
elif platform_lower == 'ios':
poco = iosPoco()
elif platform_lower == 'windows':
poco = WindowsPoco()
else:
poco = None
return dev,poco
(3)实现第一次脚本编写,这里大家根据自己的实际情况编写,以下为实例(使用poco框架)
# -*- encoding=utf8 -*-
__author__ = "Dell"
from airtest.core.api import *
auto_setup(__file__,devices=["Android:///127.0.0.1:7555"],logdir=True)
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
poco(text = "游戏中心").click() #点击操作
poco(text = "游戏中心").long_click() #长按
print(poco(text = "游戏中心").get_name()) #获取name
print(poco(text = "游戏中心").attr('text')) #获取指定key的值
print(poco(text = "游戏中心").get_text()) #获取text属性的值
poco(type = "android.widget.EditText").set_text("123") # 设置文本输入框的值,注意,只能是文本编辑组件
print(poco(type = "android.widget.EditText").exists()) #判断元素是否存在,True/False
poco(text = "游戏中心").drag_to(poco(text = "仙遇")) #拖拽操作
poco(text = "游戏中心").focus([0.2,0.5]).long_click() #内部偏移和外部偏移
poco(text = "游戏中心").wait_for_appearance() #等待出现
poco(text = "游戏中心").wait_for_disappearance() #等待消失
poco.wait_for_all([poco(text = "首页")]) #等待所有控件出现
poco.wait_for_any([poco(text = "分类")]) #等待其中一个空间出现
这里解释一下poco框架,它是airtest实现的一个非常强大的定位方式,能够通过UI渲染树的控件来进行定位,非常稳定且高效。如下图,注意在设备连接时开启

(4)日志工具脚本的编写(由于pytest和airtest两个框架设计理念的问题,导致在airtest写入日志文件时会报错,这里我找到一种方法,既可以去除airtest运行时刷屏的控制台打印,又能够完美消除日志写入错误的问题)
import atexit
import os
from config.settings import LOG_DIR
def close_airtest_log():
"""
配置 airtest 日志:只写入文件,不输出到控制台
:return:
"""
import logging
airtest_logger = logging.getLogger("airtest")
airtest_logger.setLevel(logging.DEBUG) # 记录所有级别的日志
airtest_logger.propagate = False # 阻止传播到根 logger(避免根 logger 的控制台 handler)
# 移除所有已有的 handler(尤其是控制台 handler)
airtest_logger.handlers.clear()
log_file = os.path.join(LOG_DIR, f"airtest.log")
# 添加文件 handler(日志写入项目根目录下的 airtest.log)
file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="w") # mode="w" 每次覆盖,可改为 "a" 追加
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
airtest_logger.addHandler(file_handler)
def _remove_airtest_cleanup():
"""移除 Airtest 自动注册的退出清理钩子"""
try:
handlers = atexit._exithandlers
new_handlers = [(f, a, k) for (f, a, k) in handlers
if not (f.__name__ == '_cleanup' and 'airtest.utils.snippet' in str(f))]
handlers.clear()
handlers.extend(new_handlers)
except Exception:
pass
(5)conftest夹具的实现,这里我通过在conftest内进行设备的自动连接,以及日志问题的消除
from utils.airtest_error_solution import close_airtest_log,_remove_airtest_cleanup
import pytest
from config.settings import DEVICE_ALIAS,DEFAULT_DEVICE,DEFAULT_PLATFORM,LOG_DIR
from base.device_driver import DeviceDriver
def pytest_addoption(parser):
parser.addoption(
"--alias", action="store", default=None,
help="使用settions.DEVICE_ALIAS 中的别名,如android_real"
)
parser.addoption(
"--platform", action="store",default=None,
choices=["android","ios","Windows"],
help="直接指定平台(Android/ios/windows)"
)
parser.addoption(
"--device",action="store",default=None,
help="直接指定设备ID(Android填serialno,ios填UDID,Windows填窗口标题)"
)
@pytest.fixture(scope="session")
def driver(request):
alias = request.config.getoption("--alias")
if alias:
if alias not in DEVICE_ALIAS:
raise ValueError(f"未知的设备别名:{alias},可用的别名:{list(DEVICE_ALIAS.keys())}")
cfg = DEVICE_ALIAS[alias]
platform = cfg["platform"]
device_id = cfg["device"]
params = cfg.get("uri_params", {})
else:
platform = request.config.getoption("--platform")
device_id = request.config.getoption("--device")
if platform is None:
platform = DEFAULT_PLATFORM
if device_id is None:
device_id = DEFAULT_DEVICE
params = {}
close_airtest_log()
dev, poco = DeviceDriver.connect(device_id=device_id, platform=platform, params=params)
_remove_airtest_cleanup()
yield dev, poco
try:
print("执行 airtest 手动退出清理.....")
dev.disconnect()
print("设备清理完成!")
except Exception as e:
print(f"手动清理失败(可忽略): {e}")
print(f"测试完成,设备 {device_id} 已释放")
五、自动化框架的实现
这里我已经实现了基本的框架,大家也可以自取可直接下载源码
https://gitee.com/j888844449999/pytest-airtest-framework
也可按照一下步骤:
(1)在pycahrm中选择来自版本控制的项目
(2)填入我的gitee地址进行clone即可

(3)BasePage操作调用
项目架构:
app_ui_auto/
├── config/ # 配置层:全局变量、环境、超时、设备、日志
│ └── settings.py
├── common/ # 公共工具层:异常、截图、日志、工具函数
│ ├── logger.py
│ └── utils.py
├── base/ # 底层基础层:驱动 + 页面基类(核心封装)
│ ├── android_driver.py # 设备连接、Poco单例、生命周期管理
│ └── base_page.py # 封装所有原生操作(点击/长按/拖拽/输入等)
├── pages/ # PO页面对象层:每个页面一个类,只存元素+页面行为
│ ├── home_page.py
│ └── mine_page.py
└── testcases/ # 测试用例层:纯业务流程+断言,不碰定位/底层API
├── test_home_flow.py
└── test_mine_flow.py
基础说明
- 环境:
airtest==1.4.3、pocoui==1.0.94 - 通用参数:
locator为元素定位名称;默认超时FIND_TIMEOUT - 带返回自身 (
self) 的方法支持链式调用
简易调用示例
# 实例化对象
page = BasePage(poco, dev)
# 普通调用
page.click_poco("login_btn")
page.input_poco("account_input", "test123")
# 链式调用
page.click_poco("home_tab").swipe_poco("scroll_view")
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)