pytest+yaml+appium实现App-UI自动化测试

本文介绍了如何结合pytest、yaml和appium实现App的UI自动化测试。项目结构包括common、config、outputs、reports、pageobjects、testcase等文件夹,以及conftest.py、pytest.ini和requirements.txt等文件。common文件夹中包含了appium操作、页面元素基础操作、yaml文件读写等方法。config文件夹存储配置参数,pageobjects用于封装测试方法,testcase存放测试用例。conftest.py用于定义钩子和共享夹具,pytest.ini配置pytest行为,requirements.txt列出项目依赖。

一、项目整体结构

common:用于存放一些公共方法文件;

config:用于存放相关配置文件;

outputs:用于存放log及测试报告文件;

pageobjects:用于存放测试的相关方法封装文件;

testcase:用于存放测试用例文件;

二、common文件夹

common文件中目前主要存放了4个基础方法文件,本别是项目相关路径、被测app相关信息、页面元素基础操作方法、yaml文件读取写入方法文件,以下分别介绍具体内容。

1.all_path.py

此文件主要用于配置相关文件的读取路径,包括:log文件存储路径、测试报告输出路径、图片存储路径等。

import os

base_path = os.path.dirname(os.path.dirname(__file__))  # 获取当前文件所在目录的上两级路径即项目根目录

appPath = os.path.join(base_path, 'app')
appium_server_logPath = os.path.join(base_path, 'Outputs', 'appium_log.log')
configPath = os.path.join(base_path, 'Config')
outputsPath = os.path.join(base_path, 'Outputs')
logPath = os.path.join(base_path, 'Outputs', 'logs')
picturePath = os.path.join(base_path, 'Outputs', 'picture')
reportsPath = os.path.join(base_path, 'Outputs', 'reports')

2.app_info.py

存放cmd命令操作app方法,当前项目中暂未使用待后续研究熟悉后可使用该文件封装的方法进行自动启动、停止appium服务及操作app。

import subprocess
import logging
import os
from Common.all_path import appPath


def exec_cmd(cmd) -> str:
    result = os.popen(cmd).read()
    return result


def get_app_name(file_dir) -> str:
    for root, dirs, files in os.walk(file_dir):
        files = [file for file in files if file.endswith(".apk")]
        if len(files) == 1:
            return files[0]
        else:
            raise FileNotFoundError("{}目录下没有测试包或者存在多个测试包 ".format(file_dir))


def get_app_package_name() -> str:
    cmd = "aapt dump badging {} | findstr package".format(os.path.join(appPath, get_app_name(appPath)))
    result = exec_cmd(cmd)
    if "package" in result:
        package_name = result.strip().split(" ")[1].split('=')[1]
        return package_name
    else:
        raise NameError("未获取到package name")


def get_app_launchable_activity() -> str:
    cmd = "aapt dump badging {} | findstr launchable".format(os.path.join(appPath, get_app_name(appPath)))
    result = exec_cmd(cmd)
    if "launchable" in result:
        launchable_activity = result.strip().split(" ")[1].split('=')[1].replace("label", '')
        return launchable_activity
    else:
        raise NameError("未获取到launchable activity")


def get_devices_version(device: str) -> str:
    if not isinstance(device, str):
        raise Exception("device type is should str..")
    result = exec_cmd("adb -s {} shell getprop ro.build.version.release".format(device))
    result = result.strip()
    if "error" not in result:
        return result
    else:
        raise Exception("获取设备系统版本失败,无法进行正常测试")


def get_all_devices() -> list:
    result = exec_cmd('adb devices')
    result = result.strip().split(" ")[3].replace("\n", '').replace("\t", ''). \
        replace("attached", '').split('device')
    result.remove('')
    if len(result) == 0:
        raise Exception("电脑未连接设备信息,无法进行正常测试")
    return result


def get_device_infos():
    """ [{'platform_version': '8.1.0', 'server_port': 4723, 'system_port': 8200, 'device': 'HDP9K18629901709'}] """
    device_infos = []
    devices = get_all_devices()
    for i in range(len(devices)):
        device_dict = {"platform_version": get_devices_version(devices[i]), "server_port": 4723 + i * 2,
                       "system_port": 8200 + i * 1, "device": devices[i]}
        device_infos.append(device_dict)

    if len(device_infos) < 1:
        raise Exception("当前电脑未连接设备信息。。。")

    return device_infos


def uninstall_app(device_list: list) -> None:
    """ 卸载 app 命令:adb -s 127.0.0.1:HDP9K18629901709 uninstall "com.xkw.client """
    if not isinstance(device_list, list):
        raise Exception("device_list is should list!")

    for device_info in device_list:
        cmd = 'adb -s {} uninstall "{}"'.format(device_info.get("device").split(':')[-1],
                                                          str(get_app_package_name())).replace("'", '')
        logging.info("开始卸载设备上应用:{}".format(cmd))
        exec_cmd(cmd)


def install_app():
    """ 下载app """
    subprocess.Popen('chcp 65001', shell=True)
    cmd = 'adb install {}'.format(os.path.join(appPath, get_app_name(appPath)))
    logging.info("开始下载设备上应用:{}".format(cmd))
    subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE)


def check_kill_appium(port):
    """ 备选方案 杀掉appium 在端口只有1个的时候作用 """
    cmd = 'netstat -o -n -a | findstr {}'.format(port['server_port'])
    result = exec_cmd(cmd)
    if "LISTENING" in result:
        package_name = result.strip().split(" ")[-1]
        cmds = 'taskkill /f /pid {}'.format(package_name)
        result = exec_cmd(cmds)
        if 'PID' in result:
            logging.info('释放端口: {} 成功'.format(result))
            return result
        else:
            raise NameError("未成功: 终止 PID 为 {}  的进程".format(result))
    else:
        raise NameError("appium服务未开启")


def kill_app():
    """ 备选方案 杀掉appium 在端口只有1个的时候作用  """
    device_dict = get_device_infos()[-1]
    cmd = 'netstat -o -n -a | findstr {}'.format(device_dict['server_port'])
    result = exec_cmd(cmd)
    if "LISTENING" in result:
        cmds = 'taskkill /f /t /im node.exe'
        result = exec_cmd(cmds)
        if 'PID' in result:
            return result
        else:
            raise NameError("未成功: 终止 PID 为 {}  的进程".format(result))
    else:
        raise NameError("appium服务未开启")

3.base.py

base文件主要存放已经封装好的一些页面元素操作方法,包括:查找元素、点击元素、获取元素文本等...

import time

import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from appium.webdriver.common.touch_action import TouchAction
from Common.all_path import picturePath
import datetime
import os
import logging


class Base:

    @classmethod
    # 自定义一个元素查找方法
    def find_element(cls, driver, feature, timeout=5, poll=1.0):
        # feature = By.XPATH,"//*[@text='显示']"
        """
        依据用户传入的元素信息特征,然后返回当前用户想要查找元素
        :param poll: 可选参数,等待元素可见的轮询间隔时间,默认为 1.0 秒。
        :param timeout: 可选参数,等待元素可见的最长时间,默认为 5秒。
        :param driver:
        :param feature: 字典类型,包含用户希望的查找方式,及该方式对应的值
        :return: 返回当前用户查找的元素
        """
        try:
            by = feature['type']
            value = feature['value']
            doc = feature['doc']
            logging.info("页面开始查找元素:{},定位类型:{},值:{}".format(doc, by, value))
            wait = WebDriverWait(driver, timeout, poll)
            if by == 'XPATH':
                loc = wait.until(lambda x: x.find_element(By.XPATH, value))
            elif by == 'ID':
                loc = wait.until(lambda x: x.find_element(By.ID, value))
            elif by == 'CLASS_NAME':
                loc = wait.until(lambda x: x.find_element(By.CLASS_NAME, value))
            elif by == 'NAME':
                loc = wait.until(lambda x: x.find_element(By.NAME, valu
<think> 我们正在设计一个基于Ubuntu系统的手机UI自动化测试框架,使用Pytest作为测试框架,Appium用于自动化测试,OpenCV用于图像处理,Uiautomator用于Android UI元素的定位。以下是一个详细的步骤指南: 1. **环境准备** - 安装Ubuntu系统(建议18.04或更高版本) - 安装Docker(用于容器化管理,可选但推荐) - 安装Java JDK(Appium需要) - 安装Android SDK(用于adb等工具) - 安装Node.js和npm(Appium安装需要) - 安装Appium(通过npm安装) - 安装Python3和pip - 安装必要的Python库:pytest, Appium-Python-Client, opencv-python, uiautomator 2. **项目结构设计** ``` ui_auto_test/ ├── config/ # 配置文件 │ └── config.yaml # 配置文件 ├── testcases/ # 测试用 │ └── test_login.py # 示测试用 ├── pages/ # 页面对象模型 │ └── login_page.py ├── utils/ # 工具类 │ ├── image_utils.py # 图像处理工具(使用OpenCV) │ └── adb_utils.py # adb命令封装 ├── reports/ # 测试报告 ├── conftest.py # pytest的fixture配置 └── requirements.txt # 依赖库列表 ``` 3. **配置Appium** 我们需要配置Appium服务器,确保它可以连接Android设备(虚拟机或真机)。在Ubuntu上,我们可以启动一个Appium服务,监听端口4723。 4. **编写第一个测试用** 使用Pytest编写测试用,结合Appium的Python客户端进行设备操作。同时,我们可以利用Uiautomator来定位元素,或者使用OpenCV进行图像识别。 **示:使用Uiautomator定位元素** ```python from uiautomator import Device def test_login(): d = Device('emulator-5554') # 设备ID d(text="Username").set_text("testuser") d(text="Password").set_text("password") d(text="Login").click() ``` **示:使用OpenCV进行图像匹配** ```python import cv2 def find_image_on_screen(template_path): # 获取当前屏幕截图 # 使用adb命令截图 # 读取模板图片 # 使用OpenCV进行模板匹配 # 返回匹配结果 ``` 5. **集成Pytest** 使用Pytest来组织测试用,并生成测试报告。我们可以使用pytest-html插件来生成HTML报告。 6. **持续集成(可选)** 结合Jenkins等工具,实现持续集成。每次代码提交后自动运行测试套件。 7. **注意事项** - 确保Appium服务在运行状态。 - 确保设备已连接并且adb devices可以识别。 - 对于OpenCV的图像识别,需要注意屏幕分辨率以及图片的匹配阈值。 现在,我们可以开始具体实现每一步。首先,我们需要设置环境。 ### 环境设置步骤 1. **安装Java JDK** ```bash sudo apt update sudo apt install openjdk-11-jdk ``` 2. **安装Android SDK** - 下载Android命令行工具(如:https://developer.android.com/studio#command-tools) - 设置环境变量: ```bash export ANDROID_HOME=/path/to/android-sdk export PATH=$PATH:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools ``` - 安装平台工具和构建工具: ```bash sdkmanager "platform-tools" "build-tools;30.0.3" sdkmanager "platforms;android-30" ``` 3. **安装Node.js和npm** ```bash sudo apt install nodejs npm ``` 4. **安装Appium** ```bash npm install -g appium ``` 5. **安装Python和pip** ```bash sudo apt install python3 python3-pip ``` 6. **安装Python依赖库** 创建`requirements.txt`文件: ``` pytest Appium-Python-Client opencv-python uiautomator pytest-html ``` 然后运行: ```bash pip3 install -r requirements.txt ``` 7. **启动Appium服务** ```bash appium & ``` 8. **连接Android设备** 使用USB连接真机或启动模拟器,并确保`adb devices`可以识别设备。 ### 编写测试用 在`testcases/test_login.py`中: ```python import pytest from appium import webdriver from uiautomator import Device @pytest.fixture(scope="session") def device(): return Device('emulator-5554') # 替换为你的设备ID def test_login(device): # 使用uiautomator操作 device(text="Username").set_text("testuser") device(text="Password").set_text("password") device(text="Login").click() # 断言登录结果 assert device(text="Welcome").exists ``` ### 集成图像识别工具 在`utils/image_utils.py`中: ```python import cv2 import numpy as np import os import subprocess def take_screenshot(filename="screen.png"): subprocess.run(["adb", "exec-out", "screencap", "-p", ">", filename], shell=True) return filename def find_template(template_path, threshold=0.8): # 截取当前屏幕 screen_file = take_screenshot() screen = cv2.imread(screen_file, 0) template = cv2.imread(template_path, 0) w, h = template.shape[::-1] # 模板匹配 res = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED) loc = np.where(res >= threshold) points = [] for pt in zip(*loc[::-1]): points.append(pt) return points ``` 这个框架结合了多种工具,可以灵活地使用元素定位和图像识别。在实际项目中,可能需要根据具体需求调整。 ### 运行测试 使用pytest运行测试: ```bash pytest --html=report.html ``` ### 相关问题 1. 如何解决Appium连接Android设备时的常见问题? 2. 在使用OpenCV进行图像识别时,如何提高匹配的准确性? 3. 如何优化测试用以提高执行速度? </think> 以下是为您在Ubuntu系统上设计基于PyTestAppium、OpenCV和Uiautomator的Android自动化测试框架的完整解决方案: --- ### 一、框架架构设计 ```mermaid graph TD A[Ubuntu服务器] --> B[PyTest测试框架] B --> C{设备交互层} C --> D[Appium Server] C --> E[Uiautomator2] B --> F{图像处理层} F --> G[OpenCV] D --> H[Android设备/模拟器] E --> H B --> I[测试报告] ``` --- ### 二、环境部署步骤 1. **基础环境配置** ```bash # 安装Java sudo apt install openjdk-11-jdk # 安装Node.js curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash - sudo apt install nodejs # 安装Android SDK wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip unzip commandlinetools-linux-*.zip && rm *.zip mkdir -p android-sdk/cmdline-tools/latest/ mv cmdline-tools/* android-sdk/cmdline-tools/latest/ ``` 2. **核心组件安装** ```bash # 安装Appium npm install -g appium # 安装Python依赖 pip install pytest appium-python-client opencv-python uiautomator2 pytest-html ``` 3. **Android环境配置** ```bash # 设置环境变量 echo 'export ANDROID_HOME="$HOME/android-sdk"' >> ~/.bashrc echo 'export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools"' >> ~/.bashrc source ~/.bashrc # 安装必要组件 sdkmanager "platform-tools" "build-tools;31.0.0" "platforms;android-31" ``` --- ### 三、框架目录结构 ``` mobile_ui_test/ ├── config/ │ ├── devices.yaml # 设备配置 │ └── caps.yaml # Appium能力配置 ├── testcases/ # PyTest测试用 │ └── test_login.py ├── pages/ # 页面对象模型 │ └── login_page.py ├── utils/ │ ├── image_utils.py # OpenCV图像处理工具 │ ├── adb_utils.py # ADB命令封装 │ └── uia_utils.py # Uiautomator扩展 ├── resources/ # 测试资源 │ ├── apks/ # 安装包 │ └── images/ # 参考图片 ├── reports/ # 测试报告 └── conftest.py # PyTest全局配置 ``` --- ### 四、核心代码实现 1. **设备初始化 (conftest.py)** ```python import pytest from appium import webdriver import uiautomator2 as u2 @pytest.fixture(scope="session") def appium_driver(): caps = { "platformName": "Android", "automationName": "UIAutomator2", "deviceName": "Pixel_5", "app": "/resources/apks/demo.apk" } driver = webdriver.Remote("http://localhost:4723/wd/hub", caps) yield driver driver.quit() @pytest.fixture def uia_device(): return u2.connect("emulator-5554") ``` 2. **图像识别工具 (utils/image_utils.py)** ```python import cv2 import numpy as np def find_element_by_image(target_img, threshold=0.8): # 获取屏幕截图 screen = cv2.imread("current_screen.png") template = cv2.imread(f"resources/images/{target_img}.png") # 模板匹配 result = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) if max_val > threshold: center_x = max_loc[0] + template.shape[1]//2 center_y = max_loc[1] + template.shape[0]//2 return (center_x, center_y) return None ``` 3. **Uiautomator扩展 (utils/uia_utils.py)** ```python def uia_safe_click(device, selector, timeout=10): """带超时等待的元素点击""" element = device(selector) if element.wait.exists(timeout * 1000): element.click() return True return False ``` 4. **测试用 (testcases/test_login.py)** ```python from pages.login_page import LoginPage from utils.image_utils import find_element_by_image class TestLogin: def test_successful_login(self, appium_driver, uia_device): # 使用Appium+POM模式 login_page = LoginPage(appium_driver) login_page.enter_username("testuser") login_page.enter_password("pass123") # 使用OpenCV识别登录按钮 if coords := find_element_by_image("login_btn"): uia_device.click(*coords) # 使用Uiautomator验证结果 assert uia_device(text="Welcome").exists(timeout=5) ``` --- ### 五、执行与报告生成 ```bash # 启动Appium服务 appium & # 执行测试并生成HTML报告 pytest --html=reports/report.html ``` --- ### 六、最佳实践建议 1. **混合定位策略**: - 常规元素:优先使用Uiautomator的resource-id定位 - 动态元素:结合XPath和CSS选择器 - 图像元素:OpenCV用于验证码识别等特殊场景 2. **设备管理**: ```yaml # config/devices.yaml pixel5: udid: emulator-5554 platform: Android 12 resolution: 1080x2340 ``` 3. **失败处理**: ```python # conftest.py添加截图功能 @pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item): outcome = yield report = outcome.get_result() if report.failed and "appium_driver" in item.funcargs: driver = item.funcargs["appium_driver"] driver.save_screenshot(f"reports/failure_{item.name}.png") ``` --- ### 七、常见问题解决方案 1. **ADB设备未识别**: ```bash sudo usermod -aG plugdev $LOGNAME echo 'SUBSYSTEM=="usb", MODE="0666", GROUP="plugdev"' | sudo tee /etc/udev/rules.d/51-android.rules sudo udevadm control --reload-rules ``` 2. **OpenCV匹配精度优化**: - 使用SIFT/SURF特征匹配替代模板匹配 - 添加图像预处理(灰度化、边缘检测) ```python gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray, 100, 200) ``` --- ### 相关问题 1. 如何实现Android和iOS跨平台自动化测试? 2. 在无界面服务器上如何管理移动设备连接? 3. OpenCV图像识别如何应对不同屏幕分辨率的适配问题? 4. 如何将自动化测试集成到Jenkins持续集成流程中?[^1] [^1]: Jenkins集成参考: LinuxGUI自动化测试框架搭建(三)-虚拟机安装(Hyper-V或者VMWare)
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Paul.Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值