頁面物件模式
WebdriverIO 的第 5 版在設計時就考慮了頁面物件模式的支援。透過引入「元素作為一等公民」的原則,現在可以使用此模式建立大型的測試套件。
建立頁面物件不需要額外的套件。事實證明,乾淨、現代的類別提供了我們所需的所有必要功能
- 頁面物件之間的繼承
- 元素的延遲載入
- 方法和動作的封裝
使用頁面物件的目標是將任何頁面資訊從實際測試中抽離出來。理想情況下,您應該將特定頁面獨有的所有選擇器或特定指令儲存在頁面物件中,以便在您完全重新設計頁面後仍然可以執行測試。
建立頁面物件
首先,我們需要一個主要頁面物件,我們稱之為 Page.js
。它將包含所有頁面物件將繼承的通用選擇器或方法。
// Page.js
export default class Page {
constructor() {
this.title = 'My Page'
}
async open (path) {
await browser.url(path)
}
}
我們始終會 export
頁面物件的實例,而永遠不會在測試中建立該實例。由於我們正在編寫端對端測試,因此我們始終將頁面視為無狀態結構,就像每個 HTTP 請求都是無狀態結構一樣。
當然,瀏覽器可以攜帶會話資訊,因此可以根據不同的會話顯示不同的頁面,但這不應反映在頁面物件中。這些類型的狀態變更應存在於您的實際測試中。
讓我們開始測試第一個頁面。為了演示目的,我們使用 The Internet 網站,該網站由 Elemental Selenium 作為實驗品。讓我們嘗試為 登入頁面 建立一個頁面物件範例。
Get
- 取用您的選擇器
第一步是以 getter 函數的形式,撰寫我們 login.page
物件中所需的所有重要選擇器
// login.page.js
import Page from './page'
class LoginPage extends Page {
get username () { return $('#username') }
get password () { return $('#password') }
get submitBtn () { return $('form button[type="submit"]') }
get flash () { return $('#flash') }
get headerLinks () { return $$('#header a') }
async open () {
await super.open('login')
}
async submit () {
await this.submitBtn.click()
}
}
export default new LoginPage()
在 getter 函數中定義選擇器可能看起來有點奇怪,但它非常有用。這些函數會在您存取屬性時,而不是在您產生物件時進行評估。這樣一來,您始終會在對其執行動作之前請求元素。
鏈式指令
WebdriverIO 在內部記住指令的最後結果。如果您將元素指令與動作指令鏈接起來,它會從上一個指令中找到該元素,並使用該結果來執行動作。這樣一來,您就可以刪除選擇器(第一個參數),並且指令看起來就像
await LoginPage.username.setValue('Max Mustermann')
這基本上與
let elem = await $('#username')
await elem.setValue('Max Mustermann')
或
await $('#username').setValue('Max Mustermann')
在您的測試中使用頁面物件
在您定義了頁面所需的元素和方法後,您可以開始為其撰寫測試。您要使用頁面物件所需要做的所有事情就是 import
(或 require
)它。就這樣!
由於您匯出了已建立的頁面物件實例,因此匯入它可讓您立即開始使用它。
如果您使用斷言框架,您的測試可以更具表達力
// login.spec.js
import LoginPage from '../pageobjects/login.page'
describe('login form', () => {
it('should deny access with wrong creds', async () => {
await LoginPage.open()
await LoginPage.username.setValue('foo')
await LoginPage.password.setValue('bar')
await LoginPage.submit()
await expect(LoginPage.flash).toHaveText('Your username is invalid!')
})
it('should allow access with correct creds', async () => {
await LoginPage.open()
await LoginPage.username.setValue('tomsmith')
await LoginPage.password.setValue('SuperSecretPassword!')
await LoginPage.submit()
await expect(LoginPage.flash).toHaveText('You logged into a secure area!')
})
})
從結構方面來看,將規格檔案和頁面物件分隔到不同的目錄中是有意義的。此外,您可以給每個頁面物件加上結尾:.page.js
。這可以更清楚地表明您正在匯入頁面物件。
更進一步
這是在 WebdriverIO 中撰寫頁面物件的基本原則。但是,您可以建立比這更複雜的頁面物件結構!例如,您可能會針對模式對話方塊使用特定的頁面物件,或將一個龐大的頁面物件分割成不同的類別(每個類別都代表整個網頁的不同部分),這些類別繼承自主要頁面物件。此模式確實提供了許多將頁面資訊從測試中分離出來的機會,這對於在專案和測試數量增加時保持測試套件的結構化和清晰非常重要。
您可以在 GitHub 上的 example
資料夾中找到此範例(以及更多頁面物件範例)。