web自动化针对PO模式进行二次封装之 basepage

Posted 守护@往昔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了web自动化针对PO模式进行二次封装之 basepage相关的知识,希望对你有一定的参考价值。

  在 PO 模式当中, 我们做到了 页面对象 与 测试用例 的 分离,但在页面对象编写时,我们仍然还有优化的空间。页面对象有一些共同的基本操作 ,可以封装起来,并可以在基本操作当中加上 日志 和 异常截图 的处理。比如说我们在查找元素时,都需要等待,在PO模式当中, 需要都写上 等待和查找元素,那么就可以将其封装起来,包括其它的一些比如:文本获取、元素属性获取、鼠标操作、窗口切换、iframe切换、 alert弹框关闭、 文件上传、下拉框选择.....

  当脚本运行的过程中,出现了用例失败,我们希望可以通过 日志和截图 来分析失败的原因,selenium 并没有主动生成日志和截图,所以需要在代码中加上,使用 webdriver 的 save_ screenshot 方法来截图,日志和截图会记录自动化用例的执行过程,并且,在用例的任何一个步骤出现了异常都会记录异常信息并生成相应的截图。当然为了更美观地跟allure集成,自然也需要将以异常截图展示在 allure 报告中,以下就是 basepage.py 的部分内容:

  • 1.优化我们写代码的效率,优化我们的 PO 模型,我们引入了 BasePage,我们想要实现 日志来记录执行过程,当用例执行失败的时候会在失败的那一步进行web页面截图,当失败的时候进行异常实时捕获、进行异常信息输出,并简洁我们页面封装的代码;
  • 2.因为我们任何操作都需要等待、找元素,封装页面的时候可能会漏掉忘记等待、找元素,这样就会导致代码的不稳定,所以我们页面的基本操作进行二次封装,以页面对象为基础,我们封装了 selenium中webdriver 网页的基本操作,如:等待,查找元素、文本获取、元素属性获取、鼠标操作、窗口切换、iframe切换、alert弹框关闭、文件上传、下拉框选择....;屏蔽了等待和查找元素;
  • 比如:二次封装点击操作时就存在等待和查找元素、日志输出、截图,所以比如:封装页面登录操作,我们只需要调用二次封装的输入操作及点击操作并指导该元素的定位,就可完成web页面登录行为,不需要在写等待元素可见及查找元素,失败还有日志及截图
# 因为在每一个测试用例当中,无论测试用例的那一步失败了,我们都要能够实时捕获异常/整个用例执行完毕,输出操作日志/生成失败截图
# 比如接口测试用例用 try --except 来判断异常,
# web测试用例中由一下构成;页面对象 + 测试数据 + 断言 = selenium 中webdriver 网页基本操作,这样就可以从底层进行捕获,
# 1、封装基本关键字,任何一个页面操作都可以实时捕获异常/输出操作日志/失败截图


import time
from datetime import datetime
import allure       # 报告
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.remote.webdriver import WebDriver
from Common.handle_logger import case_logger                # 日志封装
from Common.constants import OUTPUTS_SCREENSHOTS_DIR        # 页面截图保存的路径
from Common.upload_file import upload                       # 上传文件封装


class BasePage:
    \'\'\'
    BasePage类,针对PageObjects类的二次封装
    \'\'\'

    def __init__(self, driver: WebDriver):
        self.driver = driver

    def wait_element_to_be_visible(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        等待元素可见
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("开始等待页面元素<{}>是否可见!".format(loc))
            start_time = time.time()    # 没有抛出异常的时间
            WebDriverWait(self.driver, timeout, frequency).until(EC.visibility_of_element_located(loc))
        except Exception as e:
            case_logger.error("页面元素<{}>等待可见失败!".format(loc))    # 异常日志捕获
            self.save_screenshot(img_doc)       # 异常截图--通过截图名称,知道是哪个页面或者哪个模块出错了。
            raise e
        else:
            end_time = time.time()      # 抛出异常的时间
            case_logger.info("页面元素<{}>等待可见,等待时间:{}秒".format(loc, round(end_time - start_time, 2)))

    def wait_element_to_be_click(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        等待元素可点击
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("开始等待页面元素<{}>是否可点击!".format(loc))
            start_time = time.time()
            WebDriverWait(self.driver, timeout, frequency).until(EC.element_to_be_clickable(loc))
        except Exception as e:
            case_logger.error("页面元素<{}>等待可点击失败!".format(loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            end_time = time.time()
            case_logger.info("页面元素<{}>等待可点击,等待时间:{}秒".format(loc, round(end_time - start_time, 2)))

    def wait_element_to_be_exist(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        等待元素存在
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("开始等待页面元素<{}>是否存在!".format(loc))
            start_time = time.time()
            WebDriverWait(self.driver, timeout, frequency).until(EC.presence_of_element_located(loc))
        except Exception as e:
            case_logger.error("页面元素<{}>等待存在失败!".format(loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            end_time = time.time()
            case_logger.info("页面元素<{}>等待存在,等待时间:{}秒".format(loc, round(end_time - start_time, 2)))

    def save_screenshot(self, img_doc):
        \'\'\'
        页面截屏保存截图
        :param img_doc: 截图说明
        :return:
        \'\'\'
        file_name = OUTPUTS_SCREENSHOTS_DIR + "/{}_{}.png".format(
            datetime.strftime(datetime.now(), "%Y-%m-%d %H-%M-%S"), img_doc)      # 保存到那个路径,保存的名称为
        self.driver.save_screenshot(file_name)      # 存储到指定目录下
        with open(file_name, mode=\'rb\') as f:
            file = f.read()
        allure.attach(file, img_doc, allure.attachment_type.PNG)
        case_logger.info("页面截图文件保存在:{}".format(file_name))

    def get_element(self, loc, img_doc):
        \'\'\'
        获取页面中的元素
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :return: WebElement对象
        \'\'\'
        case_logger.info("在{}中查找元素<{}>".format(img_doc, loc))
        try:
            ele = self.driver.find_element(*loc)
        except Exception as e:
            case_logger.error("在{}中查找元素<{}>失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            return ele

    def get_elements(self, loc, img_doc):
        \'\'\'
        获取页面中的所有元素
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :return: WebElement对象
        \'\'\'
        case_logger.info("在{}中查找所有元素<{}>".format(img_doc, loc))
        try:
            ele = self.driver.find_elements(*loc)
        except Exception as e:
            case_logger.error("在{}中查找所有元素<{}>失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            return ele

    def input_text(self, loc, text, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        对输入框输入文本内容
        :param text: 输入的文本内容
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}中输入元素<{}>的内容为{}".format(img_doc, loc, text))
            self.wait_element_to_be_visible(loc, img_doc, timeout, frequency)
            self.get_element(loc, img_doc).send_keys(text)
        except Exception as e:
            case_logger.error("在元素<{}>中输入内容{}失败!".format(loc, text))
            self.save_screenshot(img_doc)
            raise e

    def clear_text(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        清除文本框的内容
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}中清除元素<{}>的文本内容".format(img_doc, loc))
            self.wait_element_to_be_click(loc, img_doc, timeout, frequency)
            self.get_element(loc, img_doc).clear()
        except Exception as e:
            case_logger.error("在{}中清除元素<{}>的文本内容失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e

    def click_button(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        点击按钮
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}中点击元素<{}>".format(img_doc, loc))
            self.wait_element_to_be_click(loc, img_doc, timeout, frequency)
            self.get_element(loc, img_doc).click()
        except Exception as e:
            case_logger.error("在{}中点击元素<{}>失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e

    def get_element_text(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        获取WebElement对象的文本值
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return: WebElement对象的文本值
        \'\'\'
        try:
            case_logger.info("在{}中获取元素<{}>的文本值".format(img_doc, loc))
            self.wait_element_to_be_visible(loc, img_doc, timeout, frequency)   # 等待元素可见
            text = self.get_element(loc, img_doc).text          # 找元素
        except Exception as e:
            case_logger.error("在{}中获取元素<{}>的文本值失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            case_logger.info("获取到的元素文本值为:{}".format(text))
            return text

    def get_elements_text(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        获取WebElement对象的所有文本值
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return: WebElement对象的文本值的列表
        \'\'\'
        try:
            case_logger.info("在{}中获取元素<{}>的所有文本值".format(img_doc, loc))
            self.wait_element_to_be_visible(loc, img_doc, timeout, frequency)
            all_text = self.get_elements(loc, img_doc)
            text_list = []
            for one_text in all_text:
                text_list.append(one_text.text)
        except Exception as e:
            case_logger.error("在{}中获取元素<{}>的所有文本值失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            case_logger.info("获取到的元素文本值列表为:{}".format(text_list))
            return text_list

    def get_element_attr(self, attr_name, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        获取WebElement对象的属性值
        :param attr_name: 属性名称
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return: WebElement对象的属性值
        \'\'\'
        try:
            case_logger.info("在{}中获取元素<{}>的属性{}的值".format(img_doc, loc, attr_name))
            self.wait_element_to_be_exist(loc, img_doc, timeout, frequency)
            value = self.get_element(loc, img_doc).get_attribute(attr_name)
        except Exception as e:
            case_logger.error("在{}中获取元素<{}>的属性{}的值失败!".format(img_doc, loc, attr_name))
            self.save_screenshot(img_doc)
            raise e
        else:
            case_logger.info("获取到的元素属性{}的值为{}".format(attr_name, value))
            return value

    def switch_to_frame(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        切换iframe页面
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}中根据元素<{}>进行iframe切换".format(img_doc, loc))
            start_time = time.time()
            WebDriverWait(self.driver, timeout, frequency).until(EC.frame_to_be_available_and_switch_to_it(loc))
        except Exception as e:
            case_logger.error("在{}中根据元素<{}>进行iframe切换失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            end_time = time.time()
            case_logger.info("在{}中根据元素<{}>进行iframe切换,等待时间:{}秒".
                             format(img_doc, loc, round(end_time - start_time, 2)))

    def switch_to_default_content(self, img_doc):
        \'\'\'
        切换iframe到main页面
        :param img_doc: 截图说明
        :return:
        \'\'\'
        try:
            case_logger.info("切换iframe到main页面")
            self.driver.switch_to.default_content()
        except Exception as e:
            case_logger.error("切换iframe到main页面失败!")
            self.save_screenshot(img_doc)
            raise e

    def switch_to_parent(self, img_doc):
        \'\'\'
        切换iframe到上一层页面
        :param img_doc: 截图说明
        :return:
        \'\'\'
        try:
            case_logger.info("切换iframe到上一层页面")
            self.driver.switch_to.parent_frame()
        except Exception as e:
            case_logger.error("切换iframe到上一层页面失败!")
            self.save_screenshot(img_doc)
            raise e

    def upload_file(self, filename, img_doc, browser_type="chrome"):
        \'\'\'
        非input标签的文件上传
        :param filename: 文件名(绝对路径)
        :param img_doc: 截图说明
        :param browser_type: 浏览器类型
        :return:
        \'\'\'
        try:
            case_logger.info("上传文件({})".format(filename))
            time.sleep(2)
            upload(filePath=filename, browser_type=browser_type)
        except Exception as e:
            case_logger.error("上传文件({})失败!".format(filename))
            self.save_screenshot(img_doc)
            raise e
        else:
            time.sleep(2)

    def suspend_mouse(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        鼠标悬浮
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}上根据元素<{}>进行悬浮".format(img_doc, loc))
            self.wait_element_to_be_click(loc, img_doc, timeout, frequency)
            ele = self.get_element(loc, img_doc)
            ActionChains(self.driver).move_to_element(ele).perform()
        except Exception as e:
            case_logger.error("在{}上根据元素<{}>进行悬浮失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e

    def alert_close(self, img_doc, alert_type=\'alert\', text=None, timeout=20, frequency=0.5):
        \'\'\'
        弹框关闭
        :param img_doc: 截图说明
        :param alert_type: 弹框类型:alert/confirm/prompt
        :param text: prompt弹框输入的文本
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}中切换并关闭{}类型的弹框".format(img_doc, alert_type))
            start_time = time.time()
            WebDriverWait(self.driver, timeout, frequency).until(EC.alert_is_present())
            if alert_type in [\'alert\', \'confirm\']:
                self.driver.switch_to.alert.accept()
            elif alert_type == \'prompt\':
                self.driver.switch_to.alert.send_keys(text)
                self.driver.switch_to.alert.accept()
            else:
                case_logger.error("不支持{},请确认alert的类型".format(alert_type))
        except Exception as e:
            case_logger.error("在{}中切换并关闭{}类型的弹框失败!".format(img_doc, alert_type))
            self.save_screenshot(img_doc)
            raise e
        else:
            end_time = time.time()
            case_logger.info("在{}中切换并关闭{}类型的弹框,等待时间:{}秒".
                             format(img_doc, alert_type, round(end_time - start_time, 2)))

    def select_action(self, loc, img_doc, content, select_type, timeout=20, frequency=0.5):
        \'\'\'
        Select操作
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param content: select_by_方法的入参
        :param select_type: select类型
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}上根据元素<{}>以{}方式进行下拉选择".format(img_doc, loc, select_type))
            self.wait_element_to_be_click(loc, img_doc, timeout, frequency)
            ele = self.get_element(loc, img_doc)
            if select_type == \'index\':
                Select(ele).select_by_index(content)
            elif select_type == \'value\':
                Select(ele).select_by_value(content)
            elif select_type == \'text\':
                Select(ele).select_by_visible_text(content)
            else:
                case_logger.error("不支持{},请确认Select的类型".format(select_type))
        except Exception as e:
            case_logger.error("在{}上根据元素<{}>以{}方式进行下拉选择失败!".format(img_doc, loc, select_type))
            self.save_screenshot(img_doc)
            raise e

    def switch_to_windows(self, loc, img_doc, timeout=20, frequency=0.5):
        \'\'\'
        窗口切换
        :param loc: 元素定位的XPATH元组表达式
        :param img_doc: 截图说明
        :param timeout: 等待的超时时间
        :param frequency: 轮询频率
        :return:
        \'\'\'
        try:
            case_logger.info("在{}中根据元素<{}>进行窗口切换".format(img_doc, loc))
            cur_handles = self.driver.window_handles  # 获取点击之前的窗口总数
            start_time = time.time()
            self.click_button(loc, img_doc, timeout, frequency)  # 点击按钮后新的窗口出现
            WebDriverWait(self.driver, timeout, frequency).until(EC.new_window_is_opened(cur_handles))
            wins = self.driver.window_handles  # 再次获取窗口总数
            self.driver.switch_to.window(wins[-1])  # 切换到新的页面
        except Exception as e:
            case_logger.error("在{}中根据元素<{}>进行窗口切换失败!".format(img_doc, loc))
            self.save_screenshot(img_doc)
            raise e
        else:
            end_time = time.time()
            case_logger.info("在{}中根据元素<{}>进行窗口切换,等待时间:{}秒".
                             format(img_doc, loc, round(end_time - start_time, 2)))

 

 

******* 尊重作者,本文是本人转载自:https://www.cnblogs.com/xiaogongjin/    ******* 

以上是关于web自动化针对PO模式进行二次封装之 basepage的主要内容,如果未能解决你的问题,请参考以下文章

selenium(12)-web UI自动化项目实战(PO模式,代码封装)

web自动化测试的PO模式是什么?

selenium(10)-PageObject模式的解释

web自动化PO模式初探

selenium自动化测试框架之PO设计模式

selenium自动化测试框架之PO设计模式