UI 自动化测试框架:PO 模式+数据驱动
本工程的 github 地址:https://github.com/juno3550/UIPOFramework
1. PO 设计模式简介
2. 工程结构说明
3. 工程代码实现
1. PO 设计模式简介
什么是 PO 模式?
PO(PageObject)设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成一个 Page 类,并以页面为单位来写测试用例,实现页面对象和测试用例的分离。
PO 模式的设计思想与面向对象相似,能让测试代码变得可读性更好,可维护性高,复用性高。
PO 模式可以把一个页面分为三个层级:对象库层、操作层、业务层。
-
对象库层:封装定位元素的方法。
-
操作层:封装对元素的操作。
-
业务层:将一个或多个操作组合起来完成一个业务功能。
一条测试用例可能需要多个步骤操作元素,将每一个步骤单独封装成一个方法,在执行测试用例时调用封装好的方法进行操作。
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 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()