1.安装依赖以及项目的基本目录
# 安装依赖 pip install pytest pip install appium-python-client pip install openpyxl # excel文件处理 pip install pytest-html # 测试报告
2.pom解析
pom 设计的核心思想 就是将不同的页面单独进行维护,在做自动化的过程中,如果前端页面进行更改,原来写自动化代码就可能不再适用,因为前端页面更改了之后,元素定位已经不再适合,自动化用例执行失败。就要重新更改代码,比较麻烦。
pom 将页面与测试用例单独封装,页面上的每个操作都单独封装起来,测试用例只需要调用封装好的方法即可。如果页面有改动。只需要改页面中封装的操作即可。
下面以登录场景为例,编写自动化:
conftest.py
from appium import webdriver import pytest import os chromedriver= os.path.join(os.path.dirname(os.path.abspath(__file__)),\'drivers/chrome/75.0.3770.140/chromedriver.exe\') @pytest.fixture(scope=\'session\') def driver(): desired_caps = { \'platformName\': \'Android\', # 测试Android系统 \'platformVersion\': \'7.1.2\', # Android版本 可以在手机的设置中关于手机查看 \'deviceName\': \'127.0.0.1:62001\', # adb devices 命令查看 设置为自己的设备 \'automationName\': \'UiAutomator2\', # 自动化引擎 \'noReset\': False, # 不要重置app的状态 \'fullReset\': False, # 不要清理app的缓存数据 \'chromedriverExecutable\': chromedriver, # chromedriver 对应的绝对路径 \'appPackage\': "org.cnodejs.android.md", # 应用的包名 \'appActivity\': ".ui.activity.LaunchActivity" # 应用的活动页名称(appium会启动app的加载页) } driver = webdriver.Remote(\'http://127.0.0.1:4723/wd/hub\', desired_capabilities=desired_caps) driver.implicitly_wait(5) # 全局的隐式等待时间 yield driver # 将driver 传递出来 driver.quit()
pom/basePage.py
""" 所有的页面都继承这个类,获得driver """ import time from appium.webdriver.webdriver import WebDriver from selenium.common.exceptions import NoSuchElementException class BasePage: def __init__(self,driver:WebDriver): self.driver = driver # 获取toast的文本值 @property def result_text(self): try: toast = self.driver.find_element_by_xpath(\'//android.widget.Toast\') return toast.text except NoSuchElementException: return "找不到这个元素,请检查自己的自动化代码"
pom/loginPage.py
""" 登陆页面 """ import time from appium.webdriver.webdriver import WebDriver from pom.basePage import BasePage class LoginPage(BasePage): # 初始化类的时候,打开登陆页面 def __init__(self,driver:WebDriver): super(LoginPage,self).__init__(driver) # 判断是否是登陆页面 current_activity = self.driver.current_activity if ".ui.activity.LoginActivity" in current_activity: pass else: # 不是登陆页面,则调用方法,打开登陆页面 self.__go_login_page() # 导航到loginPage(登陆页面),定义一个私有的方法 def __go_login_page(self): # 清空app的登陆状态(如果已经登陆,则去掉登陆状态) self.driver.reset() # 打开首页 self.driver.start_activity(app_package=\'org.cnodejs.android.md\',app_activity=\'.ui.activity.MainActivity\') toggle_but = self.driver.find_element_by_android_uiautomator(\'resourceId("org.cnodejs.android.md:id/toolbar").childSelector(new UiSelector().className("android.widget.ImageButton"))\') toggle_but.click() time.sleep(1) # 点击头像,去登陆页面 avatar = self.driver.find_element_by_android_uiautomator(\'text("点击头像登录").resourceId("org.cnodejs.android.md:id/tv_login_name")\') avatar.click() # 使用token的方式进行登录 def with_token_login(self,token): self.driver.find_element_by_id(\'org.cnodejs.android.md:id/edt_access_token\').send_keys(token) loginbtn = self.driver.find_element_by_android_uiautomator(\'text("登录").resourceId("org.cnodejs.android.md:id/btn_login")\') # 点击登陆 loginbtn.click() # 登陆失败的断言 @property def with_token_failed_text(self): # 1. 截图 ele = self.driver.find_element_by_id(\'org.cnodejs.android.md:id/edt_access_token\') png = ele.screenshot_as_base64 # 2. TODO 调用 ocr 图片识别 将图片中文字识别出来 return "" # 扫码登陆 def with_code_login(self): pass # 使用github登陆 def with_github_login(self): pass
testcases/test_user.py
from pom.loginPage import LoginPage # 登陆的测试用例 # 使用conftest.py 中定义的 driver def test_login(driver): # 打开登录页面 loginpage = LoginPage(driver) # 使用token进行登录 loginpage.with_token_login(\'d1563473-1f0d-4307-9774-6c2ff49c93ab\') # 登陆成功,验证totas文本值 result = loginpage.result_text assert result == "登录成功"
启动appium , 执行 pytest testcases\test_user.py -s -v ,查看运行结果:
(登陆成功)
(登陆失败)
下面以登录后发帖场景为例,编写自动化:
pom/homePage.py
""" 首页 """ from pom.basePage import BasePage from pom.createTopicPage import CreateTopicPage class HomePage(BasePage): def __init__(self,driver): super(HomePage,self).__init__(driver) # 判断一下,是否是首页 if \'.ui.activity.MainActivity\' in self.driver.current_activity: pass else: self.__go_home_page() # 打开首页 def __go_home_page(self): self.driver.start_activity(app_activity=\'.ui.activity.LaunchActivity\') # 去发帖页面 def go_create_topic(self): # 判断是否已经到达创建话题页面 while not \'.ui.activity.CreateTopicActivity\' in self.driver.current_activity: # 再重新点击一下 create_btn = self.driver.find_element_by_android_uiautomator( \'.resourceId("org.cnodejs.android.md:id/fab_create_topic")\') create_btn.click() return CreateTopicPage(self.driver)
pom/createTopicPage.py
""" 发帖页面 """ from pom.basePage import BasePage class CreateTopicPage(BasePage): # 发布话题 def create_new_topic(self,tab,title,content): # 选择类型 spinner = self.driver.find_element_by_android_uiautomator(\'.resourceId("org.cnodejs.android.md:id/spn_tab")\') spinner.click() tab_selcotor = f\'.resourceId("android:id/text1").text("{tab}")\' self.driver.find_element_by_android_uiautomator(tab_selcotor).click() # 输入标题 title_content = self.driver.find_element_by_android_uiautomator( \'resourceId("org.cnodejs.android.md:id/edt_title")\') title_content.send_keys(title) # 输入内容 content_area = self.driver.find_element_by_android_uiautomator( \'resourceId("org.cnodejs.android.md:id/edt_content")\') content_area.send_keys(content) # 点击发送 send_btn = self.driver.find_element_by_android_uiautomator( \'resourceId("org.cnodejs.android.md:id/action_send")\') send_btn.click()
testcases/test_topics.py
from pom.homePage import HomePage from pom.loginPage import LoginPage # 发帖的测试用例 def test_create_topic(driver): loginpage = LoginPage(driver) # 用户登录成功 loginpage.with_token_login(\'d1563473-1f0d-4307-9774-6c2ff49c93ab\') # 首页打开 hp = HomePage(driver) # 进入创建话题页面 create_page = hp.go_create_topic() create_page.create_new_topic(tab=\'分享\',title=\'123\',content=\'哈哈哈哈哈哈\') result = create_page.result_text # 根据发帖结果做断言 assert result == "标题要求10字以上"
启动appium , 执行 pytest testcases\test_topics.py -s -v ,查看运行结果:
也可以执行 pytest,查看登陆,和发帖2个测试用例的执行结果:
3. Excel数据驱动
testdata/data.xlsx
utils/file_handler.py
""" 登陆测试用例的数据驱动化测试 """ import pytest from pom.loginPage import LoginPage from utils.file_handler import FileHandler fl = FileHandler() # 从Excel文件中获取数据 data = fl.get_data_by_sheet(\'用户登录\') class TestDdtLogin: @pytest.mark.parametrize(\'token,status,expect_val\',data) def test_login(self,driver,token,status,expect_val): # 打开登录页面 loginpage = LoginPage(driver) # 使用token进行登录 loginpage.with_token_login(token) if status == \'成功\': # 登录成功, 验证toast的文本值为登录成功 result = loginpage.result_text assert result == expect_val if status == "失败": result = loginpage.with_token_failed_text assert result == expect_val
testcases/test_ddt/test_ddt_login.py
""" 登陆测试用例的数据驱动化测试 """ import pytest from pom.loginPage import LoginPage from utils.file_handler import FileHandler fl = FileHandler() # 从Excel文件中获取数据 data = fl.get_data_by_sheet(\'用户登录\') class TestDdtLogin: @pytest.mark.parametrize(\'token,status,expect_val\',data) def test_login(self,driver,token,status,expect_val): # 打开登录页面 loginpage = LoginPage(driver) # 使用token进行登录 loginpage.with_token_login(token) if status == \'成功\': # 登录成功, 验证toast的文本值为登录成功 result = loginpage.result_text assert result == expect_val if status == "失败": result = loginpage.with_token_failed_text assert result == expect_val
启动appium , 执行 pytest testcases\test_ddt\test_ddt_login.py -s -v ,查看运行结果:
4.测试报告
pytest-html https://pypi.org/project/pytest-html/
pytest-allure https://pypi.org/project/allure-pytest/
测试报告一:pytest-html
main.py
""" 项目运行文件,并添加测试报告 """ import pytest import os,time if __name__ == \'__main__\': report_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),\'reports\') if not os.path.exists(report_dir): os.mkdir(report_dir) report = time.strftime(\'%Y_%m_%d_%H_%M_%S\') reportfile = os.path.join(report_dir,report+\'.html\') pytest.main([\'testcases\',\'-s\',\'-v\',f\'--html={reportfile}\'])
运行main.py 文件,Run main.py 执行即可,会执行 testcases 里面所有的测试用例
执行结束之后,生成的测试报告,我们可以在浏览器中打开
5.优化conftest.py
增加 每个测试用例执行完成之后,如果执行失败截图,截图的名称为测试用例名称+时间格式 的相关处理
from appium import webdriver import pytest import os, time chromedriver= os.path.join(os.path.dirname(os.path.abspath(__file__)),\'drivers/chrome/75.0.3770.140/chromedriver.exe\') # scope=\'session\' 标记的方法执行域为---->所有测试用例运行之前/之后 运行的方法 @pytest.fixture(scope=\'session\',autouse=True) def driver(): desired_caps = { \'platformName\': \'Android\', # 测试Android系统 \'platformVersion\': \'7.1.2\', # Android版本 可以在手机的设置中关于手机查看 \'deviceName\': \'127.0.0.1:62001\', # adb devices 命令查看 设置为自己的设备 \'automationName\': \'UiAutomator2\', # 自动化引擎 \'noReset\': False, # 不要重置app的状态 \'fullReset\': False, # 不要清理app的缓存数据 \'chromedriverExecutable\': chromedriver, # chromedriver 对应的绝对路径 \'appPackage\': "org.cnodejs.android.md", # 应用的包名 \'appActivity\': ".ui.activity.LaunchActivity" # 应用的活动页名称(appium会启动app的加载页) } driver = webdriver.Remote(\'http://127.0.0.1:4723/wd/hub\', desired_capabilities=desired_caps) driver.implicitly_wait(5) # 全局的隐式等待时间 yield driver # 将driver 传递出来 driver.quit() # 该方法是用来获取测试用例执行的结果(passed / FAILED) @pytest.hookimpl(tryfirst=True,hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() # 获取用例的执行结果 print(\'用例的执行结果rep---->\',rep) setattr(item, "rep_" + rep.when, rep) # 将执行结果保存到 item 属性中 , req.when 执行时 # scope=\'function\' 标记的方法执行域为---->每个测试用例运行之前/之后 运行的方法 @pytest.fixture(scope=\'function\',autouse=True) def case_run(driver:webdriver,request): # request 为 pytest_runtest_makereport 方法获取到的执行结果(固定参数和用法) yield # 每个测试用例执行完成之后,如果执行失败截图,截图的名称为测试用例名称+时间格式 if request.node.rep_call.failed: screenshots = os.path.join(os.path.dirname(os.path.abspath(__file__)), \'screenshots\') if not os.path.exists(screenshots): os.mkdir(screenshots) casename: str = request.node.nodeid # print("执行测试用例的名字:", casename) # 测试用例的名字很长 testcases/test_ddt/test_ddt_login.py::TestDdtLogin::test_login[....] # 对字符串进行截取,截取之后显示为 test_ddt_login-TestDdtLogin casename = casename[casename.rfind(\'/\')+1:casename.rfind(\'::\')].replace(\'.py::\',\'-\') filename = casename + \'-\' + time.strftime(\'%Y_%m_%d-%H_%M_%S\') +".png" screenshot_file = os.path.join(screenshots, filename) # 保存截图 driver.save_screenshot(screenshot_file)
6. 多设备连接,并行执行测试代码
通过代码的方式自动的获取连接的多个设备,拿到设备的串号,启动多个appium,通过多进程的方式,在多个设备上并行的执行自动化测试用例
app自动化测试----多设备并行运行 点击查看优化后的代码
最后,执行 pip freeze > requirements.txt ,将项目中使用的第三方包的包名和版本导出到文件中