框架目录说明
pyAppium # 项目根目录
├─app # 测试APP存放目录
├─common # 公共模块目录
├─config # 配置文件目录
├─data # 测试数据目录
├─outputs # 测试输出目录
│ ├─logs # 日志目录
│ ├─picture # 截图存放目录
│ └─reports # 测试报告存放目录
├─pageViwe # PO模式页面封装模块
└─testcase # 测试用例目录
主要功能
1.自动启动appium server和杀掉appium server
2.自动获取电脑连接设备信息,如设备版本、设备udid
3.自动检测端口是否可用、释放占用端口
4.自动获取测试APP应用相关信息,如:appPackage、launchable_activity
5.自动安装APP和卸载APP
6.测试用例无需配置,自动获取用例执行,测试人员只需编写相关case
7.用例执行失败自动截图、收集错误日志信息,自动加到测试报告对应case下面
8.启动一次APP,执行所有测试用例,缩短测试用例执行间隔,提高测试执行效率
9.多进程方式在多台手机上同时执行测试,大幅提高测试效率
测试环境
- win10 64 pycharm2019
- python 3.7.0 及以上
- node v12.16.3
- appium1.20.2
- java version “1.8.0_181”
- Android SDK 百度云分享地址 密码:6666
下载框架源码
1.仓库地址:https://gitee.com/King15800/pyAppium.git
2.下载源码:git clone https://gitee.com/King15800/pyAppium.git
3.使用pycharm 打开源码项目
4.安装项目依赖包:pip install -r requirements.txt
使用说明
启动项目正常运行前提:
有手机正常已经连接电脑
修改 test_login.py文件输入账号信息,本demo基于学科网APP编写
已下载APP,放到项目目录App下
启动项目:直接运行main.py文件即可。
关键代码说明
启动入口说明 main.py
def run_parallel(device_info) 定义一个pytest 启动入口,根据设备进行启动,一个设备启动一个
def run_parallel(device_info):
pytest.main([f"--cmdopt={device_info}",
"--alluredir", "outputs/reports/data"])
os.system("allure generate outputs/reports/data -o outputs/reports/html --clean")
根据设备数量,使用进程池创建进程进行测试
device_lists = get_device_infos() # 获取连接设备信息
uninstall_app(device_lists) # 卸载APP
with Pool(len(device_lists)) as pool:
pool.map(run_parallel, device_lists)
pool.close()
pool.join()
完整代码
# _*_coding:utf-8 _*_
# @Time :2021/6/21 22:33
# @Author : king
# @File :main.py
# @Software :PyCharm
from multiprocessing import Pool
import os
import pytest
from common.app_info import get_device_infos, uninstall_app
from common.appium_auto_server import close_appium
def run_parallel(device_info):
pytest.main([f"--cmdopt={device_info}",
"--alluredir", "outputs/reports/data"])
os.system("allure generate outputs/reports/data -o outputs/reports/html --clean")
if __name__ == "__main__":
device_lists = get_device_infos()
uninstall_app(device_lists)
with Pool(len(device_lists)) as pool:
pool.map(run_parallel, device_lists)
pool.close()
pool.join()
driver实现 base_driver.py
class BaseDriver:
def __init__(self, device_info):
self.device_info = device_info
cmd = "start /b appium -a 127.0.0.1 -p {0} -bp {1}".format(self.device_info["server_port"],
self.device_info["server_port"] + 1)
open_appium(cmd, self.device_info["server_port"])
def base_driver(self, automationName="appium"):
fp = open(f"{configPath}//caps.yml", encoding='utf-8')
# 超时时间、是否重置
desired_caps = yaml.load(fp, Loader=yaml.FullLoader)
# 设备名称
desired_caps["deviceName"] = self.device_info['device']
# 版本信息
desired_caps["platform_version"] = get_devices_version(desired_caps["deviceName"])
app_path = os.path.join(appPath, get_app_name(appPath))
desired_caps['app'] = app_path
desired_caps['appPackage'] = get_app_package_name()
desired_caps['appActivity'] = get_app_launchable_activity()
# udid
desired_caps["udid"] = self.device_info['device']
# 系统端口号
desired_caps["systemPort"] = self.device_info["system_port"]
desired_caps["automationName"] = automationName
driver = webdriver.Remote(f"http://127.0.0.1:{self.device_info['server_port']}/wd/hub",
desired_capabilities=desired_caps)
return driver
核心conftest文件 conftest.py
from common.base_driver import BaseDriver
import pytest
import random
from pageViwe.loginPage import LoginPage
driver = None
def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store", default="device_info", help=None)
@pytest.fixture(scope='session')
def cmdopt(pytestconfig):
return pytestconfig.getoption("--cmdopt")
# 定义公共的fixture,根据需要进行修改
@pytest.fixture(scope='session')
def common_driver(cmdopt):
global driver
base_driver = BaseDriver(eval(cmdopt))
driver = base_driver.base_driver()
lg = LoginPage(driver)
yield lg
driver.close_app()
driver.quit()
@pytest.fixture
def device(cmdopt):
yield eval(cmdopt)
# 报告中区分多设备执行结果
def pytest_itemcollected(item):
item._nodeid = str(random.randint(1, 1000)) + '_' + item . _nodeid
执行结果
============================= test session starts =============================
platform win32 -- Python 3.8.4, pytest-7.4.0, pluggy-1.2.0
============================= test session starts =============================
platform win32 -- Python 3.8.4, pytest-7.4.0, pluggy-1.2.0
rootdir: C:\study\pythonT\pyAppium-master
configfile: pytest.ini
testpaths: testcase/
plugins: allure-pytest-2.13.2, excel-1.5.2, html-3.2.0, metadata-3.0.0, ordering-0.6, rerunfailures-12.0, xdist-3.3.1
rootdir: C:\study\pythonT\pyAppium-master
configfile: pytest.ini
testpaths: testcase/
plugins: allure-pytest-2.13.2, excel-1.5.2, html-3.2.0, metadata-3.0.0, ordering-0.6, rerunfailures-12.0, xdist-3.3.1
collected 2 items
collected 2 items
35_testcase\test_login.py 575_testcase\test_login.py . [ 50%]
239_testcase\test_login.py . [ 50%]
885_testcase\test_login.py F [100%]
执行日志
测试报告
我同时连接的是一台模拟机,一台真机,控制台显示也是两台同时运行,但是实际结果是设备一台台运行的,并不是两台设备同时运行操作。(连接两台真机时,可以并行)
case 标题动态生成,可以自行修改,目前是功能名称+设备udid
注意点
由于使用pytest的自带日志模块,修改pytest 自带loging文件的写入模式,保证记录完整日志信息,文件路径C:\python3.8\Lib\site-packages\_pytest\logging.py 需要根据自己环境地址进入
将mode='w' 改成 mode='a' 在文件505行左右(不同版本有所不同,有的在646行左右)
if log_file:
self.log_file_handler = logging.FileHandler(
log_file, mode="a", encoding="UTF-8"
)
未来规划
集成到Jenkins
不同机器能够采用不同数据
可以对于无线连接手机进行测试
appium server远程分布式调用执行
————————————————