霍格沃兹测试开发学社
总结:加班 + 背锅
Hogwarts $ tree
.
├── __init__.py
├── base
│ ├── __init__.py
│ ├── app.py
│ └── base_page.py
├── cases
│ ├── __init__.py
│ └── test_schedule.py
├── log
│ ├── test.log
├── datas
│ └── people.yml
├── page
│ ├── __init__.py
│ ├── choose_page.py
│ ├── edit_schedule_page.py
│ ├── main_page.py
│ ├── schedule_page.py
│ └── workbench_page.py
├── pytest.ini
└── utils
├── __init__.py
└── generate_info.py
# test_add_member.py
# pip install Appium-Python-Client
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from faker import Faker
from selenium.common import NoSuchElementException
class TestContact:
IMPLICITLY_WAIT = 10
def setup_class(self):
# mock随机数据,姓名 ,地址,手机号。。。
self.faker = Faker("zh_CN")
def setup(self):
# 准备资源
caps = {}
# 被测手机的平台名
caps["platformName"] = "Android"
caps['platformVersion'] = '6'
caps["deviceName"] = "hogwarts"
caps["appPackage"] = "com.tencent.wework"
caps["appActivity"] = ".launch.LaunchSplashActivity"
# 防止清缓存 "true" True
caps["noReset"] = True
# 关键的步骤!!!调用Remote() 建立连接 ,返回 一个session对象
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)
# 隐式等待, 动态的等到元素出现
self.driver.implicitly_wait(self.IMPLICITLY_WAIT)
def teardown(self):
# 销毁资源
self.driver.quit()
def test_addcontact(self):
# mock name 和phone number
name = self.faker.name()
phonenum = self.faker.phone_number()
# 进入【通讯录】页面
self.driver.find_element(AppiumBy.XPATH, "//*[@text='通讯录']").click()
# 点击【添加成员】
self.driver.find_element("添加成员").click()
# 点击【手动输入添加】
self.driver.find_element(AppiumBy.XPATH, "//*[@text='手动输入添加']").click()
# 输入【姓名】
self.driver.find_element(AppiumBy.XPATH, "//*[contains(@text, '姓名')]/../*[@text='必填']").send_keys(name)
# 输入【手机号】
self.driver.find_element(AppiumBy.XPATH, "//*[contains(@text, '手机')]/..//*[@text='必填']").send_keys(phonenum)
# 点击【保存】
self.driver.find_element(AppiumBy.XPATH, "//*[@text='保存']").click()
# 验证结果
# class="android.widget.Toast" 隐式等待 找到toast
toast_tips = self.driver.find_element(AppiumBy.XPATH, "//*[@class='android.widget.Toast']").text
assert toast_tips == "添加成功"
self.driver.find_element(MobileBy.ANDROID_UIAUTOMATOR,\
'new UiScrollable(new UiSelector().scrollable(true).instance(0))\
.scrollIntoView(new UiSelector().text("添加成员").instance(0));')
def swipe_find(self, text, max_num = 3):
"""滑动查找一个文本 text
如果没有找元素,完成滑动操作
如果找到了,则返回元素
"""
self.driver.implicitly_wait(1)
for num in range(max_num):
try:
# find_element() 每次调用这个方法的时候,都会激活隐式等待,也就是在隐式等待的时长之内,动态的找元素
element = self.driver.find_element(AppiumBy.XPATH, f"//*[@text='{text}']")
# 找到了元素之后,再设置回全局的隐式等待时长 10秒
self.driver.implicitly_wait(self.IMPLICITLY_WAIT)
return element
except NoSuchElementException as e:
print("未找到元素")
# 滑动 从下向上
size = self.driver.get_window_size()
# 'width', 'height'
width = size.get("width")
height = size.get("height")
startx = width/2
starty = height*0.8
endx = startx
endy = height*0.2
duration= 2000
self.driver.swipe(startx, starty, endx, endy, duration)
if num == max_num-1:
# 没有找到的情况。在抛出异常之前,把这个隐式等待改回全局的等待时长 10 秒
self.driver.implicitly_wait(self.IMPLICITLY_WAIT)
# 执行到最大次数,仍然没有找到这个文本,则抛出异常
raise NoSuchElementException(f"找了{num} 次,未找到{text}")
PO
原则解读属性意义
方法意义
PO
建模PO
与链式调用
PageObject
:编写PO
类PageObject
:融入元素定位class WeworkApp(BasePage):
IMPLICITLY_WAIT = 10
def start(self):
if self.driver == None:
# 准备资源
caps = {}
# 被测手机的平台名
caps["platformName"] = "Android"
caps["deviceName"] = "hogwarts"
caps["appPackage"] = "com.tencent.wework"
caps["appActivity"] = ".launch.LaunchSplashActivity"
# 防止清缓存 "true" True
caps["noReset"] = "true"
caps['settings[waitForIdleTimeout]'] = 1
caps['dontStopAppOnReset'] = True
# 关键的步骤!!!调用Remote() 建立连接 ,返回 一个session对象
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)
# 隐式等待, 动态的等到元素出现
self.driver.implicitly_wait(self.IMPLICITLY_WAIT)
else:
# 直接启动desire里设置的package及activity的相关设置参数,只启动当前测试的应用
self.driver.launch_app()
return self
# conftest.py
import os
import pytest
import yaml
import sys
from wework_po.utils.log_util import logger
# 添加前项目路径到环境变量
root_path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(root_path)
# 解决用例描述中中文乱码的问题
def pytest_collection_modifyitems(
session, config, items
) -> None:
for item in items:
item.name = item.name.encode('utf-8').decode('unicode-escape')
item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
# 获取钩子方法的调用结果
out = yield
# 从钩子方法的调用结果中获取测试报告
report = out.get_result()
fail_case_info = []
# 错误类型
error_type = None
# 错误信息
error_msg = None
# 错误完整信息
error_longrepr = None
# 如果用例执行不通过
if report.outcome != "passed":
# 如果运行时有相应的错误日志则捕获日志,赋值到一个变量中
logger.info(call.excinfo)
if call.excinfo:
error_type = call.excinfo.typename
# error_msg = call.excinfo.value.msg
error_msg = call.excinfo.value
error_longrepr = str(out._result.longrepr)
case_info = {
"nodeid": report.nodeid,
"result": report.outcome,
"type": error_type,
"msg": error_msg,
"longrepr": error_longrepr
}
fail_case_info.append(case_info)
error_path = root_path + '/fail_record'
if not os.path.isdir(error_path):
os.mkdir(error_path)
# 用例信息写入 yaml 文件
with open(error_path + '/fail_cases_info.yaml', 'a', encoding='utf-8') as f:
yaml.dump(fail_case_info, f, allow_unicode=True)
logger.error(f'错误类型 =>> {error_type},\n'
f'错误信息 =>> {error_msg},\n'
f'错误详情 =>> {error_longrepr} \n')
# base_page.py
class BasePage:
def screenshot(self, root_path):
'''
截图
:param path: 截图保存路径
'''
logger.info(f"没有找到元素,截图")
# 以当前时间命名的截图
image_name = Utils.get_current_time() + ".png"
# 拼接当前要输出图片的路径
image_dir_path = os.sep.join([root_path, '..', f'images/'])
if not os.path.isdir(image_dir_path):
os.mkdir(image_dir_path)
image_path = image_dir_path + image_name
logger.info(f"截图路径为:{image_path}")
self.driver.save_screenshot(image_path)
return image_path
def save_page_source(self, root_path):
'''
保存页面源码
:return: 返回源码文件路径
'''
logger.info(f"没有找到元素,保存页面源码")
pagesource_name = Utils.get_current_time() + "_page_source.xml"
pagesource_dir_path = os.sep.join([root_path, '..', f'page_source/'])
if not os.path.isdir(pagesource_dir_path):
os.mkdir(pagesource_dir_path)
pagesource_path = pagesource_dir_path + pagesource_name
logger.info(f"page source 路径为:{pagesource_path}")
with open(pagesource_path, "w", encoding="u8") as f:
f.write(self.driver.page_source)
return pagesource_path
# error_handler.py
black_list = [
(AppiumBy.XPATH, "//*[@text='确定']"),
(AppiumBy.XPATH, "//*[@text='取消']")
]
# 传入的 fun 相当于 find 方法
def black_wrapper(fun):
def run(*args, **kwargs):
# 相当于传入的第一个参数 self
basepage = args[0]
try:
logger.info(f"开始查找元素:{args[2]}")
return fun(*args, **kwargs)
except Exception as e:
logger.warning("未找到元素,处理异常")
# 遇到异常截图
# 获取当前工具文件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))
image_path = basepage.screenshot(root_path)
allure.attach.file(image_path, name="查找元素异常截图", attachment_type=allure.attachment_type.PNG)
# 保存页面源码
pagesource_path = basepage.save_page_source(root_path)
allure.attach.file(pagesource_path, name="page_source", attachment_type=allure.attachment_type.TEXT)
for b in black_list:
# 查找黑名单中的每一个元素
eles = basepage.driver.find_elements(*b)
if len(eles) > 0:
# 点击弹框
eles[0].click()
# 继续查找元素
return fun(*args, **kwargs)
logger.error(f"遍历黑名单,仍未找到元素,异常信息为 ====> {e}")
logger.error(f"traceback.format_exc() 信息为 ====> {traceback.format_exc()}")
raise e
return run