PO的思想最早是2013年由IT大佬Martin Flower提出的:
https://martinfowler.com/bliki/PageObject.html
没错,就是他
— 没错,就是他 —

在他的文章里有这样一张经典样图,图片中展示了测试代码中直接操作HTML元素和使用PO模式将page对象封装成一个HTML页面,通过特定方法来操作元素的对比;如下图:

我们知道,PO主要就是应用在UI自动化测试上(Web端和App端均适用),因此2015年,Selenium官方给出了PO的设计原则说明:https://github.com/SeleniumHQ/selenium/wiki/PageObjects

对官方的原则进行解读,我们可以得到如下的信息:

  • 用公共方法代表UI所提供的功能

  • 如企业微信的通讯录页面,其中有“添加成员”、“批量导入,导出”、“设置所在部门”、“删除”等功能,这些功能都可以封装成通讯录这个UI界面所提供的方法;当然,部分数据较多或者较为复杂,复用性也比较高的话,例如添加成员,也可以单独抽离出来做一个page。

  • 方法应该返回其他的PageObject或者返回用于断言的数据

  • 我们既然以页面为对象进行业务操作,那么一个方法结束后必然要有返回值:

  • 要么返回一个页面,这个页面可以是当前页(因为可能还要在这个页面进行其他操作),可以是其他页面(我们操作某个方法后很可能会跳转到另一个页面进行下一步操作);

  • 要么返回需要断言的值,测试用例总归有预期结果的对吧,那么最后肯定要有方法返回一个值,用来给我们做断言,来判断用例执行是否符合预期结果。
    不要返回null或者写一个void没有返回值的方法,这样的方法没有意义,既不能为下一步操作创造条件,也不能为用例的断言提供结果。

  • 同样的行为不同的结果可以建模为不同的方法

  • 这个就比较好理解了,拿最简答的登录场景来说:

  • 同样的行为: 无论输入的账号密码正确与否,都是按照输入账号密码,点击登录这样的行为去操作

  • 不同的结果:账号密码错误和正确得到的登录响应一定是不同的。

  • 建模为不同的方法:对于登录页来说,就可以根据登录信息正确与否建模出正确登录、账号错误登录、密码错误登录等方法了

  • 不要在方法内加断言

  • 对一个测试用例的执行结果进行判断一定是在测试用例里的,方法只是提供给我们业务上需要的操作,因此断言不要加在方法里,而是应该写在用例里

    • 不要暴露页面内部的元素给外部
  • 我们使用PO的目的就是为了提高测试用例的可读性和可维护性,只要我们人能操作的事,通过page对象封装好的客户端都可以做到;就类似于一个接口,我们只关心请求操作后接口的返回值是什么,而不需要关心接口内部到底是如何工作的

    • 不需要建模UI内的所有元素
  • 一个UI页面可能会包含很多的元素,但是我们只要根据实际业务需求,将我们用的上的元素进行建模即可

  • 以页面为单位独立建模

    • 隐藏实现细节
    • 本质是面向接口编程
  • page :完成对页面的封装

    • driver :完成对Web、Android、Ios、接口的驱动
    • testcase :调用各类page完成业务流程并进行断言
    • data :配置文件和数据驱动
    • utils :其他便捷的功能封装(可选)
      1.3.3 PO的优点
  • 减少例如find click这类样板代码的重复

    • 测试用例的可读性提高,只关心业务流程
    • 测试用例可维护性提高,UI页面频繁被修改了,我们只需要去修改对应PO即可,用例无需修改

说的再多,不如动手,下面以QQ邮箱登录为例,演示PO模式在UI自动化中的应用

2.1 登录场景预设
登录页面提供login功能——LoginPage类+login方法
登录页面内有多少元素并不关心,隐藏内部细节
登录成功和失败会返回不同的页面
loginSuccess——MainPage(进入主页面)
loginFail——LoginPage(停留在登录页)
通过方法返回值判断登录是否符合预期

1)创建基础类BasePage,初始化driver,并封装常用的元素操作方法,如click、sendKeys等

package poshow.page;import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;import java.util.List;public class BasePage {public static WebDriver driver;public WebElement findElement(By by){return driver.findElement(by);}public List<WebElement> finElements(By by){return driver.findElements(by);}public void click(By by){findElement(by).click();}public void sendKeys(By by,String context){findElement(by).sendKeys(context);}public String getText(By by){return findElement(by).getText();}}

2)创建MainPage类,用于登录成功后的返回页面,由于这里并未演示登录后的操作,所以类中无具体方法实现,仅作为loginSuccess后的返回对象

package poshow.page;public class MainPage extends BasePage{
}

3)创建LoginPage类,继承BasePage类。定义所需元素定位方式并根据操作动作(输入账号、输入密码、点击登录)将其封装成具体的业务操作方法,例如登录成功,用户名错误登录、密码错误登录等,输入的测试数据作为方法的入参传入(username,password)

package poshow.page;import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import java.util.concurrent.TimeUnit;public class LoginPage extends BasePage{//定位器By usernameInput = By.name("u");  //获取用户名输入框By passwordInput = By.id("p");    //获取密码输入框By submitLogin = By.cssSelector("#login_button"); //获取登录按钮By ErrM = By.id("err_m");  //获取错误提示信息public void openUrl(){String url = "https://mail.qq.com/";driver = new ChromeDriver();driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);driver.get(url);driver.manage().window().maximize();driver.switchTo().frame("login_frame");}private void sleepWait(){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}//业务方法/*登录方法*/private void login(String username,String password){findElement(usernameInput).clear();findElement(passwordInput).clear();sendKeys(usernameInput,username);sendKeys(passwordInput,password);click(submitLogin);}/*成功登录*/public MainPage loginSuccess(String username,String password){login(username,password);return new MainPage();}/*密码错误登录message:你输入的帐号或密码不正确,请重新输入。*/public String loginWithErrPassword(String username,String password ){login(username,password);sleepWait();return getText(ErrM);}/*账号为空登录你还没有输入帐号!*/public String loginWithErrUsername(String username,String password){login(username,password);sleepWait();return getText(ErrM);}/*密码为空登录*/public String loginWithoutPassword(String username,String password){login(username,password);sleepWait();return getText(ErrM);}}

4)最后创建LoginTest测试类,编写测试用例;用例的编写更接近于人的行为,人想要登录邮箱,只需要依靠用户名和密码完成登录的行为即可,无需关注具体的输入框和登录按钮是如何定位,如何进行输入点击的。并在用例中加入断言进行判断。

package poshow.testcase;import org.junit.jupiter.api.*;
import poshow.page.LoginPage;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LoginTest {LoginPage loginPage = new LoginPage();@BeforeAllstatic void openUrl(){new LoginPage().openUrl();}@Test@DisplayName("密码错误登录")@Order(1)void loginWithErrPassword(){String username = "376057520";String password = "123456";String expectedErrM = "你输入的帐号或密码不正确,请重新输入。";String errM = loginPage.loginWithErrPassword(username, password);assertThat(errM,equalTo(expectedErrM));}@Test@DisplayName("账号错误登录")@Order(2)void loginWithErrUsername(){String username = "111";String password = "123456";String expectedErrM = "请输入正确的帐号!";String errM = loginPage.loginWithErrUsername(username, password);assertThat(errM,equalTo(expectedErrM));}@Test@DisplayName("空密码登录")@Order(3)void loginWithoutPassword(){String username = "376057520";String password = "";String expectedErrM = "你还没有输入密码!";String errM = loginPage.loginWithoutPassword(username, password);assertThat(errM,equalTo(expectedErrM));}@Test@DisplayName("正确登录")@Order(4)void logSuccess(){String username = "376057520";String password = "xxx";loginPage.loginSuccess(username,password);}
}

5)整体结构展示:

  • case尽量保持独立
    • suite体系管理用例的顺序
    • 不要把大量的业务校验逻辑放到UI自动化测试里, UI主要校验的是用户交付,操作流程,样式、数据、兼容性。
    • 与接口测试合理的分工 #### 3.2 补充说明 以上仅仅是为了演示PO而举的一个简单的demo,实际上还有很大的优化空间:
    • 常用元素操作方法可以进一步封装的更完善
    • 可封装常用的操作util类,例如滑动
    • 特定元素的等待采用显示等待
    • 登录用例可以利用参数化来以数据驱动的方式完成,使用例代码更简洁易懂
    • PO代码和testcase代码可以分开,test下只放case代码
  • 原文链接
    哈喽,喜欢这篇文章的话烦请点个赞哦!万分感谢(*^▽^*)PS:有问题可以联系我们哦v ceshiren001
    [获取更多技术文章分享] https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=csdn2&timestamp=1663233229&author=ML

PageObject(PO)设计模式在 UI 自动化中的实践总结(以 QQ 邮箱登陆为例)相关推荐

  1. PO设计模式在 UI 自动化中的实践

    PO的思想最早是2013年由IT大佬Martin Flower提出的: https://martinfowler.com/bliki/PageObject.html 没错,就是他 - 没错,就是他 - ...

  2. UI 自动化中的分层设计

    背景 2,3 年前更写过一些 UI 自动化的相关文章, 包括一些设计原则,怎么设计划分页面封装, 常用的设计模式等. 但是没有详细描述 UI 自动化中的分层理念, 赶上最近在新项目里做 UI 自动化测 ...

  3. UI自动化中的分层设计

    分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请轻击http://www.captainbed.net 以前的设计 在过去 UI 自动化测试领域有一个规范的 ...

  4. UI 自动化中如何处理上传文件事件

    在UI自动化中,我们经常会遇到上传文件操作.处理上传事件是一个比较麻烦的操作,因为点击上传控件会弹出Windows窗口供用户选择文件,但是Windows窗口是浏览器之外的组件,所以selenium本身 ...

  5. 浅尝UI自动化之Airtest实践

    1.浅尝UI自动化之Airtest实践 背景 由于很多公司都采用敏捷开发的模式,测试也要跟着进行敏捷测试.而每个迭代的周期非常短,经常要对原有功能进行回归测试,这样就增加了大量重复人力成本. 引入UI ...

  6. android 图像对比,图像对比在UI自动化中的应用

    引子 继多版本模拟器的支持工作告一段落之后,如何利用这些技术产生更大的价值,成为了接下来需要思考的问题.当然,接下来的课题就涉及到了今天的图像对比技术.说来有点内疚,虽然也算是科班出身,只可惜大学还没 ...

  7. 在 UI 自动化中调用浏览器 API 的方法与使用场景

    背景 事情的背景是最近在新项目中做 UI 自动化, 就想调研一下如何与浏览器的 API 交互获取一些额外的信息以在 case 失败的时候获取一些额外的信息帮助 debug. 后来就又慢慢延伸到了前端性 ...

  8. Web UI自动化中使用cookies登录

    在Web自动化中,我们经常涉及到用户登录,如果执行每个测试用例都打开登录页面,会产生大量重复代码 这里写目录标题 一.获取当前登录用户的cookies 二.使用cookies登录 一.获取当前登录用户 ...

  9. 【得物技术】浅尝UI自动化之Airtest实践

    一.背景 由于很多公司都采用敏捷开发的模式,测试也要跟着进行敏捷测试.而每个迭代的周期非常短,经常要对原有功能进行回归测试,这样就增加了大量重复人力成本.引入UI自动化测试可以用来快速回归测试app原 ...

  10. python中使用selenium模块登录QQ邮箱

    直接上代码,需要安装selenium模块,各个函数作用可以百度. 注意:下面程序中需要替换你自己的账户和密码. #-*-coding:utf-8-*-from selenium import webd ...

最新文章

  1. 比特币脚本及交易分析 - 智能合约雏形
  2. java 建树源码_Java实现的二叉树常用操作【前序建树,前中后递归非递归遍历及层序遍历】...
  3. 无法分配更多的internet句柄怎么回事_一文精通Java NIO(内容较多,无耐心者勿点)...
  4. 1.5 编程基础之循环控制 36 计算多项式的值 python
  5. Trying to embed a platform view but the PrerollContext does not support embedding
  6. [leetcode]207. Course Schedule课程表
  7. Asp.net通过Gmail发送邮件
  8. 两轮差速驱动机器人运动模型及应用分析
  9. Linux如何产看系统信息
  10. 腾讯云服务器如何开启虚拟化,腾讯云服务器虚拟化驱动是什么
  11. 方钢管弹性模量计算方式_弹性模量计算公式.doc
  12. fatal:unable to access ‘https://github....‘:Failed to conect to github.com port 443:拒绝连接的解决方法
  13. 发现了个神奇的图片压缩工具
  14. 【中科三方】什么是云解析DNS?云解析DNS有必要购买吗?
  15. php eval()函数
  16. 有线广电如何转型和发展
  17. The server time zone value ‘� й ��� ׼ʱ ��‘ is unrecognized or represents more than one time zone.
  18. c语言键盘驱动程序,c语言键盘扫描程序
  19. 十折交叉验证pythoniris_python机器学习-交叉验证(cross-vaildation)
  20. Redis—列表(List)、集合(Set)、哈希(Hash)、有序集合 Zset

热门文章

  1. 22讲MySQL有哪些“饮鸩止渴”提高性能的方法
  2. 部署大宗商品撮合交易平台,实现高效交易与资源信息对接
  3. halcon算子翻译——cooc_feature_image
  4. oracle weituxinxi,Oracle 语句记录
  5. [CyanogenMOD移植教程]第一章:环境的搭建
  6. Convex Optimization: 3 Convex functions
  7. c# autoscroll_禁用Firefox中令人讨厌的AutoScroll功能
  8. 解决warnings potentially fixable with the `--fix`
  9. 解决 3 errors and 0 warnings potentially fixable with the `--fix` option.问题
  10. 靶机渗透练习99-hacksudo:FOG