1、appium+python 实现单设备的 app 自动化测试

  1. 启动 appium server,占用端口 4723
  2. 电脑与一个设备连接,通过 adb devices 获取已连接的设备
  3. 在 python 代码当中,编写启动参数,通过 pytest 编写测试用例,来进行自动化测试。

2、若要多设备并发,同时执行自动化测试,那么需要:

  1. 确定设备个数
  2. 每个设备对应一个 appium server 的端口号,并启动 appium
  3. pytest 要获取到每个设备的启动参数,然后执行自动化测试。

3、实现策略

第一步:从设备池当中,获取当前连接的设备。若设备池为空,则无设备连接。

第二步:若设备池不为空,启动一个线程,用来启动appium server.与设备个数对应。
            起始server端口为4723,每多一个设备,端口号默认+4

第三步:若设备池不为空,则启用多个线程,来执行app自动化测试。

4、具体实现步骤

4.1 通过 adb 命令,获取当前已连接的设备数、设备名称、设备的安卓版本号。

定义一个 ManageDevices 类。

1. 重启adb服务。
2. 通过adb devices命令获取当前平台中,已连接的设备个数,和设备uuid.
3. 通过adb -P 5037 -s 设备uuid shell getprop ro.build.version.release获取每一个设备的版本号。
4. 将所有已连接设备的设备名称、设备版本号存储在一个列表当中。
5. 通过调用get_devices_info函数,即可获得4中的列表。

实现的部分代码为:

  1. """
  2. @Title : app多设备并发-appium+pytest
  3. @Author : 柠檬班-小简
  4. @Email : lemonban_simple@qq.com
  5. """
  6.  
  7. class ManageDevices:
  8. """
  9. 1、重启adb服务。
  10. 2、通过adb devices命令获取当前平台中,已连接的设备个数,和设备uuid.
  11. 3、通过adb -P 5037 -s 设备uuid shell getprop ro.build.version.release获取每一个设备的版本号。
  12. 4、将所有已连接设备的设备名称、设备版本号存储在一个列表当中。
  13. 5、通过调用get_devices_info函数,即可获得4中的列表。
  14. """
  15.  
  16. def __init__(self):
  17. self.__devices_info = []
  18. # 重启adb服务
  19. self.__run_command_and_get_stout("adb kill-server")
  20. self.__run_command_and_get_stout("adb start-server")
  21.  
  22. def get_devices_info(self):
  23. """
  24. 获取已连接设备的uuid,和版本号。
  25. :return: 所有已连接设备的uuid,和版本号。
  26. """
  27. self.__get_devices_uuid()
  28. print(self.__devices_info)
  29. self.__get_device_platform_vesion()
  30. return self.__devices_info

4.2 定义一个设备配置池。

设备启动参数管理池。
每一个设备:对应一个启动参数,以及appium服务的端口号。

1. desired_caps_config/desired_caps.yaml文件中存储了启动参数模板。
2. 从1中的模板读取出启动参数。
3. 从设备列表当中,获取每个设备的设备uuid、版本号,与2中的启动参数合并。
4. 每一个设备,指定一个appium服务端口号。从4723开始,每多一个设备,默认递增4
5. 每一个设备,指定一个本地与设备tcp通信的端口号。从8200开始,每多一个设备,默认递增4.
   在启动参数当中,通过systemPort指定。
   因为appium服务会指定一个本地端口号,将数据转发到安卓设备上。
   默认都是使用8200端口,当有多个appium服务时就会出现端口冲突。会导致运行过程中出现socket hang up的报错。

实现的部分代码:

  1. def devices_pool(port=4723,system_port=8200):
  2. """
  3. 设备启动参数管理池。含启动参数和对应的端口号
  4. :param port: appium服务的端口号。每一个设备对应一个。
  5. :param system_port: appium服务指定的本地端口,用来转发数据给安卓设备。每一个设备对应一个。
  6. :return: 所有已连接设备的启动参数和appium端口号。
  7. """
  8. desired_template = __get_yaml_data()
  9. devs_pool = []
  10. # 获取当前连接的所有设备信息
  11. m = ManageDevices()
  12. all_devices_info = m.get_devices_info()
  13. # 补充每一个设备的启动信息,以及配置对应的appium server端口号
  14. if all_devices_info:
  15. for dev_info in all_devices_info:
  16. dev_info.update(desired_template)
  17. dev_info["systemPort"] = system_port
  18. new_dict = {
  19. "caps": dev_info,
  20. "port": port
  21. }
  22. devs_pool.append(new_dict)
  23. port += 4
  24. system_port += 4
  25. return devs_pool

特别注意事项:2 个及 2 个以设备并发时,会遇到设备 socket hang up 的报错。

原因是什么呢:

在 appium server 的日志当中,有这样一行 adb 命令:adb -P 5037 -s 08e7c5997d2a forward tcp\:8200 tcp\:6790

什么意思呢?

将本地 8200 端口的数据,转发到安卓设备的 6790 端口
   所以,本地启动多个 appium server,都是用的 8200 端口,就会出现冲突。

解决方案:

应该设置为,每一个 appium server 用不同的本地端口号,去转发数据给不同的设备。
   启动参数当中:添加 systemPort= 端口号 来设置。
   这样,每个设备都使用不同的本地端口,那么可解决此问题。

4.3 appium server 启停管理 。

(ps 此处可以使用 appium 命令行版,也可以使用桌面版)

  1. 在自动化用例运行之前,必须让 appium server 启动起来。
  2. 在自动化用例执行完成之后,要 kill 掉 appium 服务。这样才不会影响下一次运行。

代码实现如下:

  1. import subprocess
  2. import os
  3.  
  4. from Common.handle_path import appium_logs_dir
  5.  
  6. class ManageAppiumServer:
  7. """
  8. appium desktop通过命令行启动appium服务。
  9. 不同平台上安装的appium,默认的appium服务路径不一样。
  10. 初始化时,设置appium服务启动路径
  11. 再根据给定的端口号启动appium
  12. """
  13.  
  14. def __init__(self,appium_server_apth):
  15. self.server_apth = appium_server_apth
  16.  
  17. # 启动appium server服务
  18. def start_appium_server(self,port=4723):
  19. appium_log_path = os.path.join(appium_logs_dir,"appium_server_{0}.log".format(port))
  20. command = "node {0} -p {1} -g {2} " \
  21. "--session-override " \
  22. "--local-timezone " \
  23. "--log-timestamp & ".format(self.server_apth, port, appium_log_path)
  24. subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True).communicate()
  25.  
  26. # 关闭appium服务
  27. @classmethod
  28. def stop_appium(cls,pc,post_num=4723):
  29. '''关闭appium服务'''
  30. if pc.upper() == 'WIN':
  31. p = os.popen(f'netstat -aon|findstr {post_num}')
  32. p0 = p.read().strip()
  33. if p0 != '' and 'LISTENING' in p0:
  34. p1 = int(p0.split('LISTENING')[1].strip()[0:4]) # 获取进程号
  35. os.popen(f'taskkill /F /PID {p1}') # 结束进程
  36. print('appium server已结束')
  37. elif pc.upper() == 'MAC':
  38. p = os.popen(f'lsof -i tcp:{post_num}')
  39. p0 = p.read()
  40. if p0.strip() != '':
  41. p1 = int(p0.split('\n')[1].split()[1]) # 获取进程号
  42. os.popen(f'kill {p1}') # 结束进程
  43. print('appium server已结束')

4.4 pytest 当中根据不同的启动参数来执行自动化测试用例

在使用 pytest 执行用例时,是通过 pytest.main()会自动收集所有的用例,并自动执行生成结果。

这种情况下,appium 会话的启动信息是在代码当中给定的。

以上模式当中,只会读取一个设备的启动信息,并启动与设备的会话。

虽然 fixture 有参数可以传递多个设备启动信息,但它是串行执行的。

需要解决的问题的是:

  1. 可以传递多个设备的启动参数,但不是通过 fixture 的参数。
  2. 每传递一个设备启动参数进来,执行一次 pytest.main()

解决方案:

  1. 通过 pytest 的命令行参数。即在 pytest.main()的参数当中,将设备的启动信息传进来。
  2. 使用 python 的多线程来实现。每接收到一个设备启动参数,就启动一个线程来执行 pytest.main
4.4.1 第一个,pytest 的命令行参数。

首先需要在 conftest.py 添加命令行选项,命令行传入参数”--cmdopt“。

用例如果需要用到从命令行传入的参数,就调用 cmdopt 函数。

  1. def pytest_addoption(parser):
  2. parser.addoption(
  3. "--cmdopt", action="store", default="{platformName:'Android',platformVersion:'5.1.1'}",
  4. help="my devices info"
  5. )
  6.  
  7. @pytest.fixture(scope="session")
  8. def cmdopt(request):
  9. return request.config.getoption("--cmdopt")
  10.  
  11. @pytest.fixture
  12. def start_app(cmdopt):
  13. device = eval(cmdopt)
  14. print("开始与设备 {} 进行会话,并执行测试用例 !!".format(device["caps"]["deviceName"]))
  15. driver = start_appium_session(device)
  16. yield driver
  17. driver.close_app()
  18. driver.quit()
4.4.2 使用多线程实现: 每接收到一个设备启动参数,就启动一个线程来执行 pytest.main

定义一个 main.py。

  1. run_case 函数。

此方法主要是:接收设备启动参数,通过 pytest.main 去收集并执行用例。

  1. # 根据设备启动信息,通过pytest.main来收集并执行用例。
  2. def run_cases(device):
  3. """
  4. 参数:device为设备启动参数。在pytest.main当中,传递给--cmdopt选项。
  5. """
  6. print(["-s", "-v", "--cmdopt={}".format(device)])
  7. reports_path = os.path.join(reports_dir,"test_result_{}_{}.html".format(device["caps"]["deviceName"], device["port"]))
  8. pytest.main(["-s", "-v",
  9. "--cmdopt={}".format(device),
  10. "--html={}".format(reports_path)]
  11. )
  1. 每有一个设备,就启动一个线程,执行 run_cases 方法。
  1. # 第一步:从设备池当中,获取当前连接的设备。若设备池为空,则无设备连接。
  2. devices = devices_pool()
  3.  
  4. # 第二步:若设备池不为空,启动appium server.与设备个数对应。起始server端口为4723,每多一个设备,端口号默认+4
  5. if devices and platform_name and appium_server_path:
  6. # 创建线程池
  7. T = ThreadPoolExecutor()
  8. # 实例化appium服务管理类。
  9. mas = ManageAppiumServer(appium_server_path)
  10. for device in devices:
  11. # kill 端口,以免占用
  12. mas.stop_appium(platform_name,device["port"])
  13. # 启动appium server
  14. task = T.submit(mas.start_appium_server,device["port"])
  15. time.sleep(1)
  16.  
  17. # 第三步:若设备池不为空,在appium server启动的情况下,执行app自动化测试。
  18. time.sleep(15)
  19. obj_list = []
  20. for device in devices:
  21. index = devices.index(device)
  22. task = T.submit(run_cases,device)
  23. obj_list.append(task)
  24. time.sleep(1)
  25.  
  26. # 等待自动化任务执行完成
  27. for future in as_completed(obj_list):
  28. data = future.result()
  29. print(f"sub_thread: {data}")
  30.  
  31. # kill 掉appium server服务,释放端口。
  32. for device in devices:
  33. ManageAppiumServer.stop_appium(platform_name, device["port"])

app 自动化测试 - 多设备并发 -appium+pytest+ 多线程的更多相关文章

  1. 篇2 安卓app自动化测试-初识python调用appium

    篇2              安卓app自动化测试-初识python调用appium --lamecho辣么丑 1.1概要 大家好!我是lamecho(辣么丑),上一篇也是<安卓app自动化测 ...

  2. 篇4 安卓app自动化测试-Appium API进阶

    篇4                 安卓app自动化测试-Appium API进阶 --lamecho辣么丑 1.1概要 大家好! 我是lamecho(辣么丑),今天是<安卓app自动化测试& ...

  3. 移动app自动化测试

    原文出处https://www.toutiao.com/i6473606106970063374/ 原文作者是今日头条的:一个字头的诞生 在此感谢原文作者的无私分享! 移动App自动化测试(一) 目前 ...

  4. app自动化测试环境安装

    一.环境依赖 app自动化测试环境如下: appium服务 第三方库appium-python-client 手机或模拟器 java环境jdk1.8 android环境 二.appium服务安装 1. ...

  5. Appium+python自动化(三十六)- 士兵突击许三多 - 多个appium服务启动,多个设备启动,多进程并发启动设备-并发测试 - 上(超详解)

    简介 前面课程只是启动了单个appium服务,只能控制单台设备.如果需要针对多台设备测试那么该如何处理?而且发现群里的小伙伴们也在时不时地在讨论这个问题,想知道怎么实现的,于是宏哥就决定写一片这样的文 ...

  6. Python appium搭建app自动化测试环境

    appium做app自动化测试,环境搭建是比较麻烦的. 也是很多初学者在学习app自动化之时,花很多时间都难跨越的坎. 但没有成功的环境,就没有办法继续后续的使用. 在app自动化测试当中,我们主要是 ...

  7. 基于appium的app自动化测试框架

    基于appium框架的app自动化测试 App自动化测试主要难点在于环境的搭建,appium完全是基于selenium进行的扩展,所以app测试框架也是基于web测试框架开发的 一.设备连接 (即构建 ...

  8. 基于Appium的APP自动化测试基础--美团APP的实例

    转:https://blog.csdn.net/Tigerdong1/article/details/80159156 前段时间用一种流行语言,一个主流工具,一个实用框架,写了一个美团app自动化测试 ...

  9. app自动化测试Appium+python

    一.node.js安装 https://nodejs.org/en/download/ ##一直下一步 ###cmd查看 二.  .NET Framework安装 https://www.micros ...

随机推荐

  1. EAM在不同行业的应用

    EAM在不同行业的应用 EAM从出现至今,已让很多资产密集型企业受益,甚至在一些行业领域里已经是公认的.不可或缺的管理方案,比如电力行业.轨道交通行业等.但由于不同行业或者企业业务类型不同,导致了资产 ...

  2. 关于JSON的零碎小知识

    1.ali的fastjson在将实体类转成jsonString的时候,一些首字母大写的字段会自动修改为小字母,这种字段加 @JsonProperty(value = "DL_id" ...

  3. vue v-for渲染数据出现DOMException: Failed to execute 'removeChild' on 'Node': The node .....

    在项目中,使用了vue的v-for渲染数组数据,在一次改变数组的时候出现异常报错,而实际的数组是已经变化过的了,页面卡死 网上找了一下原因,说是vue的DOM渲染的时候,删除之后DOM里面的还没有反应 ...

  4. 朴素版和堆优化版dijkstra和朴素版prim算法比较

    1.dijkstra 时间复杂度:O(n^2) n次迭代,每次找到距离集合S最短的点 每次迭代要用找到的点t来更新其他点到S的最短距离. #include<iostream> #inclu ...

  5. C#类型与变量

    C#入门笔记 8.28开始看刘铁猛的视频,到9.22看完.大概觉得自己入门了,对OOP也有一定了解了,稍微写点笔记,当复习了. 类型与变量 数据类型 数据类型[1]是数据在内存中存储时的"型 ...

  6. get请求传递json格式数据的两种方法

    get请求参数为json格式数据,使用pyhton+request的两种实现方式如下: 方法一:使用requests.request() 示例代码如下: 1.导入requests和json impor ...

  7. mysql 空值(null)和空字符('')的区别

    日常开发中,一般都会涉及到数据库增删改查,那么不可避免会遇到Mysql中的NULL和空字符. 空字符(")和空值(null)表面上看都是空,其实存在一些差异: 定义: 空值(NULL)的长度 ...

  8. Apache CXF基本使用

    一.服务端开发 1.创建web项目 2.导入jar包 3.web.xml中配置Servlet 1 <!-- 配置CXF框架提供的Servlet --> 2 <servlet> ...

  9. Spring 配置文件配置事务

    一.引入事务的头文件 xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework. ...

  10. sqlserver和oracle修改表结构

    sqlserver和oracle修改表结构常用SQL Server:1.增加列  ALTER TABLE users ADD address varchar(30);2.删除列  ALTER TABL ...