聊聊UI自动化的PageObject设计模式
当我们开发UI自动化测试用例时,需要引用页面中的元素(数据)才能够进行点击(动作)并显示出页面内容。如果我们开发的用例是直接对HTML元素进行操作,则这样的用例无法“应对”页面中UI的更改。
PageObject模式就是对HTML页面以及元素细节的封装,并对外提供应用级别的API,使你摆脱与HTML的纠缠。
什么是PageObject模型?
PageObject模型是一种设计模式,其核心是减少代码重复(最小化代码更新/维护用例)以降低用例开发的工作量。利用PageObject模型,为每个网页创建Page类,测试场景中用的定位器/元素存储在单独的类文件中,并且测试用例在不同的文件中,使代码更加模块化。由于元素定位器和测试脚本是分开存储的,因此对 Web UI 元素的任何更改只需要在测试场景代码中进行更改即可。
基于PageObject模型的实现包含以下两点:
Page类——将页面封装成 Page 类,页面元素为 Page 类的成员元素,页面功能放在 Page 类方法里。
测试类——针对这个 Page 类定义一个测试类,在测试类调用 Page 类的各个类方法完成测试。它使用Page类中的页面方法/方法与页面的 UI 元素进行交互。如果网页的UI有变化,只需要更新Page类,测试类无需改动。
为什么使用PageObject模型?
随着项目新需求的不断迭代,开发代码和测试代码的复杂性增加。因此,开发自动化测试代码时必须遵循正确的项目结构。否则,代码可能会变得难以维护。
Web由各种 WebElement(例如,菜单项、文本框、复选框、单选按钮等)的不同网页组成。测试用例与这些元素交互,如果Selenium 定位器没有以正确的方式管理,代码的复杂性将成倍增加。
测试代码的重复或定位器的重复使用会降低代码的可读性,从而导致代码维护的开销成本增加。例如,测试电子商务网站的登录功能,我们使用Selenium进行自动化测试,测试代码可以与网页的底层 UI 或定位器进行交互。如果修改了UI或该页面上元素的路径发生了变化,会发生什么情况?自动化测试用例将失败,因为该用例执行的过程在网页上找不到依赖的页面元素。如果你对所有网页采用相同的测试开发方法。在这种情况下,测试者必须花费大量精力来即时更新分散在不同页面中的定位器。
PO模式优点
PageObject模型的优点
现在大家已经了解了PageObject设计模式的基础知识,让我们来看看使用该设计模式的一些优点:
提高可重用性——不同 POM 类中的PageObject方法可以在不同的测试用例/测试套件中重用。因此,由于页面方法的可重用性增加,整体代码量将大大减少。
提升可维护性——由于测试场景和定位器是分开存储的,它使代码更清晰,并且在维护测试代码上花费的精力更少。
降低UI更改对用例造成的影响——即使 UI 中经常发生更改,也只需要在对象存储库(存储定位器)中进行更改,对测试场景几乎没有影响。
便与多个测试框架集成——由于测试实现与PageObject的存储库分离,我们可以将相同的存储库与不同的测试框架一起使用。例如,Test Case-1可以使用 Robot 框架,Tese Case - 2 可以使用 pytest 框架等,单个测试套件可以包含使用不同测试框架实现的测试用例。
PageObject实践
首先我们先看一个反例,一个不使用PageObject模式的自动化测试示例(测试用户登录场景):
/***
* Tests login feature
*/
public class Login { public void testLogin() {
// fill login data on sign-in page
driver.findElement(By.name("user_name")).sendKeys("userName");
driver.findElement(By.name("password")).sendKeys("my supersecret password");
driver.findElement(By.name("sign-in")).click(); // verify h1 tag is "Hello userName" after login
driver.findElement(By.tagName("h1")).isDisplayed();
assertThat(driver.findElement(By.tagName("h1")).getText(), is("Hello userName"));
}
}
这种写法有两个问题:
测试用例和 AUT 的定位器没有分离,两者耦合在一起。如果AUT的UI更改布局或登录的输入和处理方式,则用例本身必须更改。
如果多个页面都需要登录,则定位器将分布在多个测试用例中。
使用PageObject模式,测试方法(登录)写法如下:
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; /**
* Page Object encapsulates the Sign-in page.
*/
public class SignInPage {
protected WebDriver driver; // <input name="user_name" type="text" value="">
private By usernameBy = By.name("user_name");
// <input name="password" type="password" value="">
private By passwordBy = By.name("password");
// <input name="sign_in" type="submit" value="SignIn">
private By signinBy = By.name("sign_in"); public SignInPage(WebDriver driver){
this.driver = driver;
} /**
* Login as valid user
*
* @param userName
* @param password
* @return HomePage object
*/
public HomePage loginValidUser(String userName, String password) {
driver.findElement(usernameBy).sendKeys(userName);
driver.findElement(passwordBy).sendKeys(password);
driver.findElement(signinBy).click();
return new HomePage(driver);
}
}
用户登录以后的元素定位(用于断言)方法写法如下:
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; /**
* Page Object encapsulates the Home Page
*/
public class HomePage {
protected WebDriver driver; // <h1>Hello userName</h1>
private By messageBy = By.tagName("h1"); public HomePage(WebDriver driver){
this.driver = driver;
if (!driver.getTitle().equals("Home Page of logged in user")) {
throw new IllegalStateException("This is not Home Page of logged in user," +
" current page is: " + driver.getCurrentUrl());
}
} /**
* Get message (h1 tag)
*
* @return String message text
*/
public String getMessageText() {
return driver.findElement(messageBy).getText();
} public HomePage manageProfile() {
// Page encapsulation to manage profile functionality
return new HomePage(driver);
}
/* More methods offering the services represented by Home Page
of Logged User. These methods in turn might return more Page Objects
for example click on Compose mail button could return ComposeMail class object */
}
登录测试用例使用上述两个PageObject,如下所示。
/***
* Tests login feature
*/
public class TestLogin { @Test
public void testLogin() {
SignInPage signInPage = new SignInPage(driver);
/// login
HomePage homePage = signInPage.loginValidUser("userName", "password");
// assert login result
assertThat(homePage.getMessageText(), is("Hello userName"));
} }
注意事项
从上述例子中,可以看出PageObject的设计方式有很大的灵活性,这里也总结一下使用PageObject开发用例的注意事项:
PageObject本身不进行断言。断言是测试用例的一部分,应该始终包含在测试代码中,即与测试内容相关的代码不应包含在PageObject中。
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
inbox.assertMessageWithSubjectIsUnread("I like cheese");
inbox.assertMessageWithSubjectIsNotUnread("I'm not fond of tofu");
}
应该重写为:
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu"));
}
单一的验证可以包含在PageObject内,即验证页面以及页面上的关键元素是否正确加载,且此验证应在实例化PageObject时完成。在上面的示例中, HomePage 构造函数检查预期页面是否加载完毕以执行测试代码。
附:以PageObject模式开发的完整的登录场景代码
public class LoginPage {
private final WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
// Check that we're on the right page.
if (!"Login".equals(driver.getTitle())) {
// Alternatively, we could navigate to the login page, perhaps logging out first
throw new IllegalStateException("This is not the login page");
}
}
// The login page contains several HTML elements that will be represented as WebElements.
// The locators for these elements should only be defined once.
By usernameLocator = By.id("username");
By passwordLocator = By.id("passwd");
By loginButtonLocator = By.id("login");
// The login page allows the user to type their username into the username field
public LoginPage typeUsername(String username) {
// This is the only place that "knows" how to enter a username
driver.findElement(usernameLocator).sendKeys(username);
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
return this;
}
// The login page allows the user to type their password into the password field
public LoginPage typePassword(String password) {
// This is the only place that "knows" how to enter a password
driver.findElement(passwordLocator).sendKeys(password);
// Return the current page object as this action doesn't navigate to a page represented by another PageObject
return this;
}
// The login page allows the user to submit the login form
public HomePage submitLogin() {
// This is the only place that submits the login form and expects the destination to be the home page.
// A seperate method should be created for the instance of clicking login whilst expecting a login failure.
driver.findElement(loginButtonLocator).submit();
// Return a new page object representing the destination. Should the login page ever
// go somewhere else (for example, a legal disclaimer) then changing the method signature
// for this method will mean that all tests that rely on this behaviour won't compile.
return new HomePage(driver);
}
// The login page allows the user to submit the login form knowing that an invalid username and / or password were entered
public LoginPage submitLoginExpectingFailure() {
// This is the only place that submits the login form and expects the destination to be the login page due to login failure.
driver.findElement(loginButtonLocator).submit();
// Return a new page object representing the destination. Should the user ever be navigated to the home page after submiting a login with credentials
// expected to fail login, the script will fail when it attempts to instantiate the LoginPage PageObject.
return new LoginPage(driver);
}
// Conceptually, the login page offers the user the service of being able to "log into"
// the application using a user name and password.
public HomePage loginAs(String username, String password) {
// The PageObject methods that enter username, password & submit login have already defined and should not be repeated here.
typeUsername(username);
typePassword(password);
return submitLogin();
}
}
荐书
本书致力于帮助Python开发人员挖掘这门语言及相关程序库的优秀特性,避免重复劳动,同时写出简洁、流畅、易读、易维护,并且具有地道Python风格的代码。本书尤其深入探讨了Python语言的高级用法,涵盖数据结构、Python风格的对象、并行与并发,以及元编程等不同的方面。
加质量君微信,邀请进群免费领软件测试进阶测试开发1T自学视频
聊聊UI自动化的PageObject设计模式的更多相关文章
- 『心善渊』Selenium3.0基础 — 30、UI自动化测试之POM设计模式
目录 (一)POM模式介绍 1.什么是POM介绍 2.为什么要使用POM模式 3.POM的优势 4.POM模式封装思路 (1)POM模式将页面分成三层 (2)POM模式的核心要素(重点) (3)总结 ...
- 基于PO和单例设计模式用python+selenium进行ui自动化框架设计
一)框架目录的结构 二)config包当中的config.ini文件主要是用来存项目的绝对路径,是为了后续跑用例和生成测试报告做准备然后目前的配置文件大都会用yaml,ini,excel,还有.py也 ...
- UI自动化学习笔记- PO模型介绍和使用
一.PO模型 1.PO介绍:page(页面) object(对象) 在自动化中,Selenium 自动化测试中有一个名字经常被提及 PageObject (思想与面向对象的特征相同),通常PO 模型可 ...
- 【Android测试】UI自动化代码优化之路(临时发布, 随时删除)
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5993622.html 关于UI自动化的抱怨 听过不少人这样 ...
- 移动UI自动化-Page Objects Pattern
移动UI自动化,看起来美好,践行起来却难.做个目光短见的务实主义者.Page Objects Pattern是Selenium官方推崇的方式,最近研究写测试用例最佳实践之Page Objects,同时 ...
- Page-Object设计模式
自动化脚本初写之际一定是只求完成功能测试,页面by.id.by.name.by.xpath满篇飞.业务逻辑代码重复率也是越来越高.慢慢的写着写着开始重构,开始封装一些方法.代码量好一些的人会在代码开始 ...
- PageObject设计模式,在selenium自动化测试中的运用
PageObject设计模式1. Web自动化测试框架(WebTestFramework)是基于Selenium框架且采用PageObject设计模式进行二次开发形成的框架. 2. web测试时,建议 ...
- UI自动化selenium
1.什么是UI自动化?模拟人用代码的方式去操作页面2.为什么要做UI自动化?后期迭代的时候,老功能比较多,人工维护成本较大,重复性工作较多,这个时候就考虑因为UI自动化3.什么时候做UI自动化?项目稳 ...
- 【Android测试】UI自动化代码优化之路
◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5993622.html 关于UI自动化的抱怨 听过不少人这样 ...
随机推荐
- 6月5日 python复习 模块
"""1. os和sys都是干什么的?2. 你工作中都用过哪些内置模块?3. 有没有用过functools模块?"""1. os 系统相关 ...
- (bzoj4408)[FJOI2016]神秘数(可持久化线段树)
(bzoj4408)[FJOI2016]神秘数(可持久化线段树) bzoj luogu 对于一个区间的数,排序之后从左到右每一个数扫 如果扫到某个数a时已经证明了前面的数能表示[1,x],那么分情况: ...
- JavaScript 事件循环(1) —— 从 setTimeout 说起
转变认知 setTimeout 可能是很多前端工程师爱用的方法,它可以使得一段代码延迟执行,例如: setTimeout(() => console.log('A'), 1000); // 在1 ...
- 《前端运维》五、k8s--3灰度发布、滚动更新与探针
一.灰度发布 灰度发布是一种发布方式,也叫金丝雀发布,起源是矿工在下井之前会先放一只金丝雀到井里,如果金丝雀不叫了,就代表瓦斯浓度高.原因是金丝雀对瓦斯气体很敏感.灰度发布的做法是:会在现存旧应用的基 ...
- 漫长的旅途--C++primer学习-命名空间以及类的自动转换和强制转换
C++用名称空间来控制名称的作用域: 1不同命名空间的同名变量可以同时存在,不会发生冲突 2命名空间不能出现在代码块中 3我们用作用域运算符::,使用空间名来限定名称,最常用的std::cout 4u ...
- redis整理:常用命令,雪崩击穿穿透原因及方案,分布式锁实现思路,分布式锁redission(更新中)
redis个人整理笔记 reids常见数据结构 基本类型 String: 普通key-value Hash: 类似hashMap List: 双向链表 Set: 不可重复 SortedSet: 不可重 ...
- 匿名内部类编译生成的class文件
一.问题描述 跳槽到的新公司,相比原来的公司,软件开发非常不规范,经常各种坑.项目部署到生产现场之后,出现问题,我在修改之后编译生成class文件,然后将class文件发给现场的技术支持,技术支持将c ...
- Error和Exception有什么区别?
Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题:比如内存溢出,不可能指望程序能处理这样的情况:Exception表示需要捕捉或者需要程序进行处理的异常, ...
- kafka客户端打印日志
kafka 0.10.0 java客户端使用slf4j作为日志门面,需要我们加入具体的日志实现依赖才能打印日志,日志框架:http://www.cnblogs.com/set-cookie/p/883 ...
- 学习Kvm(五)
KVM(一)搭建部署与概述 一,KVM概述 1.1 虚拟化概述 在计算机技术中,虚拟化意味着创建设备或资源的虚拟版本,如服务器,存储设备,网络或者操作系统等等 [x] 虚拟化技术分类: 系统虚拟化(我 ...