株式会社カオナビでE2Eテストを推進している iamapen です。
現在はテスティングフレームワーク(TAF)に TestCafe を使用しています。
TypeScriptで書くことができ、セットアップが簡単なのが使いやすいところです。
Smart Assertionという仕組みがあり、待ち処理を書かなくてもよいとされています。
待ち処理はE2Eテストの安定性に深く関わるため、それを書かなくてよいのであれば助かります。
TestCafeにおける待ち処理の書きかた、を紹介します。
Smart Assertion の例
たとえばこのように書くと、ボタンが表示されるまでクリックの試行を自動でリトライしてくれます。
表示待ちのコードを自前で書く必要がありません。
const btn = Selector('button.save');
await t.click(btn); // 表示されるまでリトライしてくれる
たとえば「ID・PWを入力してログインボタンを押す」のようなシンプルな画面であれば、
Smart Assertion の暗黙の待ち処理を頼るのみで、表示待ちのコードがゼロで、テストが実装できます。
Smart Assertionの詳細な仕様は公式ドキュメントへ。
https://testcafe.io/documentation/402837/guides/basic-guides/assert#smart-assertion-query-mechanism
Smart Assertion を使って待ち処理を書く
しかし万能というわけではなく、よほどシンプルな画面でない限り、
現実的には「Smart Assertion を利用して待ち処理を簡潔に書く」ことになります。
とはいえ自前でループを書かなくて済むだけでも助かります。
多くの場合は簡潔に書くことができます。
たとえば、「編集」ボタンを押してから編集モーダルに文字を入力するような場合で、
画面は以下のような挙動をするとします。
1. 「編集」ボタンを押す
1-1. 編集モーダルと タイトルtextbox が描画される
1-2. タイトルtextbox に、現在の値が反映される
2. タイトルtextbox に新しい文字列を入力
3. 「保存」ボタンを押す
この場合、
Smart Assertion で 1-1 までは暗黙に解決できますが、1-2 の完了も待たないと、
テストで入力した文字が現在値で上書きされることがある、不安定なテストになってしまいます。
それを防ぐために、Smart Assertionを使って待ち処理を簡潔に書き、1-2 の完了を待たせます。
const textbox = Selector('input[name=title][type=text]');
// 編集モーダルオープン開始
await t
.click(Selector('btn.edit'))
.expect(textbox.filterVisible().exists)
.ok('textboxの表示待ち')
.expect(textbox.value)
.eql('変更前のタイトル', '現在値の反映待ち');
// ここまでして、編集モーダルオープン完了
await t.typeText(textbox, '変更後のタイトル');
await t.click(Selector('btn.save'));
Smart Assertion が使えないケース
Smart Assertion に対応したアサーションメソッドが使えない場合、自前で書く必要があります。
TestCafeの機能に頼れないので、ループやPromiseを使って対応します。
たとえば、要素の非表示を「画面外の上部に配置する」など、特殊な実装で実現している画面で、要素が画面内に表示されるまで待つ場合など。
export class FooPage {
/**
* 画面外の上部に配置して隠していた要素が画面内に表示されるまで待つ
*/
async waitForXxx(
selector: Selector,
config = { timeoutMillis: 5000, checkIntervalMillis: 500 }
): Promise<void> {
return new Promise((resolve, reject) => {
let elapsed = 0;
const interval = setInterval(async () => {
if (await selector.filterVisible().exists) {
const top = await selector.getBoundingClientRectProperty('top');
if (top >= 0) {
clearInterval(interval);
return resolve();
}
}
elapsed += config.checkIntervalMillis;
if (elapsed >= config.timeoutMillis) {
clearInterval(interval);
return reject(new Error('timeout'));
}
}, config.checkIntervalMillis);
});
}
}
利用側
const page = new FooPage();
const node = Selector('div.foo');
await page.waitForXxx(node); // 表示まで待つ
await t.click(node);
TestCafe の各種タイムアウト設定をテストコードから取得できないので、タイムアウト値は selector timeout などとは別管理になってしまうのが残念...。
まとめ
TestCafe で待ち処理を書く方法を紹介しました。
上記のサンプルコードもそうですが、Page Model (Page Object) パターンを採用して、
画面の各操作に必要な複雑な待ち処理をカプセル化・ライブラリ化、
シナリオの実装の容易性・可読性・メンテナンス性を保つよう心掛けています。