Page Object是Selenium自动化测试项目开发实践的最佳设计模式之一,它主要体如今对界面交互细节的封装,这样可使测试方案更关注于业务而非界面细节。从而提升测试案例的可读性。web
Page Object设计模式的优势以下:编程
减小代码的重复设计模式
提升测试用例的可读性浏览器
提升测试用例的可维护性,特别是针对UI频繁变化的项目。函数
当为Web页面编写测试时,须要操做该Web页面上的元素。然而,若是在测试代码中直接操做HTML元素,那么你的代码是及其脆弱,由于UI常常变更。咱们能够将一个page对象封装成一个HTML页面,而后经过提供的应用程序特定的API来操做页面元素,而不是在HTML中四处搜寻。测试
Page Object原理:网站
page对象的一个基本经验法则是:凡是人能作的是,page对象经过软件客户端都可以作到。所以,它也应当提供一个易于编程的接口并隐藏窗口中底层的部分。因此访问一个文本框应该经过一个访问方法(accessor method)来实现字符串的获取与返回,复选框应当使用布尔值,按钮应当被表示为行为导向的方法名。page对象应当将在GUI控件上因此查询和操做数据的行为封装为方法。一个好的经验法则是,即便改变具体的控件,page对象的接口也不该当发生变化。ui
尽管该术语是“页面”对象,但并不意味着想要针对每一个页面创建一个这样的对象,例如,页面有重要意义的元素能够独立为一个page对象。经验法则的目的是经过给页面建模,使其对应用程序的使用者变得有意义。 url
下面以登陆126邮箱为例,经过Page Object设计模式来实现。spa
from selenium import webdriver from selenium.webdriver.common.by import By from time import sleep #建立基础类 class BasePage(object): #初始化 def __init__(self, driver): self.base_url = 'https://mail.qq.com/' self.driver = driver self.timeout = 30 #定义打开登陆页面方法 def _open(self): url = self.base_url self.driver.get(url) self.driver.switch_to.frame('login_frame') #切换到登陆窗口的iframe #定义定义open方法,调用_open()进行打开 def open(self): self._open() #定位方法封装 def find_element(self,*loc): return self.driver.find_element(*loc) #建立LoginPage类 class LoginPage(BasePage): username_loc = (By.ID, "u") password_loc = (By.ID, "p") login_loc = (By.ID, "login_button") #输入用户名 def type_username(self,username): self.find_element(*self.username_loc).clear() self.find_element(*self.username_loc).send_keys(username) #输入密码 def type_password(self,password): self.find_element(*self.password_loc).send_keys(password) #点击登陆 def type_login(self): self.find_element(*self.login_loc).click() #建立test_user_login()函数 def test_user_login(driver, username, password): """测试用户名/密码是否能够登陆""" login_page = LoginPage(driver) login_page.open() login_page.type_username(username) login_page.type_password(password) login_page.type_login() #建立main()函数 def main(): driver = webdriver.Chrome() username = '' #qq号码 password = '' #qq密码 test_user_login(driver, username, password) sleep(3) driver.quit() if __name__ == '__main__': main()
使用本身的帐号密码登陆,我把代码中的帐号密码删掉了。
#建立基础类 class BasePage(object): #初始化 def __init__(self, driver): self.base_url = 'https://mail.qq.com/' self.driver = driver self.timeout = 30 #定义打开登陆页面方法 def _open(self): url = self.base_url self.driver.get(url) self.driver.switch_to.frame('login_frame') #切换到登陆窗口的iframe #定义定义open方法,调用_open()进行打开 def open(self): self._open() #定位方法封装 def find_element(self,*loc): return self.driver.find_element(*loc)
首先建立一个基础类BasePage,在初始化方法__init__()中定义驱动(driver),基本的URL(base_url)和超时时间(timeout)等。
定义open()方法用于打开URL网站,但它自己并未作这件事情,而是交由_open()方法来实现,而find_element()方法用于元素的定位。
Page类中定义的这些方法都是页面操做的基本方法。下面根据登陆页的特色再建立LoginPage类并继承Page类,这也是Page Object设计模式中最重要的对象层。
#建立LoginPage类 class LoginPage(BasePage): username_loc = (By.ID, "u") password_loc = (By.ID, "p") login_loc = (By.ID, "login_button") #输入用户名 def type_username(self,username): self.find_element(*self.username_loc).clear() self.find_element(*self.username_loc).send_keys(username) #输入密码 def type_password(self,password): self.find_element(*self.password_loc).send_keys(password) #点击登陆 def type_login(self): self.find_element(*self.login_loc).click()
LoginPage类中主要对登陆页面上的元素进行封装,使其成为更具体的操做方法。例如,用户名、密码和登陆按钮都被封装成了方法。
#建立test_user_login()函数 def test_user_login(driver, username, password): """测试用户名/密码是否能够登陆""" login_page = LoginPage(driver) login_page.open() login_page.type_username(username) login_page.type_password(password) login_page.type_login()
test_user_login()函数将单个的元素操做组成一个完整的动做,而这个动做包含了打开浏览器,输入用户名/密码、点击登陆等单步操做。在使用该函数时须要将driver、username、password等信息做为函数的实参,这样该函数具备很强的可重用性。
#建立main()函数 def main(): driver = webdriver.Chrome() username = '' #qq号码 password = '' #qq密码 test_user_login(driver, username, password) sleep(3) driver.quit() if __name__ == '__main__': main()
main()函数更接近于用户的操做行为。对用户来讲,要进行邮箱的登陆,须要关心的就是经过哪一个浏览器打开邮箱网址、登陆的用户名和密码是什么,至于输入框、按钮是如何定位的,则不须要关心。
这样分层的好处是,不一样的层关心不一样的问题。页面对象层值关心元素的定位问题,测试用例只关心测试的数据。
一个有分歧的地方是page对象是否应自身包含断言,或者仅仅提供数据给测试脚本设置断言。在page对象中包含断言的倡导者认为,这有助于避免在测试脚本中出现重复的断言,能够更容易的提供更好的错误信息,而且提供更接近制做不问风格的API。再也不page对象中包含断言的倡导者则认为,包含断言会混合访问页面数据和实现断言逻辑的责任,而且致使page对象过于臃肿。
我比较同意在page对象中不包含断言,虽然完美能够经过为经常使用的断言提供断言库的方式来消除重复,提供更好的诊断,但从用户的角度去自动化的观点来看,判断是否登陆成功是用户须要作的事情,不该该交由页面对象层来完成。
使用Page Object模式以后的另一个好处就是有助于下降冗余。若是须要在10个用例中输入不一样的用户名/密码登陆,那么用main()方法写将变得很是简洁。
所以,Page Object模型的做用在一个测试人员本身写主场景测试案例时不容易体会到的,由于你不须要和开发、业务交流案例,也不会写不少重复的动做。可是,当你真正开始测试ATDD或BDD,当你开始写一些重要的异常分支流程时,当你开始为新需求频繁维护修改案例时,就会意识到Page Object的做用。
最后,Page Object不是万灵药,也不是惟一方案,提升测试案例的可读性,避免案例步骤冗余才是终极目标。