UI 自動化測試框架:PO 模式+數據驅動


本工程的 github 地址:https://github.com/juno3550/UIPOFramework

1. PO 設計模式簡介

2. 工程結構說明

3. 工程代碼實現

 

 

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 框架的運行主入口。

 

框架特點

  1. 通過配置文件,實現頁面元素定位方式和測試代碼的分離。
  2. 使用 PO 模式,封裝了網頁中的頁面元素,方便測試代碼調用,也實現了一處維護全局生效的目標。
  3. 在 excel 文件中定義多組測試數據,每個登錄用戶都一一對應一個存放聯系人數據的 sheet,測試框架可自動調用測試數據完成數據驅動測試。
  4. 實現了測試執行過程中的日志記錄功能,可以通過日志文件分析測試腳本執行的情況。
  5. 在 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 
 6 # 登錄頁面元素定位及操作
 7 class LoginPage:
 8 
 9     def __init__(self, driver):
10         self.driver = driver
11         # 初始化跳轉登錄頁面
12         self.driver.get(LOGIN_URL)
13         # 初始化指定ini配置文件及指定分組
14         self.cf = IniParser(ELEMENT_FILE_PATH, "126mail_loginPage")
15 
16     # 獲取frame元素對象
17     def get_frame_obj(self):
18         locate_method, locate_exp = self.cf.get_value("loginPage.frame").split(">")
19         return find_element(self.driver, locate_method, locate_exp)
20 
21     # 切換frame
22     def switch_frame(self):
23         self.driver.switch_to.frame(self.get_frame_obj())
24 
25     # 獲取用戶名輸入框元素對象
26     def get_username_input_obj(self):
27         locate_method, locate_exp = self.cf.get_value("loginPage.username").split(">")
28         return find_element(self.driver, locate_method, locate_exp)
29 
30     # 清空用戶名輸入框操作
31     def clear_username(self):
32         self.get_username_input_obj().clear()
33 
34     # 輸入用戶名操作
35     def input_username(self, value):
36         self.get_username_input_obj().send_keys(value)
37 
38     # 獲取密碼輸入框元素對象
39     def get_pwd_input_obj(self):
40         locate_method, locate_exp = self.cf.get_value("loginPage.password").split(">")
41         return find_element(self.driver, locate_method, locate_exp)
42 
43     # 輸入密碼操作
44     def input_pwd(self, value):
45         self.get_pwd_input_obj().send_keys(value)
46 
47     # 獲取登錄按鈕對象
48     def get_login_buttion_obj(self):
49         locate_method, locate_exp = self.cf.get_value("loginPage.loginbutton").split(">")
50         return find_element(self.driver, locate_method, locate_exp)
51 
52     # 點擊登錄按鈕操作
53     def click_login_button(self):
54         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 
 6 # 登錄后主頁元素定位及操作
 7 class HomePage:
 8 
 9     def __init__(self, driver):
10         self.driver = driver
11         # 初始化指定ini配置文件及指定分組
12         self.cf = IniParser(ELEMENT_FILE_PATH, "126mail_homePage")
13 
14     # 獲取“通訊錄”按鈕對象
15     def get_contact_button_obj(self):
16         locate_method, locate_exp = self.cf.get_value("homePage.addressLink").split(">")
17         return find_element(self.driver, locate_method, locate_exp)
18 
19     # 點擊“通訊錄”按鈕
20     def click_contact_button(self):
21         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 
 6 # 通訊錄頁面元素定位及操作
 7 class ContactPage:
 8 
 9     def __init__(self, driver):
10         self.driver = driver
11         # 初始化指定ini配置文件及指定分組
12         self.cf = IniParser(ELEMENT_FILE_PATH, "126mail_contactPersonPage")
13 
14     # 獲取新建聯系人按鈕對象
15     def get_contact_create_button_obj(self):
16         locate_method, locate_exp = self.cf.get_value("contactPersonPage.createButton").split(">")
17         return find_element(self.driver, locate_method, locate_exp)
18 
19     # 點擊新建聯系人按鈕
20     def click_contact_creat_button(self):
21         self.get_contact_create_button_obj().click()
22 
23     # 獲取姓名輸入框對象
24     def get_name_input_obj(self):
25         locate_method, locate_exp = self.cf.get_value("contactPersonPage.name").split(">")
26         return find_element(self.driver, locate_method, locate_exp)
27 
28     # 輸入姓名操作
29     def input_name(self, value):
30         self.get_name_input_obj().send_keys(value)
31 
32     # 獲取郵箱輸入框對象
33     def get_email_input_obj(self):
34         locate_method, locate_exp = self.cf.get_value("contactPersonPage.email").split(">")
35         return find_element(self.driver, locate_method, locate_exp)
36 
37     # 輸入郵箱操作
38     def input_email(self, value):
39         self.get_email_input_obj().send_keys(value)
40 
41     # 獲取星標聯系人單選框對象
42     def get_star_button_obj(self):
43         locate_method, locate_exp = self.cf.get_value("contactPersonPage.starContacts").split(">")
44         return find_element(self.driver, locate_method, locate_exp)
45 
46     # 點擊星標聯系人操作
47     def click_star_button(self):
48         self.get_star_button_obj().click()
49 
50     # 獲取手機輸入框對象
51     def get_phone_input_obj(self):
52         locate_method, locate_exp = self.cf.get_value("contactPersonPage.phone").split(">")
53         return find_element(self.driver, locate_method, locate_exp)
54 
55     # 輸入郵箱操作
56     def input_phone(self, value):
57         self.get_phone_input_obj().send_keys(value)
58 
59     # 獲取備注輸入框對象
60     def get_remark_input_obj(self):
61         locate_method, locate_exp = self.cf.get_value("contactPersonPage.otherinfo").split(">")
62         return find_element(self.driver, locate_method, locate_exp)
63 
64     # 輸入郵箱操作
65     def input_remark(self, value):
66         self.get_remark_input_obj().send_keys(value)
67 
68     # 獲取確定按鈕對象
69     def get_confirm_button_obj(self):
70         locate_method, locate_exp = self.cf.get_value("contactPersonPage.confirmButton").split(">")
71         return find_element(self.driver, locate_method, locate_exp)
72 
73     # 點擊星標聯系人操作
74     def click_confirm_button(self):
75         self.get_confirm_button_obj().click()

 

action 包

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

case_action.py

from selenium import webdriver
import traceback
import time
from page.contact_page import ContactPage
from page.home_page import HomePage
from page.login_page import LoginPage
from conf.global_var import *
from util.log_util import *


# 初始化瀏覽器
def init_browser(browser_name):
    if browser_name.lower() == "chrome":
        driver = webdriver.Chrome(CHROME_DRIVER)
    elif browser_name.lower() == "firefox":
        driver = webdriver.Firefox(FIREFOX_DRIVER)
    elif browser_name.lower() == "ie":
        driver = webdriver.Ie(IE_DRIVER)
    else:
        return "Error browser name!"
    return driver


def assert_word(driver, text):
    assert text in driver.page_source


# 登錄流程封裝
def login(driver, username, pwd, assert_text):
    login_page = LoginPage(driver)
    login_page.switch_frame()
    login_page.clear_username()
    login_page.input_username(username)
    login_page.input_pwd(pwd)
    login_page.click_login_button()
    time.sleep(1)
    assert_word(driver, assert_text)


# 添加聯系人流程封裝
def add_contact(driver, name, email, phone, is_star, remark, assert_text):
    home_page = HomePage(driver)
    home_page.click_contact_button()
    contact_page = ContactPage(driver)
    contact_page.click_contact_creat_button()
    contact_page.input_name(name)
    contact_page.input_email(email)
    contact_page.input_phone(phone)
    contact_page.input_remark(remark)
    if is_star == "":
        contact_page.click_star_button()
    contact_page.click_confirm_button()
    time.sleep(2)
    assert_word(driver, assert_text)


def quit(driver):
    driver.quit()


if __name__ == "__main__":
    driver = init_browser("chrome")
    login(driver, "zhangjun252950418", "zhangjun123", "退出")
    add_contact(driver, "鐵蛋", "asfhi@123.com", "12222222222", "", "這是備注", "鐵蛋")
    # 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 # 封裝測試數據文件中用例的執行邏輯
 9 # 測試數據文件中的每個登錄賬號
10 def batch_login(test_data_file, browser_name, account_sheet_name):
11         excel = Excel(test_data_file)
12         # 獲取登錄賬號sheet頁數據
13         excel.change_sheet(account_sheet_name)
14         account_all_data = excel.get_all_row_data()
15         account_headline_data = account_all_data[0]
16         for account_row_data in account_all_data[1:]:
17             # 執行登錄用例
18             account_row_data[ACCOUNT_TEST_TIME_COL] = get_english_datetime()
19             if account_row_data[ACCOUNT_IS_EXECUTE_COL].lower() == "n":
20                 continue
21             # 初始化瀏覽器
22             driver = init_browser(browser_name)
23             try:
24                 # 默認以"退出"作為斷言關鍵字
25                 login(driver, account_row_data[ACCOUNT_USERNAME_COL], account_row_data[ACCOUNT_PWD_COL], "退出")
26                 info("登錄成功【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],
27                                                             account_row_data[ACCOUNT_PWD_COL], "退出"))
28                 account_row_data[ACCOUNT_TEST_RESULT_COL] = "pass"
29             except:
30                 error("登錄失敗【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],
31                                                             account_row_data[ACCOUNT_PWD_COL], "退出"))
32                 account_row_data[ACCOUNT_TEST_RESULT_COL] = "fail"
33                 account_row_data[ACCOUNT_TEST_EXCEPTION_INFO_COL] = traceback.format_exc()
34                 account_row_data[ACCOUNT_SCREENSHOT_COL] = take_screenshot(driver)
35             # 寫入登錄用例的測試結果
36             excel.change_sheet("測試結果")
37             excel.write_row_data(account_headline_data, "red")
38             excel.write_row_data(account_row_data)
39             excel.save()
40 
41             # 切換另一個賬號時需先關閉瀏覽器,否則會自動登錄
42             driver.quit()
43 
44 
45 if __name__ == "__main__":
46     batch_login(TEST_DATA_FILE_PATH, "chrome", "126賬號")

batch_login_and_add_contact_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 # 封裝測試數據文件中用例的執行邏輯
 9 # 測試數據文件中每個登錄賬號下,添加所有聯系人數據
10 def batch_login_and_add_contact(test_data_file, browser_name, account_sheet_name):
11         excel = Excel(test_data_file)
12         # 獲取登錄賬號sheet頁數據
13         excel.change_sheet(account_sheet_name)
14         account_all_data = excel.get_all_row_data()
15         account_headline_data = account_all_data[0]
16         for account_row_data in account_all_data[1:]:
17             # 執行登錄用例
18             account_row_data[ACCOUNT_TEST_TIME_COL] = get_english_datetime()
19             if account_row_data[ACCOUNT_IS_EXECUTE_COL].lower() == "n":
20                 continue
21             # 初始化瀏覽器
22             driver = init_browser(browser_name)
23             # 獲取聯系人數據sheet
24             contact_data_sheet = account_row_data[ACCOUNT_DATA_SHEET_COL]
25             try:
26                 # 默認以"退出"作為斷言關鍵字
27                 login(driver, account_row_data[ACCOUNT_USERNAME_COL], account_row_data[ACCOUNT_PWD_COL], "退出")
28                 info("登錄成功【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],
29                                                             account_row_data[ACCOUNT_PWD_COL], "退出"))
30                 account_row_data[ACCOUNT_TEST_RESULT_COL] = "pass"
31             except:
32                 error("登錄失敗【用戶名:{}, 密碼:{}, 斷言關鍵字:{}】".format(account_row_data[ACCOUNT_USERNAME_COL],
33                                                             account_row_data[ACCOUNT_PWD_COL], "退出"))
34                 account_row_data[ACCOUNT_TEST_RESULT_COL] = "fail"
35                 account_row_data[ACCOUNT_TEST_EXCEPTION_INFO_COL] = traceback.format_exc()
36                 account_row_data[ACCOUNT_SCREENSHOT_COL] = take_screenshot(driver)
37             # 寫入登錄用例的測試結果
38             excel.change_sheet("測試結果")
39             excel.write_row_data(account_headline_data, "red")
40             excel.write_row_data(account_row_data)
41             excel.save()
42 
43             # 執行添加聯系人用例
44             excel.change_sheet(contact_data_sheet)
45             contact_all_data = excel.get_all_row_data()
46             contact_headline_data = contact_all_data[0]
47             # 在測試結果中,一個賬號下的聯系人數據標題行僅寫一次
48             contact_headline_flag = True
49             for contact_row_data in contact_all_data[1:]:
50                 if contact_row_data[CONTACT_IS_EXECUTE_COL].lower() == "n":
51                     continue
52                 contact_row_data[CONTACT_TEST_TIME_COL] = get_english_datetime()
53                 try:
54                     add_contact(driver, 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                     info("添加聯系人成功【姓名:{}, 郵箱:{}, 手機號:{}, 是否星標聯系人:{}, "
58                          "備注:{}, 斷言關鍵字:{}】".format(contact_row_data[CONTACT_NAME_COL], contact_row_data[CONTACT_EMAIL_COL],
59                                                    contact_row_data[CONTACT_PHONE_COL], contact_row_data[CONTACT_IS_STAR_COL],
60                                                    contact_row_data[CONTACT_REMARK_COL], contact_row_data[CONTACT_ASSERT_KEYWORD_COL]))
61                     contact_row_data[CONTACT_TEST_RESULT_COL] = "pass"
62                 except:
63                     error("添加聯系人失敗【姓名:{}, 郵箱:{}, 手機號:{}, 是否星標聯系人:{}, "
64                          "備注:{}, 斷言關鍵字:{}】".format(contact_row_data[CONTACT_NAME_COL], contact_row_data[CONTACT_EMAIL_COL],
65                                                    contact_row_data[CONTACT_PHONE_COL], contact_row_data[CONTACT_IS_STAR_COL],
66                                                    contact_row_data[CONTACT_REMARK_COL], contact_row_data[CONTACT_ASSERT_KEYWORD_COL]))
67                     contact_row_data[CONTACT_TEST_RESULT_COL] = "fail"
68                     contact_row_data[CONTACT_TEST_EXCEPTION_INFO_COL] = traceback.format_exc()
69                     contact_row_data[CONTACT_SCREENSHOT_COL] = take_screenshot(driver)
70                 # 寫入登錄用例的測試結果
71                 excel.change_sheet("測試結果")
72                 if contact_headline_flag:
73                     excel.write_row_data(contact_headline_data, "red")
74                     contact_headline_flag = False
75                 excel.write_row_data(contact_row_data)
76                 excel.save()
77 
78             # 切換另一個賬號時需先關閉瀏覽器,否則會自動登錄
79             driver.quit()
80 
81 
82 if __name__ == "__main__":
83     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 
  5 
  6 class Excel:
  7 
  8     def __init__(self, test_data_file_path):
  9         # 文件格式校驗
 10         if not os.path.exists(test_data_file_path):
 11             print("Excel工具類初始化失敗:【{}】文件不存在!".format(test_data_file_path))
 12             return
 13         if not test_data_file_path.endswith(".xlsx") or not test_data_file_path.endswith(".xlsx"):
 14             print("Excel工具類初始化失敗:【{}】文件非excel文件類型!".format(test_data_file_path))
 15             return
 16         # 打開指定excel文件
 17         self.wb = load_workbook(test_data_file_path)
 18         # 初始化默認sheet
 19         self.ws = self.wb.active
 20         # 保存文件時使用的文件路徑
 21         self.test_data_file_path = test_data_file_path
 22         # 初始化紅、綠色,供樣式使用
 23         self.color_dict = {"red": "FFFF3030", "green": "FF008B00"}
 24 
 25     # 查看所有sheet名稱
 26     def get_sheets(self):
 27         return self.wb.sheetnames
 28 
 29     # 根據sheet名稱切換sheet
 30     def change_sheet(self, sheet_name):
 31         if sheet_name not in self.get_sheets():
 32             print("sheet切換失敗:【{}】指定sheet名稱不存在!".format(sheet_name))
 33             return
 34         self.ws = self.wb.get_sheet_by_name(sheet_name)
 35 
 36     # 返回當前sheet的最大行號
 37     def max_row_num(self):
 38         return self.ws.max_row
 39 
 40     # 返回當前sheet的最大列號
 41     def max_col_num(self):
 42         return self.ws.max_column
 43 
 44     # 獲取指定行數據(設定索引從0開始)
 45     def get_one_row_data(self, row_no):
 46         if row_no < 0 or row_no > self.max_row_num()-1:
 47             print("輸入的行號【{}】有誤:需在0至最大行數之間!".format(row_no))
 48             return
 49         # API的索引從1開始
 50         return [cell.value for cell in self.ws[row_no+1]]
 51 
 52     # 獲取指定列數據
 53     def get_one_col_data(self, col_no):
 54         if col_no < 0 or col_no > self.max_col_num()-1:
 55             print("輸入的列號【{}】有誤:需在0至最大列數之間!".format(col_no))
 56             return
 57         return [cell.value for cell in tuple(self.ws.columns)[col_no+1]]
 58 
 59     # 獲取當前sheet的所有行數據
 60     def get_all_row_data(self):
 61         result = []
 62         # # API的索引從1開始
 63         for row_data in self.ws[1:self.max_row_num()]:
 64             result.append([cell.value if cell.value is not None else "" for cell in row_data])
 65         return result
 66 
 67     # 追加一行數據
 68     def write_row_data(self, data, fill_color=None, font_color=None, border=True):
 69         if not isinstance(data, (list, tuple)):
 70             print("追加的數據類型有誤:需為列號或元組類型!【{}】".format(data))
 71             return
 72         self.ws.append(data)
 73         # 添加字體顏色
 74         if font_color:
 75             if font_color in self.color_dict.keys():
 76                 font_color = self.color_dict[font_color]
 77             # 需要設置的單元格長度應與數據長度一致,否則默認與之前行的長度一致
 78         count = 0
 79         for cell in self.ws[self.max_row_num()]:
 80             if count > len(data) - 1:
 81                 break
 82             # cell不為None,才能設置樣式
 83             if cell:
 84                 if cell.value in ["pass", "成功"]:
 85                     cell.font = Font(color=self.color_dict["green"])
 86                 elif cell.value in ["fail", "失敗"]:
 87                     cell.font = Font(color=self.color_dict["red"])
 88                 else:
 89                     cell.font = Font(color=font_color)
 90             count += 1
 91         # 添加背景顏色
 92         if fill_color:
 93             if fill_color in self.color_dict.keys():
 94                 fill_color = self.color_dict[fill_color]
 95             count = 0
 96             for cell in self.ws[self.max_row_num()]:
 97                 if count > len(data) - 1:
 98                     break
 99                 if cell:
100                     cell.fill = PatternFill(fill_type="solid", fgColor=fill_color)
101                 count += 1
102         # 添加單元格邊框
103         if border:
104             bd = Side(style="thin", color="000000")
105             count = 0
106             for cell in self.ws[self.max_row_num()]:
107                 if count > len(data) - 1:
108                     break
109                 if cell:
110                     cell.border = Border(left=bd, right=bd, top=bd, bottom=bd)
111                 count += 1
112 
113     # 保存文件
114     def save(self):
115         self.wb.save(self.test_data_file_path)
116 
117 
118 if __name__ == "__main__":
119     from conf.global_var import *
120     excel = Excel(TEST_DATA_FILE_PATH)
121     excel.change_sheet("登錄1")
122     # print(excel.get_all_row_data())
123     excel.write_row_data((1,2,"嘻哈",None,"ddd"), "red", "green")
124     excel.save()

find_element_util.py

 1 from selenium.webdriver.support.ui import WebDriverWait
 2 
 3 
 4 # 顯式等待一個對象
 5 def find_element(driver, locate_method, locate_exp):
 6     # 顯式等待對象(最多等10秒,每0.2秒判斷一次等待的條件)
 7     return WebDriverWait(driver, 10, 0.2).until(lambda x: x.find_element(locate_method, locate_exp))
 8 
 9 
10 # 顯式等待一組對象
11 def find_elements(driver, locate_method, locate_exp):
12     # 顯式等待對象(最多等10秒,每0.2秒判斷一次等待的條件)
13     return WebDriverWait(driver, 10, 0.2).until(lambda x: x.find_elements(locate_method, locate_exp))

ini_parser.py

 1 import configparser
 2 
 3 
 4 class IniParser:
 5 
 6     # 初始化打開指定ini文件並指定編碼
 7     def __init__(self, file_path, section):
 8         self.cf = configparser.ConfigParser()
 9         self.cf.read(file_path, encoding="utf-8")
10         self.section = section
11 
12     # 獲取所有分組名稱
13     def get_sections(self):
14         return self.cf.sections()
15 
16     # 獲取指定分組的所有鍵
17     def get_options(self):
18         return self.cf.options(self.section)
19 
20     # 獲取指定分組的鍵值對
21     def get_items(self):
22         return self.cf.items(self.section)
23 
24     # 獲取指定分組的指定鍵的值
25     def get_value(self, key):
26         return self.cf.get(self.section, key)

datetime_util.py

 1 import time
 2 
 3 
 4 # 返回中文格式的日期:xxxx年xx月xx日
 5 def get_chinese_date():
 6     year = time.localtime().tm_year
 7     if len(str(year)) == 1:
 8         year = "0" + str(year)
 9     month = time.localtime().tm_mon
10     if len(str(month)) == 1:
11         month = "0" + str(month)
12     day = time.localtime().tm_mday
13     if len(str(day)) == 1:
14         day = "0" + str(day)
15     return "{}年{}月{}日".format(year, month, day)
16 
17 
18 # 返回英文格式的日期:xxxx/xx/xx
19 def get_english_date():
20     year = time.localtime().tm_year
21     if len(str(year)) == 1:
22         year = "0" + str(year)
23     month = time.localtime().tm_mon
24     if len(str(month)) == 1:
25         month = "0" + str(month)
26     day = time.localtime().tm_mday
27     if len(str(day)) == 1:
28         day = "0" + str(day)
29     return "{}/{}/{}".format(year, month, day)
30 
31 
32 # 返回中文格式的時間:xx時xx分xx秒
33 def get_chinese_time():
34     hour = time.localtime().tm_hour
35     if len(str(hour)) == 1:
36         hour = "0" + str(hour)
37     minute = time.localtime().tm_min
38     if len(str(minute)) == 1:
39         minute = "0" + str(minute)
40     second = time.localtime().tm_sec
41     if len(str(second)) == 1:
42         second = "0" + str(second)
43     return "{}時{}分{}秒".format(hour, minute, second)
44 
45 
46 # 返回英文格式的時間:xx:xx:xx
47 def get_english_time():
48     hour = time.localtime().tm_hour
49     if len(str(hour)) == 1:
50         hour = "0" + str(hour)
51     minute = time.localtime().tm_min
52     if len(str(minute)) == 1:
53         minute = "0" + str(minute)
54     second = time.localtime().tm_sec
55     if len(str(second)) == 1:
56         second = "0" + str(second)
57     return "{}:{}:{}".format(hour, minute, second)
58 
59 
60 # 返回中文格式的日期時間
61 def get_chinese_datetime():
62     return get_chinese_date() + " " + get_chinese_time()
63 
64 
65 # 返回英文格式的日期時間
66 def get_english_datetime():
67     return get_english_date() + " " + get_english_time()
68 
69 
70 if __name__ == "__main__":
71     print(get_chinese_datetime())
72     print(get_english_datetime())

log_util.py

 1 import logging
 2 import logging.config
 3 from conf.global_var import *
 4 
 5 
 6 # 日志配置文件:多個logger,每個logger指定不同的handler
 7 # handler:設定了日志輸出行的格式
 8 #          以及設定寫日志到文件(是否回滾)?還是到屏幕
 9 #          還定了打印日志的級別
10 logging.config.fileConfig(LOG_CONF_FILE_PATH)
11 logger = logging.getLogger("example01")
12 
13 
14 def debug(message):
15     logging.debug(message)
16 
17 
18 def info(message):
19     logging.info(message)
20 
21 
22 def warning(message):
23     logging.warning(message)
24 
25 
26 def error(message):
27     logging.error(message)
28 
29 
30 if __name__ == "__main__":
31     debug("hi")
32     info("gloryroad")
33     warning("hello")
34     error("這是一個error日志")

screenshot.py

 1 import traceback
 2 import os
 3 from util.datetime_util import *
 4 from conf.global_var import *
 5 
 6 
 7 # 截圖函數
 8 def take_screenshot(driver):
 9     # 創建當前日期目錄
10     dir = os.path.join(SCREENSHOT_PATH, get_chinese_date())
11     if not os.path.exists(dir):
12         os.makedirs(dir)
13     # 以當前時間為文件名
14     file_name = get_chinese_time()
15     file_path = os.path.join(dir, file_name+".png")
16     try:
17         driver.get_screenshot_as_file(file_path)
18         # 返回截圖文件的絕對路徑
19         return file_path
20     except:
21         print("截圖發生異常【{}】".format(file_path))
22         traceback.print_exc()
23         return file_path

 

conf 包

配置文件及全局變量。

elements_repository.ini

[126mail_loginPage]
loginPage.frame=xpath>//iframe[contains(@id,'x-URS-iframe')]
loginPage.username=xpath>//input[@name='email']
loginPage.password=xpath>//input[@name='password']
loginPage.loginbutton=id>dologin

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

[126mail_contactPersonPage]
contactPersonPage.createButton=xpath>//span[text()='新建聯系人']
contactPersonPage.name=xpath>//a[@title='編輯詳細姓名']/preceding-sibling::div/input
contactPersonPage.email=xpath>//*[@id='iaddress_MAIL_wrap']//input
contactPersonPage.starContacts=xpath>//span[text()='設為星標聯系人']/preceding-sibling::span/b
contactPersonPage.phone=xpath>//*[@id='iaddress_TEL_wrap']//dd//input
contactPersonPage.otherinfo=xpath>//textarea
contactPersonPage.confirmButton=xpath>//span[.='確 定']

global_var.py

 1 import os
 2 
 3 
 4 # 工程根路徑
 5 PROJECT_ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 6 
 7 # 元素定位方法的ini配置文件路徑
 8 ELEMENT_FILE_PATH = os.path.join(PROJECT_ROOT_PATH, "conf", "elements_repository.ini")
 9 
10 # 驅動路徑
11 CHROME_DRIVER = "E:\\auto_test_driver\\chromedriver.exe"
12 IE_DRIVER = "E:\\auto_test_driver\\IEDriverServer.exe"
13 FIREFOX_DRIVER = "E:\\auto_test_driver\\geckodriver.exe"
14 
15 # 測試使用的瀏覽器
16 BROWSER_NAME = "chrome"
17 
18 # 登錄主頁
19 LOGIN_URL = "https://mail.126.com"
20 
21 # 日志配置文件路徑
22 LOG_CONF_FILE_PATH = os.path.join(PROJECT_ROOT_PATH, "conf", "logger.conf")
23 
24 # 測試用例文件路徑
25 TEST_DATA_FILE_PATH = os.path.join(PROJECT_ROOT_PATH, "test_data", "測試用例.xlsx")
26 
27 # 截圖保存路徑
28 SCREENSHOT_PATH = os.path.join(PROJECT_ROOT_PATH, "screenshot_path")
29 
30 # 單元測試報告輸出目錄
31 UNITTEST_REPORT_PATH = os.path.join(PROJECT_ROOT_PATH, "report")
32 
33 # 登錄賬號sheet頁數據列號
34 ACCOUNT_USERNAME_COL = 1
35 ACCOUNT_PWD_COL = 2
36 ACCOUNT_DATA_SHEET_COL = 3
37 ACCOUNT_IS_EXECUTE_COL = 4
38 ACCOUNT_TEST_TIME_COL = 5
39 ACCOUNT_TEST_RESULT_COL = 6
40 ACCOUNT_TEST_EXCEPTION_INFO_COL = 7
41 ACCOUNT_SCREENSHOT_COL = 8
42 
43 # 聯系人sheet頁數據列號
44 CONTACT_NAME_COL = 1
45 CONTACT_EMAIL_COL = 2
46 CONTACT_IS_STAR_COL = 3
47 CONTACT_PHONE_COL = 4
48 CONTACT_REMARK_COL = 5
49 CONTACT_ASSERT_KEYWORD_COL = 6
50 CONTACT_IS_EXECUTE_COL = 7
51 CONTACT_TEST_TIME_COL = 8
52 CONTACT_TEST_RESULT_COL = 9
53 CONTACT_TEST_EXCEPTION_INFO_COL = 10
54 CONTACT_SCREENSHOT_COL = 11
55 
56 
57 if __name__ == "__main__":
58     print(PROJECT_ROOT_PATH)

logger.conf

###############################################
[loggers]
keys=root,example01,example02
[logger_root]
level=DEBUG
handlers=hand01,hand02

[logger_example01]
handlers=hand01,hand02
qualname=example01
propagate=0

[logger_example02]
handlers=hand01,hand03
qualname=example02
propagate=0

###############################################
[handlers]
keys=hand01,hand02,hand03

[handler_hand01]
class=StreamHandler
level=INFO
formatter=form01
args=(sys.stderr,)

[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form01
args=('.\\log\\126_mail_test.log', 'a')

[handler_hand03]
class=handlers.RotatingFileHandler
level=INFO
formatter=form01
args=('.\\log\\126_mail_test.log', 'a', 10*1024*1024, 5)

###############################################
[formatters]
keys=form01,form02

[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S

[formatter_form02]
format=%(name)-12s: %(levelname)-8s %(message)s
datefmt=%Y-%m-%d %H:%M:%S

 

test_data 目錄

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

 

log 目錄

日志輸出文件:126_mail_test.log

...
...
2021-02-23 16:59:15 log_util.py[line:19] INFO 登錄成功【用戶名:zhangjun252950418, 密碼:zhangjun123, 斷言關鍵字:退出】
2021-02-23 16:59:20 log_util.py[line:19] INFO 添加聯系人成功【姓名:lily, 郵箱:lily@qq.com, 手機號:135xxxxxxx1, 是否星標聯系人:是, 備注:常聯系人, 斷言關鍵字:lily@qq.com】
2021-02-23 16:59:24 log_util.py[line:27] ERROR 添加聯系人失敗【姓名:張三, 郵箱:zhangsan@qq.com, 手機號:158xxxxxxx3, 是否星標聯系人:否, 備注:不常聯系人, 斷言關鍵字:zhangsan@qq.comxx】
2021-02-23 16:59:27 log_util.py[line:19] INFO 添加聯系人成功【姓名:李四, 郵箱:lisi@qq.com, 手機號:157xxxxxx9, 是否星標聯系人:否, 備注:, 斷言關鍵字:李四】
...
...

 

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 
 6 # 示例組裝:冒煙測試
 7 def smoke_test():
 8     batch_login(TEST_DATA_FILE_PATH, "chrome", "126賬號")
 9 
10 
11 # 示例組裝:全量測試
12 def full_test():
13     batch_login_and_add_contact(TEST_DATA_FILE_PATH, "chrome", "126賬號")
14 
15 
16 if __name__ == "__main__":
17     # smoke_test()
18     full_test()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM