ページオブジェクトパターン
WebdriverIOのバージョン5は、ページオブジェクトパターンを念頭に設計されました。「要素を第一級市民として扱う」という原則を導入することで、このパターンを使用して大規模なテストスイートを構築できるようになりました。
ページオブジェクトを作成するための追加パッケージは必要ありません。クリーンでモダンなクラスが、私たちが必要とするすべての機能を提供してくれることがわかりました。
- ページオブジェクト間の継承
- 要素の遅延読み込み
- メソッドとアクションのカプセル化
ページオブジェクトを使用する目的は、ページ情報を実際のテストから抽象化することです。理想的には、特定のページに固有のすべてのセレクターまたは特定の指示をページオブジェクトに保存する必要があります。これにより、ページを完全に再設計した後でもテストを実行できます。
ページオブジェクトの作成
まず、Page.js
というメインのページオブジェクトが必要です。これには、すべてのページオブジェクトが継承する一般的なセレクターまたはメソッドが含まれます。
// Page.js
export default class Page {
constructor() {
this.title = 'My Page'
}
async open (path) {
await browser.url(path)
}
}
常にページオブジェクトのインスタンスをexport
し、テストでそのインスタンスを作成することはありません。エンドツーエンドのテストを作成しているため、ページは常にステートレスな構造であると見なします。これは、各HTTPリクエストがステートレスな構造であるのと同じです。
確かに、ブラウザーはセッション情報を保持できるため、異なるセッションに基づいて異なるページを表示できますが、これはページオブジェクト内で反映されるべきではありません。これらの種類の状態の変更は、実際のテストに含める必要があります。
最初のページのテストを開始しましょう。デモの目的で、Elemental Seleniumが提供するThe InternetのWebサイトを実験台として使用します。ログインページのページオブジェクトの例を作成してみましょう。
セレクターのGet
-ting
最初のステップは、login.page
オブジェクトで必要なすべての重要なセレクターをgetter関数として記述することです。
// 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
)することだけです。それだけです!
すでに作成されたページオブジェクトのインスタンスをexportしたため、インポートするとすぐに使用を開始できます。
アサーションフレームワークを使用すると、テストをさらに表現力豊かにすることができます。
// 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!')
})
})
構造的な側面から見ると、specファイルとページオブジェクトを異なるディレクトリに分離するのが理にかなっています。さらに、各ページオブジェクトの最後に.page.js
を付けることができます。これにより、ページオブジェクトをインポートすることがより明確になります。
さらに詳しく
これは、WebdriverIOでページオブジェクトを記述する方法の基本原則です。ただし、これよりもはるかに複雑なページオブジェクト構造を構築できます。たとえば、モーダルの特定のページオブジェクトを使用したり、メインのページオブジェクトを継承する異なるクラス(Webページ全体の異なる部分を表す)に巨大なページオブジェクトを分割したりできます。このパターンは、テストからページ情報を分離するための多くの機会を提供します。これは、プロジェクトとテストの数が増加するにつれて、テストスイートを構造化し、明確に保つために重要です。
この例(およびさらに多くのページオブジェクトの例)は、GitHubのexample
フォルダーで見つけることができます。