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 ,將項目中使用的第三方包的包名和版本導出到文件中
