一、前言

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.3pocoui==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")


 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐