e2eテストでブラウザーを操作する方法
リスト2で利用したbrowserやelementを使って、Webブラウザーの操作や結果の取得を行う方法を、図5のサンプル(P002-basic)で説明します。このWebページには、テキストボックス、ボタンと2個のラベルがあり、テキストボックスの入力内容は、データバインディングで上のラベル(メッセージ1)にすぐに反映されます。そのあとでボタンを押すと、下のラベル(メッセージ2)にもテキストボックスの入力内容が反映されます。
このWebページに対するe2eテストのコードはリスト3です。ここでは、Webブラウザーの操作方法が直感的にわかるように、PageObject(app.po.ts)は利用せず、すべてのテスト処理をテストコードに直接記述しています。
import { browser, element, by } from 'protractor'; // インポート ...(1)
describe('P002-basic', () => {
it('e2eテストの動作確認', () => { // ...(2)
// URLを指定してWebページを表示 ...(3)
browser.get('/');
// ページ内のHTML要素を取得 ...(4)
const message1 = element(by.css('#message1')); // CSSを指定できる
const message2 = element(by.id('message2')); // IDを直接指定できる
const textBox1 = element(by.id('textBox1'));
const button1 = element(by.id('button1'));
// ページの初期条件 ...(5)
expect(message1.getText()).toEqual('');
expect(message2.getText()).toEqual('初期メッセージ');
// テキストボックスに文字を入力 ...(6)
textBox1.clear();
textBox1.sendKeys('テキストボックスに文字を入力');
// message1はすぐ更新されるが、message2はまだ更新されない ...(7)
expect(message1.getText()).toEqual('テキストボックスに文字を入力');
expect(message2.getText()).toEqual('初期メッセージ');
// ボタンをクリック ...(8)
button1.click();
// ボタンクリックで、message2も更新される ...(9)
expect(message1.getText()).toEqual('テキストボックスに文字を入力');
expect(message2.getText()).toEqual('テキストボックスに文字を入力');
});
});
まず、Protractorがテスト用に提供する変数browser、element、byをインポートします(1)。テストの処理は(2)以降です。
テストでは、最初に(3)のbrowser.getメソッドに相対URL('/')を指定して、テスト対象のWebページを表示させます。browserはWebブラウザーに関連する操作を行えるオブジェクトです。
次に(4)で、ページ内のHTML要素を取得します。elementはHTML要素を取得するメソッドで、条件を指定するby.cssメソッド(CSS記述を指定)またはby.idメソッド(HTML要素のIDを指定)と組み合わせて、HTML要素を取得できます。
(5)では、取得したHTML要素のgetTextメソッドで要素の内容(テキスト)を取得して、想定値と比較しています。
(6)は、テキストボックスに文字を入力する処理です。テキストボックスの要素に対して、clearメソッドで入力値を削除してから、sendKeysメソッドで文字を入力します。ここでは入力後、(7)でラベルの値を想定値と比較しています。
(8)のようにclickメソッドを実行すると、その要素をクリックできます。ここではクリック後、(9)でラベルの値を想定値と比較しています。
このように、Protractorの機能を利用すると、人間が操作するのと同じようにブラウザーを操作して、e2eテストを実行できます。
PageObjectでテストの再利用性を高める
リスト3では、ページ遷移や要素の取得・操作など、Webページの内容に強く依存する処理を、テストのファイルに直接記述していました。この場合、Webページのレイアウトなどを変更すると、テストの記述も修正する必要があります。
こういった問題に対して、Webページの内容に強く依存する処理を別のクラスに切り出す「PageObject」と呼ばれる考え方が有効です。Angularのプロジェクトでは、PageObjectのファイルがデフォルトで生成されます。
図5のWebページに対応するPageObjectは、リスト4のように実装できます。リスト3でテストコードに記述されていたページ遷移やテキストの取得・入力、ボタンのクリックといった処理を実装します。
export class AppPage {
// ページを表示
navigateTo() {
return browser.get('/');
}
// メッセージ1を取得
getMessage1() {
return element(by.css('#message1')).getText();
}
// メッセージ2を取得
getMessage2() {
return element(by.id('message2')).getText();
}
// テキストボックスに入力
inputToTextbox(input:string) {
const textBox1 = element(by.id('textBox1'));
textBox1.clear();
textBox1.sendKeys(input);
}
// ボタンをクリック
clickButton1() {
element(by.id('button1')).click();
}
}
このPageObjectを利用すると、リスト3のテストはリスト5のように書き換えられます。
describe('P003-page-object', () => {
let page: AppPage; // PageObject ...(1)
beforeEach(() => {
page = new AppPage(); // PageObjectを初期化 ...(2)
});
it('e2eテストの動作確認', () => {
// ページ遷移
page.navigateTo();
// ページの初期条件
expect(page.getMessage1()).toEqual('');
expect(page.getMessage2()).toEqual('初期メッセージ');
// テキストボックスに文字を入力
page.inputToTextbox('テキストボックスに文字を入力');
// message1はすぐ更新されるが、message2はまだ更新されない
expect(page.getMessage1()).toEqual('テキストボックスに文字を入力');
expect(page.getMessage2()).toEqual('初期メッセージ');
// ボタンをクリック
page.clickButton1()
// ボタンクリックで、message2も更新される
expect(page.getMessage1()).toEqual('テキストボックスに文字を入力');
expect(page.getMessage2()).toEqual('テキストボックスに文字を入力');
});
});
PageObjectのAppPage(1)を、beforeEachで生成して(2)、テストで利用します。リスト3とリスト5は同じ内容のテストですが、Webページ固有の処理がPageObjectに切り出されていることに注目してください。
PageObjectを利用すると、Webページの内容に依存する処理をテストのコードから分離できるため、ページのレイアウトの変更があってもPageObjectだけを修正すればよく、テストのコードはそのまま利用できます。また、PageObjectを使いまわして、別のテストを行うこともできます。
