10 Page Object 模型
10.1 概述
? ? 在針對一個WEB頁面編寫自動化測試用例時忍啤,需要引用頁面中的元素(數(shù)據(jù))才能進(jìn)行操作(動作)并顯示出頁面內(nèi)容。如果編寫的測試用例是直接針對WEB頁面元素進(jìn)行操作胎食,則無法應(yīng)對經(jīng)常發(fā)生變化的WEB頁面扰才,增加日后自動化代碼的維護(hù)成本。而Page Object模型就是針對WEB頁面和元素細(xì)節(jié)的封裝厕怜,并對外提供應(yīng)用級別的API衩匣,從而擺脫對WEB頁面的高耦合情況。示意圖如下所示:
? ? 針對以上示例粥航,可以大概總結(jié)出大概做法琅捏,如下所示:
- 以頁面為單位聪建,獨(dú)立建立模型
- 隱藏實(shí)現(xiàn)細(xì)節(jié)
- 本質(zhì)是面向接口編程
10.2 定義
? ? Page Object模型(簡稱為PO模式)是一種設(shè)計(jì)模式励翼,其核心是分層瓦戚,實(shí)現(xiàn)松耦合早芭,從而實(shí)現(xiàn)代碼復(fù)用和其易維護(hù)性现斋。利用PO模型容诬,為每個網(wǎng)頁建立兩個類:
- Page類
? ? 將每個頁面封裝為Page類盲赊,頁面元素為Page類成員變量坦胶,頁面功能為Page類方法里面
- Test類
? ? 針對Page類定義的測試類杨凑,在測試類中調(diào)用Page類中方法完成測試滤奈。其使用Page類中的方法與頁面UI元素進(jìn)行交互操作。若UI發(fā)生變化撩满,僅需要更新Page類蜒程,測試類無需要更改绅你。
10.3 為什么使用Page Object模式
? ? WEB由各種WEB元素(文本框、復(fù)選框搞糕、多選/單選按鈕等)組成勇吊。測試代碼與這些元素進(jìn)行交互,如果不能正確管理定位器窍仰,則代碼的復(fù)雜性將成倍增加汉规。當(dāng)測試代碼和定位器的重復(fù)使用,將降低代碼的可讀性驹吮,從而進(jìn)一步加大測試代碼的維護(hù)成本针史。 隨著項(xiàng)目和需求的不斷變化,開發(fā)和測試代碼的復(fù)雜性會不斷增加碟狞,維護(hù)性也隨之增加啄枕。因此,需要一種方法來解決這種問題族沃,所以我們需要使用PO來嘗試解決這一類問題频祝。
10.4 Page Object模型優(yōu)點(diǎn)
? ? 主要優(yōu)點(diǎn)如下所示:
- 提高代碼可復(fù)用性
? ? 不同PO類中的Pabe Object方法可以在不同的測試用例中復(fù)用,極大提高代碼的復(fù)用性脆淹。
- 提高代碼可維護(hù)性
? ? 因測試場景和定位器是代碼分開常空,使代碼更加清晰,極大提高代碼的可維護(hù)性盖溺。
- 減少UI對用例造成的影響
? ? 盡管UI經(jīng)常發(fā)生變更漓糙,也僅需要修改少量代碼來應(yīng)對更改,從而減少其帶來的影響烘嘱。
10.5 Page Object示例
10.5.1 演示環(huán)境搭建
? ? 我們以官方提供的示例為演示昆禽,操作步驟如下所示:
- 1、訪問官方網(wǎng)址蝇庭,其網(wǎng)址:https://docs.cypress.io/examples/examples/recipes#Testing-the-DOM
- 2醉鳖、點(diǎn)擊HTML Web Forms跳轉(zhuǎn)到Github創(chuàng)建,下載源碼
- 3哮内、將代碼放置到指定目錄并在當(dāng)前目錄打開終端辐棒,執(zhí)行以下命令,以啟動服務(wù)
npm install minimist morgan body-parser express-session express hbs --save-dev
npm start server.js
- 4牍蜂、在瀏覽器中訪問
? ? 默認(rèn)正確的用戶名和密碼,在server.js中泰涂,可以自行修改鲫竞,如下所示:
10.5.2 演示代碼
? ? 本代碼僅僅是演示在Cypress中的Page Object模式(注意與Selenium的區(qū)別),主要示例代碼如下所示:
- 1逼蒙、新建定位器文件loginPageLoctor.json从绘,用于存儲元素定位器
{
"loginPage":{
"username":"input[name=\"username\"]",
"passwd":"input[name=\"password\"]",
"submit":"button[type=\"submit\"]",
"loginFailedPrompt":".error"
}
}
- 2、新建Page類loginPage.js,用于封裝對象和定位元素
/// <reference types="cypress" />
import LoginPageLocator from "./loginPageLoctor.json"
export default class LoginPage{
constructor(visitUrl){
this.url=visitUrl;
}
get username(){
return cy.get(LoginPageLocator.loginPage.username);
}
get passwd(){
return cy.get(LoginPageLocator.loginPage.passwd);
}
get submit(){
return cy.get(LoginPageLocator.loginPage.submit);
}
get errorPrompt(){
return cy.get(LoginPageLocator.loginPage.loginFailedPrompt);
}
get successUrl(){
return cy.url();
}
visit(){
cy.visit(this.url);
}
login(name,pwd) {
if ( name !="" && pwd !=""){
this.username.type(name);
}
if(pwd!=""){
this.passwd.type(pwd);
}
this.submit.click();
}
}
- 3僵井、新建數(shù)據(jù)文件loginData.json陕截,用于存儲登錄的數(shù)據(jù)和數(shù)據(jù)驅(qū)動
{
"success": [
{
"caseTitle": "正確的用戶名和密碼,登錄成功",
"user": "jane.lane",
"pwd": "password123",
"checkpoint": "/dashboard"
}
],
"failed": [
{
"caseTitle": "錯誤的用戶名和正確的密碼批什,登錄失敗",
"user": "Surpass",
"pwd": "password123",
"checkpoint": "Username and/or password is incorrect"
},
{
"caseTitle": "正確的用戶名和錯誤的密碼农曲,登錄失敗",
"user": "jane.lane",
"pwd": "Surpass",
"checkpoint": "Username and/or password is incorrect"
},
{
"caseTitle": "錯誤的用戶名和錯誤的密碼,登錄失敗",
"user": "Surpass",
"pwd": "Surpass",
"checkpoint": "Username and/or password is incorrect"
}
]
}
- 4驻债、新建測試類testLogin.spec.js乳规,測試用例代碼
/// <reference types="cypress" />
import LoginPage from "./loginPage"
import UserData from "./loginData.json"
describe('登錄測試', () => {
let baseUrl = "http://localhost:7077/login";
let login = new LoginPage(baseUrl);
beforeEach(() => {
login.visit(baseUrl);
});
UserData.success.forEach((item)=>{
it(item.caseTitle, () => {
login.login(item.user,item.pwd);
login.successUrl.should("contain",item.checkpoint)
});
});
UserData.failed.forEach((item)=>{
it(item.caseTitle, () => {
login.login(item.user,item.pwd);
login.errorPrompt.should("contain",item.checkpoint)
});
});
});
? ? 最終的運(yùn)行結(jié)果如下所示: