同期から非同期へ
V8の変更により、WebdriverIOチームは2023年4月までに同期コマンド実行を非推奨にすることを発表しました。チームはこの移行をできるだけ簡単にするために懸命に取り組んでいます。このガイドでは、テストスイートを同期 から非同期に徐々に移行する方法を説明します。例としてCucumber Boilerplateプロジェクトを使用しますが、このアプローチは他のすべてのプロジェクトでも同様です。
JavaScriptのPromise
WebdriverIOで同期実行が人気だった理由は、プロミスを扱う複雑さを排除できるからです。特に、この概念が同じ方法で存在しない他の言語から来た場合、最初は混乱するかもしれません。しかし、Promiseは非同期コードを扱うための非常に強力なツールであり、今日のJavaScriptでは実際にそれを扱うのが簡単になっています。Promiseを使ったことがない場合は、ここで説明するのは範囲外なので、MDNリファレンスガイドを確認することをお勧めします。
非同期への移行
WebdriverIOのテストランナーは、同じテストスイート内で非同期と同期の実行を処理できます。つまり、テストとPageObjectを自分のペースで段階的に移行できます。例えば、Cucumber Boilerplateには多数のステップ定義が定義 されており、それをプロジェクトにコピーして使用できます。一度に1つのステップ定義または1つのファイルを移行することができます。
WebdriverIOは、同期コードを非同期コードにほぼ自動的に変換できるcodemodを提供しています。まずドキュメントに記載されているようにcodemodを実行し、必要に応じてこのガイドを使用して手動で移行してください。
多くの場合、必要なのはWebdriverIOコマンドを呼び出す関数をasync
にし、すべてのコマンドの前にawait
を追加することだけです。ボイラープレートプロジェクトで変換する最初のファイルclearInputField.ts
を見ると、次のように変換します:
export default (selector: Selector) => {
$(selector).clearValue();
};
を以下のように:
export default async (selector: Selector) => {
await $(selector).clearValue();
};
これだけです。すべての書き換え例を含む完全なコミットはこちらで確認できます:
コミット:
- 全ステップ定義の変換 [af6625f]
この移行は、TypeScriptを使用しているかどうかに関係なく行えます。TypeScriptを使用している場合は、tsconfig.json
のtypes
プロパティをwebdriverio/sync
から@wdio/globals/types
に変更してください。また、コンパイルターゲットが少なくともES2018
に設定されていることを確認してください。
特殊なケース
もちろん、より注意が必要な特殊なケースもあります。
forEachループ
要素を反復処理するためのforEach
ループがある場合、イテレータコールバックが非同期の方法で適切に処理されるようにする必要があります:
const elems = $$('div')
elems.forEach((elem) => {
elem.click()
})
forEach
に渡す関数はイテレータ関数です。同期の世界では、次に進む前 にすべての要素をクリックします。これを非同期コードに変換する場合、各イテレータ関数の実行が終了するのを待つ必要があります。async
/await
を追加することで、これらのイテレータ関数は待機する必要のあるプロミスを返します。forEach
はイテレータ関数の結果(私たちが待つ必要のあるプロミス)を返さないため、もはや要素を反復処理するのに理想的ではありません。したがって、forEach
をmap
に置き換える必要があります。map
はそのプロミスを返します。map
やfind
、every
、reduce
などの配列のその他のイテレーターメソッドはすべて、イテレーター関数内のプロミスを考慮するように実装されており、非同期コンテキストでの使用が簡略化されています。上記の例は次のように変換されます:
const elems = await $$('div')
await elems.forEach((elem) => {
return elem.click()
})
例えば、すべての<h3 />
要素を取得してそのテキスト内容を取得するには、次のように実行できます:
await browser.url('https://webdriver.io')
const h3Texts = await browser.$$('h3').map((img) => img.getText())
console.log(h3Texts);
/**
* 戻り値:
* [
* 'Extendable',
* 'Compatible',
* 'Feature Rich',
* 'Who is using WebdriverIO?',
* 'Support for Modern Web and Mobile Frameworks',
* 'Google Lighthouse Integration',
* 'Watch Talks about WebdriverIO',
* 'Get Started With WebdriverIO within Minutes'
* ]
*/
これが複雑すぎる場合は、単純なforループの使用を検討してください:
const elems = await $$('div')
for (const elem of elems) {
await elem.click()
}
WebdriverIOのアサーション
WebdriverIOのアサーションヘルパーexpect-webdriverio
を使用する場合は、すべてのexpect
呼び出しの前にawait
を設定してください:
expect($('input')).toHaveAttributeContaining('class', 'form')
以下のように変換する必要があります:
await expect($('input')).toHaveAttributeContaining('class', 'form')