Appium+Pytest实现app并发测试
前言
这个功能已经写完很长时间了,一直没有发出来,今天先把代码发出来吧,有一些代码是参考网上写的,具体的代码说明今天暂时先不发了,代码解释的太详细还得我花点时间^_^, 毕竟想让每个人都能看明白也不容易,所以先放代码,有兴趣的先研究吧,等我有时间再做代码说明(will doing)
目录结构
文件源码
"""
------------------------------------
@Time : 2019/9/22 12:19
@Auth : linux超
@File : base_page.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import time
from appium.webdriver import WebElement
from appium.webdriver.webdriver import WebDriver
from appium.webdriver.common.touch_action import TouchAction
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, TimeoutException class Base(object): def __init__(self, driver: WebDriver):
self.driver = driver @property
def get_phone_size(self):
"""获取屏幕的大小"""
width = self.driver.get_window_size()['width']
height = self.driver.get_window_size()['height']
return width, height def swipe_left(self, duration=300):
"""左滑"""
width, height = self.get_phone_size
start = width * 0.9, height * 0.5
end = width * 0.1, height * 0.5
return self.driver.swipe(*start, *end, duration) def swipe_right(self, duration=300):
"""右滑"""
width, height = self.get_phone_size
start = width * 0.1, height * 0.5
end = width * 0.9, height * 0.5
return self.driver.swipe(*start, *end, duration) def swipe_up(self, duration):
"""上滑"""
width, height = self.get_phone_size
start = width * 0.5, height * 0.9
end = width * 0.5, height * 0.1
return self.driver.swipe(*start, *end, duration) def swipe_down(self, duration):
"""下滑"""
width, height = self.get_phone_size
start = width * 0.5, height * 0.1
end = width * 0.5, height * 0.9
return self.driver.swipe(*start, *end, duration) def skip_welcome_page(self, direction, num=3):
"""
滑动页面跳过引导动画
:param direction: str 滑动方向,left, right, up, down
:param num: 滑动次数
:return:
"""
direction_dic = {
"left": "swipe_left",
"right": "swipe_right",
"up": "swipe_up",
"down": "swipe_down"
}
time.sleep(3)
if hasattr(self, direction_dic[direction]):
for _ in range(num):
getattr(self, direction_dic[direction])() # 使用反射执行不同的滑动方法
else:
raise ValueError("参数{}不存在, direction可以为{}任意一个字符串".
format(direction, direction_dic.keys())) @staticmethod
def get_element_size_location(element):
width = element.rect["width"]
height = element.rect["height"]
start_x = element.rect["x"]
start_y = element.rect["y"]
return width, height, start_x, start_y def get_password_location(self, element: WebElement) -> dict:
width, height, start_x, start_y = self.get_element_size_location(element)
point_1 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 1)}
point_2 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 1)}
point_3 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 1)}
point_4 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 3)}
point_5 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 3)}
point_6 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 3)}
point_7 = {"x": int(start_x + width * (1 / 6) * 1), "y": int(start_y + height * (1 / 6) * 5)}
point_8 = {"x": int(start_x + width * (1 / 6) * 3), "y": int(start_y + height * (1 / 6) * 5)}
point_9 = {"x": int(start_x + width * (1 / 6) * 5), "y": int(start_y + height * (1 / 6) * 5)}
keys = {
1: point_1,
2: point_2,
3: point_3,
4: point_4,
5: point_5,
6: point_6,
7: point_7,
8: point_8,
9: point_9
}
return keys def gesture_password(self, element: WebElement, *pwd):
"""手势密码: 直接输入需要链接的点对应的数字,最多9位
pwd: 1, 2, 3, 6, 9
"""
if len(pwd) > 9:
raise ValueError("需要设置的密码不能超过9位!")
keys_dict = self.get_password_location(element)
start_point = "TouchAction(self.driver).press(x={0}, y={1}).wait(200)". \
format(keys_dict[pwd[0]]["x"], keys_dict[pwd[0]]["y"])
for index in range(len(pwd) - 1): # 0,1,2,3
follow_point = ".move_to(x={0}, y={1}).wait(200)". \
format(keys_dict[pwd[index + 1]]["x"],
keys_dict[pwd[index + 1]]["y"])
start_point = start_point + follow_point
full_point = start_point + ".release().perform()"
return eval(full_point) def find_element(self, locator: tuple, timeout=30) -> WebElement:
wait = WebDriverWait(self.driver, timeout)
try:
element = wait.until(lambda driver: driver.find_element(*locator))
return element
except (NoSuchElementException, TimeoutException):
print('no found element {} by {}', format(locator[1], locator[0])) if __name__ == '__main__':
pass
base/base_page.py
"""
------------------------------------
@Time : 2019/9/22 12:17
@Auth : linux超
@File : check_port.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import socket
import os def check_port(host, port):
"""检测指定的端口是否被占用"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建socket对象
try:
s.connect((host, port))
s.shutdown(2)
except OSError:
print('port %s is available! ' % port)
return True
else:
print('port %s already be in use !' % port)
return False def release_port(port):
"""释放指定的端口"""
cmd_find = 'netstat -aon | findstr {}'.format(port) # 查找对应端口的pid
print(cmd_find) # 返回命令执行后的结果
result = os.popen(cmd_find).read()
print(result) if str(port) and 'LISTENING' in result:
# 获取端口对应的pid进程
i = result.index('LISTENING')
start = i + len('LISTENING') + 7
end = result.index('\n')
pid = result[start:end]
cmd_kill = 'taskkill -f -pid %s' % pid # 关闭被占用端口的pid
print(cmd_kill)
os.popen(cmd_kill)
else:
print('port %s is available !' % port) if __name__ == '__main__':
host = '127.0.0.1'
port = 4723
if not check_port(host, port):
print("端口被占用")
release_port(port)
common/check_port.py
"""
------------------------------------
@Time : 2019/9/22 13:47
@Auth : linux超
@File : get_main_js.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import subprocess
from config.root_config import LOG_DIR """
获取main.js的未知,使用main.js启动appium server
""" class MainJs(object):
"""获取启动appium服务的main.js命令""" def __init__(self, cmd: str = "where main.js"):
self.cmd = cmd def get_cmd_result(self):
p = subprocess.Popen(self.cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
with open(LOG_DIR + "/" + "cmd.txt", "w", encoding="utf-8") as f:
f.write(p.stdout.read().decode("gbk"))
with open(LOG_DIR + "/" + "cmd.txt", "r", encoding="utf-8") as f:
cmd_result = f.read().strip("\n")
return cmd_result if __name__ == '__main__':
main = MainJs("where main.js")
print(main.get_cmd_result())
common/get_main_js.py
automationName: uiautomator2
platformVersion: 5.1.1
platformName: Android
appPackage: com.xxzb.fenwoo
appActivity: .activity.addition.WelcomeActivity
noReset: True
ip: "127.0.0.1"
config/desired_caps.yml
"""
------------------------------------
@Time : 2019/9/22 12:29
@Auth : linux超
@File : root_config.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import os """
project dir and path
"""
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOG_DIR = os.path.join(ROOT_DIR, "log")
CONFIG_DIR = os.path.join(ROOT_DIR, "config")
CONFIG_PATH = os.path.join(CONFIG_DIR, "desired_caps.yml")
config/root_config.py
"""
------------------------------------
@Time : 2019/9/22 12:23
@Auth : linux超
@File : app_driver.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import subprocess
from time import ctime
from appium import webdriver
import yaml from common.check_port import check_port, release_port
from common.get_main_js import MainJs
from config.root_config import CONFIG_PATH, LOG_DIR class BaseDriver(object):
"""获取driver"""
def __init__(self, device_info):
main = MainJs("where main.js")
with open(CONFIG_PATH, 'r') as f:
self.data = yaml.load(f, Loader=yaml.FullLoader)
self.device_info = device_info
js_path = main.get_cmd_result()
cmd = r"node {0} -a {1} -p {2} -bp {3} -U {4}:{5}".format(
js_path,
self.data["ip"],
self.device_info["server_port"],
str(int(self.device_info["server_port"]) + 1),
self.data["ip"],
self.device_info["device_port"]
)
print('%s at %s' % (cmd, ctime()))
if not check_port(self.data["ip"], int(self.device_info["server_port"])):
release_port(self.device_info["server_port"])
subprocess.Popen(cmd, shell=True, stdout=open(LOG_DIR + "/" + device_info["server_port"] + '.log', 'a'),
stderr=subprocess.STDOUT) def get_base_driver(self):
desired_caps = {
'platformName': self.data['platformName'],
'platformVerion': self.data['platformVersion'],
'udid': self.data["ip"] + ":" + self.device_info["device_port"],
"deviceName": self.data["ip"] + ":" + self.device_info["device_port"],
'noReset': self.data['noReset'],
'appPackage': self.data['appPackage'],
'appActivity': self.data['appActivity'],
"unicodeKeyboard": True
}
print('appium port:%s start run %s at %s' % (
self.device_info["server_port"],
self.data["ip"] + ":" + self.device_info["device_port"],
ctime()
))
driver = webdriver.Remote(
'http://' + self.data['ip'] + ':' + self.device_info["server_port"] + '/wd/hub',
desired_caps
)
return driver if __name__ == '__main__':
pass
drivers/app_driver.py
"""
------------------------------------
@Time : 2019/9/22 12:16
@Auth : linux超
@File : conftest.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
from drivers.app_driver import BaseDriver
import pytest
import time from common.check_port import release_port base_driver = None def pytest_addoption(parser):
parser.addoption("--cmdopt", action="store", default="device_info", help=None) @pytest.fixture(scope="session")
def cmd_opt(request):
return request.config.getoption("--cmdopt") @pytest.fixture(scope="session")
def common_driver(cmd_opt):
cmd_opt = eval(cmd_opt)
print("cmd_opt", cmd_opt)
global base_driver
base_driver = BaseDriver(cmd_opt)
time.sleep(1)
driver = base_driver.get_base_driver()
yield driver
# driver.close_app()
driver.quit()
release_port(cmd_opt["server_port"])
conftest.py
"""
------------------------------------
@Time : 2019/9/22 12:17
@Auth : linux超
@File : run_case.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import pytest
import os
from multiprocessing import Pool device_infos = [
{
"platform_version": "5.1.1",
"server_port": "",
"device_port": "",
},
{
"platform_version": "5.1.1",
"server_port": "",
"device_port": "",
}
] def main(device_info):
pytest.main(["--cmdopt={}".format(device_info),
"--alluredir", "./allure-results", "-vs"])
os.system("allure generate allure-results -o allure-report --clean") if __name__ == "__main__":
with Pool(2) as pool:
pool.map(main, device_infos)
pool.close()
pool.join()
run_case.py
"""
------------------------------------
@Time : 2019/9/22 12:17
@Auth : linux超
@File : test_concurrent.py
@IDE : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ : 28174043@qq.com
@GROUP: 878565760
------------------------------------
"""
import pytest
import time
from appium.webdriver.common.mobileby import MobileBy from base.base_page import Base class TestGesture(object): def test_gesture_password(self, common_driver):
"""这个case我只是简单的做了一个绘制手势密码的过程"""
driver = common_driver
base = Base(driver)
base.skip_welcome_page('left', 3) # 滑动屏幕
time.sleep(3) # 为了看滑屏的效果
driver.start_activity(app_package="com.xxzb.fenwoo",
app_activity=".activity.user.CreateGesturePwdActivity")
commit_btn = (MobileBy.ID, 'com.xxzb.fenwoo:id/right_btn')
password_gesture = (MobileBy.ID, 'com.xxzb.fenwoo:id/gesturepwd_create_lockview')
element_commit = base.find_element(commit_btn)
element_commit.click()
password_element = base.find_element(password_gesture)
base.gesture_password(password_element, 1, 2, 3, 6, 5, 4, 7, 8, 9)
time.sleep(5) # 看效果 if __name__ == '__main__':
pytest.main()
cases/test_concurrent.py
启动说明
1. 我代码中使用的是模拟器,如果你需要使用真机,那么需要修改部分代码,模拟器是带着端口号的,而真机没有端口号,具体怎么修改先自己研究,后面我再详细的介绍
2. desired_caps.yml文件中的配置需要根据自己的app配置修改
3. 代码中没有包含自动连接手机的部分代码,所以执行项目前需要先手动使用adb连接上手机(有条件的,可以自己把这部分代码写一下,然后再运行项目之前调用一下adb连接手机的方法即可)
4. 项目目录中的allure_report, allure_results目录是系统自动生成的,一个存放最终的测试报告,一个是存放报告的依赖文件,如果你接触过allure应该知道
5. log目录下存放了appium server启动之后运行的日志
效果展示
最后
我只是初步实现了这样一个多手机并发的需求,并没有写的很详细,比如,让项目更加的规范还需要引入PO设计模式,我这里没写这部分,其次base_page.py中还可以封装更多的方法,我也只封装了几个方法,如果真正的把这个并发引入到项目中肯定还需要完善的,但是需要添加的东西都是照葫芦画瓢了,有问题多思考!yes i can!
明天就是2020年了,大家加油!
Appium+Pytest实现app并发测试的更多相关文章
- app 自动化测试 - 多设备并发 -appium+pytest+ 多线程
1.appium+python 实现单设备的 app 自动化测试 启动 appium server,占用端口 4723 电脑与一个设备连接,通过 adb devices 获取已连接的设备 在 pyth ...
- Appium+python自动化(三十六)- 士兵突击许三多 - 多个appium服务启动,多个设备启动,多进程并发启动设备-并发测试 - 上(超详解)
简介 前面课程只是启动了单个appium服务,只能控制单台设备.如果需要针对多台设备测试那么该如何处理?而且发现群里的小伙伴们也在时不时地在讨论这个问题,想知道怎么实现的,于是宏哥就决定写一片这样的文 ...
- Appium+python自动化(三十七)- 士兵突击许三多 - 多个appium服务启动,多个设备启动,多进程并发启动设备-并发测试 - 下(超详解)
简介 接着上一篇继续看一下如何并发测试以及并发测试的过程中,可能遇到的问题,在这里宏哥把宏哥遇到的和小伙伴或者童鞋们,一起分享一下. Appium端口检测 问题思考 经过前面学习,我们已经能够使用py ...
- Appium Grid并发测试
背景 Selenium玩的比较6的同学比较清楚:在Selenium中三大组件中有包含了Selenium Grid,而其作用就是分布式执行测试用例.主要的应用场景在于: 缩短测试执行时间,提高自动化测试 ...
- appium 并发测试
Android并发测试 Appium提供了在一台设备上启动多个Android会话的方案,而这个方案需要你输入不同的指令来启动多个Appium服务来实现. 启动多个Android会话的重要指令包括: - ...
- appium+pytest+allure+jenkins 如何实现多台手机连接
使用appium可以实现app自动化测试,我们之前是连接一台手机去运行,如何同时连接多台手机呢?很多人可能想到的是多线程(threading).今天分享一种比多线程更简单的方法,虽然不是多台手机同时运 ...
- appium for hybrid app 处理webview
之前研究了一段时间的appium for native app 相应的总结如下: appium测试环境搭建 :ht ...
- 深圳尚学堂:Android APP的测试流程
每一个新开发的软件都避免不了测试,我在这里总结了一些Android系统的移动端APP测试的一些测试流程,希望可以给大家一些帮助. 1. UI 测试App主要核ui与实际设计的效果图是否一致:交互方面的 ...
- appium+Python 启动app(二)
我们上步操作基本完成,下面介绍编写Python脚本启动app 打开我们pycharm新建.py文件 第一步:输入Python脚本代码: #coding=utf-8 from appium import ...
随机推荐
- Kubernetes排错:用容器的元数据提供新思路
在这篇文章中,让我们讨论一下Kubernetes中的元数据(Metadata),以及如何利用它来监控系统的性能. 元数据(Metadata) 是一个较为高大上的词.它的含义是"用来描述其他数 ...
- Warning!程序员们小心被技术绑架
通常我们说程序员需要在某个技术方向上积累到一定的厚度,要能够运用技术有效地解决实际问题.可是当程序员在某一项技术上浸淫时间长了之后,却经常会出现另外的问题,那就是:看待问题时受限于自身的技术积累. 我 ...
- @bzoj - 4709@ 柠檬
目录 @desription@ @solution@ @accepted code@ @details@ @desription@ 一共有 N 只贝壳,编号为 1...N,贝壳 i 的大小为 si. ...
- 【[Offer收割]编程练习赛9 B】水陆距离
[题目链接]:http://hihocoder.com/problemset/problem/1478 [题意] [题解] 一开始把所有的水域的位置都加入到队列中去; 然后跑一个bfs. 第一次到达的 ...
- 微信支付、支付宝支付和QQ钱包支付
最近忙于对接微信支付和支付宝支付,注册微信公众号,认证公众号,注册微信支付商户号并进行认证: 签约支付宝支付产品(手机网站支付.PC网站支付),注册支付宝企业账号(企业账号权限更大): 注册QQ钱包商 ...
- 谷歌BERT预训练源码解析(一):训练数据生成
目录预训练源码结构简介输入输出源码解析参数主函数创建训练实例下一句预测&实例生成随机遮蔽输出结果一览预训练源码结构简介关于BERT,简单来说,它是一个基于Transformer架构,结合遮蔽词 ...
- 给radio添加点击事件
1.单独给每个radio添加点击事件 <fieldset id="form-gra-time"> <legend>请选择日期粒度:</legend&g ...
- 微软的可疑更新DhMachineSvc.exe
最近微软大范围的推出了一个只针对中国的更新,包含了DhMachineSvc.exe,也就是所谓的'微软设备健康助手服务'. 这个更新很神秘,首先这个更新只针对中国区,其次这个更新支持WinXP,第三这 ...
- tensorflow -gpu安装,史上最新最简单的途径(不用自己装cuda,cdnn)
tensorflow -gpu安装首先,安装Anoconda1. 官网下载点我: 2.安装 点击 python 3.6 version自动下载x64版,下载好之后,然后安装. 如图,打上勾之后,一路n ...
- H3C 配置基本ACL