Appium+Python+pytest自动化测试框架

news2025/1/19 2:56:15

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

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

Basic/base.py

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

# coding=utf-8import 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 os

log = 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_path

else:

print("请按规则使用")

return res_path

def assert_ele_in(self, text, element):

'''

封装断言操作函数

'''try:

assert text in self.find_element(element).text

assert 0

except Exception:

assert 1

def 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')]" % message

ele = 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 True

except Exception:

# 如果目标 toast 不存在那么就说明我们的实际结果和预期结果不一样# 因此我们想要的是断言失败return False

def get_mysql(self, table, value):

'''连接数据库'''# 打开数据库连接

db = pymysql.connect(host='', port=, db=, user='', passwd='', charset='utf8')

# 使用 cursor() 方法创建一个游标对象 cursor

cursor = 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 data

def get_xpath(self, value):

'''封装获取xpath方法'''

text = By.XPATH, '//*[@text="%s"]' % value

return 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.9

start_y = self.get_device_size()[1] * 0.5

end_x = self.get_device_size()[0] * 0.4

end_y = self.get_device_size()[1] * 0.5

self.driver.swipe(start_x, start_y, end_x, end_y)

# 自定义一个功能,可以实现向上滑屏操作。def swipe_up(self):

start_x = self.get_device_size()[0] * 1/2

start_y = self.get_device_size()[1] * 1/2

end_x = self.get_device_size()[0] * 1/2

end_y = self.get_device_size()[1] * 1/7

self.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,Y

X = 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 notin [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 webdriver

def 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'] = True

desired_caps['resetKeyboard'] = True

desired_caps['autoGrantPermissions'] = True

desired_caps['noReset'] = False

# 手机驱动对象

driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)

return driver

def 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'] = True

desired_caps['resetKeyboard'] = True

desired_caps['noReset'] = True

# desired_caps["newCommandTimeout"] = 30# desired_caps['fullReset'] = 'false'# desired_caps['newCommandTimeout'] = 10# desired_caps['recreateChromeDriverSessions'] = True

desired_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 yaml

def getData(funcname, file):

PATH = os.getcwd() + os.sep

with 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 time

LEVELS = {

'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('/')]

ifnot os.path.isdir(path):

os.makedirs(path)

ifnot 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')

@staticmethod

def debug(log_meg):

set_handler('debug')

logger.debug("[DEBUG " + get_current_time() + "]" + log_meg)

remove_handler('debug')

@staticmethod

def info(log_meg):

set_handler('info')

logger.info("[INFO " + get_current_time() + "]" + log_meg)

remove_handler('info')

@staticmethod

def warning(log_meg):

set_handler('warning')

logger.warning("[WARNING " + get_current_time() + "]" + log_meg)

remove_handler('warning')

@staticmethod

def error(log_meg):

set_handler('error')

logger.error("[ERROR " + get_current_time() + "]" + log_meg)

remove_handler('error')

@staticmethod

def 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 subprocess

class Shell:

@staticmethod

def 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

@property

def 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_list

requirements.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

总结:

现阶段很多人都在说软件测试太内卷了,工作太难找了,竞争太激烈了。那么如何在这样的现状下使得自己更具有竞争力呢?笔者认为大家需要迅速学习软件测试的硬技能,提升自己的业务能力,早日摆脱初中级测试的Title,虽然测试人员众多,但是高级的软件测试人员还是很稀缺,有很多人挂着高级测试的头衔却还在干着初中级测试的活。在掌握这些硬技能的同时,软技能的培养同样重要,沟通能力、自主学习能力越来越被企业看重。

如果不想被这个时代淘汰,就要做好持续学习的准备。下方给大家准备了全套的软件测试,自动化测试全套教程。

【需要的可以点击下方官方推广小卡片扫码备注000免费领取】

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

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

相关文章

Linux从入门到精通

Linux从入门到精通 1. Linux 简介 Linux 内核最初只是由芬兰人林纳斯托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。 Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX(可移植操作系统接…

Nacos 注册监听器

文章目录前言项目文件说明pom依赖bootstrap.ymlNacosConfig 配置类监听器实现类-默认实现监听器实现类-json配置处理注册监听器监听器的效果前言 本文主要讨论Nacos作为配置中心时,其中配置内容发生更改时,我们的应用程序能够做的事。 一般使用监听器来…

DeepLabV3+:Mobilenetv2的改进以及浅层特征和深层特征的融合

目录 Mobilenetv2的改进 浅层特征和深层特征的融合 完整代码 参考资料 Mobilenetv2的改进 在DeeplabV3当中,一般不会5次下采样,可选的有3次下采样和4次下采样。因为要进行五次下采样的话会损失较多的信息。 在这里mobilenetv2会从之前写好的模块中…

第四天链表

24. 两两交换链表中的节点力扣题目链接(opens new window)给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。接下来就是交换相邻两个元素了,此时一定要画…

vite --- 为什么选Vite

目录 什么是Vite 为什么选Vite 现实问题 为什么生产环境仍需打包 Vite 与竞品 什么是Vite Vite(法语意为 "快速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,能够显著提升前端开发体…

SpringBoot+Vue图书馆管理系统1.0

简介:本项目采用了基本的SpringBootVue设计的图书馆管理系统。详情请看截图。经测试,本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 项目描述 项目名称SpringBootVue图书馆管理系统1.0源码作者LHL项目类型Java EE项目 &#xff…

Zebec 上线投票治理机制,全新流支付生态正在起航

随着加密货币的兴起,其除了成为一种备受关注的投资品外,它也正在成为一种新兴的支付手段。虽然在加密行业发展早期,以BTC、LTC等为代表的以支付为定位老牌加密资产,因支付效率低下、支付成本高、合规等问题而没能实现早期的愿景&a…

Node.js:CommonJS模块化规范

CommonJS 上文提到了 Node 采用的模块化规范是 CommonJS,它主要规定了如何定义模块,如果导出模块和如何导入模块: 定义模块:一个文件就是一个模块导出模块:通过 module.exports 导出模块导入模块:通过 re…

【Linux】第八部分 Linux常用基本命令

【Linux】第八部分 Linux常用基本命令 文章目录【Linux】第八部分 Linux常用基本命令8. Linux常用基本命令8.1 帮助命令8.2 文件目录类命令pwd 显示当前工作目录的绝对路径cd 切换目录ls 列出目录的内容mkdir 创建目录rmdir 删除目录touch 创建文件cp 复制文件或者目录rm 删除文…

Kaggle系列之预测泰坦尼克号人员的幸存与死亡(随机森林模型)

Kaggle是开发商和数据科学家提供举办机器学习竞赛、托管数据库、编写和分享代码的平台,本节是对于初次接触的伙伴们一个快速了解和参与比赛的例子,快速熟悉这个平台。当然提交预测结果需要注册,这个可能需要科学上网了。我们选择一个预测的入…

【操作系统】4、设备管理

文章目录四、设备管理4.1 I/O设备基本概念4.2 I/O控制方式4.2.1 程序控制方式4.2.2 中断方式4.2.3 DMA控制方式4.2.4 通道控制方式4.3 缓冲技术4.4 假脱机技术四、设备管理 I/O控制方式:程序控制、中断、DMA、通道, 缓冲技术;假脱机技术(SPO…

大龄学长的浙大MBA提面优秀之路分享

作为今年上岸浙大MBA项目的一名中年老学长,想把自己在提面中取得优秀资格的经验做个梳理供大家参考,因为以我的经历来说,我认为浙大MBA提前批面试是非常有价值的,而且在提面过程中也发现了优秀资格其实遍布于各个年龄段和层级&…

2023-02-04 Elasticsearch环境安装

1 JDK-8的安装 查询资料自我安装即可,这里不做展示。 2 Elasticsearch 的安装 Elasticsearch目录结构: 配置文件: #节点名称,集群内要唯一 node.name: node-1001node.master: true node.data: true#ip 地址 network.host: localhost htt…

细讲TCP三次握手四次挥手(一)

计算机网络体系结构 在计算机网络的基本概念中,分层次的体系结构是最基本的。计算机网络体系结构的抽象概念较多,在学习时要多思考。这些概念对后面的学习很有帮助。 网络协议是什么? 在计算机网络要做到有条不紊地交换数据,就必…

lsof - list open file

lsof 指令全称 list open file,用官方的话说 Lsof revision 4.91 lists on its standard output file information about files opened by processes -i 平常工作中,用到最多的就是 -i 参数,后面跟端口号,可以查看和这个端口有关…

【嵌入式】MDK使用sct文件将代码段放入RAM中执行

sct文件即分散加载文件,是ARMCC编译器使用的链接脚本文件,等同于GCC编译器的ld链接脚本。MDK IDE使用的是ARMCC。 支持NorFlash中运行代码(XIP)的MCU例如STM32,一般将所有代码(text段)都放在FL…

[ 云计算 | AWS ] 亚马逊云科技核心服务之计算服务(Part1:AWS EC2 星巴克为什么横向排队)

(星爸爸网络上的一张图) 注意上图中的5个人,对没错这5个人。一般情况星巴克的人员配置大概是这样的: 1个经理,在办公室两个收银,在收银台(本文关注的重点)三个人做咖啡 当你去过星巴克买咖啡时&#xff0…

【NS2】tcl与c++互相调用/传参

在NS2,做实验的时候,为了能通过循环配合传值实验,一直找不到tcl传参给c的方法,网上的只po出一部分看不懂,只能通过源码自己研究。最后的解决办法就是,模仿源码的操作,以下通过tcl→ex→sat-irid…

Navicat Monitor 3.0 现已上市 | 欢迎下载试用

Navicat Monitor 3.0 现已上市Navicat Montior 3.0 现已发布!一经发布,受到广大专业运维人员的关注与选择! 五大新亮点带给运维团队最为实用且有效地提升监控能力。其具备 PostgreSQL 服务器监控能力、支持优化慢查询、构建自定义指标、性能分析工具优化…

flutter问题

问题一1.报错:Flutter ios/Flutter/Debug.xcconfig: unable to open file (in target "Runner" in project "Runner")2.解决:cd 项目目录flutter cleanflutter create --org solanddriver .运行Xcode问题二1.Cannot run with sound …