自動(dòng)化設(shè)計(jì)模式 POM (Page Object Model)

本文將使用Maven + TestNG + Java + Selenium 的項(xiàng)目來(lái)作為例子來(lái)講解如何實(shí)現(xiàn)POM。

1.POM是什么

Page Object Model 是一種自動(dòng)化測(cè)試的設(shè)計(jì)模式,為Web UI元素創(chuàng)建Object Repository。它的優(yōu)點(diǎn)就是減少了代碼的重復(fù),提高了測(cè)試維護(hù)。

一般情況下,POM的項(xiàng)目需要包括3部分:

  • PageModle:每個(gè)頁(yè)面都應(yīng)該有自己的的PageClass文件,這個(gè)PageClass將標(biāo)識(shí)該頁(yè)面的Web元素,并包含對(duì)這些Web元素執(zhí)行操作的方法
  • TestCase:測(cè)試用例管理部分,一個(gè)功能點(diǎn)可以對(duì)應(yīng)一個(gè)或者多個(gè)case,盡可能的提高覆蓋率
  • Utilities:可以包含一些報(bào)告等內(nèi)容

按照上邊的解釋,我們可以把項(xiàng)目分為三部分,在項(xiàng)目中創(chuàng)建三個(gè)packages: pages,tests,utilities,如下圖所示:

2.使用POM的好處

  1. POM模式使得測(cè)試用例和頁(yè)面元素操作分開,這樣可以使我們的代碼更干凈,更容易理解,且更容易維護(hù);
  2. 對(duì)象存儲(chǔ)庫(kù)獨(dú)立于測(cè)試用例,因此我們可以使用不同的工具為不同的目的使用相同的對(duì)象存儲(chǔ)庫(kù)。 例如,我們可以將POM與用于功能測(cè)試的Test NG/JUnit集成,同時(shí)與用于驗(yàn)收測(cè)試的JBehave/Cucumber集成;
  3. 測(cè)試用例更加簡(jiǎn)短,而且可重用頁(yè)面元素和方法,代碼變得更少,更簡(jiǎn)潔;
  4. 方法使用更真實(shí)的名稱,這些名稱可以很容易地與UI中發(fā)生的操作映射。例如:如果單擊按鈕后,回返回主頁(yè)面,方法名稱將類似于gotoHomePage(),易于理解。

3.如何創(chuàng)建一個(gè)POM項(xiàng)目

接下來(lái)我們通過(guò)一個(gè)簡(jiǎn)單的測(cè)試用例(登錄賬戶的驗(yàn)證)來(lái)進(jìn)行擴(kuò)展,一步一步演變成POM的模式的項(xiàng)目。

最原始的代碼如下:

    @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(),"密碼錯(cuò)誤,請(qǐng)輸入正確的密碼(還剩余4次)");

        //3. Quite browser
        driver.quit();
    }
  1. 首先,通過(guò)上述代碼,可以看到一些common的方法例如:sendKeys, click, getText, assert,幾乎在每個(gè)頁(yè)面,都會(huì)頻繁使用這些方法。如果單純的使用Selenium自己的這些方法,可能會(huì)遇到一些問(wèn)題,例如因?yàn)榧虞d慢某個(gè)按鈕還沒(méi)有加載完成,但是已經(jīng)觸發(fā)了點(diǎn)擊事件,此時(shí)會(huì)因?yàn)檎也坏桨粹o而失敗,等等很多類似的問(wèn)題。基于這些問(wèn)題,我們首先可以對(duì)selenium這些最基本的方法進(jìn)行重寫。在pages package下邊創(chuàng)建一個(gè)BasePage.java文件來(lái)重寫這些方法,之后所有的頁(yè)面都繼承于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. 接下來(lái),分析整個(gè)代碼塊,可以分為三部分:
  1. 在執(zhí)行測(cè)試步驟之前,我們需要先打開瀏覽器,輸入測(cè)試的地址;
  2. 執(zhí)行測(cè)試用例;
  3. 執(zhí)行完測(cè)試用例之后,關(guān)閉瀏覽器。
  • 第1和3這兩部分:
    分析: 因?yàn)槊織l測(cè)試用例都會(huì)打開瀏覽器和關(guān)閉瀏覽器,所以我們可以將他們放在一個(gè)BaseTest類中,使用@BeforeMethod和@AfterMethod注解(這個(gè)是TestNG的注解,如果不知道請(qǐng)學(xué)習(xí)TestNG的注解),在每一個(gè)測(cè)試方法執(zhí)行之前都會(huì)先執(zhí)行@BeforeMethod,在每一個(gè)測(cè)試方法執(zhí)行之后都會(huì)執(zhí)行@AfterMethod。
    做法: 在tests的Package下邊創(chuàng)建一個(gè)BaseTest.java類,將1和3 兩部分寫在這個(gè)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部分是測(cè)試代碼部分,可以看到 1). 要對(duì)兩個(gè)頁(yè)面(Home page 和Login page)進(jìn)行操作;2). 每個(gè)頁(yè)面都有各自的元素和方法。所以我們可以按照頁(yè)面進(jìn)行拆分,每個(gè)頁(yè)面為一個(gè)類,每個(gè)頁(yè)面的類中包含對(duì)應(yīng)頁(yè)面的元素部分和方法部分,這樣就比較清晰,而且當(dāng)添加新的Case的時(shí)候也可以去復(fù)用現(xiàn)有的方法。
    做法: 在pages這個(gè)package下邊創(chuàng)建一個(gè)HomePage.java 類和一個(gè)LoginPage.java 類,將各自頁(yè)面的方法也在各自頁(yè)面中。 然后在每個(gè)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. 此時(shí),已經(jīng)給每個(gè)頁(yè)面創(chuàng)建了自己的類,而且每個(gè)頁(yè)面類包含自己的元素定位和方法。接下來(lái),需要?jiǎng)?chuàng)建測(cè)試用例來(lái)調(diào)用每個(gè)頁(yè)面的方法,在tests package 下創(chuàng)建 loginTests.java 類,內(nèi)容如下。注釋部分是之前的代碼,已經(jīng)被新的代碼代替。這樣測(cè)試用例部分就很清晰,很簡(jiǎn)潔。
    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(),"賬號(hào)密碼不正確");
        loginPage.verifyErrorMessage("賬號(hào)密碼不正確");
    }
}
  1. 分析結(jié)束后,我們的代碼結(jié)構(gòu)如下圖:
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容