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. 工程代码示例

本框架的 github 地址:https://github.com/juno3550/UIPOFramework

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()

UI自动化测试框架:PO模式+数据驱动的更多相关文章

  1. Web自动化测试框架-PO模式

    Web自动化测试框架(WebTestFramework)是基于Selenium框架且采用PageObject设计模式进行二次开发形成的框架. 一.适用范围:传统Web功能自动化测试.H5功能自动化测试 ...

  2. 数据驱动 vs 关键字驱动:对搭建UI自动化测试框架的探索

    UI自动化测试用例剖析 让我们先从分析一端自动化测试案例的代码开始我们的旅程.以下是我之前写的一个自动化测试的小Demo.这个Demo基于Selenium与Java.由于现在Selenium在自动化测 ...

  3. UI自动化测试框架(项目实战)python、Selenium(日志、邮件、pageobject)

    其实百度UI自动化测试框架,会出来很多相关的信息,不过就没有找到纯项目的,无法拿来使用的:所以我最近就写了一个简单,不过可以拿来在真正项目中可以使用的测试框架. 项目的地址:https://githu ...

  4. 简单Web UI 自动化测试框架 pyse

    WebUI automation testing framework based on Selenium and unittest. 基于 selenium 和 unittest 的 Web UI自动 ...

  5. 基于selenium+Python3.7+yaml+Robot Framework的UI自动化测试框架

    前端自动化测试框架 项目说明 本框架是一套基于selenium+Python3.7+yaml+Robot Framework而设计的数据驱动UI自动化测试框架,Robot Framework 作为执行 ...

  6. 自动化测试中级篇——LazyAndroid UI自动化测试框架使用指南

    原文地址https://blog.csdn.net/iamhuanggua/article/details/53104345 简介   一直以来,安卓UI自动化测试都存在以下两个障碍,一是测试工具Mo ...

  7. Ui自动化测试框架

    为了提高我们的UI测试效率,我们引用Ui自动化测试框架,这里简单先描述一下,后续会详细补充: 了解一个测试框架,我们就需要了解一下源码,能看懂源码即可: 1.稳定先封装wait EC,电脑性能配置较好 ...

  8. UI自动化测试框架 ---TestCafe

    UI自动化测试框架 ---TestCafe 官网文档链接: https://devexpress.github.io/testcafe/ https://devexpress.github.io/te ...

  9. UI自动化测试框架:关键字驱动+数据驱动

    1. 关键字驱动框架简介 2. 工程结构说明 3. 工程代码实现 action 包  page_action.py business_process 包 case_process.py data_so ...

随机推荐

  1. java基础学习——Swing图形化用户界面编程

    原文链接:https://blog.csdn.net/yiziweiyang/article/details/52317240 链接有详细解释,也有例子,以下是个人参照例子实验的代码. package ...

  2. 微信小程序:利用map方法方便获得对象数组中的特定属性值们

  3. 后端程序员之路 58、go wlog

    daviddengcn/go-colortext: Change the color of console text.https://github.com/daviddengcn/go-colorte ...

  4. 【Android笔记】Thread类中关于join()方法的源码分析

    1.join()方法的作用: 例如有一个线程对象为Thread1,在main()方法中调用Thread1.join()方法可使得当前线程(即主线程)阻塞,而执行Thread1线程. 2.源码分析(以上 ...

  5. 【资源下载】Linux下的Hi3861一站式鸿蒙开发烧录(附工具)

    下载附件 2021春节前夕,华为发布了 HUAWEI DevEco Device Tool 2.0 Beta1,整体提供了异常强大的功能.得知消息后,我在第一时间带着无比兴奋的心情下载尝鲜,但结果却是 ...

  6. go中sync.Cond源码解读

    sync.Cond 前言 什么是sync.Cond 看下源码 Wait Signal Broadcast 总结 sync.Cond 前言 本次的代码是基于go version go1.13.15 da ...

  7. 前后端分离之DRF——1

    1. 作用 1. 序列化,序列化器会把模型对象转成字典,经过 response 以后变成 json 字符串 2. 反序列化,把客户端发送过来的数据,经过 request 以后变成字典,序列化器可以把字 ...

  8. Shell:如何写一个多选菜单的脚本

    Blog:博客园 个人 翻译自How to Create a Multiple Choice Menu in Bash Scripts 目录 多选菜单脚本介绍 配置输入提示 创建预定选项列表 创建预选 ...

  9. AbstractQueuedSynchronizer之AQS

    一.可重入锁 可参考:可重入锁和递归锁 1,定义 指的是同一线程外层函数获得锁后,再进入该线程的内层方法会自动获取锁(前提:锁对象是同一个对象). Java中的ReentranLock(显示锁)和Sy ...

  10. IDEA的注册方式

    http://idea.lanyus.com/ 使用前请将"0.0.0.0 account.jetbrains.com"添加到hosts文件中 hosts文件在C:\Windows ...