PO

PO:Page Object,po是一种设计模式,提供通用的方法,简单来说就是分层设计。
  • 官方文档:https://martinfowler.com/bliki/PageObject.html

  • PO在Selenium中的应用,官方文档:https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/

场景:我们最初做UI自动化测试时,一个python文件可以实现整个流程的操作,那么会带来的问题有:
  • 代码可读性差,一旦有一个元素的位置发生改动,改起来特别费劲;
  • 代码维护性差,时间久了看不懂,别人也看不懂;
  • 可扩展性弱,比如登录,这个页面需要登录,到了另外一个页面可能还需要登录,需要再写一遍。

要解决这种问题,我们需要把场景抽象化,通过PO思想进行封装。把操作细节封装成方法,对外只提供方法,不提供细节。当需要修改时只改操作细节,不改对外暴露的方法。但是,不是所有页面都封装,只封装主要模块,不封装次要模块。


截图.png
PO原则
  • 用一个方法代替页面的服务
  • 对外提供的方法不暴露细节
  • 对外提供的方法中不包含断言
  • 如果页面a导航到页面b,page a应该return page b
  • 不为页面的所有元素封装方法
  • 当出现正确的和错误的结果时,我们封装时要分开写,不要揉到一起写

举个例子,以企业微信后台为例,先构思一下用例:


image.png

用PO思想,我需要创建几个文件:

  • main_page.py:主页的po,提供主页的方法,不提供细节
  • base_page.py:抽离通用方法的po
  • add_member_page.py:添加成员页po,继承base_page,对应具体添加成员的操作细节
  • contact_page.py:通讯录页的po,继承base_page,对应具体通讯录的操作细节
  • test_add_member.py:添加成员的测试用例及断言

main_page.py

from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
from test_selenium.work_weixin.api.add_member_page import AddMemberPage
from test_selenium.work_weixin.api.base_page import BasePage
from test_selenium.work_weixin.api.contact_page import ContactPage

class MainPage(BasePage):
    """
    首页
    """
    _base_url = 'https://work.weixin.qq.com/wework_admin/frame#index'
    def goto_contact(self):
        """
        跳转通讯录页面
        :return:
        """
        return ContactPage()

    def goto_add_member(self):
        """
        跳转添加成员页面
        :return:
        """
        self.driver.find_element(By.XPATH, '//*[@id="_hmt_click"]/div[1]/div[4]/div[2]/a[1]/div/span[2]').click()
        sleep(1)
        return AddMemberPage(self.driver)

base_page.py

from time import sleep
import yaml
from selenium import webdriver
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.opera.options import Options

class BasePage:
    """
    封装所有页面对象通用的操作,如driver的实例化
    """
    _base_url = None
    def __init__(self, base_driver=None):
        """
        子类没有构造函数,在实例化过程中会自动调用父类的构造函数,所以每个PO在实例化过程中都会执行构造函数的逻辑,这样就会初始化多个driver
        所以需要避免driver的重复实例化
        """
        # 如果base_driver为真,就不需要重复driver实例化操作
        if base_driver:
            self.driver =base_driver
        # 如果base_driver为None,就需要对driver实例化
        else:
            # 不加self是局部变量
            self.driver = webdriver.Chrome()
            self.driver.implicitly_wait(5)
            # url优化
            if self._base_url != None:
                self.driver.get(self._base_url)
                cookie = yaml.safe_load(open('../data/login_cookie.yml'))
                print(cookie)
                # 种cookie
                for c in cookie:
                    self.driver.add_cookie(c)
                sleep(2)
                self.driver.get(self._base_url)

    def find(self, by, locaotor):
        """
        封装find_element操作
        :param by:
        :param locaotor:
        :return:
        """
        res = self.driver.find_element(by, locaotor)
        print(f"找到的元素为{res}")
        return res

add_member_page.py

from time import sleep
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
from test_selenium.work_weixin.api.base_page import BasePage
from test_selenium.work_weixin.api.contact_page import ContactPage

class AddMemberPage(BasePage):
    """
    添加成员页
    """
    _base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'

    def goto_contact(self):
        """
        跳转通讯录页面
        :return:
        """
        return ContactPage()

    def add_member(self, username, acctid, phonenum):
        """
        添加成员操作
        :return:
        """
        self.find(By.ID, 'username').send_keys(username)
        self.find(By.ID, 'memberAdd_acctid').send_keys(acctid)
        self.find(By.ID, 'memberAdd_phone').send_keys(phonenum)
        self.driver.find_element(By.CSS_SELECTOR, '.js_btn_save').click()
        return ContactPage(self.driver)

    def add_member_fail(self):
        """
        添加成员操作
        :return:
        """
        self.find(By.ID, 'username').send_keys('')
        self.find(By.ID, 'memberAdd_acctid').send_keys('111')
        self.find(By.ID, 'memberAdd_phone').send_keys('13600001243')
        error_list = self.driver.find_elements(By.CSS_SELECTOR, '.ww_inputWithTips_tips')
        print(error_list)

        # 寻找所有的错误信息,如果不为空,则返回
        err_message = [ele.text for ele in error_list if ele.text != ""]
        return err_message
        return ContactPage(self.driver)

contact_page.py

from test_selenium.work_weixin.api.base_page import BasePage
class ContactPage(BasePage):
    """
    通讯录列表页
    """
    _base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'

    def goto_add_member(self):
        """
        添加成员
        :return:
        """
        # 如果出现A到B,B到A循环导入的场景,需要把其中一个的导包放在方法里
        from test_selenium.work_weixin.api.add_member_page import AddMemberPage

        return AddMemberPage()

    def member_list(self, member_name):
        """
        成员列表list
        :return:
        """
        return ['member_name']

test_add_member.py

from test_selenium.work_weixin.api.add_member_page import AddMemberPage
from test_selenium.work_weixin.api.contact_page import ContactPage
from test_selenium.work_weixin.api.main_page import MainPage

class TestAddMember:
    """
    添加成员的测试用例
    """
    def setup_class(self):
        """
        页面实例化MainPage
        :return:
        """
        self.main = MainPage()

    def test_add_member(self):
        """
        1.在首页,点击添加成员按钮,跳转到添加成员页面
        2.在添加成员页面,填写成员信息,点击保存
        3.在通讯录页面查看信息是否添加成功

        :return:
        """
        # # 方法一
        # # 页面实例化,调用过于复杂
        # main = MainPage()
        # add_member_page = AddMemberPage()
        # contact_page = ContactPage()
        #
        # # 1.在首页,点击添加成员按钮,跳转到添加成员页面
        # main.goto_add_member()
        #
        # # 2在添加成员页面,填写成员信息,点击保存
        # add_member_page.add_member()
        #
        # # 3.在通讯录页面查看成员是否添加成功
        # contact_page.member_list()

        # 方法二
        username, acctid, phonenum = "吱吱3", '3', '18700001234'

        # 通过前面return实例对象,可以直接用方法.方法的方式,实现链式调用
        res = self.main.goto_add_member().add_member(username, acctid, phonenum).member_list('member_name')

        # 断言与手工测试一致,断言添加的成员是否在结果中,如果在表示用例通过,否则失败
        assert 'member_name' in res

    def test_add_member_fail(self):
        """
        反向用例,成员名称为空
        :return:
        """
        error_list = self.main.goto_add_member().add_member_fail()
        assert "请填写姓名" in error_list

    def teardown_class(self):
        self.main.driver.quit()

源码彩蛋》》》
链接: https://pan.baidu.com/s/14p0Q38whTbjlRwlMZA-iVg
提取码: 7wzh

你可能感兴趣的:(PO)