WebdriverIO v9 リリース
Webdriverioの開発チーム全体が、本日WebdriverIO v9をリリースできることを嬉しく、誇りに思っています!
これは、WebdriverIOをテスト自動化ツールとして使用するすべてのプロジェクトにとって、新たなエキサイティングな時代の始まりを告げるものです。例えばChromeやFirefoxに取り組んでいるブラウザチームの素晴らしい働きにより、新しいWebDriver Bidiプロトコルのおかげで、これまで以上に優れた自動化機能を提供する新たな時代に突入しました。WebdriverIO v9では、この新しい時代の最前線で採用者となり、最初にプロトコルの力を活用できるように取り組んできました。
このリリースにおける新機能、アップデート、最適化について見ていきましょう。
新機能
v9の新機能の大部分は、ブラウザで利用可能になったWebDriver Bidi機能によって有効になっています。v9にアップグレードすると、新しいwdio:enforceWebDriverClassic
機能を使用して明示的に無効にしない限り、すべてのセッションで自動的にBidiが使用されます。
これらの機能は、リモート環境がWebDriver Bidiをサポートしていない場合は利用できません。browser.isBidi
プロパティを確認することで、セッションでBidiがサポートされているかどうかを確認できます。
新しいurl
コマンドパラメータ
url
コマンドは、単なるナビゲーションツールから、機能満載の強力なコマンドへと進化しました。
カスタムヘッダーの受け渡し
ブラウザがリクエストを行う際に適用されるカスタムヘッダーを渡せるようになりました。これは、セッションクッキーを設定して自動的にログインする場合に便利です。
await browser.url('https://webdriverio.dokyumento.jp', {
headers: {
Authorization: 'Bearer XXXXX'
}
});
mock
コマンドを使用してブラウザがすべてのリクエストを処理する方法を変更できますが、url
コマンドに適用された変更は、その特定のページロードの間のみ有効であり、ナビゲーションが完了するとリセットされることに注意してください。
基本認証の克服
基本認証を介したユーザー認証の自動化が、これまで以上に簡単になりました
await browser.url('https://the-internet.herokuapp.com/basic_auth', {
auth: {
user: 'admin',
pass: 'admin'
}
});
await expect($('p=Congratulations! You must have the proper credentials.').toBeDisplayed();
初期化スクリプトの実行
beforeLoad
パラメータを使用して、Webサイトの読み込み前にJavaScriptを挿入します。これは、例えばWeb APIを操作する場合に便利です。
// navigate to a URL and mock the battery API
await browser.url('https://pazguille.github.io/demo-battery-api/', {
onBeforeLoad (win) {
// mock "navigator.battery" property
// returning mock charge object
win.navigator.getBattery = () => Promise.resolve({
level: 0.5,
charging: false,
chargingTime: Infinity,
dischargingTime: 3600, // seconds
})
}
})
// now we can assert actual text - we are charged at 50%
await expect($('.battery-percentage')).toHaveText('50%')
// and has enough juice for 1 hour
await expect($('.battery-remaining')).toHaveText('01:00)
この例では、Navigator
インターフェースのgetBattery
メソッドを上書きします。
新しいaddInitScript
コマンド
addInitScript
コマンドを使用すると、新しいブラウジングコンテキストが開かれるたびにトリガーされるスクリプトをブラウザに挿入できます。これには、URLへのナビゲーションやアプリケーション内のiframeの読み込みなどのアクションが含まれます。このコマンドに渡すスクリプトは、最後のパラメータとしてコールバックを受け取り、ブラウザからNode.js環境に値を返すことができます。
たとえば、アプリケーション内のノードから要素が追加または削除されるたびに通知を受け取るには、次の例を使用できます
const script = await browser.addInitScript((myParam, emit) => {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
emit(mutation.target.nodeName)
}
})
observer.observe(document, { childList: true, subtree: true })
})
script.on('data', (data) => {
console.log(data) // prints: BODY, DIV, P, ...
})
この初期化スクリプトは、グローバル変数を変更したり、組み込みのWeb APIプリミティブを上書きしたりできるため、特定の要件を満たすようにテスト環境を構成できます。
クロスブラウザリクエストのモック
WebdriverIOは、v6.3.0
でリクエストのモックを導入しましたが、これはChromiumブラウザに限定されていました。v9では、WebDriver Bidiを使用し、すべてのブラウザへのサポートを拡張しています。この強化により、リクエストがネットワークに送信される前に変更できます。
// mock all API requests
const mock = await browser.mock('**/api/**')
// apply auth token to each request
mock.request({
headers: { 'Authorization': 'Bearer XXXXXX' }
})
自動シャドウルートピアッシング
Webコンポーネントを使用したアプリケーションのテストが、自動シャドウルートピアッシングによりシームレスになりました。WebdriverIOは、すべてのShadowRoot
ノードを追跡し、それらを検索するようになりました。
たとえば、複数のネストされたシャドウルートを含む次の日付ピッカーコンポーネントを自動化したいとします。アプリケーション内のいずれかの要素を右クリック > 検査
で確認し、さまざまなシャドウルート内にどれだけネストされているかを確認してください。
日付を変更するには、次のように呼び出すだけです。
await browser.url('https://ionic.dokyumento.jp/docs/usage/v8/datetime/basic/demo.html?ionic:mode=md')
await browser.$('aria/Sunday, August 4]').click()
await browser.$('.aux-input').getValue() // outputs "2024-08-04T09:00:00"
WebdriverIOは以前は「ディープセレクター」をサポートしていましたが、この機能はCSSセレクターに限定されていました。新しいセレクターエンジンは、アクセシビリティラベルを含むすべてのセレクタータイプをサポートするようになりました。
強化されたロケーターエンジンを使用すると、複数のネストされたシャドウルート内の要素を簡単に見つけることができます。WebdriverIOは、open
モードとclosed
モードの両方でシャドウルートに対してこの機能をサポートする最初のフレームワークです。
引数のシリアル化の改善
WebDriver Classicでは、テストからブラウザ環境にデータオブジェクトを移動する機能は、DOM要素とシリアル化可能なオブジェクトおよび型にかなり限定されていました。WebDriver Bidiを使用すると、シリアル化不可能なデータオブジェクトを、それがオブジェクトであるようにブラウザで使用できるように変換する作業をよりうまく行うことができるようになりました。Map
やSet
などの既知のJavaScriptプリミティブに加えて、プロトコルでは、Infinity
、null
、undefined
、BigInt
などの値をシリアル化できます。
Node.jsでJavaScript Mapオブジェクトを作成し、ブラウザに渡す例を次に示します。ブラウザでは、Mapに自動的に逆シリアル化されます。
const data = new Map([
['username', 'Tony'],
['password', 'secret']
])
const output = await browser.execute(
(data) => `${data.size} entrie(s), username: ${data.get('username')}, password: ${data.get('secret')}`,
data
)
console.log(output)
// outputs: "1 entrie(s), username: Tony, password: secret"
これにより、データの受け渡しが容易になり、アプリケーションの状態をより適切に監視するのに役立つ豊富なデータオブジェクトを返すことができるカスタムスクリプトを操作することが容易になります。これにより、WebdriverIOのようなフレームワークがブラウザ環境とより深く統合し、将来的により便利な機能を構築できるようになります。
ビューポートの設定
WebdriverIOでは、デスクトップブラウザとモバイルブラウザの両方でアプリケーションをテストできますが、アプリケーションがレスポンシブモードで適切にレンダリングされるかどうかを確認するために、ブラウザのビューポートを調整してモバイルユーザーをエミュレートする方が簡単な場合がよくあります。WebDriver Classicでは、ブラウザウィンドウを非常に小さいサイズに縮小することができず、多くの場合、最小幅が500px
を維持するため、これは難しい場合があります。たとえば
await browser.url('https://webdriverio.dokyumento.jp')
await browser.setWindowSize(393, 659)
console.log(await browser.execute(() => window.innerWidth)) // returns `500`
WebdriverIO v9では、devicePixelRatio
の変更を含む、アプリケーションのビューポートを任意のサイズに調整できるsetViewport
コマンドが導入されました。ブラウザウィンドウ全体のサイズを変更するsetWindowSize
とは異なり、setViewport
は、アプリケーションがレンダリングされるキャンバスのサイズを特に変更し、ブラウザウィンドウの寸法は変更しません。
モバイルデバイスのエミュレーションを簡略化するために、emulateコマンドを強化しました。この新しい機能を使用すると、名前を指定するだけで、特定のモバイルデバイスのビューポートサイズ、デバイスピクセル比率、ユーザーエージェントを同時に調整できます。例
await browser.url('https://webdriverio.dokyumento.jp')
await browser.emulate('device', 'iPhone 15')
console.log(await browser.execute(() => window.innerWidth)) // returns `393`
console.log(await browser.execute(() => navigator.userAgent)) // returns `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36`
モバイルテストは実際のモバイルデバイスで実行することをお勧めしますが、モバイルブラウザエンジンはデスクトップブラウザで使用されているものとは異なるため、これは、モバイルビューポートでアプリケーションがどのようにレンダリングされるかをすばやく検証したい場合に便利なエスケープハッチになります。
フェイクタイマーのサポート
ブラウザの時間を変更したいですか?WebdriverIO v9を使用すると、テストのためにブラウザ内の時間を偽装できるようになりました。emulate
コマンドを新しいプロパティclock
で強化しました。これにより、日付と時間を必要なものに設定し、時間が進むタイミングを制御できます。仕組みは次のとおりです。
const clock = await browser.emulate('clock', { now: new Date(2021, 3, 14) })
console.log(await browser.execute(() => new Date().getTime())) // returns 1618383600000
await clock.tick(1000)
console.log(await browser.execute(() => new Date().getTime())) // returns 1618383601000
新しいclock
エミュレーションは、テストで時間を正確に制御するためのtick
、setSystemTime
、restore
などのメソッドを備えたClock
オブジェクトを返します。
自動ダイアログ処理
アプリケーションがalert
やconfirm
などのネイティブブラウザダイアログで動作する場合、これらのプロンプトが予期せず表示された場合に問題が発生することがあります。以前のバージョンでは、適切に処理しないと、すべてのコマンドが失敗していました。WebdriverIO v9では、明示的にリスナーを登録しない限り、ダイアログを自動的に抑制し始めます。例:
await browser.url('https://webdriverio.dokyumento.jp')
browser.on('dialog', async (dialog) => {
console.log(dialog.message()) // outputs: "Hello Dialog"
await dialog.dismiss()
})
await browser.execute(() => alert('Hello Dialog'))
新しいdialog
イベントでは、dialogオブジェクトが渡され、それに対してaccept
またはdismiss
を呼び出したり、ダイアログの種類やメッセージ、デフォルト値を取得したりできます。これにより、ブラウザによる予期しないアラートによってテストが失敗する頻度が将来的に減少することを期待しています。
要素が操作可能になるまでの自動待機
要素が表示されていない、ビューポートにスクロールできない、または無効になっている場合、要素は操作可能ではないと見なされます。以前のWebdriverIOでは、この状況が発生するとエラーがスローされていました。v8では、要素のHTMLを表示するように改善しましたが、v9では、クリックやsetValueの使用など、要素に対して直接的なアクションを実行する際に、要素が操作可能になるまで自動的に待機するようになりました。
つまり、このようなコードを書く必要はなくなりました。
const submitButton = await $('button[type="submit"]');
await submitButton.waitForEnabled(); // validation of the form takes time, during which the button is disabled
await submitButton.click();
代わりに、以下のように簡略化できます。
const submitButton = await $('button[type="submit"]');
await submitButton.click(); // automatically waits for the button to become enabled
Webコンポーネントのスナップショットテスト
アプリケーションで多くのWebコンポーネントを使用している場合、WebdriverIOがShadow Root内部を調べることが困難だったため、スナップショット機能の使用が不可能でした。v9では、WebdriverIOはすべての要素を完全に可視化し、それらを宣言的Shadow DOMに変換することで、開閉されたWebコンポーネントのスナップショットを撮影できるようになりました。例:
await browser.url('https://ionic.dokyumento.jp/docs/usage/v8/button/basic/demo.html?ionic:mode=md')
// get snapshot of web component without its styles
const snapshot = await $('ion-button').getHTML({ excludeElements: ['style'] })
// assert snapshot
await expect(snapshot).toMatchInlineSnapshot(`
<ion-button class="md button button-solid ion-activatable ion-focusable hydrated">Default
<template shadowrootmode="open">
<button type="button" class="button-native" part="native">
<span class="button-inner">
<slot name="icon-only"></slot>
<slot name="start"></slot>
<slot></slot>
<slot name="end"></slot>
</span>
<ion-ripple-effect role="presentation" class="md hydrated">
<template shadowrootmode="open"></template>
</ion-ripple-effect>
</button>
</template>
</ion-button>
`)
この機能を有効にするために、getHTML
コマンドを強化し、以前のブール値パラメーターを、コマンドの動作をより細かく制御できるオブジェクトに置き換えました。新しいGetHTMLOptions
オプションの詳細については、getHTML
コマンドのドキュメントを参照してください。
注目すべき破壊的変更
WebdriverIOの最新バージョンへのアップグレードに多大な時間を費やす必要がないように、破壊的な変更を最小限に抑えるよう努めています。ただし、メジャーリリースでは、もはや使用を推奨しない非推奨のインターフェースを削除する機会が得られます。
要素プロパティへのアクセス
ご存知のように、WebdriverIOでは、テストで操作するためにブラウザから要素を取得できます。要素を操作するとき、WebdriverIOは要素への参照と追加のメタデータを保持して、コマンドを正しい要素に適切に指示したり、例えば、要素が古くなった場合に再フェッチしたりします。$('elem').selector
を介してセレクターを評価したり、$('elem').elementId
を介してその参照を取得したりするなど、他の要素プロパティにアクセスしたことがあるかもしれません。
これらのプロパティには、この方法ではアクセスできなくなりました。代わりに、WebdriverIO.Element
のプロパティにアクセスするにはgetElement
を、WebdriverIO.ElementArray
のプロパティにアクセスするにはgetElements
を呼び出す必要があります。以下に例を示します。
// WebdriverIO v8 and older
const elem = await $('elem')
console.log(elem.selector) // ❌ returns a `Promise<string>` now
// WebdriverIO v9
const elem = await $('elem').getElement()
console.log(elem.selector) // ✅ returns "elem"
この変更により、IDEで非常に迷惑なバグが修正されました。このバグは、IDEが$('elem')
がPromiseを返すと誤解し、チェーンされた要素呼び出しを複数のawait
ステートメントで自動的にラップしていました(例:await (await $('elem')).getText()
)。この改善により、テストの作成がはるかに簡単になります。
XXXContaining
マッチャーの削除
expect-webdriverio
では、WebdriverIO要素とエンドツーエンドテストシナリオに合わせて調整されたカスタムマッチャーを維持しています。以前は、部分的な値のマッチングを可能にするために、各マッチャーのXXXContaining
バージョンを提供していました。ただし、基盤となるexpect
パッケージは、非対称マッチャーを介してこの機能を既にサポートしています。維持するマッチャーの数を減らすために、これらの非対称マッチャーの使用を優先して、カスタムXXXContaining
マッチャーを削除することにしました。
非対称マッチャーへの移行は簡単です。以下に例を示します。
- await expect($('elem')).toHaveTextContaining('Hello')
+ await expect($('elem')).toHaveText(expect.stringContaining('Hello'))
JSON Wire Protocolコマンドの削除
JSON Wire Protocolは、Seleniumによって開発された最初の自動化プロトコルであり、HTTPをサポートするあらゆる言語を使用してリモートブラウザの自動化を可能にしました。2012年、Seleniumの作成者は、プロトコルを正式に標準化する作業を開始し、現在ではすべてのブラウザでサポートされています。この取り組みは、WebDriverプロトコルがW3C推奨規格になることで頂点に達し、ブラウザドライバとクラウドベンダーは、この公式標準への移行を促しました。
古いJSON Wire Protocolに別れを告げ、WebDriverの未来を受け入れる時が来たと考えています。古いブラウザでテストを実行していて、自動化スクリプトにこれらのプロトコルコマンドが必要な場合は、@wdio/jsonwp-service
をインストールして、必要なコマンドを引き続き使用できます。
devtools
および@wdio/devtools-service
パッケージの削除
WebdriverIO v8.15
で、ブラウザドライバの自動セットアップをコア機能として導入しました。この変更により、devtools
パッケージの本来の目的は不要になりました。当初、devtoolsパッケージは、ブラウザのセットアップを処理するPuppeteerを通じてWebDriver仕様を実装するように設計されました。これにより、ブラウザドライバをダウンロードせずにWebdriverIOを使用することができました。この機能は不要になったため、devtools
パッケージをautomationProtocol
オプションとともに削除することにしました。
さらに、WebDriver Bidiの採用により、現在、ほとんどの自動化ニーズに対応する標準化されたプロトコルがあります。それにもかかわらず、より複雑なブラウザのイントロスペクションにChrome Devtoolsを使用することは、WebdriverIOで人気のある機能でした。以前は、これらのユースケースに対して@wdio/devtools-service
パッケージを推奨していました。ただし、ほとんどのユーザーは、カスタムDevToolsインターフェイスを使用するよりも、Puppeteerを直接使用することを好むことがわかりました。WebdriverIOユーザーは、WebdriverIOプリミティブとPuppeteerを同時に使用してブラウザを自動化できます。getPuppeteer
コマンドを呼び出すことで、自動化しているブラウザのPuppeteerインスタンスを取得し、より堅牢でメンテナンスされたインターフェイスを通じてすべてのDevtools機能にアクセスできるようになります。結果として、ユーザーは主にパフォーマンスやその他のGoogle Lighthouse機能をテストするために@wdio/devtools-service
パッケージを使用します。今後は、これらの機能は`@wdio/lighthouse-serviceという新しいサービスパッケージで利用可能になります。
これらのパッケージの適切なユースケースはすでに見つけており、それらを放棄することはないのでご安心ください。
プロジェクトの更新
WebおよびモバイルアプリケーションのテストにおいてWebdriverIOをより効果的にするために、新機能の追加に尽力してきた一方で、プロジェクトの明るい未来を確実にするためのいくつかの内部イニシアチブにも焦点を当ててきました。
pnpmへの移行
コアパッケージが時間の経過とともに成長するにつれて、特にモノリポジトリでの依存関係の解決において、NPMでますます課題に直面してきました。さらに、カスタムリンクにより、テストインフラストラクチャが不必要に複雑になりました。
進化するJavaScriptツールエコシステムを考慮して、プロジェクト内の開発者エクスペリエンスを向上させるために、新しいテクノロジーを評価して採用しました。pnpmに移行することにより、多くのカスタムスクリプトを排除し、プロジェクト全体のセットアップを簡素化し、Dependabot経由の定期的な更新で、より高速で信頼性の高い依存関係の解決と、マージ競合の減少を実現できるようになりました。
Esbuildへの移行
v7
でTypeScriptを導入して以来、TypeScriptコンパイラーを使用して、ファイルをJavaScriptに変換してNPMに公開してきました。ただし、コードベースが拡大するにつれて、このセットアップで課題に直面しました。たとえば、webdriverio
または@wdio/browser-runner
パッケージ内の一部のファイルはブラウザ環境でのみ使用されますが、Node.jsの場合のようにコンパイルされていたため、互換性の問題が発生しました。
WebdriverIO v9では、すべてのプロジェクトファイルはEsbuildでコンパイルされます。この汎用性の高い超高速バンドラーを使用すると、目的の環境に合わせてコードをコンパイルし、適切なポリフィルを適用できます。さらに、すべてのパッケージが単一のJavaScriptファイルにバンドルされるようになり、特にコンポーネントテストのパフォーマンスがわずかに向上しました。
ts-node
からtsx
への移行
WebdriverIOは、TypeScriptテストをランタイムでJavaScriptにコンパイルするために、ts-node
からtsx
に移行しました。この変更により、ESMサポートの向上、パフォーマンスの向上、およびTypeScriptテストでのエラーに対するより正確なスタックトレースが提供されます。
Node.js v16サポートの終了
すべてのリリースと同様に、古い、メンテナンスされていないNode.jsバージョンのサポートを終了します。WebdriverIO v9は、Node.js v16以下をサポートしなくなります。Node.js v20にアップグレードすることをお勧めします。
今後の展望
WebdriverIOの開発13年目を迎えるにあたり、新しいテスト機能の必要性は、進化するテスト業界とWeb標準とともに増え続けています。最新のブラウザでのWebDriver Bidiプロトコルの採用により、Selenium、Nightwatch、WebdriverIOなどのツールは、プロプライエタリなブラウザパッチや特定の実行環境に依存することなく、PlaywrightやCypressの能力に匹敵することができます。
WebdriverIOは、強力な新機能を構築するために、WebDriver Bidi機能をさらに統合し続けます。テスト対象のアプリケーションの検査とデバッグのための強力なUIを提供することで、テストエクスペリエンスを大幅に向上させるWebdriverIO devtoolsアプリケーションを含む、今後の開発に期待しています。このイニシアチブの詳細については、近日中に共有します!
謝辞
プレミアムスポンサーであるBrowserStackとSauce Labs、そしてプロジェクトに貢献してくださるすべての方々に感謝いたします。WebdriverIOは、皆様のような方々からの貢献に支えられた、コミュニティ主導で独立したプロジェクトであり続けています。オープンオフィスアワーに参加して、プロジェクトへの貢献をご検討ください。
今後も革新とコラボレーションが続くことを願って!🚀
お読みいただきありがとうございました!