随笔记录方便自己和同路人查阅。
#------------------------------------------------我是可耻的分割线-------------------------------------------
学习selenium自动化之前,最好先学习HTML、CSS、JavaScript等知识,有助于理解定位及操作元素的原理。关于python和selenium安装请自行搜索别的资料,这里就不多做介绍了,所有例子均使用python3.6+selenium执行的。
#------------------------------------------------我是可耻的分割线-------------------------------------------
Page Object实例
下面以登录QQ邮箱为例,通过Page Object设计模式来实现。新建po_model.py
# !/usr/bin/env python # -*- coding: UTF-8 –*- __author__ = 'Mr.Li' from selenium import webdriver from selenium.webdriver.common.by import By from time import sleep class Page(object): ''' 基础类,用于页面对象类的继承 ''' login_url = 'https://www.126.com' def __init__(self,selenium_driver,base_url=login_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 + self.url) def _open(self,url): url = self.base_url + url self.driver.get(url) self.driver.find_element_by_link_text('密码登录').click() self.driver.switch_to_frame(self.driver.find_element_by_xpath("//iframe[@scrolling='no']")) # 进入iframe框架 assert self.on_page(),'Did not land on %s' % url def open(self): self._open(self.url) def find_element(self,*loc): return self.driver.find_element(*loc) class LoginPage(Page): ''' 126邮箱登入页面模型 ''' url = '/' #定位器 username_loc = (By.XPATH,"//input[@name='email']") passwrod_loc = (By.XPATH,"//input[@name='password']") submit_loc = (By.XPATH, "//a[@id='dologin']") #Action def type_username(self,username): self.find_element(*self.username_loc).send_keys(username) sleep(1) def type_password(self,password): self.find_element(*self.passwrod_loc).send_keys(password) sleep(1) def submit(self): self.find_element(*self.submit_loc).click() sleep(1) def test_user_login(driver,username,password): ''' 测试获取的用例名/密码是否可以登录 :param username: :param password: :return: ''' login_page = LoginPage(driver) login_page.open() login_page.type_username(username) login_page.type_password(password) login_page.submit() def main(): try: driver = webdriver.Chrome() driver.maximize_window() username = 'xxxx' password = 'xxxx' test_user_login(driver,username,password) sleep(3) text = driver.find_element_by_xpath("//span[@id='spnUid']").text assert text == 'lirongyang123@126.com','用户名称不匹配,登录失败!' finally: #关闭浏览器窗口 driver.close() if __name__ == '__main__': main()
Page Object设计模式的实现方法显然使结构变得复杂了很多。下面我们对其进行逐段分析,来体会这样设计的好处。
- 创建page类
cclass Page(object): ''' 基础类,用于页面对象类的继承 ''' login_url = 'https://www.126.com' def __init__(self,selenium_driver,base_url=login_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 + self.url) def _open(self,url): url = self.base_url + url self.driver.get(url) self.driver.find_element_by_link_text('密码登录').click() self.driver.switch_to_frame(self.driver.find_element_by_xpath("//iframe[@scrolling='no']")) # 进入iframe框架 assert self.on_page(),'Did not land on %s' % url def open(self): self._open(self.url) def find_element(self,*loc): return self.driver.find_element(*loc)
首先创建了一个基础类Page,在初始化方法__init__()中定义驱动(driver)、基本的URL(base_url)和超时时间(timeout)等。
定义open()方法用于打开URL网站,但它本身并未做这件事情,而是交友_open()方法来实现。关于URL地址的断言部分,则就有on_page()方法来实现,而find_element()方法用于元素的定位。
2.创建LoginPage类
Page类中定义的这些方法都是页面操作的基本方法。下面根绝登录页面的特点在创建LoginPage类并继承Page类,这也是Page Object设计模式中最重要的对象层。
class LoginPage(Page): ''' 126邮箱登入页面模型 ''' url = '/' #定位器 username_loc = (By.XPATH,"//input[@name='email']") passwrod_loc = (By.XPATH,"//input[@name='password']") submit_loc = (By.XPATH, "//a[@id='dologin']") #Action def type_username(self,username): self.find_element(*self.username_loc).send_keys(username) sleep(1) def type_password(self,password): self.find_element(*self.passwrod_loc).send_keys(password) sleep(1) def submit(self): self.find_element(*self.submit_loc).click() sleep(1)
LoginPage类中主要对登录页面上的元素进行封装,使其成为更具体的操作方法。例如,用户名、密码和登录按钮都被封装成了方法。
3.创建test_user_login()函数
def test_user_login(driver,username,password): ''' 测试获取的用例名/密码是否可以登录 :param username: :param password: :return: ''' login_page = LoginPage(driver) login_page.open() login_page.type_username(username) login_page.type_password(password) login_page.submit()
test_user_login()函数将单个元素操作组成一个完整的动作,而这个动作包含了打开浏览器,输入用户名/密码、点击登录等单步。在使用该函数时需要将driver、username、password等信息作为函数的入参,这样该函数具有很强的可重用性。
4.创建main()函数
def main(): try: driver = webdriver.Chrome() driver.maximize_window() username = 'lirongyang123' password = '123lirongyang' test_user_login(driver,username,password) sleep(3) text = driver.find_element_by_xpath("//span[@id='spnUid']").text assert text == 'lirongyang123@126.com','用户名称不匹配,登录失败!' finally: #关闭浏览器窗口 driver.close() if __name__ == '__main__': main()
main()函数更接近于用户的操作行为。对用户来说,要进行邮箱的登录,需要关心的就是通过哪个浏览器打开邮箱网址、登录的用户名和密码是什么,至于输入框、按钮是如何定位的,则不需要关心。
这样分层的好处是,不同的层关心不同的问题。页面对象层只关心元素的定位问题,测试用例只关心测试的数据。
一个有分歧的地方是page对象是否应自身包含断言,或者仅仅提供数据给测试脚本来设置断言。在page对象中包含断言的倡导者认为,这有助于避免在测试脚本中出现重复的断言。可以更容易地提供更好的错误信息,并且提供更接近只做风格的API。不在page对象中包含断言的倡导者则认为,包含断言会混合访问页面数据和实现断言逻辑的职责,并且导致page对象过于臃肿。
笔者赞成在page对象中不包含断言,虽然我们可以通过为常用的断言提供断言库的方式来消除重复,提供更好的诊断,但从用户的角度去自动化的观点来看,判断是否登录成功是用户需要做的事情,不应该交由页面对象层来完成。
使用Page Object模式之后的另外一个好处就是有助于降低冗余。如果需要在十个用例中输入不同的用户名/密码登录,那么用main()方法写将会变得非常简洁。
因此,Page Object模型的作用在一个测试人员自己写主场景测试案例时是不容易体会到的,因为你不需要和开发、业务交流案例,也不会写很多重复的动作。但是,当你真正开始尝试ATDD或BDD,当你开始写一些重要的异常分支流程时,当你开始为新需求频繁维护修改案例时,就会意识到Page Object的作用。
最后,Page Object不是万灵药,也不是唯一方案,提高测试案例的可读性,避免案例步骤冗余才是终极目标。