UI自動化測試框架:PO 模式+數據驅動(超詳細)

1. PO 設計模式簡介

什么是 PO 模式?

PO(PageObject)設計模式將某個頁面的所有元素對象定位和對元素對象的操作封裝成一個 Page 類,并以頁面為單位來寫測試用例,實現頁面對象和測試用例的分離。

PO 模式的設計思想與面向對象相似,能讓測試代碼變得可讀性更好,可維護性高,復用性高。

PO 模式可以把一個頁面分為三個層級:對象庫層、操作層、業務層。

  1. 對象庫層:封裝定位元素的方法。
  2. 操作層:封裝對元素的操作。
  3. 業務層:將一個或多個操作組合起來完成一個業務功能。

一條測試用例可能需要多個步驟操作元素,將每一個步驟單獨封裝成一個方法,在執行測試用例時調用封裝好的方法進行操作。

PO 模式的優點
  • 通過頁面分層,將測試代碼和被測試頁面的頁面元素及其操作方法進行分離,降低代碼冗余。
  • 頁面對象與用例分離,業務代碼與測試代碼分離,降低耦合性。
  • 不同層級分屬不同用途,降低維護成本。
  • 代碼可閱讀性增強,整體流程更為清晰。

2. 工程結構簡介

工程結構

整個測試框架分為四層,通過分層的方式,測試代碼更容易理解,維護起來較為方便。

第一層是“測試工具層”:

  • util 包:用于實現測試過程中調用的工具類方法,例如讀取配置文件、頁面元素的操作方法、操作Excel文件等。
  • conf 包:配置文件及全局變量。
  • test_data 目錄:Excel 數據文件,包含測試數據輸入、測試結果輸出。
  • log 目錄:日志輸出文件。
  • screenshot_path 目錄:異常截圖保存目錄。

第二層是“服務層”,相當于對測試對象的一個業務封裝。對于接口測試,是對遠程方法的一個實現;對于頁面測試,是對頁面元素或操作的一個封裝。

  • page 包:對象庫層及操作層,將所有頁面的元素對象定位及其操作分別封裝成一個類。

第三層是“測試用例邏輯層”,該層主要是將服務層封裝好的各個業務對象,組織成測試邏輯,進行校驗。

  • action 包:組裝單個用例的流程。
  • business_process 包:基于業務層和測試數據文件,執行測試用例集合。
  • test_data 目錄:Excel 數據文件,包含測試數據輸入、測試結果輸出。 
  • 第四層是“測試場景層”,將測試用例組織成測試場景,實現各種級別 cases 的管理、冒煙,回歸等測試場景。 
  • main.py:本 PO 框架的運行主入口。

框架特點
  • 通過配置文件,實現頁面元素定位方式和測試代碼的分離。
  • 使用 PO 模式,封裝了網頁中的頁面元素,方便測試代碼調用,也實現了一處維護全局生效的目標。
  • 在 excel 文件中定義多組測試數據,每個登錄用戶都一一對應一個存放聯系人數據的 sheet,測試框架可自動調用測試數據完成數據驅動測試。
  • 實現了測試執行過程中的日志記錄功能,可以通過日志文件分析測試腳本執行的情況。
  • 在 excel 數據文件中,通過設定“測試數據是否執行”列的內容為 y 或 n,自定義選擇測試數據,測試執行結束后會在"測試結果列"中顯示測試執行的時間和結果,方便測試人員查看。

3. 工程代碼示例

page 包

對象庫層及操作層,將所有頁面的元素對象定位及其操作分別封裝成一個類。

login_page.py

  1. from conf.global_var import *

  2. from util.ini_parser import IniParser

  3. from util.find_element_util import *

  4. # 登錄頁面元素定位及操作

  5. class LoginPage:

  6. def __init__(self, driver):

  7. self.driver = driver

  8. # 初始化跳轉登錄頁面

  9. self.driver.get(LOGIN_URL)

  10. # 初始化指定ini配置文件及指定分組

  11. self.cf = IniParser(ELEMENT_FILE_PATH, "126mail_loginPage")

  12. # 獲取frame元素對象

  13. def get_frame_obj(self):

  14. locate_method, locate_exp = self.cf.get_value("loginPage.frame").split(">")

  15. return find_element(self.driver, locate_method, locate_exp)

  16. # 切換frame

  17. def switch_frame(self):

  18. self.driver.switch_to.frame(self.get_frame_obj())

  19. # 獲取用戶名輸入框元素對象

  20. def get_username_input_obj(self):

  21. locate_method, locate_exp = self.cf.get_value("loginPage.username").split(">")

  22. return find_element(self.driver, locate_method, locate_exp)

  23. # 清空用戶名輸入框操作

  24. def clear_username(self):

  25. self.get_username_input_obj().clear()

  26. # 輸入用戶名操作

  27. def input_username(self, value):

  28. self.get_username_input_obj().send_keys(value)

  29. # 獲取密碼輸入框元素對象

  30. def get_pwd_input_obj(self):

  31. locate_method, locate_exp = self.cf.get_value("loginPage.password").split(">")

  32. return find_element(self.driver, locate_method, locate_exp)

  33. # 輸入密碼操作

  34. def input_pwd(self, value):

  35. self.get_pwd_input_obj().send_keys(value)

  36. # 獲取登錄按鈕對象

  37. def get_login_buttion_obj(self):

  38. locate_method, locate_exp = self.cf.get_value("loginPage.loginbutton").split(">")

  39. return find_element(self.driver, locate_method, locate_exp)

  40. # 點擊登錄按鈕操作

  41. def click_login_button(self):

  42. self.get_login_buttion_obj().click()

home_page.py

  1. from conf.global_var import *

  2. from util.ini_parser import IniParser

  3. from util.find_element_util import *

  4. # 登錄后主頁元素定位及操作

  5. class HomePage:

  6. def __init__(self, driver):

  7. self.driver = driver

  8. # 初始化指定ini配置文件及指定分組

  9. self.cf = IniParser(ELEMENT_FILE_PATH, "126mail_homePage")

  10. # 獲取“通訊錄”按鈕對象

  11. def get_contact_button_obj(self):

  12. locate_method, locate_exp = self.cf.get_value("homePage.addressLink").split(">")

  13. return find_element(self.driver, locate_method, locate_exp)

  14. # 點擊“通訊錄”按鈕

  15. def click_contact_button(self):

  16. self.get_contact_button_obj().click()

contact_page.py

  1. from conf.global_var import *

  2. from util.ini_parser import IniParser

  3. from util.find_element_util import *

  4. # 通訊錄頁面元素定位及操作

  5. class ContactPage:

  6. def __init__(self, driver):

  7. self.driver = driver

  8. # 初始化指定ini配置文件及指定分組

  9. self.cf = IniParser(ELEMENT_FILE_PATH, "126mail_contactPersonPage")

  10. # 獲取新建聯系人按鈕對象

  11. def get_contact_create_button_obj(self):

  12. locate_method, locate_exp = self.cf.get_value("contactPersonPage.createButton").split(">")

  13. return find_element(self.driver, locate_method, locate_exp)

  14. # 點擊新建聯系人按鈕

  15. def click_contact_creat_button(self):

  16. self.get_contact_create_button_obj().click()

  17. # 獲取姓名輸入框對象

  18. def get_name_input_obj(self):

  19. locate_method, locate_exp = self.cf.get_value("contactPersonPage.name").split(">")

  20. return find_element(self.driver, locate_method, locate_exp)

  21. # 輸入姓名操作

  22. def input_name(self, value):

  23. self.get_name_input_obj().send_keys(value)

  24. # 獲取郵箱輸入框對象

  25. def get_email_input_obj(self):

  26. locate_method, locate_exp = self.cf.get_value("contactPersonPage.email").split(">")

  27. return find_element(self.driver, locate_method, locate_exp)

  28. # 輸入郵箱操作

  29. def input_email(self, value):

  30. self.get_email_input_obj().send_keys(value)

  31. # 獲取星標聯系人單選框對象

  32. def get_star_button_obj(self):

  33. locate_method, locate_exp = self.cf.get_value("contactPersonPage.starContacts").split(">")

  34. return find_element(self.driver, locate_method, locate_exp)

  35. # 點擊星標聯系人操作

  36. def click_star_button(self):

  37. self.get_star_button_obj().click()

  38. # 獲取手機輸入框對象

  39. def get_phone_input_obj(self):

  40. locate_method, locate_exp = self.cf.get_value("contactPersonPage.phone").split(">")

  41. return find_element(self.driver, locate_method, locate_exp)

  42. # 輸入郵箱操作

  43. def input_phone(self, value):

  44. self.get_phone_input_obj().send_keys(value)

  45. # 獲取備注輸入框對象

  46. def get_remark_input_obj(self):

  47. locate_method, locate_exp = self.cf.get_value("contactPersonPage.otherinfo").split(">")

  48. return find_element(self.driver, locate_method, locate_exp)

  49. # 輸入郵箱操作

  50. def input_remark(self, value):

  51. self.get_remark_input_obj().send_keys(value)

  52. # 獲取確定按鈕對象

  53. def get_confirm_button_obj(self):

  54. locate_method, locate_exp = self.cf.get_value("contactPersonPage.confirmButton").split(">")

  55. return find_element(self.driver, locate_method, locate_exp)

  56. # 點擊星標聯系人操作

  57. def click_confirm_button(self):

  58. self.get_confirm_button_obj().click()

action 包

業務層,將一個或多個操作組合起來完成一個業務功能。

case_action.py

  1. from selenium import webdriver

  2. import traceback

  3. import time

  4. from page.contact_page import ContactPage

  5. from page.home_page import HomePage

  6. from page.login_page import LoginPage

  7. from conf.global_var import *

  8. from util.log_util import *

  9. # 初始化瀏覽器

  10. def init_browser(browser_name):

  11. if browser_name.lower() == "chrome":

  12. driver = webdriver.Chrome(CHROME_DRIVER)

  13. elif browser_name.lower() == "firefox":

  14. driver = webdriver.Firefox(FIREFOX_DRIVER)

  15. elif browser_name.lower() == "ie":

  16. driver = webdriver.Ie(IE_DRIVER)

  17. else:

  18. return "Error browser name!"

  19. return driver

  20. def assert_word(driver, text):

  21. assert text in driver.page_source

  22. # 登錄流程封裝

  23. def login(driver, username, pwd, assert_text):

  24. login_page = LoginPage(driver)

  25. login_page.switch_frame()

  26. login_page.clear_username()

  27. login_page.input_username(username)

  28. login_page.input_pwd(pwd)

  29. login_page.click_login_button()

  30. time.sleep(1)

  31. assert_word(driver, assert_text)

  32. # 添加聯系人流程封裝

  33. def add_contact(driver, name, email, phone, is_star, remark, assert_text):

  34. home_page = HomePage(driver)

  35. home_page.click_contact_button()

  36. contact_page = ContactPage(driver)

  37. contact_page.click_contact_creat_button()

  38. contact_page.input_name(name)

  39. contact_page.input_email(email)

  40. contact_page.input_phone(phone)

  41. contact_page.input_remark(remark)

  42. if is_star == "是":

  43. contact_page.click_star_button()

  44. contact_page.click_confirm_button()

  45. time.sleep(2)

  46. assert_word(driver, assert_text)

  47. def quit(driver):

  48. driver.quit()

  49. if __name__ == "__main__":

  50. driver = init_browser("chrome")

  51. login(driver, "zhangjun252950418", "zhangjun123", "退出")

  52. add_contact(driver, "鐵蛋", "asfhi@123.com", "12222222222", "是", "這是備注", "鐵蛋")

  53. # quit(driver)

business_process 包

基于業務層和測試文件,實現數據驅動的測試執行腳本。

batch_login_process.py

  1. from action.case_action import *

  2. from util.excel_util import *

  3. from conf.global_var import *

  4. from util.datetime_util import *

  5. from util.screenshot import take_screenshot

  6. # 封裝測試數據文件中用例的執行邏輯

  7. # 測試數據文件中的每個登錄賬號

  8. def batch_login(test_data_file, browser_name, account_sheet_name):

  9. excel = Excel(test_data_file)

  10. # 獲取登錄賬號sheet頁數據

  11. excel.change_sheet(account_sheet_name)

  12. account_all_data = excel.get_all_row_data()

  13. account_headline_data = account_all_data[0]

  14. for account_row_data in account_all_data[1:]:

  15. # 執行登錄用例

  16. account_row_data[ACCOUNT_TEST_TIME_COL] = get_english_datetime()

  17. if account_row_data[ACCOUNT_IS_EXECUTE_COL].lower() == "n":

  18. continue

  19. # 初始化瀏覽器

  20. driver = init_browser(browser_name)

  21. try:

  22. # 默認以"退出"作為斷言關鍵字

  23. login(driver, account_row_data[ACCOUNT_USERNAME_COL], account_row_data[ACCOUNT_PWD_COL], "退出")

  24. info("登錄成功【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],

  25. account_row_data[ACCOUNT_PWD_COL], "退出"))

  26. account_row_data[ACCOUNT_TEST_RESULT_COL] = "pass"

  27. except:

  28. error("登錄失敗【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],

  29. account_row_data[ACCOUNT_PWD_COL], "退出"))

  30. account_row_data[ACCOUNT_TEST_RESULT_COL] = "fail"

  31. account_row_data[ACCOUNT_TEST_EXCEPTION_INFO_COL] = traceback.format_exc()

  32. account_row_data[ACCOUNT_SCREENSHOT_COL] = take_screenshot(driver)

  33. # 寫入登錄用例的測試結果

  34. excel.change_sheet("測試結果")

  35. excel.write_row_data(account_headline_data, "red")

  36. excel.write_row_data(account_row_data)

  37. excel.save()

  38. # 切換另一個賬號時需先關閉瀏覽器,否則會自動登錄

  39. driver.quit()

  40. if __name__ == "__main__":

  41. batch_login(TEST_DATA_FILE_PATH, "chrome", "126賬號")

batch_login_and_add_contact_process.py

from action.case_action import *

  1. from util.excel_util import *

  2. from conf.global_var import *

  3. from util.datetime_util import *

  4. from util.screenshot import take_screenshot

  5. # 封裝測試數據文件中用例的執行邏輯

  6. # 測試數據文件中每個登錄賬號下,添加所有聯系人數據

  7. def batch_login_and_add_contact(test_data_file, browser_name, account_sheet_name):

  8. excel = Excel(test_data_file)

  9. # 獲取登錄賬號sheet頁數據

  10. excel.change_sheet(account_sheet_name)

  11. account_all_data = excel.get_all_row_data()

  12. account_headline_data = account_all_data[0]

  13. for account_row_data in account_all_data[1:]:

  14. # 執行登錄用例

  15. account_row_data[ACCOUNT_TEST_TIME_COL] = get_english_datetime()

  16. if account_row_data[ACCOUNT_IS_EXECUTE_COL].lower() == "n":

  17. continue

  18. # 初始化瀏覽器

  19. driver = init_browser(browser_name)

  20. # 獲取聯系人數據sheet

  21. contact_data_sheet = account_row_data[ACCOUNT_DATA_SHEET_COL]

  22. try:

  23. # 默認以"退出"作為斷言關鍵字

  24. login(driver, account_row_data[ACCOUNT_USERNAME_COL], account_row_data[ACCOUNT_PWD_COL], "退出")

  25. info("登錄成功【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],

  26. account_row_data[ACCOUNT_PWD_COL], "退出"))

  27. account_row_data[ACCOUNT_TEST_RESULT_COL] = "pass"

  28. except:

  29. error("登錄失敗【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],

  30. account_row_data[ACCOUNT_PWD_COL], "退出"))

  31. account_row_data[ACCOUNT_TEST_RESULT_COL] = "fail"

  32. account_row_data[ACCOUNT_TEST_EXCEPTION_INFO_COL] = traceback.format_exc()

  33. account_row_data[ACCOUNT_SCREENSHOT_COL] = take_screenshot(driver)

  34. # 寫入登錄用例的測試結果

  35. excel.change_sheet("測試結果")

  36. excel.write_row_data(account_headline_data, "red")

  37. excel.write_row_data(account_row_data)

  38. excel.save()

  39. # 執行添加聯系人用例

  40. excel.change_sheet(contact_data_sheet)

  41. contact_all_data = excel.get_all_row_data()

  42. contact_headline_data = contact_all_data[0]

  43. # 在測試結果中,一個賬號下的聯系人數據標題行僅寫一次

  44. contact_headline_flag = True

  45. for contact_row_data in contact_all_data[1:]:

  46. if contact_row_data[CONTACT_IS_EXECUTE_COL].lower() == "n":

  47. continue

  48. contact_row_data[CONTACT_TEST_TIME_COL] = get_english_datetime()

  49. try:

  50. add_contact(driver, contact_row_data[CONTACT_NAME_COL], contact_row_data[CONTACT_EMAIL_COL],

  51. contact_row_data[CONTACT_PHONE_COL], contact_row_data[CONTACT_IS_STAR_COL],

  52. contact_row_data[CONTACT_REMARK_COL], contact_row_data[CONTACT_ASSERT_KEYWORD_COL])

  53. info("添加聯系人成功【姓名:{}, 郵箱:{}, 手機號:{}, 是否星標聯系人:{}, "

  54. "備注:{}, 斷言關鍵字:{}】".format(contact_row_data[CONTACT_NAME_COL], contact_row_data[CONTACT_EMAIL_COL],

  55. contact_row_data[CONTACT_PHONE_COL], contact_row_data[CONTACT_IS_STAR_COL],

  56. contact_row_data[CONTACT_REMARK_COL], contact_row_data[CONTACT_ASSERT_KEYWORD_COL]))

  57. contact_row_data[CONTACT_TEST_RESULT_COL] = "pass"

  58. except:

  59. error("添加聯系人失敗【姓名:{}, 郵箱:{}, 手機號:{}, 是否星標聯系人:{}, "

  60. "備注:{}, 斷言關鍵字:{}】".format(contact_row_data[CONTACT_NAME_COL], contact_row_data[CONTACT_EMAIL_COL],

  61. contact_row_data[CONTACT_PHONE_COL], contact_row_data[CONTACT_IS_STAR_COL],

  62. contact_row_data[CONTACT_REMARK_COL], contact_row_data[CONTACT_ASSERT_KEYWORD_COL]))

  63. contact_row_data[CONTACT_TEST_RESULT_COL] = "fail"

  64. contact_row_data[CONTACT_TEST_EXCEPTION_INFO_COL] = traceback.format_exc()

  65. contact_row_data[CONTACT_SCREENSHOT_COL] = take_screenshot(driver)

  66. # 寫入登錄用例的測試結果

  67. excel.change_sheet("測試結果")

  68. if contact_headline_flag:

  69. excel.write_row_data(contact_headline_data, "red")

  70. contact_headline_flag = False

  71. excel.write_row_data(contact_row_data)

  72. excel.save()

  73. # 切換另一個賬號時需先關閉瀏覽器,否則會自動登錄

  74. driver.quit()

  75. if __name__ == "__main__":

  76. batch_login_and_add_contact(TEST_DATA_FILE_PATH, "chrome", "126賬號")

util 包

用于實現測試過程中調用的工具類方法,例如讀取配置文件、頁面元素的操作方法、操作Excel文件等。

excel_util.py
(openpyxl 版本:3.0.4)

  1. from openpyxl import load_workbook

  2. from openpyxl.styles import PatternFill, Font, Side, Border

  3. import os

  4. class Excel:

  5. def __init__(self, test_data_file_path):

  6. # 文件格式校驗

  7. if not os.path.exists(test_data_file_path):

  8. print("Excel工具類初始化失敗:【{}】文件不存在!".format(test_data_file_path))

  9. return

  10. if not test_data_file_path.endswith(".xlsx") or not test_data_file_path.endswith(".xlsx"):

  11. print("Excel工具類初始化失敗:【{}】文件非excel文件類型!".format(test_data_file_path))

  12. return

  13. # 打開指定excel文件

  14. self.wb = load_workbook(test_data_file_path)

  15. # 初始化默認sheet

  16. self.ws = self.wb.active

  17. # 保存文件時使用的文件路徑

  18. self.test_data_file_path = test_data_file_path

  19. # 初始化紅、綠色,供樣式使用

  20. self.color_dict = {"red": "FFFF3030", "green": "FF008B00"}

  21. # 查看所有sheet名稱

  22. def get_sheets(self):

  23. return self.wb.sheetnames

  24. # 根據sheet名稱切換sheet

  25. def change_sheet(self, sheet_name):

  26. if sheet_name not in self.get_sheets():

  27. print("sheet切換失敗:【{}】指定sheet名稱不存在!".format(sheet_name))

  28. return

  29. self.ws = self.wb.get_sheet_by_name(sheet_name)

  30. # 返回當前sheet的最大行號

  31. def max_row_num(self):

  32. return self.ws.max_row

  33. # 返回當前sheet的最大列號

  34. def max_col_num(self):

  35. return self.ws.max_column

  36. # 獲取指定行數據(設定索引從0開始)

  37. def get_one_row_data(self, row_no):

  38. if row_no < 0 or row_no > self.max_row_num()-1:

  39. print("輸入的行號【{}】有誤:需在0至最大行數之間!".format(row_no))

  40. return

  41. # API的索引從1開始

  42. return [cell.value for cell in self.ws[row_no+1]]

  43. # 獲取指定列數據

  44. def get_one_col_data(self, col_no):

  45. if col_no < 0 or col_no > self.max_col_num()-1:

  46. print("輸入的列號【{}】有誤:需在0至最大列數之間!".format(col_no))

  47. return

  48. return [cell.value for cell in tuple(self.ws.columns)[col_no+1]]

  49. # 獲取當前sheet的所有行數據

  50. def get_all_row_data(self):

  51. result = []

  52. # # API的索引從1開始

  53. for row_data in self.ws[1:self.max_row_num()]:

  54. result.append([cell.value if cell.value is not None else "" for cell in row_data])

  55. return result

  56. # 追加一行數據

  57. def write_row_data(self, data, fill_color=None, font_color=None, border=True):

  58. if not isinstance(data, (list, tuple)):

  59. print("追加的數據類型有誤:需為列號或元組類型!【{}】".format(data))

  60. return

  61. self.ws.append(data)

  62. # 添加字體顏色

  63. if font_color:

  64. if font_color in self.color_dict.keys():

  65. font_color = self.color_dict[font_color]

  66. # 需要設置的單元格長度應與數據長度一致,否則默認與之前行的長度一致

  67. count = 0

  68. for cell in self.ws[self.max_row_num()]:

  69. if count > len(data) - 1:

  70. break

  71. # cell不為None,才能設置樣式

  72. if cell:

  73. if cell.value in ["pass", "成功"]:

  74. cell.font = Font(color=self.color_dict["green"])

  75. elif cell.value in ["fail", "失敗"]:

  76. cell.font = Font(color=self.color_dict["red"])

  77. else:

  78. cell.font = Font(color=font_color)

  79. count += 1

  80. # 添加背景顏色

  81. if fill_color:

  82. if fill_color in self.color_dict.keys():

  83. fill_color = self.color_dict[fill_color]

  84. count = 0

  85. for cell in self.ws[self.max_row_num()]:

  86. if count > len(data) - 1:

  87. break

  88. if cell:

  89. cell.fill = PatternFill(fill_type="solid", fgColor=fill_color)

  90. count += 1

  91. # 添加單元格邊框

  92. if border:

  93. bd = Side(style="thin", color="000000")

  94. count = 0

  95. for cell in self.ws[self.max_row_num()]:

  96. if count > len(data) - 1:

  97. break

  98. if cell:

  99. cell.border = Border(left=bd, right=bd, top=bd, bottom=bd)

  100. count += 1

  101. # 保存文件

  102. def save(self):

  103. self.wb.save(self.test_data_file_path)

  104. if __name__ == "__main__":

  105. from conf.global_var import *

  106. excel = Excel(TEST_DATA_FILE_PATH)

  107. excel.change_sheet("登錄1")

  108. # print(excel.get_all_row_data())

  109. excel.write_row_data((1,2,"嘻哈",None,"ddd"), "red", "green")

  110. excel.save()

find_element_util.py

  1. from selenium.webdriver.support.ui import WebDriverWait

  2. # 顯式等待一個對象

  3. def find_element(driver, locate_method, locate_exp):

  4. # 顯式等待對象(最多等10秒,每0.2秒判斷一次等待的條件)

  5. return WebDriverWait(driver, 10, 0.2).until(lambda x: x.find_element(locate_method, locate_exp))

  6. # 顯式等待一組對象

  7. def find_elements(driver, locate_method, locate_exp):

  8. # 顯式等待對象(最多等10秒,每0.2秒判斷一次等待的條件)

  9. return WebDriverWait(driver, 10, 0.2).until(lambda x: x.find_elements(locate_method, locate_exp))

ini_parser.py

  1. import configparser

  2. class IniParser:

  3. # 初始化打開指定ini文件并指定編碼

  4. def __init__(self, file_path, section):

  5. self.cf = configparser.ConfigParser()

  6. self.cf.read(file_path, encoding="utf-8")

  7. self.section = section

  8. # 獲取所有分組名稱

  9. def get_sections(self):

  10. return self.cf.sections()

  11. # 獲取指定分組的所有鍵

  12. def get_options(self):

  13. return self.cf.options(self.section)

  14. # 獲取指定分組的鍵值對

  15. def get_items(self):

  16. return self.cf.items(self.section)

  17. # 獲取指定分組的指定鍵的值

  18. def get_value(self, key):

  19. return self.cf.get(self.section, key)

datetime_util.py

  1. import time

  2. # 返回中文格式的日期:xxxx年xx月xx日

  3. def get_chinese_date():

  4. year = time.localtime().tm_year

  5. if len(str(year)) == 1:

  6. year = "0" + str(year)

  7. month = time.localtime().tm_mon

  8. if len(str(month)) == 1:

  9. month = "0" + str(month)

  10. day = time.localtime().tm_mday

  11. if len(str(day)) == 1:

  12. day = "0" + str(day)

  13. return "{}年{}月{}日".format(year, month, day)

  14. # 返回英文格式的日期:xxxx/xx/xx

  15. def get_english_date():

  16. year = time.localtime().tm_year

  17. if len(str(year)) == 1:

  18. year = "0" + str(year)

  19. month = time.localtime().tm_mon

  20. if len(str(month)) == 1:

  21. month = "0" + str(month)

  22. day = time.localtime().tm_mday

  23. if len(str(day)) == 1:

  24. day = "0" + str(day)

  25. return "{}/{}/{}".format(year, month, day)

  26. # 返回中文格式的時間:xx時xx分xx秒

  27. def get_chinese_time():

  28. hour = time.localtime().tm_hour

  29. if len(str(hour)) == 1:

  30. hour = "0" + str(hour)

  31. minute = time.localtime().tm_min

  32. if len(str(minute)) == 1:

  33. minute = "0" + str(minute)

  34. second = time.localtime().tm_sec

  35. if len(str(second)) == 1:

  36. second = "0" + str(second)

  37. return "{}時{}分{}秒".format(hour, minute, second)

  38. # 返回英文格式的時間:xx:xx:xx

  39. def get_english_time():

  40. hour = time.localtime().tm_hour

  41. if len(str(hour)) == 1:

  42. hour = "0" + str(hour)

  43. minute = time.localtime().tm_min

  44. if len(str(minute)) == 1:

  45. minute = "0" + str(minute)

  46. second = time.localtime().tm_sec

  47. if len(str(second)) == 1:

  48. second = "0" + str(second)

  49. return "{}:{}:{}".format(hour, minute, second)

  50. # 返回中文格式的日期時間

  51. def get_chinese_datetime():

  52. return get_chinese_date() + " " + get_chinese_time()

  53. # 返回英文格式的日期時間

  54. def get_english_datetime():

  55. return get_english_date() + " " + get_english_time()

  56. if __name__ == "__main__":

  57. print(get_chinese_datetime())

  58. print(get_english_datetime())

log_util.py

  1. import logging

  2. import logging.config

  3. from conf.global_var import *

  4. # 日志配置文件:多個logger,每個logger指定不同的handler

  5. # handler:設定了日志輸出行的格式

  6. # 以及設定寫日志到文件(是否回滾)?還是到屏幕

  7. # 還定了打印日志的級別

  8. logging.config.fileConfig(LOG_CONF_FILE_PATH)

  9. logger = logging.getLogger("example01")

  10. def debug(message):

  11. logging.debug(message)

  12. def info(message):

  13. logging.info(message)

  14. def warning(message):

  15. logging.warning(message)

  16. def error(message):

  17. logging.error(message)

  18. if __name__ == "__main__":

  19. debug("hi")

  20. info("gloryroad")

  21. warning("hello")

  22. error("這是一個error日志")

screenshot.py

  1. import logging

  2. import logging.config

  3. from conf.global_var import *

  4. # 日志配置文件:多個logger,每個logger指定不同的handler

  5. # handler:設定了日志輸出行的格式

  6. # 以及設定寫日志到文件(是否回滾)?還是到屏幕

  7. # 還定了打印日志的級別

  8. logging.config.fileConfig(LOG_CONF_FILE_PATH)

  9. logger = logging.getLogger("example01")

  10. def debug(message):

  11. logging.debug(message)

  12. def info(message):

  13. logging.info(message)

  14. def warning(message):

  15. logging.warning(message)

  16. def error(message):

  17. logging.error(message)

  18. if __name__ == "__main__":

  19. debug("hi")

  20. info("gloryroad")

  21. warning("hello")

  22. error("這是一個error日志")

conf 包

配置文件及全局變量。

elements_repository.ini

  1. [126mail_loginPage]

  2. loginPage.frame=xpath>//iframe[contains(@id,'x-URS-iframe')]

  3. loginPage.username=xpath>//input[@name='email']

  4. loginPage.password=xpath>//input[@name='password']

  5. loginPage.loginbutton=id>dologin

  6. [126mail_homePage]

  7. homePage.addressLink=xpath>//div[text()='通訊錄']

  8. [126mail_contactPersonPage]

  9. contactPersonPage.createButton=xpath>//span[text()='新建聯系人']

  10. contactPersonPage.name=xpath>//a[@title='編輯詳細姓名']/preceding-sibling::div/input

  11. contactPersonPage.email=xpath>//*[@id='iaddress_MAIL_wrap']//input

  12. contactPersonPage.starContacts=xpath>//span[text()='設為星標聯系人']/preceding-sibling::span/b

  13. contactPersonPage.phone=xpath>//*[@id='iaddress_TEL_wrap']//dd//input

  14. contactPersonPage.otherinfo=xpath>//textarea

  15. contactPersonPage.confirmButton=xpath>//span[.='確 定']

global_var.py

  1. import os

  2. # 工程根路徑

  3. PROJECT_ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

  4. # 元素定位方法的ini配置文件路徑

  5. ELEMENT_FILE_PATH = os.path.join(PROJECT_ROOT_PATH, "conf", "elements_repository.ini")

  6. # 驅動路徑

  7. CHROME_DRIVER = "E:\\auto_test_driver\\chromedriver.exe"

  8. IE_DRIVER = "E:\\auto_test_driver\\IEDriverServer.exe"

  9. FIREFOX_DRIVER = "E:\\auto_test_driver\\geckodriver.exe"

  10. # 測試使用的瀏覽器

  11. BROWSER_NAME = "chrome"

  12. # 登錄主頁

  13. LOGIN_URL = "https://mail.126.com"

  14. # 日志配置文件路徑

  15. LOG_CONF_FILE_PATH = os.path.join(PROJECT_ROOT_PATH, "conf", "logger.conf")

  16. # 測試用例文件路徑

  17. TEST_DATA_FILE_PATH = os.path.join(PROJECT_ROOT_PATH, "test_data", "測試用例.xlsx")

  18. # 截圖保存路徑

  19. SCREENSHOT_PATH = os.path.join(PROJECT_ROOT_PATH, "screenshot_path")

  20. # 單元測試報告輸出目錄

  21. UNITTEST_REPORT_PATH = os.path.join(PROJECT_ROOT_PATH, "report")

  22. # 登錄賬號sheet頁數據列號

  23. ACCOUNT_USERNAME_COL = 1

  24. ACCOUNT_PWD_COL = 2

  25. ACCOUNT_DATA_SHEET_COL = 3

  26. ACCOUNT_IS_EXECUTE_COL = 4

  27. ACCOUNT_TEST_TIME_COL = 5

  28. ACCOUNT_TEST_RESULT_COL = 6

  29. ACCOUNT_TEST_EXCEPTION_INFO_COL = 7

  30. ACCOUNT_SCREENSHOT_COL = 8

  31. # 聯系人sheet頁數據列號

  32. CONTACT_NAME_COL = 1

  33. CONTACT_EMAIL_COL = 2

  34. CONTACT_IS_STAR_COL = 3

  35. CONTACT_PHONE_COL = 4

  36. CONTACT_REMARK_COL = 5

  37. CONTACT_ASSERT_KEYWORD_COL = 6

  38. CONTACT_IS_EXECUTE_COL = 7

  39. CONTACT_TEST_TIME_COL = 8

  40. CONTACT_TEST_RESULT_COL = 9

  41. CONTACT_TEST_EXCEPTION_INFO_COL = 10

  42. CONTACT_SCREENSHOT_COL = 11

  43. if __name__ == "__main__":

  44. print(PROJECT_ROOT_PATH)

logger.conf

  1. ###############################################

  2. [loggers]

  3. keys=root,example01,example02

  4. [logger_root]

  5. level=DEBUG

  6. handlers=hand01,hand02

  7. [logger_example01]

  8. handlers=hand01,hand02

  9. qualname=example01

  10. propagate=0

  11. [logger_example02]

  12. handlers=hand01,hand03

  13. qualname=example02

  14. propagate=0

  15. ###############################################

  16. [handlers]

  17. keys=hand01,hand02,hand03

  18. [handler_hand01]

  19. class=StreamHandler

  20. level=INFO

  21. formatter=form01

  22. args=(sys.stderr,)

  23. [handler_hand02]

  24. class=FileHandler

  25. level=DEBUG

  26. formatter=form01

  27. args=('.\\log\\126_mail_test.log', 'a')

  28. [handler_hand03]

  29. class=handlers.RotatingFileHandler

  30. level=INFO

  31. formatter=form01

  32. args=('.\\log\\126_mail_test.log', 'a', 10*1024*1024, 5)

  33. ###############################################

  34. [formatters]

  35. keys=form01,form02

  36. [formatter_form01]

  37. format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

  38. datefmt=%Y-%m-%d %H:%M:%S

  39. [formatter_form02]

  40. format=%(name)-12s: %(levelname)-8s %(message)s

  41. datefmt=%Y-%m-%d %H:%M:%S

test_data 目錄

測試用例.xlsx:包含測試數據輸入、測試結果輸出

log 目錄

日志輸出文件:126_mail_test.log

  1. ...

  2. ...

  3. 2021-02-23 16:59:15 log_util.py[line:19] INFO 登錄成功【用戶名:zhangjun252950418, 密碼:zhangjun123, 斷言關鍵字:退出】

  4. 2021-02-23 16:59:20 log_util.py[line:19] INFO 添加聯系人成功【姓名:lily, 郵箱:lily@qq.com, 手機號:135xxxxxxx1, 是否星標聯系人:是, 備注:常聯系人, 斷言關鍵字:lily@qq.com】

  5. 2021-02-23 16:59:24 log_util.py[line:27] ERROR 添加聯系人失敗【姓名:張三, 郵箱:zhangsan@qq.com, 手機號:158xxxxxxx3, 是否星標聯系人:否, 備注:不常聯系人, 斷言關鍵字:zhangsan@qq.comxx】

  6. 2021-02-23 16:59:27 log_util.py[line:19] INFO 添加聯系人成功【姓名:李四, 郵箱:lisi@qq.com, 手機號:157xxxxxx9, 是否星標聯系人:否, 備注:, 斷言關鍵字:李四】

  7. ...

  8. ...

screenshot_path 目錄

異常截圖保存目錄:

main.py

本 PO 框架的運行主入口。

  1. from business_process.batch_login import *

  2. from business_process.batch_login_and_add_contact import *

  3. from conf.global_var import *

  4. # 示例組裝:冒煙測試

  5. def smoke_test():

  6. batch_login(TEST_DATA_FILE_PATH, "chrome", "126賬號")

  7. # 示例組裝:全量測試

  8. def full_test():

  9. batch_login_and_add_contact(TEST_DATA_FILE_PATH, "chrome", "126賬號")

  10. if __name__ == "__main__":

  11. # smoke_test()

  12. full_test()

總結:

感謝每一個認真閱讀我文章的人!!!

作為一位過來人也是希望大家少走一些彎路,如果你不想再體驗一次學習時找不到資料,沒人解答問題,堅持幾天便放棄的感受的話,在這里我給大家分享一些自動化測試的學習資源,希望能給你前進的路上帶來幫助。

軟件測試面試文檔

我們學習必然是為了找到高薪的工作,下面這些面試題是來自阿里、騰訊、字節等一線互聯網大廠最新的面試資料,并且有字節大佬給出了權威的解答,刷完這一套面試資料相信大家都能找到滿意的工作。

?

? ? ? ? ? 視頻文檔獲取方式:
這份文檔和視頻資料,對于想從事【軟件測試】的朋友來說應該是最全面最完整的備戰倉庫,這個倉庫也陪伴我走過了最艱難的路程,希望也能幫助到你!以上均可以分享,點下方小卡片即可自行領取。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/41976.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41976.shtml
英文地址,請注明出處:http://en.pswp.cn/web/41976.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python學習中進行條件判斷(if, else, elif)

條件判斷是編程中必不可少的一部分&#xff0c;它讓程序可以根據不同的條件執行不同的代碼塊。在Python中&#xff0c;主要使用if、elif和else語句來實現條件判斷。 基本語法 在Python中&#xff0c;條件判斷的基本語法如下&#xff1a; if condition:# 當condition為True時…

一篇讀懂128陷阱

128陷阱 128陷阱的概念包裝器類自動裝箱自動拆箱128陷阱 Intager源碼equals 128陷阱的概念 首先想要清楚什么是128陷阱&#xff0c;需要了解一些概念 包裝器類 包裝器類&#xff08;Wrapper classes&#xff09;是Java中的一組類&#xff0c;它們允許將基本數據類型&#xf…

NCCL 中的一些輔助debug 知識點

1&#xff0c;調試nccl 啟動kernel的方法 ncclLaunchKernel cuLaunchKernelEx ncclStrongStreamLaunchKernel cudaLaunchKernel ncclLaunchOneRank cudaLaunchKernel 在 nccl lib 中&#xff0c;不存在使用<<<grid, block,,>>> 這種類似方式啟…

算法題型歸類整理及同類題型解法思路總結(持續更新)

1、最優路線 通用思路 1、遞歸 #案例1-最優路測路線 題目描述 評估一個網絡的信號質量&#xff0c;其中一個做法是將網絡劃分為柵格&#xff0c;然后對每個柵格的信號質量計算。 路測的時候&#xff0c;希望選擇一條信號最好的路線&#xff08;彼此相連的柵格集合&#x…

12種增強Python代碼的函數式編程技術

前言 什么是函數式編程&#xff1f; 一句話總結&#xff1a;函數式編程(functional programming)是一種編程范式&#xff0c;之外還有面向對象&#xff08;OOP&#xff09;、面向過程、邏輯式編程等。 函數式編程是一種高度抽象的編程范式&#xff0c;它倡導使用純函數&#x…

算法·二分

二分枚舉 適用條件&#xff1a; 答案有明顯上下界答案具有單調性:a滿足,若b>a可以知b必定滿足。本質上是枚舉的對數優化 思維技巧 解決問題->>驗證答案,明顯前者比后者更加困難若題目有最大值最小&#xff0c;最小值最大這種經典條件&#xff0c;隱含著答案有界 …

Docker-11☆ Docker Compose部署RuoYi-Cloud

一、環境準備 1.安裝Docker 附:Docker-02-01☆ Docker在線下載安裝與配置(linux) 2.安裝Docker Compose 附:Docker-10☆ Docker Compose 二、源碼下載 若依官網:RuoYi 若依官方網站 鼠標放到"源碼地址"上,點擊"RuoYi-Cloud 微服務版"。 跳轉至G…

深入理解計算機系統 CSAPP 家庭作業8.22

書本知識夠你寫出答案,但是如果你想驗證你寫的答案,就要一些額外的東西.這本書很多題目都是如此 /** mysystem.c*/ #include <stdio.h> #include "csapp.h"int mysystem(char* command) {pid_t pid;int status;if ((pid Fork()) 0) {/*這里是關鍵用子程序去…

新加坡工作和生活指北:工作篇

文章首發于公眾號&#xff1a;Keegan小鋼 一年多以前&#xff08;2022 年 8 月初&#xff09;&#xff0c;那時我過來新加坡才 4 個多月&#xff0c;就寫了篇文章分享了當時在新加坡的生活和工作體驗。文章得到的反響不錯&#xff0c;但也反饋出了一些新的問題&#xff0c;比如…

預訓練對齊:數學理論到工程實踐的橋梁

在人工智能和機器學習領域&#xff0c;預訓練模型的對齊是一個至關重要的概念。本篇博客源自聽了一場黃民烈老師關于大模型對齊的分享&#xff0c;整理內容如下&#xff0c;供大家參考。 數學理論中的預訓練對齊 數學理論上&#xff0c;預訓練對齊是什么&#xff1f; 序列…

Java-關鍵字(static,final)

1.1 static關鍵字 static關鍵字 : 靜態的意思 , 可以修飾變量 , 也可以修飾方法 , 被static修飾的成員 , 我們叫做靜態成員 static特點 : 靜態成員被所類的所有對象共享 隨著類的加載而加載 , 優先于對象存在 可以通過對象調用 , 也可以通過類名調用 , 建議使用類名 1. 靜…

Keepalived+HAProxy 集群及虛IP切換實踐

1、軟件介紹 ①Keepalived keepalive是一個用c語言編寫的路由軟件&#xff0c;這個項目的主要目標是為Linux系統和基于Linux的基礎設施提供簡單而健壯的負載平衡和高可用性設施。負載均衡框架依賴于眾所周知且廣泛使用的Linux Virtual Server (IPVS)內核模塊提供第4層負載均衡…

srs直播內網拉流帶寬飆升問題記錄

問題背景 srs部署在云服務器上&#xff0c;32核cpu&#xff0c;64G內存&#xff0c;帶寬300M. 客戶端從srs拉流&#xff0c;發現外網客戶端拉流&#xff0c;cpu和帶寬都正常。然而內網客戶端拉流&#xff0c;拉流人數超過5人以上&#xff0c;帶寬就會迅速飆升。 排查 用srs…

數學建模論文寫作文檔word

目錄 1. 摘要寫法1.1 確定題目與方法1.2 編寫開頭段落1.3 填寫問題一1.4 重復步驟3填寫其他問題1.5 編寫結尾段落1.6 編寫關鍵詞 2. 問題重述2.1 問題背景2.2 問題提出 3. 問題分析4. 問題X模型的建立與求解5. 模型的分析5.1 靈敏度分析5.2 誤差分析&#xff08;主要用于預測類…

Milvus lite start 及存儲策略

背景 今天開始寫下Milvus&#xff0c;為了方便&#xff0c;我直接使用的是 milvus-lite 版本&#xff0c;default 情況下&#xff0c;你可能不知道他到底將 db 存儲到什么位置了。啟動 default-server&#xff0c;看下Milvus 的start及存儲邏輯 主邏輯 def start(self):sel…

adb參數詳解

文章目錄 1. -d2. -e3. -s4. -t5. -H6. -P7. -L8. --one-device9. --exit-on-write-error10. connect / disconnect11. pair12. forward13. forward --list14. reverse15. mdns check16. mdns services17. push18. pull19. sync20.shell21. install22. uninstall23. bugreport2…

最小二乘支持向量機(Least Squares Support Vector Machine,LSSVM)及其Python和MATLAB實現

LSSVM&#xff08;Least Squares Support Vector Machine&#xff09;又稱最小二乘支持向量機&#xff0c;是支持向量機&#xff08;SVM&#xff09;的一種變體&#xff0c;它通過將SVM的優化問題轉化為帶約束的二次規劃問題&#xff0c;利用最小二乘法進行優化求解&#xff0c…

redis集群部署 (通過redis工具快速部署,手動部署)

目錄 一、快速部署集群 1、 進入集群目錄&#xff0c;創建集群 2、 查看正常啟動 二、部署集群 1、分配集群節點 2、驗證集群可用性 3、停止redis進程 三、手動部署集群 1、配置redis.conf配置文件 2、啟動redis集群 3、手動創建redis集群 4、驗證 四、集群…

mysql異常數據損壞處理,報錯:Operating system error number 2 in a file operation

一、問題描述 某次一線反應&#xff0c;某主庫表全部丟失&#xff0c;查看為空&#xff0c;登陸主機查看mysqld.log后報錯&#xff1a;Operating system error number 2 in a file operation數據目錄OS重裝后修改過&#xff0c;但只是指向方式不同&#xff0c;目錄還是同一目錄…

【綠色版】Mysql下載、安裝、配置與使用(保姆級教程)

大家都知道&#xff0c;Mysql安裝版的卸載過程非常繁瑣&#xff0c;而且卸載不干凈會出現許多問題&#xff0c;很容易讓大家陷入重裝系統的窘境。基于此&#xff0c;博主今天給大家分享綠色版Mysql的安裝、配置與使用。 目錄 一、Mysql安裝、配置與使用 1、下載解壓 2、創建…