web自动化——PO模式

PO模式解决的问题

  • 平铺直叙(不封装,直接在py文件中进行打开浏览器——查找元素——操作元素的代码)的脚本存在很多缺点
    • 首先:页面发生变化(元素定位,操作步骤等),会导致后期代码维护困难
    • 其次:有部分模块会在很多操作中都用到,例如登录模块,在每条用例的操作中都编写,代码冗余严重,工作量很大
  • PO模式可以解决后期维护困难和代码冗余的问题

PO模式的解释

  • PageBbject == 面向对象模式
  • web页面的用例=不同页面当中,不用的功能串起来的
  • 例如P2P的投资成功用例=登录界面的登录+首页选标+标的页面的投标+个人页面的余额和交易记录;
  • 投资失败的用例=登录界面的登录+首页选标+标的页面的投标+错误提示;
  • 由上面可以看出,平铺直述写用例的方式会写很多冗余的代码
  • PO模式是UI自动化测试中广泛使用的一种分层设计模式,核心是通过页面层封装所有的页面元素及操作,测试用例层通过调用页面层操作组装业务逻辑
    • 用例层:调用页面层元素,操作租场业务逻辑
    • 页面层:封装页面元素定位和操作
  • 因此,我们可以将每个页面所需要的元素都封装起来,写用例的时候根据操作步骤直接调用页面的功能方法即可

PO模式

  • 类和对象的关系:对象是类的实例化
  • 定义类的时候,可以定义页面类,共用类和用例类
    • 页面类中包含:
    • 元素的定位信息——相当于类的属性;其中包含元素定位的方法和元素定位的值,可以用元组的形式存放;命名的时候,建议locator后缀来区分
    • 元素的操作——相当于类的方法。
    • 元素的操作,可以认为是类的方法
    • 一个页面一个py文件/一个类,包含页面中的元素定位和页面的操作
  • PO模式的优点:
    • 提高代码用例的可读性
    • 提高测试用例的可维护性
    • 减少代码重复
  • 页面分离的层级如下图:
    在这里插入图片描述

页面操作

首页操作

  • 需要有首页中元素的定位和操作
  • 浏览器的driver,从外部传入
  • 代码示例:
from selenium.webdriver.common.by import By

class HomePage:
    # 首页登录按钮
    login_button_locator = (By.XPATH,'//a[text()="登录"]')
	
	#点击首页登录按钮
    def click_login(self,driver):
        driver.find_element(*self.login_button_locator).click()

登录页面的操作

  • 需要有登录页面的元素定位和页面操作
  • 元素定位:用户名输入框,密码输入框,登录按钮,登录成功的标志
  • 操作:输入用户名,输入密码,点击登录
  • driver从外部传入,输入的用户名和密码信息也需要参数化
  • 代码示例:
from selenium.webdriver.common.by import By

class Login:

    # 登录输入框
    phone_box_locator = (By.XPATH, '//div[@class="login-con"]//input[contains(@placeholder,"手机号")]')
    # 密码输入框
    pwd_box_locator = (By.XPATH, '//div[@class="login-con"]//input[contains(@placeholder,"密码")]')
    # 登录按钮
    login_locator = (By.XPATH, '//div[@class="login-con"]//a[text()="登录"]')
    # 判断登录成功
    login_success = (By.XPATH, '//div[@id="__layout"]//span[@class="text"]')

	
	#登录操作
    def login(self,driver,phone,pwd):
        #输入账号
        driver.find_element(*self.phone_box_locator).send_keys(phone)
        #输入密码
        driver.find_element(*self.pwd_box_locator).send_keys(pwd)
        #点击登录按钮
        driver.find_element(*self.login_locator).click()

等待操作

  • 需要封装等待可见,等待存在,等待可点击的场景
  • 其他场景看情况封装
  • 需要做异常处理,如果符合条件,就返回True,不符合就返回False,方便断言
  • 代码示例如下:
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class WaitOperate:

    # 等待元素可见
    def wait_located(self, driver, ele):
        try:
            return WebDriverWait(driver, 20).until(EC.visibility_of_element_located(ele))
        except Exception as e:
            print(e)

    # 等待元素可点击
    def wait_click(self, driver, ele):
        try:
            return WebDriverWait(driver, 20).until(EC.element_to_be_clickable(ele))
        except Exception as e:
            print(e)

    # 等待元素存在
    def wait_presence(self, driver, ele):
        try:
            return WebDriverWait(driver, 20).until(EC.presence_of_element_located(ele))
        except Exception as e:
            print(e)

运行界面

  • 需要定义driver
  • 需要导入不同页面的操作和等待操作
  • 需要调用不同页面中的不同方法,来确认运行是否成功
  • 代码示例如下:
import time
from selenium import webdriver
from Hw.hw_2023_1016_yiyi.pagebjects.login_page import Login
#导入登录页面的类
from Hw.hw_2023_1016_yiyi.pagebjects.home_page import HomePage
#导入首页的类
from Hw.hw_2023_1016_yiyi.pagebjects.wait_operate import WaitOperate
#导入等待操作的类


def login():
    #定义driver
    with webdriver.Chrome() as driver:
        driver.get("http://mall.lemonban.com:3344")
        driver.maximize_window()
        HomePage().click_login(driver)#首页点击登录按钮
        #传入用户名和密码的元素
        Login().login(driver,"lemon_auto","lemon123456")#进行登录操作
        time.sleep(2)
        actual= WaitOperate().wait_located(driver,Login.login_success)#判断登录成功
        ex = True
        try:
            # 断言
            assert actual==ex
            print("断言正确")
        except Exception:
            raise

if __name__ == '__main__':
    login()
=======================run_result=================
断言正确

PO模式的优化——公共类的提取

  • 上面所讲述的是基础的PO模式,其分层只包括页面层和用例层
  • 使用这种方式,在写代码的时候会出现一个问题:项目中存在非常多的相同操作的方法,例如元素等待/文本信息获取/页面滚动等
  • 如果页面中需要上面的操作,就需要在每个页面层中都写相同的代码,很冗余
  • 因此,就需要将公用的一些方法提取出来,放在一个类中进行维护,其他页面类公用该类中的操作方法即可,这就是三层PO模式

三层PO模式

  • 三层PO模式中,有公共页面层,页面层和用例层
    • 公共页面层:封装页面公共的操作方法,例如等待,文件上传,滚动等
    • 页面层:封装页面元素定位和操作
    • 用例层:调用页面层元素组装业务逻辑
  • 分层后的页面显示如下:
    在这里插入图片描述

assert断言

  • 断言是自动化测试中不可缺少的部分,当我们使用测试脚本对业务逻辑进行操作的时候,需要检查交互操作之后的结果是否正确,此时需要通过断言机制来进行验证、
  • 通过assert断言,常见的断言方法有:
  • 比较相等(等于/不等于):assert a==b
  • 比较大小(大于,小于,大于等于,小于等于):assert a > b
  • 比较包含(a在b中/a不在b中):assert a in bassert a not in b
  • 验证表达式是否为真:assert condition;condition为判断条件

UI自动化断言

  • UI自动化中常见的断言条件包括:
  • 当前页面的URL地址
    • 常用于元素操作之后,URL地址会发生变化的场景
  • 当前页面的标题
    • 常用于元素操作之后,页面标题会发生变化的场景
  • 当前页面的提示文本信息
    • 常用于元素操作之后,页面会出现提示信息的场景,例如登录密码错误
  • 当前页面的某些元素的变化/显示
    • 常用于元素操作之后,页面元素会发生变化的场景,例如登录成功
  • UI自动化测试通过的条件是:所有断言都需要通过,有一条断言失败了,测试就是不通过
  • 断言的代码,放在页面层上

PO模式结合断言的代码示例

公用层代码

    • 包含每个页面都会使用的操作,例如等待,滚动条,上传文件等
  • 代码示例如下:
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import os

class BasicOperate:

    def __init__(self,driver):
        #初始化函数,实例化(创建对象的时候)会先执行init方法
        #可以将driver驱动作为传递进来
        self.driver = driver


    # 等待元素可见
    def wait_located(self, ele):
        try:
            return WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(ele))
        except Exception:
            print("等待超时")
    # 等待元素可点击
    def wait_click(self, ele):
        try:
            return WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(ele))
        except Exception:
            print("等待超时")


    # 等待元素存在
    def wait_presence(self, ele):
        try:
            return WebDriverWait(self.driver, 20).until(EC.presence_of_element_located(ele))
        except Exception:
            print("等待超时")

    # 滚动条
    def scroll_operate(self,ele_locator,text):
        '''
        :param ele_locator:元素定位的元组
        样式:(By.XPATH, '//a[text()="登录"]')
        :param text: 判断代码在HTML中的元素
        :return:
        '''
        try:
            while True:
                if text in self.driver.page_source:
                    break
                ele = self.wait_located(ele_locator)
                self.driver.execute_script("arguments[0].scrollIntoView(true)", ele)
                time.sleep(1)
        except Exception:
            print("滚动error")

    #javascript元素点击
    def js_click(self,ele_locator):
        '''
        :param ele_locator: ele_locator:元素定位的元组
        样式:(By.XPATH, '//a[text()="登录"]')
        :return:
        '''
        try:
            ele = self.wait_located(ele_locator)
            self.driver.execute_script("arguments[0].click()",ele)
        except Exception:
            print("js_click错误")

    #去除元素的属性
    def remove_ele_att(self,ele_locator,att_name):
        '''
        :param ele_locator: ele_locator: ele_locator:元素定位的元组
        样式:(By.XPATH, '//a[text()="登录"]')
        :param att_name: 要去除的属性的名称
        :return:
        '''
        try:
            ele = self.wait_located(ele_locator)
            self.driver.execute_script(f"arguments[0].removeAttribute({att_name})",ele)
        except Exception:
            print("js_click错误")

    #鼠标点击
    def mouse_click(self,ele_locator):
        '''
        :param ele_locator: ele_locator: ele_locator:元素定位的元组
        样式:(By.XPATH, '//a[text()="登录"]')
        :return:
        '''
        try:
            ele = self.wait_located(ele_locator)
            ActionChains(self.driver).click(ele).perform()
        except Exception:
            print("js_click错误")

    #窗口切换

    def switch_win(self,text):
        '''
        :param text: 判断是否切换窗口,一般是窗口的title
        :return:
        '''
        try:
            handles = self.driver.handles
            for handle in handles:
                if self.driver.title == text:
                    break
                self.driver.switch_to_window(handle)
        except Exception:
            print("切换窗口失败")

    def upload_file(self,file_path,exe_path):
        '''
        :param file_path: 上传文件的路径
        :return:
        '''
        try:
            os.system(f'{exe_path} {file_path}')
        except Exception:
            print("上传文件失败")

页面层代码

  • 包含每个页面的元素定位和操作
  • 页面中需要包含断言方式
  • 需要继承公用层的属性和方法
  • 登录页面代码如下:
from selenium.webdriver.common.by import By
from pritice.Study_PO_three.Common.basic_operate import BasicOperate

class LoginPage(BasicOperate):

    # 登录输入框
    login_box_locator = (By.XPATH, '//div[@class="login-con"]//input[contains(@placeholder,"手机号")]')
    # 密码输入框
    pwd_box_locator = (By.XPATH, '//div[@class="login-con"]//input[contains(@placeholder,"密码")]')
    # 登录按钮
    login_button_locator = (By.XPATH, '//div[@class="login-con"]//a[text()="登录"]')

    def login_operate(self,phone,pwd):
        self.wait_located(self.login_box_locator).send_keys(phone)#输入手机号码
        self.wait_located(self.pwd_box_locator).send_keys(pwd)#输入密码
        self.wait_click(self.login_button_locator).click()#点击登录
  • 首页代码如下:
from selenium.webdriver.common.by import By
from pritice.Study_PO_three.Common.basic_operate import BasicOperate

class HomePage(BasicOperate):

    # 首页登录按钮
    login_button_locator = (By.XPATH, '//a[text()="登录"]')
    # 判断登录成功_文本
    login_success_msg_locator = (By.XPATH, '//div[@id="__layout"]//span[@class="text"]')
    # 判断登录成功_用户名
    login_success_user_locator = (By.XPATH,'//a[@class="link-name"]')


    #点击登录按钮
    def click_login(self):
        self.wait_click(self.login_button_locator).click()

    # 断言:获取提示信息,实际
    def get_wel_msg(self):
        return self.wait_located(self.login_success_msg_locator).text

    # 断言,获取用户名实际
    def get_success_user(self):
        return self.wait_located(self.login_success_user_locator).text

    # 断言方式二:判断欢迎提示信息是否存在
    def is_display_wel_text(self):
        return self.wait_located(self.login_success_msg_locator).is_displayed()

    #断言方式二:判断用户名是否存在
    def is_display_user_name(self):
        return self.wait_located(self.login_success_msg_locator).is_displayed()

测试用例层的代码

  • 需要调用页面层的方法,来实现测试用例的操作
  • 代码示例如下:
from pritice.Study_PO_three.PageObject.login_pages import LoginPage
from pritice.Study_PO_three.PageObject.home_pages import HomePage
import time
from selenium import webdriver
import pytest

def test_login():
    with webdriver.Chrome() as driver:
        driver.get("http://mall.lemonban.com:3344")
        driver.maximize_window()
        home = HomePage(driver)
        home.click_login()  # 点击登录按钮
        LoginPage(driver).login_operate("lemon_auto", "lemon123456")  # 登录操作
        time.sleep(2)
        try:
            # 断言欢迎提示信息
            assert  home.get_wel_msg()== "XXX"
            print("断言欢迎信息成功")
        except Exception:
            print("断言欢迎信息错误")
        try:
            #断言用户名
            assert home.get_success_user() == "lemon_auto"
            print("断言用户名成功")
        except Exception:
            print("断言用户名错误")

        try:
            #断言提示信息是否存在
            assert home.is_display_wel_text()
            print("断言欢迎提示信息成功")
        except Exception:
            print("断言欢迎提示信息错误")

        try:
            #断言用户名是否存在
            assert home.is_display_user_name()
            print("断言用户名成功")
        except Exception:
            print("断言用户名错误")

如果遇到只出现一会儿就消失的提示信息怎么办

  • 浏览器的F12中,有一个source(来源),里面有个暂停按钮,相当于代码运行的断点
  • 只要元素出来之后,点击暂停按钮,代码运行就可以停止
  • 然后使用元素查找图标,点击相关元素,在element(元素)中进行定位
  • 具体如下图:在这里插入图片描述
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值