目录结构1.0
把设备连接单独移出去了
模块操作代码,有一些流程操作和断言方法
from devices import dv
from time import sleep
import random
from tool.jt import capture_screenshotdef initialization(func):def wrapper():sleep(1)dv.app_stop('com.visteon.txzing.accountcenter')dv.app_start('com.visteon.txzing.accountcenter')sleep(2)func()return wrapper@initialization
def f1():'''1、打开账号切换,点击其他的账号,点击确认登录,点击取消'''dv(text='账号切换').click()dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/change_account_recycler"]/android.view.ViewGroup[2]').click()dv(text='确定').click()dv(text='取消').click()@initialization
def f2():'''2、点击设置、点击退出登录、点击取消'''dv(text='设置').click()dv(text='退出登录').click()dv(text='取消').click()@initialization
def f3():'''3、点击设置、点击用户协议,点击返回,点击隐私政策,点击返回,再次点击返回到个人中心主页面'''dv(text='设置').click()dv(text='用户协议').click()sleep(1.5)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/top_bar_back"]').click()dv(text='隐私政策').click()sleep(1.5)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/top_bar_back"]').click()sleep(1.5)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/top_bar_back"]').click()@initialization
def f4():'''4、点击账号关联,点击高德地图绑定,点击确定'''dv(text='账号关联').click()try:dv.xpath('//*[@content-desc="绑定高德地图"]').click()except:dv.xpath('//*[@content-desc="绑定高德地图"]').click()dv(text='确定').click()def f5():'''5、进入问题反馈,点击开始录音,5s内结束录音,重新开始录音,切换问题类别从第一个切到最后一个,然后提交,最后播放语音'''dv(text='问题反馈').click()dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()sleep(4)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()sleep(1)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()for i in ['多媒体', '导航', '蓝牙电话', '维保类', '设置', '其他']:dv(text=i).click()sleep(1)# 录音秒数随机sleep(random.randint(1, 80))dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()dv(text='提交').click()sleep(2)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/mine_feedback_recyclerview"]/android.view.ViewGroup[1]/android.view.ViewGroup[1]').click()def run():while True:try:f1()f2()f3()f5()f4()except Exception as e:capture_screenshot(e)print(f'报错了重新运行{e}')# 通过获取文本来断言
b = '//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/mine_feedback_recyclerview"]/android.view.ViewGroup[1]/android.widget.TextView[1]'
a = dv.xpath(b).get_text()
print(a)# 通过判断元素是否存在来断言
c = dv.xpath('//*[@text="2023/11/30 15:49"]').exists
print(c)# 通过元素属性是否发生变化来进行断言
d = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_record_bar_time"]')
# 获取语音条初始状态的info信息
initial_info = d.info
d.click()
clicked_info = d.info
# 比较前后的info信息,找出差异数据
diff = {k: clicked_info[k] for k in clicked_info if clicked_info[k] != initial_info[k]}
print("前后的差异数据:", diff)
if len(diff) == 0:print('状态未发生变化')
else:print('断言成功')if __name__ == '__main__':while True:run()
目录结构1.1
新增test_accountcenter.py编写测试用例
新增run.py执行pytest
新增pytest.ini配置文件
test_accountcenter.py
删除了3个场景,留下2个场景,每个场景加了2种断言方式f1(ex):f5(ex):,测试用例是test_zhanghao():test_luyin():,f1(ex):f5(ex):内部实现了断言并且返回结果
from devices import dv
from time import sleep
import random
from tool.jt import capture_screenshot
def f1(ex):'''1、打开账号切换,点击其他的账号,点击确认登录,点击取消'''dv.app_stop('com.visteon.txzing.accountcenter')dv.app_start('com.visteon.txzing.accountcenter')dv(text='账号切换').click()dv.xpath('//*[@resource-id="com.visteon.txzing.accountcenter:id/change_account_recycler"]/android.view.ViewGroup[2]').click()dv(text='确定').click()text= dv(text='请在互联小程序确认登录').get_text()if not dv.xpath('//*[@resource-id="com.visteon.txzing.accountcenter:id/loading_cancel"]').exists:return Falseass =ex==textreturn assdef f5(ex):'''5、进入问题反馈,点击开始录音,5s内结束录音,重新开始录音,切换问题类别从第一个切到最后一个,然后提交,最后播放语音'''dv.app_stop('com.visteon.txzing.accountcenter')dv.app_start('com.xxxxxxx.xxxxxxx.accountcenter')dv(text='问题反馈').click()dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()sleep(4)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()sleep(1)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()lb=['多媒体', '导航', '蓝牙电话', '维保类', '其他', '设置']for i in lb:dv(text=i).click()sleep(1)# 录音秒数随机sleep(random.randint(5, 10))dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()dv(text='提交').click()ass= '设置'==ex'''元素信息变化'''d = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_record_bar_time"]')# 获取语音条初始状态的info信息initial_info = d.infod.click()clicked_info = d.info# 比较前后的info信息,找出差异数据diff = {k: clicked_info[k] for k in clicked_info if clicked_info[k] != initial_info[k]}print("前后的差异数据:", diff)if len(diff) == 0:return Falsesleep(2)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/mine_feedback_recyclerview"]/android.view.ViewGroup[1]/android.view.ViewGroup[1]').click()return assdef test_zhanghao():f1('请在互联小程序确认登录')assert Truedef test_luyin():assert f5('设置')run.py文件
运行文件,暂未配置参数
```python
import pytest
pytest.main([])
pytest.ini文件
配置了下报告的一些参数
[pytest]
addopts = -s --alluredir ./report/data
目录结构1.2
新增断言方法文件
assertion.py文件
首先dw()判断元素是否存在,不存在不需要进行后续操作,element_existence方法适用于断言元素是否存在,element_info_existence方式适用于判断元素在点击后是否发生了属性变化来断言,element_text_existence适用于判断元素文本和预期结果是否一致
from time import sleep
def dw(dv, element, positioning_method):''':param dv: 设备:param element: 定位元素:param positioning_method: 定位方式:return: 判定元素是否存在,存在则返回元素对象用于后续操作'''if positioning_method == 'xpath':if not dv.xpath(element).exists:print('断言元素不存在,直接判定断言失败')return Falsereturn dv.xpath(element)if positioning_method == 'text':if not dv(text=element).exists:print('断言元素不存在,直接判定断言失败')return Falsereturn dv(text=element)def element_existence(dv, element, positioning_method):''':param dv: 设备:param element: 定位元素:param positioning_method: 定位方式:return:通过元素是否存在来断言'''element_ = dw(dv, element, positioning_method)if not element_:return Falsereturn Truedef element_info_existence(dv, element, positioning_method):''':param dv: 设备:param element: 定位元素:param positioning_method: 定位方式:return:通过元素属性是否发生变化来断言'''if not dw(dv, element, positioning_method):return Falseelse:el = dw(dv, element, positioning_method)initial_info = el.infoel.click()sleep(1)clicked_info = el.infodiff = {k: clicked_info[k] for k in clicked_info if clicked_info[k] != initial_info[k]}print(f'元素差异字段{diff}')if len(diff) == 0:print('元素属性未发生变化,断言失败')return Falsereturn Truedef element_text_existence(dv, element, positioning_method, text):''':param dv: 设备:param element: 定位元素:param positioning_method: 定位方式:param text: 预期结果文本:return: 通过文本是否相等来断言'''if not dw(dv, element, positioning_method):return Falseelse:el = dw(dv, element, positioning_method)el_text=el.get_text()if not text ==el_text :print(f'文本不匹配预期结果{text},实际结果{el_text}')return Falseelse:return True
目录结构1.3
新增case文件夹
case-fun 以每个模块一个文件,里面包含多个场景操作,和场景断言
case-test_case 以每个模块一个文件,和fun文件夹文件一一对应,负责执行模块的每个场景
accountcenter.py文件
import random
from time import sleep
from tool.assertion import element_existence, element_info_existence, element_text_existencedef f1(dv):'''1、打开账号切换,点击其他的账号,点击确认登录'''dv.app_stop('com.visteon.txzing.accountcenter')dv.app_start('com.visteon.txzing.accountcenter')dv(text='账号切换').click()dv.xpath('//*[@resource-id="com.visteon.txzing.accountcenter:id/change_account_recycler"]/android.view.ViewGroup[2]').click()dv(text='确定').click()'''断言'''return element_existence(dv, '取消', 'text')def f2(dv):dv.app_stop('com.xxxxxxx.xxxxxxx.accountcenter')dv.app_start('com.xxxxxxx.xxxxxxx.accountcenter')'''2、点击设置、点击退出登录、点击取消'''dv(text='设置').click()dv(text='退出登录').click()dv(text='取消').click()return element_text_existence(dv, '退出登录', 'text', '退出登录')def f5(dv):'''5、进入问题反馈,点击开始录音,5s内结束录音,重新开始录音,切换问题类别从第一个切到最后一个,然后提交,最后播放语音'''dv.app_stop('com.xxxxxxx.xxxxxxx.accountcenter')dv.app_start('com.xxxxxxx.xxxxxxx.accountcenter')dv(text='问题反馈').click()dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()sleep(2)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()sleep(1)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()lb = ['多媒体', '导航', '蓝牙电话', '维保类', '其他', '设置']for i in lb:dv(text=i).click()sleep(1)# 录音秒数随机sleep(random.randint(5, 10))dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_start_record"]').click()dv(text='提交').click()sleep(2)dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_record_bar_time"]').click() # 点击语音条'''断言'''return element_info_existence(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.accountcenter:id/feedback_record_bar_time"]','xpath')
test_accountcenter.py文件
from case.fun.accountcenter import f1, f2, f5
from devices import dv
def test_f1():assert f1(dv)
def test_f2():assert f2(dv)
def test_f5():assert f5(dv)
familytime.py
import random
from time import sleep
from devices import dv
from tool.assertion import element_existence, element_info_existence, element_text_existence'''这些是通过观察元素属性观察得出的结论,这些是通用值'''def tu(number):'''家人时光每个图片定位'''tu = dv.xpath(f'//*[@resource-id="com.visteon.txzing.familytime:id/recycler_view"]/android.view.ViewGroup[{number}]/android.widget.ImageView[1]')return tu# 设为按钮
set_as = dv(text='设为')
# 设置壁纸
set_wallpaper = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_wallpaper"]')
# 设置屏保
set_screensaver = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_screensaver"]')
# 设为按钮
confirm = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/btn_normal_allow"]')
# 取消按钮
cancel = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/btn_normal_cancel"]')def set_s_w():dv.app_stop('com.xxxxxxx.xxxxxxx.familytime')dv.app_start('com.xxxxxxx.xxxxxxx.familytime')tu(1).click()set_as.click()set_wallpaper.click()set_screensaver.click()confirm.click()# 多个断言处理ass_1 = element_text_existence(dv, '//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/tv_message"]', 'xpath','壁纸、屏保设置成功')ass_2 = element_text_existence(dv, '//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/tv_label"]', 'xpath','壁纸 屏保')ass_list = [ass_1, ass_2]for i in ass_list:if not i:print(f'断言{i}失败')return Falsereturn Truedef cancel_s_w():dv.app_stop('com.xxxxxxx.xxxxxxx.familytime')dv.app_start('com.xxxxxxx.xxxxxxx.familytime')tu(1).click()set_as.click()set_wallpaper.click()set_screensaver.click()confirm.click()return element_text_existence(dv, '//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/tv_message"]', 'xpath','壁纸、屏保取消成功')
test_familytime.py
from case.fun.familytime import set_s_w, cancel_s_w
from devices import dv
from time import sleep
def test_set_s_w():set_s_w()
def test_cancel_s_w():cancel_s_w()
目录结构1.4
新增dw.py文件,增加元素定位封装,定位失败截图
新增conftest.py,配合pytest.fixture实现前后置
dw.py
from time import sleep
from lxml import etree
from tool.screenshot import capture_screenshot
class DwFail(Exception):pass
def jt(case_name, positioning_instructions):print('定位元素不存在,进行截图')info = f'用例标题{case_name},定位元素说明:{positioning_instructions}'capture_screenshot(info)
def find_elements(dv, element, positioning_method, case_name, positioning_instructions):''':param dv: 设备对象:param element:元素定位数据:param positioning_method:元素定位方式:param case_name:测试用例名称:param positioning_instructions: 这个元素测试的对象说明:return:'''sleep(1.5)if positioning_method == 'xpath':try:if dv.xpath(element).exists:print('定位成功')return dv.xpath(element)else:jt(case_name, positioning_instructions)raise DwFail(f'定位失败,用例标题{case_name},定位元素说明:{positioning_instructions}')except etree.XPathEvalError:jt(case_name, positioning_instructions)raise DwFail(f'xpath路径错误定位,用例标题{case_name},定位元素说明:{positioning_instructions}')elif positioning_method == 'text':if dv(text=element).exists:return dv(text=element)else:jt(case_name, positioning_instructions)raise DwFail(f'定位失败,用例标题{case_name},定位元素说明:{positioning_instructions}')else:print('暂时只支持xpath,text定位')if __name__ == '__main__':from devices import dv# find_elements(dv, '.ViewGroup[2]', 'xpath', 'xpath', '点击设12222211111111置')find_elements(dv, '//*[@resource-id="com.visteon.txzing.accountcenter:id/feedback_record_bar_time', 'xpath','进行录音问题反馈', '点击语音条').click()# find_elements(dv, '//*[@resource-id="com.visteon.txzing.accountcenter:id/mine_feedback_recyclerview"]/android.view.ViewGroup[1]/android.view.ViewGroup[1]', 'xpath',# '进行录音问题反馈', '点击语音条').click()
dw.py文件find_elements方法应用
import random
from time import sleep
from devices import dv
from tool.assertion import element_existence, element_info_existence, element_text_existence
from tool.dw import find_elements'''这些是通过观察元素属性观察得出的结论,这些是通用值'''
def tu(number):'''家人时光每个图片定位'''tu = dv.xpath(f'//*[@resource-id="com.visteon.txzing.familytime:id/recycler_view"]/android.view.ViewGroup[{number}]/android.widget.ImageView[1]')return tu# 设为按钮
set_as = dv(text='设为')
# 设置壁纸
set_wallpaper = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_wallpaper"]')
# 设置屏保
set_screensaver = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_screensaver"]')
# 设为按钮
confirm = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/btn_normal_allow"]')
# 取消按钮
cancel = dv.xpath('//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/btn_normal_cancel"]')
def set_s_w():dv.app_stop('com.xxxxxxx.xxxxxxx.familytime')dv.app_start('com.xxxxxxx.xxxxxxx.familytime')find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/recycler_view"]/android.view.ViewGroup[1]/android.widget.ImageView[1]','xpath', '照片设置为屏保壁纸', '点击照片进入详情').click()find_elements(dv, '设为','text', '照片设置为屏保壁纸', '点击设为按钮').click()find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_wallpaper"]','xpath', '照片设置为屏保壁纸', '勾选壁纸').click()find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_screensaver"]','xpath', '照片设置为屏保壁纸', '勾选屏保').click()find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/btn_normal_allow"]','xpath', '照片设置为屏保壁纸', '进行设置').click()# 多个断言处理ass_1 = element_text_existence(dv, '//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/tv_message"]', 'xpath','壁纸、屏保设置成功','照片设置为屏保壁纸断言1')ass_2 = element_text_existence(dv, '//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/tv_label"]', 'xpath','壁纸 屏保','照片设置为屏保壁纸断言2')ass_list = [ass_1, ass_2]for i in ass_list:if not i:print(f'断言{i}失败')return Falsereturn Truedef cancel_s_w():dv.app_stop('com.xxxxxxx.xxxxxxx.familytime')dv.app_start('com.xxxxxxx.xxxxxxx.familytime')find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/recycler_view"]/android.view.ViewGroup[1]/android.widget.ImageView[1]','xpath', '照片取消为屏保壁纸', '点击照片进入详情').click()find_elements(dv, '设为', 'text', '照片取消为屏保壁纸', '点击设为按钮').click()find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_wallpaper"]','xpath', '照片取消为屏保壁纸', '取消勾选壁纸').click()find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/check_screensaver"]','xpath', '照片取消为屏保壁纸', '取消勾选屏保').click()find_elements(dv,'//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/btn_normal_allow"]','xpath', '照片取消为屏保壁纸', '进行设置').click()return element_text_existence(dv, '//*[@resource-id="com.xxxxxxx.xxxxxxx.familytime:id/tv_message"]', 'xpath','壁纸、屏保取消成功','照片取消为屏保壁纸')
conftest.py文件
import pytest
from uiautomator2 import UiObjectNotFoundError
from tool.screenshot import capture_screenshot
@pytest.fixture(scope='function',autouse=True)
#autouse=True这将使该 fixture 在每个测试用例中自动执行,而无需显式地添加装饰器或参数
def zx(request):print('开始')print(f'用例信息{request}')yieldprint('结束')# def pytest_runtest_protocol(item, nextitem):
# reports = yield
# for report in reports:
# if report.when == "call" and report.failed:
# # 检查是否是 UiObjectNotFoundError 异常
# if isinstance(report.longrepr, UiObjectNotFoundError):
# # 获取异常信息
# err_info = str(report.longrepr)
#
# # 调用截图方法
# capture_screenshot(err_info)
截图效果