以QQ邮箱登陆为例,浅谈PageObject(PO)设计模式在UI自动化中的应用

一、认识PO

1.1 PO首次提出

PO的思想最先是2103年由IT大佬Martin Flower提出的:
martinfowler.com/bliki/PageO…
没错,就是他 css

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

1.2 PO原则解读

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

在这里插入图片描述
对官方的原则进行解读,咱们能够获得以下的信息:

1.2.1 方法意义
  • 用公共方法表明UI所提供的功能java

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

    在这里插入图片描述

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

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

    要么返回一个页面,这个页面能够是当前页(由于可能还要在这个页面进行其余操做),能够是其余页面(咱们操做某个方法后极可能会跳转到另外一个页面进行下一步操做);编程

    要么返回须要断言的值,测试用例总归有预期结果的对吧,那么最后确定要有方法返回一个值,用来给咱们作断言,来判断用例执行是否符合预期结果。api

    不要返回null或者写一个void没有返回值的方法,这样的方法没有意义,既不能为下一步操做创造条件,也不能为用例的断言提供结果。微信

  • 一样的行为不一样的结果能够建模为不一样的方法测试

    这个就比较好理解了,拿最简答的登陆场景来讲:
    一样的行为 不管输入的帐号密码正确与否,都是按照输入帐号密码点击登陆这样的行为去操做
    不一样的结果:帐号密码错误和正确获得的登陆响应必定是不一样的。
    建模为不一样的方法:对于登陆页来讲,就能够根据登陆信息正确与否建模出正确登陆帐号错误登陆密码错误登陆等方法了

  • 不要在方法内加断言

    对一个测试用例的执行结果进行判断必定是在测试用例里的,方法只是提供给咱们业务上须要的操做,所以断言不要加在方法里,而是应该写在用例里

1.2.2 字段意义
  • 不要暴露页面内部的元素给外部

    咱们使用PO的目的就是为了提升测试用例的可读性可维护性,只要咱们人能操做的事,经过page对象封装好的客户端均可以作到;就相似于一个接口,咱们只关心请求操做后接口的返回值是什么,而不须要关心接口内部究竟是如何工做的

  • 不须要建模UI内的全部元素

    一个UI页面可能会包含不少的元素,可是咱们只要根据实际业务需求,将咱们用的上的元素进行建模便可

1.3 PO的作法和优势

1.3.1 PO的作法总结
  • 以页面为单位独立建模
  • 隐藏实现细节
  • 本质是面向接口编程
1.3.2 基于POM的用例组织结构
  • page :完成对页面的封装
  • driver :完成对Web、Android、Ios、接口的驱动
  • testcase :调用各种page完成业务流程并进行断言
  • data :配置文件和数据驱动
  • utils :其余便捷的功能封装(可选)
1.3.3 PO的优势
  • 减小例如find click这类样板代码的重复
  • 测试用例的可读性提升,只关心业务流程
  • 测试用例可维护性提升,UI页面频繁被修改了,咱们只须要去修改对应PO便可,用例无需修改

二、PO封装演示

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

2.1 登陆场景预设

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

2.2 代码实现

1)建立基础类BasePage,初始化driver,并封装经常使用的元素操做方法,如clicksendKeys

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();

   @BeforeAll
   static 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)总体结构展现:

在这里插入图片描述

2.3 运行效果

在这里插入图片描述

三、补充说明

3.1 用例设计

  • case尽可能保持独立
  • suite体系管理用例的顺序
  • 不要把大量的业务校验逻辑放到UI自动化测试里, UI主要校验的是用户交付,操做流程,样式、数据、兼容性。
  • 与接口测试合理的分工

3.2 补充说明

以上仅仅是为了演示PO而举的一个简单的demo,实际上还有很大的优化空间:

  • 经常使用元素操做方法能够进一步封装的更完善
  • 可封装经常使用的操做util类,例如滑动
  • 特定元素的等待采用显示等待
  • 登陆用例能够利用参数化来以数据驱动的方式完成,使用例代码更简洁易懂
  • PO代码和testcase代码能够分开,test下只放case代码
  • 等等~后续须要继续完善!
相关文章
相关标签/搜索