PO模型的概念和理解:
PO就是一个设计思想,将代码以页面为单位进行组织,针对这个页面上的所有信息、相关操作都放到一个类中,从而使具体的测试用例变成了简单的调用和验证操作。
优点:进行了拆分和分层
缺点:对于复杂的业务page层变了,case也需要去改动
目录结构:
run_main.py 执行文件
common 公共方法
data 存放配置文件、元素定位文件、测试数据文件
logs 存放日志文件
report 测试报告
pageobj 页面元素
action 页面操作
testcase 测试用例
1、执行文件
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2018-04-15 09:00:00 # @Author : Canon # @Link : https://www.python.org # @Version : 3.6.1 import os import sys import unittest from common.HTMLTestRunner import HTMLTestRunner from common.logger import Log # 当前脚本所在文件真实路径 CUR_PATH = os.path.dirname(os.path.realpath(__file__)) # 日志 log = Log() def add_case(case_name="testcase", rule="test*.py"): """ 第一步:加载所有的测试用例 :meth case_name: 测试用例文件夹名称 type: str :meth rule: 匹配规则 type: str :return: """ # 用例文件夹路径 case_path = os.path.join(CUR_PATH, case_name) # 如果不存在这个case文件夹,就自动创建一个 if not os.path.exists(case_path): os.mkdir(case_path) log.info("test case path: %s" % case_path) # 定义discover方法的参数 discover = unittest.defaultTestLoader.discover(case_path, pattern=rule, top_level_dir=None) return discover def run_case(all_case, report_name="report"): """ 第二步:执行所有的用例, 并把结果写入 HTML 测试报告 :meth all_case: 所有测试用例 type: str :meth report_name: 测试报告文件夹名称 type: str :return: """ # now = time.strftime("_%Y-%m-%d %H-%M-%S") now = "" # 测试报告文件夹 report_dir = os.path.join(CUR_PATH, report_name) # 如果不存在这个report文件夹,就自动创建 if not os.path.exists(report_dir): os.mkdir(report_dir) report_abspath = os.path.join(report_dir, "TestResult" + now + ".html") log.info("report path: %s" % report_abspath) fp = open(report_abspath, "wb") runner = HTMLTestRunner(stream=fp, title='自动化测试报告,测试结果如下:', description='用例执行情况:') # 调用 add_case 函数返回值 log.info("----- 开始执行测试用例 -----") runner.run(all_case) log.info("----- 结束执行测试用例 -----") fp.close() def main(): case_dir = "testcase/gateway/check" # case_dir = "testcase" # # 测试项目 # program = sys.argv[1] # if program == "gateway": # case_dir = "testcase/gateway/pay" # elif program == "check": # case_dir = "testcase/gateway/check" # 1、加载用例 load_case = add_case(case_dir) # 2、执行用例 run_case(load_case) if __name__ == "__main__": main()
2、公共方法目录
页面基本操作
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2018-04-15 09:00:00 # @Author : Canon # @Link : https://www.python.org # @Version : 3.6.1 import win32gui import win32con import time import platform from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.select import Select from selenium.webdriver.common.action_chains import ActionChains from common.logger import Log # 日志 log = Log() class Action(object): """ BasePage封装所有页面的公共方法 """ def __init__(self, selenium_driver): """ 初始化 driver :meth selenium_driver: 浏览器驱动 """ self.driver = selenium_driver def _open(self, url, page_title): """ 打开页面,校验页面链接是否加载正确 :meth url: 链接地址 type: str :meth page_title: 页面标题 type: str :return: """ # 使用get打开访问链接地址 self.driver.get(url) try: # 使用assert进行校验,打开的链接地址是否与配置的地址一致, 调用on_page()方法 assert self.on_page(page_title), "打开页面失败 {}".format(url) except Exception as err: log.error("打开页面失败 \n%s" % err) raise Exception("打开页面失败") def open(self, base_url, page_title): """ 定义 open 方法,调用 _open() 打开链接 :return: """ self._open(base_url, page_title) def on_page(self, page_title): """ 使用 current_url 获取当前窗口 url 地址,进行与配置地址作比较,返回比较结果(True or False) :meth page_title: 页面标题 type: str :return: type: bool """ return page_title in self.driver.title def find_element(self, time_out=10, *loc): """ 重写元素定位方法(单个元素) :meth time_out: 超时时间 type: int :meth loc: 元素定位 type: str :return: """ try: # 等待元素出现 # WebDriverWait(self.driver, 10).until(self.driver.find_element(*loc).is_displayed()) WebDriverWait(self.driver, time_out).until(lambda driver: driver.find_element(*loc).is_displayed()) return self.driver.find_element(*loc) except Exception as err: log.error("%s 页面中未能找到 %s 元素 \n页面链接:%s \n%s" % (self.driver.title, loc, self.driver.current_url, err)) def find_elements(self, time_out=10, *loc): """ 重写元素定位方法(多个元素) :meth time_out: 超时时间 type: int :meth loc: 元素定位 type: str :return: """ try: # 等待元素出现 WebDriverWait(self.driver, time_out).until(lambda driver: driver.find_element(*loc).is_displayed()) return self.driver.find_elements(*loc) except Exception as err: log.error("%s 页面中未能找到 %s 元素 \n页面链接:%s \n%s" % (self.driver.title, loc, self.driver.current_url, err)) def upload_file(self, loc, file_path): """ 上传文件 :param loc: 元素定位 type: tuple :param file_path: 文件路径 type: str :return: """ try: # 点击上传按钮 self.click_button(loc) # 判断是哪个操作系统 if platform.system() == "Windows": file_path = file_path.replace("/", "\\") # 主窗口(上传文件对话框), chrome 浏览器弹窗标题是"打开", firefox 浏览器弹窗标题是"文件上传" dialog = win32gui.FindWindow('#32770', '打开') # 重试次数 num = 5 for i in range(num): # 判断是否获取到窗口句柄 if dialog == 0: time.sleep(1) dialog = win32gui.FindWindow('#32770', '打开') else: # 上面三句依次寻找对象,直到找到输入框 edit 对象的句柄 combobox_ex32 = win32gui.FindWindowEx(dialog, 0, 'ComboBoxEx32', None) combobox = win32gui.FindWindowEx(combobox_ex32, 0, 'ComboBox', None) edit = win32gui.FindWindowEx(combobox, 0, 'Edit', None) # 确定按钮 button = win32gui.FindWindowEx(dialog, 0, 'Button', None) # 在输入框中, 输入绝对路径 win32gui.SendMessage(edit, win32con.WM_SETTEXT, None, file_path) # 点击打开按钮 win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button) break if dialog == 0: log.error("上传文件失败\n 上传按钮定位: %s\n 上传文件: %s\n " % (loc, file_path)) except Exception as err: log.error("上传文件失败: %s %s\n%s" % (loc, file_path, err)) def switch_frame(self, tag_name, num): """ 切换 frame 标签 :meth tag_name: 元素定位 type: str :meth num: 位置 type: int :return: """ try: ele_list = self.driver.find_elements_by_tag_name(tag_name) self.driver.switch_to.frame(ele_list[num]) except Exception as err: log.error("切换 frame 错误: %s \n%s" % (tag_name, err)) def exec_script(self, src): """ 用于执行js脚本,返回执行结果 :meth src: js 脚本 type: str :return: """ try: self.driver.execute_script(src) except Exception as err: log.error("执行js脚本错误: %s \n%s" % (src, err)) def send_keys(self, loc, value, *text_name): """ 重写 send_keys 方法 :meth loc: 元素定位 type: tuple :meth value: 输入值 type: str :meth text_name: 文本框名称 type: tuple :return: """ try: if loc[0] == 'js': self.exec_script(loc[2]) else: # 点击文本框 self.find_element(int(loc[1]), *(loc[0], loc[2].format(text_name))).click() # 清空文本框 self.find_element(int(loc[1]), *(loc[0], loc[2].format(text_name))).clear() # 输入文本值 self.find_element(int(loc[1]), *(loc[0], loc[2].format(text_name))).send_keys(value) except AttributeError as err: log.error("输入文本错误: %s \n%s" % (loc, err)) def click_button(self, loc, *btn_name): """ 点击操作 :meth loc: 元素定位 type: tuple :meth btn_name: 按钮名称 type: tuple :return: """ try: if loc[0] == 'js': self.exec_script(loc[2]) else: self.find_element(int(loc[1]), *(loc[0], loc[2].format(btn_name))).click() except AttributeError as err: log.error("点击元素错误: %s \n%s" % (loc, err)) def select_checkbox(self, loc): """ 勾选复选框 :meth loc: 元素定位 type: tuple :return: """ try: if loc[0] == 'js': self.exec_script(loc[2]) else: # 判断复选框是否被勾选 if not self.find_element(int(loc[1]), *(loc[0], loc[2])).is_selected(): self.find_element(int(loc[1]), *(loc[0], loc[2])).click() except Exception as err: log.error("勾选文本框错误: %s \n%s" % (loc, err)) def mouse_move(self, loc): """ 模拟鼠标悬停 :meth loc: 元素定位 type: tuple :return: """ try: # 鼠标移到悬停元素上 ActionChains(self.driver).move_to_element(self.find_element(int(loc[1]), *(loc[0], loc[2]))).perform() except Exception as err: log.error("鼠标悬停错误: %s \n%s" % (loc, err)) def select_combobox(self, loc, value): """ 选择下拉框的值, <select>标签下拉菜单 :meth loc: 元素定位 type: tuple :meth value: 选项值 type: str :return: """ try: if loc[0] == 'js': self.exec_script(loc[2]) else: Select(self.find_element(int(loc[1]), *(loc[0], loc[2]))).select_by_value(value) except Exception as err: log.error("选择下拉框错误: %s \n%s" % (loc, err)) def select_ul(self, select_loc, item_loc, select, item): """ 选择下拉框的值, 非<select>标签下拉菜单 :meth select_loc: 下拉框元素定位 type: tuple :meth item_loc: 选项值元素定位 type: tuple :meth select: 下拉框名称 type: str :meth item: 选项值 type: str :return: """ try: if item_loc[0] == 'js': self.exec_script(item_loc[2]) elif select_loc[0] == 'js': self.exec_script(select_loc[2]) else: loc_sele = (select_loc[0], int(select_loc[1]), select_loc[2].format(select)) self.click_button(loc_sele) loc_val = (item_loc[0], int(item_loc[1]), select_loc[2].format(select, item)) self.click_button(loc_val) # 拖动控件内的滚动条 # self.driver.execute_script("document.getElementsByClassName('dropdown-menu inner')[1].scrollTop=500;") except Exception as err: log.error("选择下拉框错误: %s, %s \n%s" % (select_loc, item_loc, err)) def get_attrval(self, loc, value): """ 获取文本框的值 :meth loc: 元素定位 type: tuple :meth value: 文本框的属性名 type: str :return: """ try: if loc[0] == 'js': return self.exec_script(loc[2]) else: return self.find_element(int(loc[1]), *(loc[0], loc[2])).get_attribute(value) except Exception as err: log.error("获取文本框错误: %s \n%s" % (loc, err)) def get_text(self, loc): """ 获取标签中的文本值 :meth loc: 元素定位 type: tuple :return: """ try: if loc[0] == 'js': return self.exec_script(loc[2]) else: return self.find_element(int(loc[1]), *(loc[0], loc[2])).text except Exception as err: log.error("获取标签中的文本值错误: %s \n%s" % (loc, err))
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2018-04-15 09:00:00 # @Author : Canon # @Link : https://www.python.org # @Version : 3.6.1 from selenium import webdriver def get_driver(): """ 无界面运行 """ # 配置浏览器参数 # options = webdriver.ChromeOptions() # options.add_argument('--headless') # return webdriver.Chrome(chrome_options=options) """ 图形界面运行 """ return webdriver.Chrome()
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2018-04-15 09:00:00 # @Author : Canon # @Link : https://www.python.org # @Version : 3.6.1 import os from configparser import ConfigParser # 项目路径 CUR_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) class Project(object): def __init__(self): self.conf = ConfigParser() # 读取项目的配置文件 self.conf.read(CUR_PATH + "/data/config/project.conf", encoding='UTF-8') def read_gateway(self): return CUR_PATH + self.conf.get("conf", "gateway") def read_oms(self): return CUR_PATH + self.conf.get("conf", "oms") def read_log(self): """ 读取日志的配置文件 :return: """ return CUR_PATH + self.conf.get("log", "path") class Gateway(object): def __init__(self): self.conf = ConfigParser() # 读取支付网关的配置文件 self.conf.read(Project().read_gateway(), encoding='UTF-8') def read_link(self): # 读取支付网关登录链接 return self.conf.get("gateway", "login") def read_domain(self, section): """ 读取支付域名名称 :meth section: 支付域名名称 type: str :return: 包含元组的列表 """ return self.conf.items(section) def read_path(self, sec, opt): return CUR_PATH + self.conf.get(sec, opt) def read_val(self, sec, opt): return self.conf.get(sec, opt) if __name__ == '__main__': val = Gateway().read_domain("domain") print(val)