Appium+Python+pytest自动化测试框架的实战

本文主要介绍了Appium+Python+pytest自动化测试框架的实战,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

先简单介绍一下目录,再贴一些代码,代码里有注释

Basic目录下写的是一些公共的方法,Data目录下写的是测试数据,image存的是测试失败截图,Log日志文件,Page测试的定位元素,report测试报告,Test测试用例,pytest.ini是pytest启动配置文件,requirements.txt需要安装的py模块,run.py运行文件

在这里插入图片描述
Basic/base.py

里面封装了 一些方法,元素的点击,输入,查找,还有一些自己需要的公共方法也封装在里面,如果你们有别的需要可以自己封装调用

# coding=utf-8
import random
import allure
import pymysql
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from Basic import Log
import oslog = Log.MyLog()class Base(object):def __init__(self, driver):self.driver = driver# 自定义一个元素查找方法def find_element(self, feature,timeout=5, poll=1.0):# feature = By.XPATH,"//*[@text='显示']""""依据用户传入的元素信息特征,然后返回当前用户想要查找元素:param feature: 元组类型,包含用户希望的查找方式,及该方式对应的值:return: 返回当前用户查找的元素"""by = feature[0]value = feature[1]wait = WebDriverWait(self.driver, timeout, poll)if by == By.XPATH:# print( "说明了用户想要使用 xpath 路径的方式来获取元素" )value = self.make_xpath(value)return wait.until(lambda x: x.find_element(by,value))def find_elements(self, feature):wait = WebDriverWait(self.driver, 5, 1)return wait.until(lambda x: x.find_elements(feature[0], feature[1]))def click_element(self, loc):'''封装点击操作函数'''self.find_element(loc).click()def input_text(self, loc, text):'''封装输入操作函数'''self.fm = self.find_element(loc)self.fm.clear()  # 需要先清空输入框,防止有默认内容self.fm.send_keys(text)# 自定义了一个可以自动帮我们拼接 xpath 路径的工具函数def make_xpath(self, feature):start_path = "//*["end_path = "]"res_path = ""if isinstance(feature, str):# 如果是字符串 我们不能直接上来就拆我们可以判断一下它是否是默认正确的 xpath 写法if feature.startswith("//*["):return feature# 如果用户输入的是字符串,那么我们就拆成列表再次进行判断split_list = feature.split(",")if len(split_list) == 2:# //*[contains(@text,'设')]res_path = "%scontains(@%s,'%s')%s" % (start_path, split_list[0], split_list[1], end_path)elif len(split_list) == 3:# //[@text='设置']res_path = "%s@%s='%s'%s" % (start_path, split_list[0], split_list[1], end_path)else:print("请按规则使用")elif isinstance(feature, tuple):for item in feature:# 默认用户在元组当中定义的数据都是字符串split_list2 = item.split(',')if len(split_list2) == 2:res_path += "contains(@%s,'%s') and " % (split_list2[0], split_list2[1])elif len(split_list2) == 3:res_path += "@%s='%s' and " % (split_list2[0], split_list2[1])else:print("请按规则使用")andIndex = res_path.rfind(" and")res_path = res_path[0:andIndex]res_path = start_path + res_path + end_pathelse:print("请按规则使用")return res_pathdef assert_ele_in(self, text, element):'''封装断言操作函数'''try:assert text in self.find_element(element).textassert 0except Exception:assert 1def get_assert_text(self, element):ele = self.find_element(element, timeout=5, poll=0.1)return ele.text# 自定义一个获取 toast内容的方法def get_toast_content(self, message):tmp_feature = By.XPATH, "//*[contains(@text,'%s')]" % messageele = self.find_element(tmp_feature)return ele.text# 自定义一个工具函数,可以接收用户传递的部分 toast 信息,然后返回一个布尔值,来告诉# 用户,目标 toast 到底是否存在def is_toast_exist(self, mes):# 拿着用户传过来的 message 去判断一下包含该内容的 toast 到底是否存在。try:self.get_toast_content(mes)return Trueexcept Exception:# 如果目标 toast 不存在那么就说明我们的实际结果和预期结果不一样# 因此我们想要的是断言失败return Falsedef get_mysql(self,  table, value):'''连接数据库'''# 打开数据库连接db = pymysql.connect(host='', port=, db=, user='', passwd='', charset='utf8')# 使用 cursor() 方法创建一个游标对象 cursorcursor = db.cursor()try:# 使用 execute()  方法执行 SQL 查询cursor.execute(value)db.commit()except Exception as e:print(e)db.rollback()# 使用 fetchone() 方法获取单条数据.data = cursor.fetchone()# 关闭数据库连接db.close()return datadef get_xpath(self, value):'''封装获取xpath方法'''text = By.XPATH, '//*[@text="%s"]' % valuereturn text# 自定义一个获取当前设备尺寸的功能def get_device_size(self):x = self.driver.get_window_size()["width"]y = self.driver.get_window_size()["height"]return x, y# 自定义一个功能,可以实现向左滑屏操作。def swipe_left(self):start_x = self.get_device_size()[0] * 0.9start_y = self.get_device_size()[1] * 0.5end_x = self.get_device_size()[0] * 0.4end_y = self.get_device_size()[1] * 0.5self.driver.swipe(start_x, start_y, end_x, end_y)# 自定义一个功能,可以实现向上滑屏操作。def swipe_up(self):start_x = self.get_device_size()[0] * 1/2start_y = self.get_device_size()[1] * 1/2end_x = self.get_device_size()[0] * 1/2end_y = self.get_device_size()[1] * 1/7self.driver.swipe(start_x, start_y, end_x, end_y, 500)# 切换到微信def switch_weixxin(self):self.driver.start_activity("com.tencent.mm", ".ui.LauncherUI")# 切换到医生端def switch_doctor(self):self.driver.start_activity("com.rjjk_doctor", ".MainActivity")# 切换到销售端def switch_sale(self):self.driver.start_activity("com.rjjk_sales", ".MainActivity")def switch_webview(self):# 切换到webviewprint(self.driver.contexts)time.sleep(5)self.driver.switch_to.context("WEBVIEW_com.tencent.mm:tools")print("切换成功")time.sleep(3)# 自定义根据坐标定位def taptest(self, a, b):# 设定系数,控件在当前手机的坐标位置除以当前手机的最大坐标就是相对的系数了# 获取当前手机屏幕大小X,YX = self.driver.get_window_size()['width']Y = self.driver.get_window_size()['height']# 屏幕坐标乘以系数即为用户要点击位置的具体坐标self.driver.tap([(a * X, b * Y)])# 自定义截图函数def take_screenShot(self):'''测试失败截图,并把截图展示到allure报告中'''tm = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time()))self.driver.get_screenshot_as_file(os.getcwd() + os.sep + "image/%s.png" % tm)allure.attach.file(os.getcwd() + os.sep + "image/%s.png" %tm, attachment_type=allure.attachment_type.PNG)# 自定义随机生成11位手机号def create_phone(self):# 第二位数字second = [3, 4, 5, 7, 8][random.randint(0, 4)]# 第三位数字third = {3: random.randint(0, 9),4: [5, 7, 9][random.randint(0, 2)],5: [i for i in range(10) if i != 4][random.randint(0, 8)],7: [i for i in range(10) if i not in [4, 9]][random.randint(0, 7)],8: random.randint(0, 9),}[second]# 最后八位数字suffix = random.randint(9999999, 100000000)# 拼接手机号return "1{}{}{}".format(second, third, suffix)

Basic/deiver.py
APP启动的前置条件,一个是普通的app,一个是微信公众号,配置微信公众号自动化测试和一般的APP是有点区别的,微信需要切换webview才能定位到公众号

from appium import webdriverdef init_driver():desired_caps = {}# 手机 系统信息desired_caps['platformName'] = 'Android'desired_caps['platformVersion'] = '9'# 设备号desired_caps['deviceName'] = 'emulator-5554'# 包名desired_caps['appPackage'] = ''# 启动名desired_caps['appActivity'] = ''desired_caps['automationName'] = 'Uiautomator2'# 允许输入中文desired_caps['unicodeKeyboard'] = Truedesired_caps['resetKeyboard'] = Truedesired_caps['autoGrantPermissions'] = Truedesired_caps['noReset'] = False# 手机驱动对象driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)return driverdef driver_weixin():desired_caps = {}# 手机 系统信息desired_caps['platformName'] = 'Android'desired_caps['platformVersion'] = '9'# 设备号desired_caps['deviceName'] = ''# 包名desired_caps['appPackage'] = 'com.tencent.mm'# 启动名desired_caps['appActivity'] = '.ui.LauncherUI'# desired_caps['automationName'] = 'Uiautomator2'# 允许输入中文desired_caps['unicodeKeyboard'] = Truedesired_caps['resetKeyboard'] = Truedesired_caps['noReset'] = True# desired_caps["newCommandTimeout"] = 30# desired_caps['fullReset'] = 'false'# desired_caps['newCommandTimeout'] = 10# desired_caps['recreateChromeDriverSessions'] = Truedesired_caps['chromeOptions'] = {'androidProcess': 'com.tencent.mm:tools'}# 手机驱动对象driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)return driver

Basic/get_data.py

这是获取测试数据的方法

import os
import yamldef getData(funcname, file):PATH = os.getcwd() + os.sepwith open(PATH + 'Data/' + file + '.yaml', 'r', encoding="utf8") as f:data = yaml.load(f, Loader=yaml.FullLoader)# 1 先将我们获取到的所有数据都存放在一个变量当中tmpdata = data[funcname]# 2 所以此时我们需要使用循环走进它的内心。res_arr = list()for value in tmpdata.values():tmp_arr = list()for j in value.values():tmp_arr.append(j)res_arr.append(tmp_arr)return res_arr

Basic/Log.py

日志文件,不多介绍

# -*- coding: utf-8 -*-"""
封装log方法"""import logging
import os
import timeLEVELS = {'debug': logging.DEBUG,'info': logging.INFO,'warning': logging.WARNING,'error': logging.ERROR,'critical': logging.CRITICAL
}logger = logging.getLogger()
level = 'default'def create_file(filename):path = filename[0:filename.rfind('/')]if not os.path.isdir(path):os.makedirs(path)if not os.path.isfile(filename):fd = open(filename, mode='w', encoding='utf-8')fd.close()else:passdef set_handler(levels):if levels == 'error':logger.addHandler(MyLog.err_handler)logger.addHandler(MyLog.handler)def remove_handler(levels):if levels == 'error':logger.removeHandler(MyLog.err_handler)logger.removeHandler(MyLog.handler)def get_current_time():return time.strftime(MyLog.date, time.localtime(time.time()))class MyLog:path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))log_file = path+'/Log/log.log'err_file = path+'/Log/err.log'logger.setLevel(LEVELS.get(level, logging.NOTSET))create_file(log_file)create_file(err_file)date = '%Y-%m-%d %H:%M:%S'handler = logging.FileHandler(log_file, encoding='utf-8')err_handler = logging.FileHandler(err_file, encoding='utf-8')@staticmethoddef debug(log_meg):set_handler('debug')logger.debug("[DEBUG " + get_current_time() + "]" + log_meg)remove_handler('debug')@staticmethoddef info(log_meg):set_handler('info')logger.info("[INFO " + get_current_time() + "]" + log_meg)remove_handler('info')@staticmethoddef warning(log_meg):set_handler('warning')logger.warning("[WARNING " + get_current_time() + "]" + log_meg)remove_handler('warning')@staticmethoddef error(log_meg):set_handler('error')logger.error("[ERROR " + get_current_time() + "]" + log_meg)remove_handler('error')@staticmethoddef critical(log_meg):set_handler('critical')logger.error("[CRITICAL " + get_current_time() + "]" + log_meg)remove_handler('critical')if __name__ == "__main__":MyLog.debug("This is debug message")MyLog.info("This is info message")MyLog.warning("This is warning message")MyLog.error("This is error")MyLog.critical("This is critical message")

Basic/Shell.py

执行shell语句方法

# -*- coding: utf-8 -*-
# @Time    : 2018/8/1 下午2:54
# @Author  : WangJuan
# @File    : Shell.py"""
封装执行shell语句方法"""import subprocessclass Shell:@staticmethoddef invoke(cmd):output, errors = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()o = output.decode("utf-8")return o

Page/page.py

class Page:def __init__(self, driver):self.driver = driver@propertydef initloginpage(self):return Login_Page(self.driver)

Test/test_login.py

登陆的测试用,我贴一条使用数据文件的用例

class Test_login:@pytest.mark.parametrize("args", getData("test_login_error", 'data_error_login'))def test_error_login(self, args):"""错误登陆"""self.page.initloginpage.input_user(args[0])self.page.initloginpage.input_pwd(args[1])self.page.initloginpage.click_login()toast_status = self.page.initloginpage.is_toast_exist(args[2])if toast_status == False:self.page.initpatientpage.take_screenShot()assert False

pytest.ini

pytest配置文件,注释的是启动失败重试3次,因为appium会因为一些不可控的原因失败,所有正式运行脚本的时候需要加上这个

[pytest]
;addopts = -s --html=report/report.html --reruns 3
addopts = -s --html=report/report.html
testpaths = ./Test
python_files = test_*.py
python_classes = Test*
python_functions = test_add_prescription_listrequirements.txt
框架中需要的患教,直接pip install -r requirements.txt 安装就可以了,可能会失败,多试几次```python
adbutils==0.3.4
allure-pytest==2.7.0
allure-python-commons==2.7.0
Appium-Python-Client==0.46
atomicwrites==1.3.0
attrs==19.1.0
certifi==2019.6.16
chardet==3.0.4
colorama==0.4.1
coverage==4.5.3
decorator==4.4.0
deprecation==2.0.6
docopt==0.6.2
enum34==1.1.6
facebook-wda==0.3.4
fire==0.1.3
humanize==0.5.1
idna==2.8
importlib-metadata==0.18
logzero==1.5.0
lxml==4.3.4
more-itertools==7.1.0
namedlist==1.7
packaging==19.0
Pillow==6.1.0
pluggy==0.12.0
progress==1.5
py==1.8.0
PyMySQL==0.9.3
pyparsing==2.4.0
pytest==5.0.0
pytest-cov==2.7.1
pytest-html==1.21.1
pytest-metadata==1.8.0
pytest-repeat==0.8.0
pytest-rerunfailures==7.0
PyYAML==5.1.1
requests==2.22.0
retry==0.9.2
selenium==3.141.0
six==1.12.0
tornado==6.0.3
uiautomator2==0.3.3
urllib3==1.25.3
wcwidth==0.1.7
weditor==0.2.3
whichcraft==0.6.0
zipp==0.5.1

到此这篇关于Appium+Python+pytest自动化测试框架的实战的文章就介绍到这了

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/169358.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux-基本指令(1.0)

Linux是一个非常流行的操作的知识,并提供实例帮助读者更好地理解。让我们一起来学习吧!系统,也是云计算、大数据、人工智能等领域的重要基础。学习Linux命令是Linux系统管理的基础,也是开发过程中必不可少的技能。本博客将介绍Lin…

212. 单词搜索 II

212. 单词搜索 II Java&#xff1a;搜索全部可能&#xff0c;超出时间限制&#xff01; class Solution {StringBuilder sb;List<String> list;Set<String> set;private void dfs(int x, int y, int m, int n, char[][] board){if (x < 0 || x > m || y <…

leetcode中“辅助栈”类题目和“单调栈”类题目的异同

1 总结 1 栈中元素的特性 2 单调栈存在一次性连续删除多个栈顶的情况&#xff0c;但是普通的栈&#xff0c;一次只pop掉一个栈顶元素 2 LC1209. 删除字符串中的所有相邻重复项 II - 普通辅助栈 class Solution {public String removeDuplicates(String s, int k) {int ns.l…

华为ospf路由协议防环和次优路径中一些难点问题分析

第一种情况是ar3的/0/0/2口和ar4的0/0/2口发布在区域1时&#xff0c;当ar1连接ar2的线断了以后&#xff0c;骨干区域就断了&#xff0c;1.1.1.1到2.2.2.2就断了&#xff0c;ping不通了。但ar5和ar6可以ping通2.2.2.2和1.1.1.1&#xff0c;ar3和ar4不可以ping通2.2.2.2和1.1.1.1…

软件测试 | MySQL 主键自增详解:实现高效标识与数据管理

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

The Bridge:从临床数据到临床应用(预测模型总结)

The Bridge:从临床数据到临床应用&#xff08;预测模型总结&#xff09; 如果说把临床预测模型比作临床数据和临床应用之间的一座“桥梁”&#xff0c;那它应该包括这样几个环节&#xff1a;模型的构建和评价、模型的概率矫正、模型决策阈值的确定和模型的局部再评价。 模型的构…

数据库实验7

实验报告&#xff08;七&#xff09;数据更新 1、实验目的 &#xff08;1&#xff09; 掌握插入、更新和删除表数据的方法 &#xff08;2&#xff09; 掌握更新操作与子查询结合的用法 2、实验预习与准备 &#xff08;1&#xff09; Update&#xff0c;Delete&am…

【LeetCode】每日一题 2023_11_24 统计和小于目标的下标对数目(暴力/双指针)

文章目录 刷题前唠嗑题目&#xff1a;统计和小于目标的下标对数目题目描述代码与解题思路 结语 刷题前唠嗑 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 题目&#xff1a;统计和小于目标的下标对数目 题目链接&#xff1a;2824. 统计和小于目标的下标对数…

C/C++ 运用Npcap发送UDP数据包

Npcap 是一个功能强大的开源网络抓包库&#xff0c;它是 WinPcap 的一个分支&#xff0c;并提供了一些增强和改进。特别适用于在 Windows 环境下进行网络流量捕获和分析。除了支持通常的网络抓包功能外&#xff0c;Npcap 还提供了对数据包的拼合与构造&#xff0c;使其成为实现…

【迅搜03】全文检索、文档、倒排索引与分词

全文检索、文档、倒排索引与分词 今天还是概念性的内容&#xff0c;但是这些概念却是整个搜索引擎中最重要的概念。可以说&#xff0c;所有的搜索引擎就是实现了类似的概念才能称之为搜索引擎。而且今天的内容其实都是相关联的&#xff0c;所以不要以为标题上有四个名词就感觉好…

【探索Linux】—— 强大的命令行工具 P.16(进程信号 —— 信号产生 | 信号发送 | 核心转储)

阅读导航 引言一、概念&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;kill -l命令&#xff08;察看系统定义的信号列表&#xff09; 二、产生信号&#xff08;1&#xff09;通过终端按键产生信号-- 信号产生-- Core Dump&#xff08;核心转储&#xff09; &#…

ZC-OFDM模糊函数原理及仿真

文章目录 前言一、ZC 序列二、ZC-OFDM 信号1、OFDM 信号表达式2、模糊函数表达式三、MATLAB 仿真1、MATLAB 核心源码2、仿真结果①、ZC-OFDM 模糊函数②、ZC-OFDM 距离分辨率③、ZC-OFDM 速度分辨率前言 本文进行 ZC-OFDM 的原理讲解及仿真,首先看一下 ZC-OFDM 的模糊函数仿真…

【持续更新】汇总了一份前端领域必看面试题

文章目录 1. 写在前面2. 前端面试汇总2.0.1. 如何提⾼webpack的打包速度2.0.2. 数组去重2.0.3. 前端有几种缓存方式&#xff1f;2.0.4. nextTick描述一下&#xff1f;2.0.5. Webpack层面的优化&#xff1f;2.0.6. 代码层面的优化&#xff1f;2.0.7. Web 技术的优化&#xff1f;…

【C++】PACS医学图像存储和传输系统源码带三维重建

PACS&#xff08;Picture Archiving and Communication System&#xff09;系统作为医学图像的存储和传输平台&#xff0c;为医生和患者提供了便捷高效的诊疗服务支持。近年来&#xff0c;三维重建技术在PACS系统中的应用越来越广泛。 三维后处理功能是临床数字技术中的重要组成…

【Linux】Linux权限管理

目录 一、Linux中权限的概念 二、 Linux下的用户 2.1 用户的类型 2.2 用户创建、切换和删除 2.2.1 useradd或adduser命令创建用户 2.2.2 passwd命令设置用户密码 2.2.3 userdel命令删除用户 2.2.4 su命令切换用户身份等来管理和操作用户 2.3 注意事项 三、权限的管理…

浅析基于物联网的远程抄表系统的设计及应用

安科瑞 华楠 摘 要&#xff1a;本文基于物联网的概念&#xff0c;使用 ZigBee、通用分组无线服务技术两种无线通信技术相结合的方式实现远程抄表并对数据进行存储和管理。此系统设计主要分为硬件方面的设计和软件方面的设计&#xff0c;硬件方面的设计需要完成三个部分的硬件制…

redis运维(二十)redis 的扩展应用 lua(二)

一 redis 的扩展应用 lua redis lua脚本语法 ① 什么是脚本缓存 redis 缓存lua脚本 说明&#xff1a; 重启redis,脚本缓存会丢失 下面讲解 SCRIPT ... 系列 SCRIPT ② LOAD 语法&#xff1a;SCRIPT LOAD lua代码 -->载入一个脚本,只是预加载,不执行思考1&#xff1…

Vue解析器

解析器本质上是一个状态机。但我们也曾提到&#xff0c;正则表达式其实也是一个状态机。因此在编写 parser 的时候&#xff0c;利用正则表达式能够让我们少写不少代码。本章我们将更多地利用正则表达式来实现 HTML 解析器。另外&#xff0c;一个完善的 HTML 解析器远比想象的要…

PyQt基础_004_ 按钮类控件QPushButton以及自定义按钮控件

Qpushbutton基本操作 1.热键 2.按钮加图标 3.按钮事件等 import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import *class Form(QDialog):def __init__(self, parentNone):super(Form, self).__init__(parent)layout QVBoxLayout()se…

Android 打包aar包含第三方aar 解决方案

Android 打包aar包含第三方aar 因项目需要&#xff0c;打包aar包含第三方aar&#xff0c;如果直接对module进行打包会产生一些问题。 * What went wrong: Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR would be broken be…