App自动化测试框架appium+python+appnium_python_client及实现
“原创,并未实现数据驱动,所以此框架仅供参考,前提需要查看者熟悉unittest、selenium的使用方法,和良好的python代码编程技术”
实现材料:python、appium服务器、selenium、夜神模拟器、哔哩哔哩手机客户端
目标:通过自动化脚本实现哔哩哔哩app端搜索肯德基,进入视频,并判断是否进入成功
1.Appium配置
运行appium需要sdk和jdk的支持,安装方法网上有很多,在此不赘述
系统变量ANDROID_HOME:sdk所在的目录
系统变量JAVA_HOME:jdk所在目录
(路径仅供参考,需要联合实际目录)

点击startserver启动服务,默认开在本机的4723端口(127.0.0.1:4723)
可以通过访问http://127.0.0.1:4723/wd/hub验证服务是否开启成功 (此为成功开启)

为方便调试,可以把adb的所在目录添加进系统变量,方便命令提示符的调用

启动模拟器,输入adb命令查看是否连接成功

2.通过POM设计自动化测试脚本
所谓POM可称为页面对象模式(page object model),主旨是将动作、数据、程序相分离
动作和程序的关系相当于:动作是跑、跳、看、卧倒等基础动作,但你如果指挥一个人去卧室躺在床上,光靠单个动作可完成不了,须要由动作组成的程序来完成
所以需要计划创建三个python包裹,分别是页面基类包、动作程序包、测试用例包
首先创建页面基类包,并在其中创建py文件appbasic

这个包里面包括对象实例、基本动作
代码主体:
#系统包
import os
#appium模块
from appium import webdriver
from appium.webdriver.common.appiumby import By
#selenium模块,用于元素判断和元素等待
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
#时间包,用于等待
import time
'''
页面基类driver和一些常用的动作方法
'''
#定义一个driver类
class driver:
# 初始化驱动
def __init__(self, device, msg):
self.caps = msg
#appium对象驱动是依靠webdriver.Remote()方法建造的,需要传入appium服务地址和连接信息
self.dv = webdriver.Remote(device, self.caps)
# 等待某个元素
def element_wait(self, ele):
#这里用try是防止人为误判,如果没有该元素会报错,所以保底让他停留1秒
try:
'''
WebDriverWait方法需要传入驱动对象和最大等待时间,
EC(这里是EC因为在导包的时候使用了EC作为别名,所以直接引用),
EC.presence_of_element_located((By.XPATH, f'{ele}')此方法是判断该元素是否存在
,结合until方法会持续判断,判断到10秒钟结束。如果存在则提前结束等待
'''
WebDriverWait(self.dv, 10).until(EC.presence_of_element_located((By.XPATH, f'{ele}')))
except:
#如果没有这个元素则强制等待1秒
time.sleep(1)
# 点击某个元素
def element_click(self, ele):
#通过提前等待,让点击动作更智能,降低报错率,因为有时候程序执行会快过页面加载
self.element_wait(ele)
#元素点击的动作
self.dv.find_element(By.XPATH, f"{ele}").click()
# 往指定元素输入值
def element_sendkey(self, ele, value):
self.element_wait(ele)
#元素输入值的动作
self.dv.find_element(By.XPATH, f"{ele}").send_keys(value)
# 按键事件,需输入按键对应值
def element_event(self, num):
'''通过调用命令提示符(cmd)调用adb命令,来执行手机的按键操作
(任务栏、home键等,是用数字代替的,具体动作对应的数字可以百度查询)'''
os.system(f"adb shell input keyevent {num}")
# 获得所有指定元素内的值
def element_gettexts(self, ele):
self.element_wait(ele)
#通过一个循环将符合该元素特征的元素值添加到一个列表里,并返回该列表
text = []
for i in self.dv.find_elements(By.XPATH, f'{ele}'):
text.append(i.text)
return text
# 输出页面源码
def backpagesource(self):
#self.dv.page_source方法用于查看页面源代码
return self.dv.page_source
# 通过元素的text属性值判断元素是否存在
def is_element_exist(self, ele):
#这里用上面定义的等待方法初筛,没等到则返回FALSE
try:
self.element_wait(f"//*[@text='{ele}']")
except:
return False
#二次筛选,判断该元素的text属性是否存在于源码中
if ele in self.dv.page_source:
return True
else:
return False
#关闭程序
def close_package(self,packagename):
try:
self.dv.terminate_app(packagename)
except:
pass
第二步创建页面程序包,包括具体的动作,并创建py文件findKFC_bili

代码主体:
#导入页面基类模块和driver基类
from base.appbasic import driver
import time
'''
页面动作类,将基类方法组成动作程序
'''
#定义action类,并继承driver基类
class action(driver):
# 往输入框输入值
#别担心为什么没有传入驱动,因为action继承了driver基类,可以通过实例action类来创建驱动
def Search_Word(self, str):
#点击按钮的元素属性
clickele = "//*[@resource-id='tv.danmaku.bili:id/expand_search']"
#等待元素的属性
waitele = "//*[@content-desc='搜索查询']"
#查找框的属性
searchele = "//*[@content-desc='搜索查询']"
#视频标题的元素
videonameele = "//*[@class='android.widget.TextView'][@resource-id='tv.danmaku.bili:id/title']"
#将元素属性传入动作方法中
self.element_wait(clickele)
self.element_click(clickele)
self.element_sendkey(searchele, str)
self.element_wait(waitele)
self.element_event("KEYCODE_ENTER")
self.element_wait(videonameele)
#查询动作结束
# 进入到第X个视频里,执行该方法的前提是搜索动作执行完毕且无误
def Into_Video(self, ele, str):
self.element_wait(ele)
#获得视频标题列表
self.midbox = self.element_gettexts(ele)
#点击第X个视频
self.element_click(f"//*[@text='{self.midbox[str]}']")
#返回视频列表用于断言
return self.midbox
# 关闭弹窗
#通过输入弹窗标题关闭
def alertclose(self, txt):
time.sleep(1.5)
#backpagesource()方法用于返回页面源码
source = self.backpagesource()
#如果弹窗元素存在于源码中,按两次返回键
if txt in source:
self.element_event(4)
self.element_event(4)
第三步创建测试用例包,并创建py文件testcas

代码主体:
#导入unittest包
import unittest
#导入页面动作包和action类
from page.findKFC_bili import action
#省略警告信息的包
import warnings
# 测试用例类,继承unittest.TestCase类
class bilicase(unittest.TestCase):
#每一个测试用例开始前都会调用该方法,所以将驱动信息放于此,并实例驱动
def setUp(self):
#忽略警告信息
warnings.simplefilter("ignore", ResourceWarning)
warnings.simplefilter("ignore", DeprecationWarning)
#驱动信息
self.caps = {}
#模拟器的地址和端口,通过cmd命令adb devices可查看
self.caps["deviceName"] = "127.0.0.1:62001"
#模拟器的型号
self.caps["platformName"] = "Android"
#模拟器版本,通过手机设置查看
self.caps["platformVersion"] = "7.1"
#运行程序的包名,桌面程序的程序包名,通过点击桌面上的哔哩哔哩app来进入软件
#直接用unittest打开哔哩哔哩是一个初始化的程序,需要等待很久很久
#所以直接通过桌面点击的方法来跳过初始化
self.caps["appPackage"] = "com.android.launcher3"
#活动窗口名,运行程序包的哪一个窗口
self.caps["appActivity"] = ".launcher3.Launcher"
#appium服务器的地址
appiumlocate = "http://127.0.0.1:4723/wd/hub"
#实例驱动对象
self.obj = action(appiumlocate, self.caps)
#每次测试结束都会运行该方法
def tearDown(self):
#关闭哔哩哔哩app
self.obj.close_package('tv.danmaku.bili')
#测试方法,以test_开头
def test_findcase(self):
#点击桌面的哔哩哔哩客户端
self.obj.element_click("//*[@text='哔哩哔哩']")
#关闭登录弹窗
self.obj.alertclose('登录注册解锁更多精彩内容')
#查询肯德基的信息
self.obj.Search_Word("肯德基")
#进入第X个视频
num = 0
#通过调用Into_Video方法,既进入了视频,又返回了一个视频列表
box = self.obj.Into_Video(
"//*[@resource-id='tv.danmaku.bili:id/title'][@class='android.widget.TextView'][@index='1']", num)
#通过is_element_exist()方法判断元素是否存在,并断言
self.assertEqual(True, self.obj.is_element_exist(box[num]))
3.通过一个控制面板生成测试报告
代码体
#导入html测试报告包,此包为第三方作者所编写
#下载请百度
import HTMLTestRunner
import unittest
#导入用例
from cases.testcas import bilicase
#创建一个加载器,并通过loadTestsFromTestCase()方法载入测试类bilicase
suite = unittest.TestLoader().loadTestsFromTestCase(bilicase)
#创建文件流,报告将存放在桌面
fp = open(r'C:\Users\Administrator\Desktop\baogao.html', 'wb')
#创建执行器,标题为:哔哩哔哩搜索功能测试报告
runner = HTMLTestRunner.HTMLTestRunner(fp,title='哔哩哔哩搜索功能测试报告')
#执行加载器
runner.run(suite)
执行过程

HTML报告:
