python+selenium自动化—Page Object模式

 什么是Page Object Model模式

  Page Object模式是Selenium中的一种测试设计模式,主要是将每一个页面设计为一个Class,其中包含页面中需要测试的元素(按钮,输入框,标题 等),这样在Selenium测试页面中可以通过调用页面类来获取页面元素,这样巧妙的避免了当页面元素id或者位置变化时,需要改测试页面代码的情况。 当页面元素id变化时,只需要更改测试页Class中页面的属性即可。

    Page Object模式是一种自动化测试设计模式,将页面定位和业务操作分开,分离测试对象(元素对象)和测试脚本(用例脚本),提高用例的可维护性。

POM的优势

参见非POM和POM对比图

在自动化测试中,引入了Page Object Model(POM):页面对象模式来解决,POM能让我们的测试代码变得可读性更好,高可维护性,高复用性。
POM的优势
1.  POM提供了一种在UI层操作、业务流程与验证分离的模式,这使得测试代码变得更加清晰和高可读性。

2.  对象库与用例分离,使得我们更好的复用对象,甚至能与不同的工具进行深度结合应用。

3.  可复用的页面方法代码会变得更加优化。

4.  更加有效的命名方式使得我们更加清晰的知道方法所操作的UI元素。例如我们要回到首页,方法名命名为: gotoHomePage(),通过方法名即可清晰的知道具体的功能实现。

 案例说明

以下126邮箱登录的普通用例

from selenium import webdriver
def
test_login_mail(): driver = webdriver.Chrome() driver.get("http://www.126.com") driver.find_element_by_id("idInput").clear() driver.find_element_by_id("xxxxxxx").send_keys("xxxxx") driver.find_element_by_id("xxxxxxx").clear() driver.find_element_by_id("xxxxxxx").send_keys("xxxxxx") driver.find_element_by_id("loginBtn").click()

如上是一个简单的小脚本,脚本维护看起来很简单。但随着时间测试套件的增长。随着你在代码中添加越来越多的行,事情变得艰难。那么如何进行改造成POM模式?

改造成POM模式的基础思路

  1.我们要分离测试对象(元素对象)和测试脚本(用例脚本),那么我们分别创建两个脚本文件,分别为: LoginPage.py 用于定义页面元素对象,每一个元素都封装成组件(可以看做存放页面元素对象的仓库)  CaseLoginTest.py 测试用例脚本。
  2.设计实现思想,一切元素和元素的操作组件化定义在Page页面,用例脚本页面,通过调用Page中的组件对象,进行拼凑成一个登录脚本。

创建BasePage类

#! /usr/bin/python3
# coding:utf-8

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
import time

class Action(object):
    """基础类封装所有页面都公用的方法,例如driver,url,findElement等"""

    #初始化driver、url等
    def __init__(self, selenium_driver, base_url):
        self.base_url = base_url
        self.driver = selenium_driver
        self.timeout = 30

    def on_page(self):
        return self.driver.current_url == self.base_url

    #打开页面,校验页面链接是否加载正确
    def _open(self, url):
        # 使用get打开访问链接
        self.driver.get(url)
        self.driver.maximize_window()
        # 使用assert进行检验,打开的链接是否与配置的地址一致
        assert self.on_page(), 'Did not land on %s' % url

    # 定义open方法,调用_open()进行打开链接
    def open(self):
        self._open(self.base_url)

    # 重写元素定位方法
    def find_element(self, *loc):
        # return self.driver.find_element(*loc)
        try:
            WebDriverWait(self.driver,10).until(lambda driver:driver.find_element(*loc).is_displayed())
            return self.driver.find_element(*loc)
        except:
            print("%s页面中未能找到%s元素"%(self, loc))

    # 重写switch_frame方法
    def switch_frame(self, loc):
        return self.driver.switch_to_frame(loc)

    # 定义script方法,用于执行JS脚本,范围执行结果
    def script(self, src):
        self.driver.execute_script(src)

    # 重写定义send_key方法
    def send_keys(self, loc, vaule, clear_first=True, click_first=True):
        try:
            loc = getattr(self,"_%s"%loc)
            if click_first:
                self.driver.find_element(*loc).click()
            elif clear_first:
                self.driver.find_element(*loc).clear()
                self.driver.find_element(*loc).send_keys(vaule)
            else:
                pass
        except AttributeError:
            print("%s页面中未能找到%s元素"%(self, loc))
    # ....
    
# 脚本调试
if __name__ == '__main__':
    base_url = 'https://www.baidu.com/'
    input_kw = (By.ID, "kw")
    click_su = (By.ID, "su")
    driver = webdriver.Chrome()
    cc = Action(driver,base_url)
    cc.open()
    cc.find_element(*input_kw).send_keys("selenium")
    cc.find_element(*click_su).click()
    time.sleep(9)
    cc.driver.quit()

首先创建一个基础类BasePage,在初始化方法__init__()中定义驱动(driver)、基本的URL(base_url)和超过时间(timeout)等。

定义open()方法用于打开URL网站,但它本身并未做这件事情,而是交由_open()方法来实现。关于URL地址断言部分,则交由on_page()方法来实现,而find_element()方法用于元素的定位。

创建LoginPage类

#! /usr/bin/python3
# coding:utf-8
from selenium.webdriver.common.by import By
import BasePage

#继承BasePage类
class LoginPage(BasePage.Action):
#定位器,通过元素属性定位元素对象
 username_loc=(By.ID,"idInput")
 password_loc =(By.ID,"pwdInput")
 submit_loc =(By.ID,"loginBtn")
 span_loc=(By.CSS_SELECTOR,"div.error-tt>p")
 dynpw_loc =(By.ID,"lbDynPw")
 userid_loc =(By.ID,"spnUid")

#Action
def open(self):
#调用page中的_open打开连接
    self._open(self.base_url,self.pagetitle)

#调用send_keys对象,输入用户名
def input_username(self, username):
 self.find_element(*self.username_loc).send_keys(username)
 
#调用send_keys对象,输入密码
def input_password(self, password):
 self.find_element(*self.password_loc).send_keys(password)

#调用send_keys对象,点击登录
def click_submit(self):
 self.find_element(*self.submit_loc).click()
 
#用户名或密码不合理是Tip框内容展示
def show_span(self):
    return self.find_element(*self.span_loc).text

#切换登录模式为动态密码登录(IE下有效)
def swich_DynPw(self):
 self.find_element(*self.dynpw_loc).click()
 
#登录成功页面中的用户ID查找
def show_userid(self):
    return self.find_element(*self.userid_loc).text    

#.......

LoginPage类中主要对登录页面上的元素进行封装,使其成为更具体的操作方法。例如,用户名、密码和登录按钮都被封装成了方法。

创建CaseLoginTest.py类

#! /usr/bin/python3
# -*- coding: utf-8-*-

import unittest
import LoginPage
from selenium import webdriver

class Caselogin126mail(unittest.TestCase):
    """登录case"""
    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome()
        cls.driver.implicitly_wait(30)
        cls.url = "http://xxxx.xxx.com"
        cls.username = "xxxxx"
        cls.password = "xxxxx"

    # 用例执行体
    def test_login_mail(self):
        # 声明LoginPage类对象
        login_page = LoginPage.LoginPage(self.driver, self.url)
        # 调用打开页面组件
        login_page.open()
        # 调用用户名输入组件
        login_page.input_username(self.username)
        # 调用密码输入组件
        login_page.input_password(self.password)
        # 调用点击登录按钮组件
        login_page.click_submit()

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()


if __name__ == "__main__":
    unittest.main()

使用POM进行重新构造代码结构后,发现代码测试用例代码的可读性提高很多,元素写成组件的方式,不需要每次都写findElement直接在脚本中调用组件就可以使用。

posted @ 2019-04-03 13:57  pathbreaker  阅读(249)  评论(0)    收藏  举报