本文为博主原创,未经许可严禁转载。
本文链接:https://blog.csdn.net/zyooooxie/article/details/113841107
2018年我开始在csdn写博客,最初就是在写UI自动化测试;
现如今2021年了,想着为了锻炼自己,写个app的自动化测试框架。
这是 App自动化测试的 category ,有兴趣 可以看看。
个人博客:https://blog.csdn.net/zyooooxie
需求
框架设计的目的:应用在冒烟测试、回归测试阶段;
脚本的用例:核心且稳定的业务;
要实现:
- 分层设计【po】;
- 用例执行失败时 重跑 + 断言失败、元素查找失败 自动截图 + 自定义用例执行的顺序;
- 截图、日志、测试报告的清理;
- 邮件提醒【Jenkins构建邮件 + 测试报告邮件】;
- 企微机器人提醒;
框架设计
使用: appium + pytest + allure + pytest-ordering + pytest-rerunfailures
- 清理本地文件(截图、日志);
- 卸载、再安装App;
- po 执行用例、 断言;Appium启动session,发送命令;
- 清理、重新生成测试报告;
- 发送(测试报告)邮件;企微通知;
- Jenkins定时构建;邮件通知Jobs构建情况;
具体实现
本系列分享 拿某Q极速版【安卓客户端】举例;
appPackage=csdn.zyooooxie.qqlite
appActivity=csdn.zyooooxie.mobileqq.activity.SplashActivity
本期主要讲述:
清理本地的 截图、日志文件
File:run_app.py
def delete_file():
for i in [log_path, screen_ele_path, screen_assert_path]:
os.chdir(i)
all_file = os.listdir(i)
if i == log_path:
all_file.sort(key=os.path.getmtime, reverse=True)
# 最新生成的日志 要保留
all_file = all_file[1:]
for f in all_file:
os.remove(f)
卸载、安装App
File:apk_install.py
class ApkInstall(object):
"""adb 命令来执行卸载、安装"""
apk_path = app_path
def __init__(self, package='csdn.zyooooxie.qqlite'):
self.package = package
def get_apk_name(self):
os.chdir(self.apk_path)
all_files = os.listdir(self.apk_path)
apk = [a for a in all_files if a.endswith('apk')]
apk_list = sorted(apk, key=lambda b: os.path.getctime(b), reverse=False)
Log.info(apk_list[-1])
return apk_list[-1]
def uninstall_apk(self, first=None):
# 不加-k ('-k' means keep the data and cache directories)
cmd = """adb uninstall {}""".format(self.package)
r = os.popen(cmd)
text = r.read()
r.close()
Log.info(cmd)
if text.find('Failure') != -1:
if first is not None:
raise Exception('卸载报错')
else:
Log.info('未安装过,so 卸载失败')
def install_apk(self, first_install=None, uninstall=None):
apk = self.get_apk_name()
if uninstall is not None:
self.uninstall_apk(first=first_install)
sleep(5)
if first_install is None:
cmd = """adb install -r {}""".format(apk) # -r (reinstall) 重装
else:
cmd = """adb install {}""".format(apk)
Log.info(cmd)
new_text = subprocess.check_output(cmd, shell=True)
new_text = new_text.decode()
print(new_text)
sleep(5)
if new_text.find('Success') != -1:
Log.info('安装成功')
else:
raise Exception('安装有异常')
File:run_app.py
def install():
ApkInstall('csdn.zyooooxie.qqlite').install_apk(first_install='yes', uninstall='yes')
appium启动session、退出
File:base_driver.py
class BaseDriver(object):
def appium_desired(self, devices):
desired_data = CommonFun.read_config(devices)
desired_data = {d[0]: d[1] for d in desired_data}
desired_caps = deepcopy(desired_data)
desired_caps.update({'noReset': eval(desired_data['noReset'])})
desired_caps.update({'unicodeKeyboard': eval(desired_data['unicodeKeyboard'])})
desired_caps.update({'resetKeyboard': eval(desired_data['resetKeyboard'])})
desired_caps.pop('ip')
desired_caps.pop('port')
u = "http://{}:{}/wd/hub".format(desired_data['ip'], desired_data['port'])
driver = webdriver.Remote(u, desired_caps)
return driver
def driver_quit(self, driver):
# Sending command to android: {"cmd":"shutdown"} + driver.deleteSession() + kill all uiautomator processes
driver.quit()
po
File:base_method.py
class BaseMethod(object):
"""基础方法"""
def driver_find_element_and_wait(self, driver: WebDriver, by, location, the_time=10):
"""
等待元素可见
:param driver:
:param by:
:param location:
:param the_time:
:return:
"""
if by not in MobileBy.__dict__.values() and by not in By.__dict__.values():
raise NameError("Please enter the correct targeting elements.")
try:
ele = WebDriverWait(driver, the_time, poll_frequency=1).until(ec.visibility_of_element_located((by, location)))
except Exception:
Log.error('元素查找失败:{} '.format((by, location)))
img_name = ''.join([time.strftime("%Y%m%d_%H%M%S"), '.png'])
Log.info('已截图:{}'.format(img_name))
driver.save_screenshot(os.path.join(screen_ele_path, img_name))
raise NoSuchElementException
return ele
def element_click(self, driver: WebDriver, by, location, the_time=10):
self.driver_find_element_and_wait(driver, by, location, the_time).click()
def element_send_keys(self, driver: WebDriver, keys, by, location, the_time=10):
self.driver_find_element_and_wait(driver, by, location, the_time).clear().send_keys(keys)
def assert_FindElement(self, driver: WebDriver, by, location, the_time=10):
try:
WebDriverWait(driver, the_time).until(ec.visibility_of_element_located((by, location)), '查找失败')
# TimeoutException
except Exception as e:
Log.debug(repr(e))
Log.error('断言时 元素找不到:{}'.format((by, location)))
img_name = ''.join([time.strftime("%Y%m%d_%H%M%S"), '.png'])
Log.info('已截图:{}'.format(img_name))
driver.save_screenshot(os.path.join(screen_assert_path, img_name))
raise AssertionError
def assert_app(self, driver: WebDriver, app_name):
print(driver.current_activity, driver.current_package)
assert driver.current_activity != app_name
File:page_test.py
class PageTest(BaseMethod):
# 暂不使用
not_in_use = (By.ID, 'csdn.zyooooxie.qqlite:id/dialogLeftBtn')
# 同意
in_use = (By.ID, 'csdn.zyooooxie.qqlite:id/dialogRightBtn')
in_use2 = (MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("同意")')
in_use3 = (MobileBy.ANDROID_UIAUTOMATOR, 'new UiSelector().description("同意")')
in_use4 = (MobileBy.ACCESSIBILITY_ID, '同意')
in_use5 = (MobileBy.ID, 'dialogRightBtn')
# 服务协议
service_agreement = (By.ID, 'csdn.zyooooxie.qqlite:id/dialogText')
login = (By.ID, 'csdn.zyooooxie.qqlite:id/btn_login')
# 完全不存在
fail = (By.ID, 'csdn.zyooooxie.qqlite:id/fail')
# 选择 暂不使用
def choose_NotInUse(self, driver: WebDriver):
self.driver_find_element_and_wait(driver, *self.not_in_use).click()
sleep(2)
# 点击 服务协议
def click_Service(self, driver: WebDriver):
self.driver_find_element_and_wait(driver, *self.service_agreement).click()
sleep(2)
File:test_test.py
@pytest.mark.first
@allure.feature('测试TEST')
class TestTest(PageTest):
@allure.story('暂不使用')
@allure.title('用例1')
@mark_smoke
def test_0(self, driver):
allure.dynamic.description("动态description0")
self.choose_NotInUse(driver)
time.sleep(1)
self.assert_app(driver, CommonFun.read_config('phone_honor', option='appPackage'))
@allure.story('服务协议')
@allure.title('用例2')
def test_1(self, driver):
allure.dynamic.description("动态description1")
self.click_Service(driver)
self.assert_FindElement(driver, *self.not_in_use)
# @pytest.mark.run(order=14)
@allure.story('同意')
@allure.title('用例1')
def test_2a(self, driver):
allure.dynamic.description("动态description2")
print(self.driver_find_element_and_wait(driver, *self.in_use).text)
self.assert_FindElement(driver, *self.not_in_use)
@allure.story('同意')
@allure.title('用例2')
def test_2b(self, driver):
allure.dynamic.description("动态description3")
print(self.driver_find_element_and_wait(driver, *self.in_use2).text)
self.assert_FindElement(driver, *self.not_in_use)
@pytest.mark.skip(reason='跳过')
@allure.story('同意')
@allure.title('用例3')
def test_2c(self, driver):
allure.dynamic.description("动态description4")
print(self.driver_find_element_and_wait(driver, *self.in_use3).text)
self.assert_FindElement(driver, *self.not_in_use)
@allure.story('同意')
@allure.title('用例4')
def test_2d(self, driver):
allure.dynamic.description("动态description5")
print(self.driver_find_element_and_wait(driver, *self.in_use4).text)
# 断言失败
self.assert_FindElement(driver, *self.fail)
@allure.story('同意')
@allure.title('用例5')
def test_3(self, driver):
allure.dynamic.description("动态description6")
self.driver_find_element_and_wait(driver, *self.in_use5).click()
self.assert_FindElement(driver, *self.login)
@allure.story('同意')
@pytest.mark.run(order=13)
@allure.title('用例6')
def test_4(self, driver):
allure.dynamic.description("动态description7")
print(driver.current_activity)
@allure.story('失败')
@allure.title('用例1')
def test_5(self, driver):
allure.dynamic.description("动态description8")
# 查找元素失败
self.driver_find_element_and_wait(driver, *self.fail).click()
self.assert_FindElement(driver, *self.fail)
@pytest.mark.skip
@allure.story('跳过')
@allure.title('用例0')
def test_6(self, driver):
allure.dynamic.description("动态description9")
print(driver.current_activity)
@skip_mark
@allure.story('跳过')
@allure.title('用例1')
def test_7a(self, driver):
allure.dynamic.description("动态description10")
print(driver.current_activity)
@skip_mark
@allure.story('跳过')
@allure.title('用例2')
def test_7b(self, driver):
allure.dynamic.description("动态description")
print(driver.current_activity)
File:conftest.py
@pytest.fixture(scope='function')
def driver():
b = BaseDriver()
device = 'phone_honor'
dr = b.appium_desired(devices=device)
yield dr
b.driver_quit(dr)
结果展示
这一篇主要的内容就这些;
本系列 第二篇 生成测试报告,邮件、企微通知+Jenkins构建通知
交流技术 欢迎+QQ 153132336 zy
个人博客 https://blog.csdn.net/zyooooxie