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

2023-05-16

整理一下 selenium 自动化测试实践中使用较多的 PO(PageObject)设计模式

面向对象的特性:封装\继承\多态.在自动化中一样适用,selenium 自动化测试中有一个名字常常被提及 PageObject(思想与面向对象的特性相同),通过 PO 模式可以大大提高测试用例的维护效率

传统测试脚本的弊端:

测试脚本分离,维护成本高

可扩展性差

复用性低等

 

PageObject 设计模式

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

PO 的核心要素:

在 PO 模式中抽象封装成一个BasePage 类,该基累应该拥有一个只实现 webdriver 实例的属性.

每个一个 page 都继承 BasePage,通过 driver 来管理本 page 中元素,讲 page 中的操作封装成一个个的方法.

TestCase 继承 unittest.Testcase 类,并且依赖 page 类,从而实现相应的测试步骤.

 

例:

1.页面元素(登录页面)

from selenium.webdriver.common.by import By

class loginPageLocator:

    # 用户名输入框
    user_input = (By.XPATH,'//input[@name="username"]')
    # 密码输入框
    passwd_input = (By.XPATH,'//input[@name="password"]')
    # 登录按钮
    login_button = (By.XPATH,'//button[@class="el-button el-button--primary"]')
    # 错误提示 - 登录表单区域
    form_error_info = (By.XPATH,'//div[@class="el-form-item__error"]')
    # 登录页弹框 - 标题
    login_box_title = (By.XPATH,'//span[text()="确定登出"]')
    # 登录页弹框 - 文本
    login_box_text = (By.XPATH,'//p[text()="你已被登出,可以取消继续留在该页面,或者重新登录"]')
    # 登录页弹框 - 重新登录
    login_box_relog = (By.XPATH,'//button[@class="el-button el-button--default el-button--small el-button--primary "]')
    # 登录页弹框 - 取消
    login_box_cancel = (By.XPATH,'//button[@class="el-button el-button--default el-button--small"]')
    # 登录页弹框 - 关闭
    login_box_close = (By.XPATH,'//i[@class="el-message-box__close el-icon-close"]')

2.页面元素(首页)

from selenium.webdriver.common.by import By

class indexPageLocator:
    
    # 右上角用户名
    user_link = (By.XPATH,'//li[@class="logout"]')

3.关键字封装(基础操作)

"""
1.生成执行日志;测试用例的执行日志
2.测试用例的任何一行代码失败,都希望能在日志当中看到异常信息,并且生成失败的网页截图
3.精简一下我的 pageobjects 页面

测试用例 = 页面对象(步骤+断言) + 测试数据
页面对象 = 页面行为 + 页面元素定位
页面行为 = selenium webdrive基本 API{查找元素\等待\点击\输入\清除\文本获取\属性获取}组合起来的

对 WebDrive 的基础函数封装一下.加入日志\异常处理\失败截图
BasePage 具备以上三点
"""

from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import logging,time,datetime
from Common import file_path
from Common.testLogging import MyLog

class basePage:

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

    # 等待元素可见
    def wait_eleVisible(self,loc,timeout=20,poll_frequency=0.5,model=None):
        """
        :param loc:元素定位表达;元组类型,表达方式(元素定位类型,元素定位方法)
        :param timeout:等待的上限
        :param poll_frequency:轮询频率
        :param model:等待失败时,截图操作,图片文件中需要表达的功能标注
        :return:None
        """
        logging.info('{} 等待元素可见:{}'.format(model,loc))
        try:
            start = time.time()
            WebDriverWait(self.driver,timeout,poll_frequency).until(EC.visibility_of_element_located(loc))
            end = time.time()
            MyLog().logger().info('等待时长:%.2f 秒'%(end - start))
        except:
            MyLog().logger().exception('{} 等待元素可见失败:{}'.format(model,loc))
            # 截图
            self.save_webImgs(model)
            raise

    # 等待元素不可见
    def wait_eleNoVisible(self,loc,timeout=20,poll_frequency=0.5,model=None):
        """
        :param loc:元素定位表达;元组类型,表达方式(元素定位类型,元素定位方法)
        :param timeout:等待的上限
        :param poll_frequency:轮询频率
        :param model:等待失败时,截图操作,图片文件中需要表达的功能标注
        :return:None
        """
        logging.info('{} 等待元素不可见:{}'.format(model,loc))
        try:
            start = time.time()
            WebDriverWait(self.driver,timeout,poll_frequency).until_not(EC.visibility_of_element_located(loc))
            end = time.time()
            MyLog().logger().info('等待时长:%.2f 秒'%(end - start))
        except:
            MyLog().logger().exception('{} 等待元素不可见失败:{}'.format(model,loc))
            # 截图
            self.save_webImgs(model)
            raise

    # 查找一个元素element
    def find_Element(self,loc,model=None):
        MyLog().logger().info('{} 查找元素 {}'.format(model,loc))
        try:
            return self.driver.find_element(*loc)
        except:
            MyLog().logger().exception('查找元素失败.')
            # 截图
            self.save_webImgs(model)
            raise

    # 查找元素elements
    def find_Elements(self,loc,model=None):
        MyLog().logger().info('{} 查找元素 {}'.format(model,loc))
        try:
            return self.driver.find_element(*loc)
        except:
            MyLog().logger().exception('查找元素失败.')
            # 截图
            self.save_webImgs(model)
            raise

    # 输入操作
    def input_Text(self,loc,text,model=None):
        # 查找元素
        ele = self.find_Element(loc,model)
        # 输入操作
        MyLog().logger().info('{} 在元素 {} 中输入文本: {}'.format(model,loc,text))
        try:
            ele.send_keys(text)
        except:
            MyLog().logger().exception('输入操作失败')
            # 截图
            self.save_webImgs(model)
            raise

    # 清除操作
    def clean_Input_Text(self,loc,model=None):
        ele = self.find_Element(loc,model)
        # 清除操作
        MyLog().logger().info('{} 在元素 {} 中清除'.format(model,loc))
        try:
            ele.clear()
        except:
            MyLog().logger().exception('清除操作失败')
            # 截图
            self.save_webImgs(model)
            raise

    # 点击操作
    def click_Element(self,loc,model=None):
        # 先查找元素在点击
        ele = self.find_Element(loc,model)
        # 点击操作
        MyLog().logger().info('{} 在元素 {} 中点击'.format(model,loc))
        try:
            ele.click()
        except:
            MyLog().logger().exception('点击操作失败')
            # 截图
            self.save_webImgs(model)
            raise

    # 获取文本内容
    def get_Text(self,loc,model=None):
        # 先查找元素在获取文本内容
        ele = self.find_Element(loc,model)
        # 获取文本
        MyLog().logger().info('{} 在元素 {} 中获取文本'.format(model,loc))
        try:
            text = ele.text
            MyLog().logger().info('{} 元素 {} 的文本内容为 {}'.format(model,loc,text))
            return text
        except:
            MyLog().logger().exception('获取元素 {} 的文本内容失败,报错信息如下:'.format(loc))
            # 截图
            self.save_webImgs(model)
            raise

    # 获取属性值
    def get_Element_Attribute(self,loc,model=None):
        # 先查找元素在去获取属性值
        ele = self.find_Element(loc,model)
        # 获取元素属性值
        MyLog().logger().info('{} 在元素 {} 中获取属性值'.format(model,loc))
        try:
            ele_attribute = ele.get_attribute()
            MyLog().logger().info('{} 元素 {} 的文本内容为 {}'.format(model, loc, ele_attribute))
            return ele_attribute
        except:
            MyLog().logger().exception('获取元素 {} 的属性值失败,报错信息如下:'.format(loc))
            self.save_webImgs(model)
            raise

    # iframe 切换
    def switch_iframe(self,frame_refer,timeout=20,poll_frequency=0.5,model=None):
        # 等待 iframe 存在
        MyLog().logger().info('iframe 切换操作:')
        try:
            # 切换 == index\name\id\WebElement
            WebDriverWait(self.driver,timeout,poll_frequency).until(EC.frame_to_be_available_and_switch_to_it(frame_refer))
            time.sleep(0.5)
            MyLog().logger().info('切换成功')
        except:
            MyLog().logger().exception('iframe 切换失败!!!')
            # 截图
            self.save_webImgs(model)
            raise

    # 窗口切换 = 如果是切换到新窗口,new. 如果是回到默认的窗口,default
    def switch_window(self,name,cur_handles=None,timeout=20,poll_frequency=0.5,model=None):
        """
        调用之前要获取window_handles
        :param name: new 代表最新打开的一个窗口. default 代表第一个窗口. 其他的值表示为窗口的 handles
        :param cur_handles:
        :param timeout:等待的上限
        :param poll_frequency:轮询频率
        :param model:等待失败时,截图操作,图片文件中需要表达的功能标注
        :return:
        """
        try:
            if name == 'new':
                if cur_handles is not None:
                    MyLog().logger().info('切换到最新打开的窗口')
                    WebDriverWait(self.driver,timeout,poll_frequency).until(EC.new_window_is_opened(cur_handles))
                    window_handles = self.driver.window_handles
                    self.driver.swich_to.window(window_handles[-1])
                else:
                    MyLog().logger().exception('切换失败,没有要切换窗口的信息!!!')
                    self.save_webImgs(model)
                    raise
            elif name == 'default':
                MyLog().logger().info('切换到默认页面')
                self.driver.switch_to.default()
            else:
                MyLog().logger().info('切换到为 handles 的窗口')
                self.driver.swich_to.window(name)
        except:
            MyLog().logger().exception('切换窗口失败!!!')
            # 截图
            self.save_webImgs(model)
            raise

    # 截图
    def save_webImgs(self,model=None):
        # filepath = 指图片保存目录/model(页面功能名称)_当前时间到秒.png
        # 当前时间
        dateNow = str(datetime.datetime.now()).split('.')[0]
        # 路径
        filePath = '{}/{}_{}.png'.format(file_path.image_path,model,dateNow)
        try:
            self.driver.save_screenshot(filePath)
            MyLog().logger().info('截屏成功,图片路径为{}'.format(filePath))
        except:
            MyLog().logger().exception('截屏失败!')

4.页面操作脚本

from pageLocator.loginPage_locator import loginPageLocator as loc
from Common.BasePage import basePage

class LoginPage(basePage):

    # 登录操作
    def login(self,username,passwd):
        # 等待用户名文本框元素出现
        self.wait_eleVisible(loc.user_input,model='等待用户名文本框元素出现')
        # 清除文本框内容
        self.clean_Input_Text(loc.user_input,model='清除用户名文本框内容')
        # 输入用户名
        self.input_Text(loc.user_input,text=username,model='输入用户名')
        # 等待密码文本框元素出现
        self.wait_eleVisible(loc.passwd_input,model='等待密码文本框元素出现')
        # 清除密码文本框内容
        self.clean_Input_Text(loc.passwd_input,model='清除密码文本框内容')
        # 输入密码
        self.input_Text(loc.passwd_input,text=passwd,model='输入密码')
        # 点击登录按钮
        self.click_Element(loc.login_button,model='点击登录按钮')

    # 获取页面错误提示 - 登录表单区域
    def get_wrongMsg_byForm(self):
        # 等待表单文本提示元素出现
        self.wait_eleVisible(loc.form_error_info,model='等待表单文本提示元素出现')
        return self.get_Text(loc.form_error_info,model='获取表单文本提示内容')

    def get_boxMsg(self):
        # 等待提示弹框出现
        self.wait_eleVisible(loc.login_box_text,model='等待提示弹框文本内容')
        return self.get_Text(loc.login_box_text,model='获取提示框文本内容')

将上面的脚本放在 login_page.py 中

5.因为是测试登录,所以写个登录成功跳转到首页获取其中一个标志元素

    # 查看用户名这个元素
    def is_user_link_exists(self):
        """
        如果存在返回True,如果不存在返回False
        :return
        """
        try:
            WebDriverWait(self.driver,20).until(EC.visibility_of_element_located(loc.user_link))
            return True
        except:
            return False

6.创建一个 py 以放于公共数据,现在公共数据中只放了 URL

7.把测试数据写到一个 login_datas.py 中

# 正常场景 - 登录成功
login_success_data = {"user":"test","passwd":"123456"}

# 异常场景 - 用户名为空\用户名密码为空\密码为空
wrong_data = [
    {"user":"","passwd":"123456","check":"请输入大于5位的用户名"},
    {"user":"","passwd":"","check":"请输入大于5位的用户名"},
    {"user":"test","passwd":"","check":"密码不能小于5位"}
]

# 异常场景 - 错误的用户名\错误的密码
error_userOrPasswd = [
    {"user":"tes","passwd":"123456","check":"你已被登出,可以取消继续留在该页面,或者重新登录"},
    {"user":"test","passwd":"1234567","check":"你已被登出,可以取消继续留在该页面,或者重新登录"}
]

8.编写测试用例:

import unittest
from selenium import webdriver
from pageObjects.login_page import LoginPage
from pageObjects.index_page import IndexPage
from testDatas import common_datas as cd
from testDatas import login_datas as ld
from ddt import ddt,data

@ddt
class TestLogin(unittest.TestCase):

    def setUp(self):
        # 打开浏览器,访问登录页面
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.get(cd.web_url)
        self.LP = LoginPage(self.driver)

    def tearDown(self):
        self.driver.quit()

    # 正常场景:登录成功
    def test_login_success(self):
        # 步骤    测试数据:sporttest/123456
        # 登录页面 - 登录功能 - 输入用户名和密码
        self.LP.login(ld.login_success_data['user'],ld.login_success_data['passwd'])
        # 断言
        self.assertTrue(IndexPage(self.driver).is_user_link_exists())

    # 异常场景:用户名为空\用户名密码为空\密码为空
    @data(*ld.wrong_data)
    def test_login_wrongData(self,data):
        # 步骤    测试数据: /123456
        """断言数据:
          请输入大于5位的用户名
        """
        self.LP.login(data['user'],data['passwd'])
        # 断言
        self.assertIn(data['check'],self.LP.get_wrongMsg_byForm())


    # 异常场景:错误的用户名\错误的密码
    @data(*ld.error_userOrPasswd)
    def test_login_errorUserOrPasswd(self,data):
        # 步骤
        self.LP.login(data['user'],data['passwd'])
        # 断言
        self.assertEqual(data['check'],self.LP.get_boxMsg())

 

这里在提及一个 setUpClass()和 tearDownClass(),这个是在 setUpClass()开始执行,执行完所有测试用例后再去执行 tearDownClass()

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

selenium自动化测试框架PO设计模式 的相关文章

随机推荐