自动化设计模式 POM (Page Object Model)

本文将使用Maven + TestNG + Java + Selenium 的项目来作为例子来讲解如何实现POM。

1.POM是什么

Page Object Model 是一种自动化测试的设计模式,为Web UI元素创建Object Repository。它的优点就是减少了代码的重复,提高了测试维护。

一般情况下,POM的项目需要包括3部分:

  • PageModle:每个页面都应该有自己的的PageClass文件,这个PageClass将标识该页面的Web元素,并包含对这些Web元素执行操作的方法
  • TestCase:测试用例管理部分,一个功能点可以对应一个或者多个case,尽可能的提高覆盖率
  • Utilities:可以包含一些报告等内容

按照上边的解释,我们可以把项目分为三部分,在项目中创建三个packages: pages,tests,utilities,如下图所示:

2.使用POM的好处

  1. POM模式使得测试用例和页面元素操作分开,这样可以使我们的代码更干净,更容易理解,且更容易维护;
  2. 对象存储库独立于测试用例,因此我们可以使用不同的工具为不同的目的使用相同的对象存储库。 例如,我们可以将POM与用于功能测试的Test NG/JUnit集成,同时与用于验收测试的JBehave/Cucumber集成;
  3. 测试用例更加简短,而且可重用页面元素和方法,代码变得更少,更简洁;
  4. 方法使用更真实的名称,这些名称可以很容易地与UI中发生的操作映射。例如:如果单击按钮后,回返回主页面,方法名称将类似于gotoHomePage(),易于理解。

3.如何创建一个POM项目

接下来我们通过一个简单的测试用例(登录账户的验证)来进行扩展,一步一步演变成POM的模式的项目。

最原始的代码如下:

    @Test
    public void ValidPasswordLogin(){

        //1. Open browser and open test url
        System.setProperty("webdriver.chrome.driver", "Driver/chromedriver");
        WebDriver driver;
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("http://jinshuju.com");

        //2. Test part

            //2. 1 Home page: Click login button go to login page
        driver.findElement(By.cssSelector(".login-link")).click();

            //2. 2 Login page: Input username and password to login
        driver.findElement(By.id("auth_key")).sendKeys("131****8959");
        driver.findElement(By.cssSelector(".gd-btn.gd-btn-primary-solid")).click();
        driver.findElement(By.id("password")).sendKeys("Abcdef");
        driver.findElement(By.cssSelector(".gd-btn.gd-btn-primary-solid")).click();
        Assert.assertEquals(driver.findElement(By.className("validate-invalid")).getText(),"密码错误,请输入正确的密码(还剩余4次)");

        //3. Quite browser
        driver.quit();
    }
  1. 首先,通过上述代码,可以看到一些common的方法例如:sendKeys, click, getText, assert,几乎在每个页面,都会频繁使用这些方法。如果单纯的使用Selenium自己的这些方法,可能会遇到一些问题,例如因为加载慢某个按钮还没有加载完成,但是已经触发了点击事件,此时会因为找不到按钮而失败,等等很多类似的问题。基于这些问题,我们首先可以对selenium这些最基本的方法进行重写。在pages package下边创建一个BasePage.java文件来重写这些方法,之后所有的页面都继承于BasePage类。

BasePage.java

package pages;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class BasePage {

    public WebDriver driver;
    public WebDriverWait wait;

    //Constructor
    public BasePage (WebDriver driver){
        this.driver = driver;
        wait = new WebDriverWait(driver,20);
    }

    //Wait
    public void waitVisibility(By elementBy) {
        wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(elementBy));
    }

    //Click
    public void click(By elementBy){
        waitVisibility(elementBy);
        driver.findElement(elementBy).click();
    }

    //Write Text
    public void writeText (By elementBy, String text) {
        waitVisibility(elementBy);
        driver.findElement(elementBy).sendKeys(text);
    }

    //Read Text
    public String readText (By elementBy) {
        waitVisibility(elementBy);
        return driver.findElement(elementBy).getText();
    }

    //Assert
    public void assertEquals (By elementBy, String expectedText) {
        waitVisibility(elementBy);
        Assert.assertEquals(readText(elementBy), expectedText);
    }
}
  1. 接下来,分析整个代码块,可以分为三部分:
  1. 在执行测试步骤之前,我们需要先打开浏览器,输入测试的地址;
  2. 执行测试用例;
  3. 执行完测试用例之后,关闭浏览器。
  • 第1和3这两部分:
    分析: 因为每条测试用例都会打开浏览器和关闭浏览器,所以我们可以将他们放在一个BaseTest类中,使用@BeforeMethod和@AfterMethod注解(这个是TestNG的注解,如果不知道请学习TestNG的注解),在每一个测试方法执行之前都会先执行@BeforeMethod,在每一个测试方法执行之后都会执行@AfterMethod。
    做法: 在tests的Package下边创建一个BaseTest.java类,将1和3 两部分写在这个BaseTest类中:

BaseTest.java

package tests;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.*;

public class BaseTest {
    public WebDriver driver;

    @BeforeMethod
    public void setUp() {
        //1. Open browser and open test url
        System.setProperty("webdriver.chrome.driver", "Driver/chromedriver");
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("http://jinshuju.com");
    }

    @AfterMethod
    public void quiteBrowser() {
        //3. Quite browser
        driver.quit();
    }
}
  • 第2部分
    分析: 第2部分是测试代码部分,可以看到 1). 要对两个页面(Home page 和Login page)进行操作;2). 每个页面都有各自的元素和方法。所以我们可以按照页面进行拆分,每个页面为一个类,每个页面的类中包含对应页面的元素部分和方法部分,这样就比较清晰,而且当添加新的Case的时候也可以去复用现有的方法。
    做法: 在pages这个package下边创建一个HomePage.java 类和一个LoginPage.java 类,将各自页面的方法也在各自页面中。 然后在每个page类中分为定位元素和方法两部分:

HomePage.java

package pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class HomePage extends BasePage{

    //Constructor
    public HomePage(WebDriver driver) {
        super(driver);
    }

    //Elements
    By loginButton = By.cssSelector(".btn-outline");

    //Actions
    public void clickLogin(){
        click(loginButton);
    }
}

LoginPage.java

package pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.testng.Assert;

public class LoginPage extends BasePage{

    //Constructor
    public LoginPage(WebDriver driver) {
        super(driver);
    }

    //Elements
    By userNameInput = By.id("auth_key");
    By loginButton = By.cssSelector(".btn-lg");
    By passwordInput = By.id("password");
    By errorMessage = By.cssSelector(".alert");

    //Actions
    public void inputUsernamePwd(String userName, String password){
        writeText(userNameInput,userName);
        writeText(passwordInput,password);
        click(loginButton);
    }

    public void verifyErrorMessage(String expectedErrorMessage){
        Assert.assertEquals(readText(errorMessage),expectedErrorMessage);
    }
}
  1. 此时,已经给每个页面创建了自己的类,而且每个页面类包含自己的元素定位和方法。接下来,需要创建测试用例来调用每个页面的方法,在tests package 下创建 loginTests.java 类,内容如下。注释部分是之前的代码,已经被新的代码代替。这样测试用例部分就很清晰,很简洁。
    loginTests.java
package tests;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.Test;
import pages.HomePage;
import pages.LoginPage;

public class loginTests extends BaseTest{

    public static WebDriver driver;
    public static LoginPage loginPage = new LoginPage(driver);
    public static HomePage homePage = new HomePage(driver);

    @Test
    public void InvalidPasswordLogin(){
        
        //driver.findElement(By.cssSelector(".btn-outline")).click();
        homePage.clickLogin();
        
        //driver.findElement(By.id("auth_key")).sendKeys("131****8959");
        //driver.findElement(By.id("password")).sendKeys("Abcdef");
        //driver.findElement(By.cssSelector(".btn-lg")).click();
        loginPage.inputUsernamePwd("131****8959","Abcdef");
        
        //Assert.assertEquals(driver.findElement(By.cssSelector(".alert")).getText(),"账号密码不正确");
        loginPage.verifyErrorMessage("账号密码不正确");
    }
}
  1. 分析结束后,我们的代码结构如下图:
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335