E2Eテストジャーニーパート1:STUIからWebdriverIOへ
公開: 2019-11-21注:これは#frontend@twiliosendgridからの投稿です。 その他のエンジニアリングの投稿については、テクニカルブログロールにアクセスしてください。
SendGridのフロントエンドアーキテクチャがWebアプリケーション全体で成熟し始めたため、通常のユニットおよび統合テストレイヤーに加えて、別のレベルのテストを追加したいと考えました。 私たちは、ブラウザ自動化ツールを使用したE2E(エンドツーエンド)テストカバレッジを使用して、新しいページと機能を構築しようとしました。
お客様の視点からテストを自動化し、スタックの任意の部分に発生する可能性のある大きな変更について、可能な限り手動の回帰テストを回避することを望んでいました。 私たちは、次の目標を持っていました。それは、フロントエンドアプリケーション用の一貫性があり、デバッグ可能で、保守可能で、価値のあるE2E自動化テストを作成し、CICDと統合する方法を提供することです(継続的インテグレーションと継続的展開)。
E2Eテストの理想的なソリューションが完成するまで、複数の技術的アプローチを試しました。 大まかに言えば、これは私たちの旅を要約したものです。
- 独自のカスタムRubySeleniumソリューション、SiteTestUI、別名STUIの構築
- STUIからノードベースのWebdriverIOへの移行
- どちらの設定にも満足せず、最終的にサイプレスに移行する
このブログ投稿は、私たちの経験、学んだ教訓、および有益なパターンとテスト戦略を使用してE2Eテストを接続する方法にあなたや他の開発者をうまく導くために使用される各アプローチとのトレードオフを文書化して強調する2つの部分の1つです。
パート1は、STUIとの初期の闘い、WebdriverIOへの移行方法を網羅していますが、それでもSTUIと同様の多くの失敗を経験しました。 WebdriverIOを使用してテストを作成し、コンテナーで実行するようにテストをDocker化して、最終的にCICDプロバイダーであるBuildkiteとテストを統合する方法について説明します。
今日のE2Eテストの段階にスキップしたい場合は、パート2に進んで、STUIおよびWebdriverIOからCypressへの最終的な移行と、さまざまなチーム間でのセットアップ方法を確認してください。
TLDR: SeleniumラッパーソリューションであるSTUIとWebdriverIOの両方で同様の苦痛と苦労を経験し、最終的にCypressで代替手段を探し始めました。 E2Eテストの作成と、DockerおよびBuildkiteとの統合に取り組むための洞察に満ちたレッスンをたくさん学びました。
目次:
E2Eテストへの最初の進出:siteTESUI別名STUI
STUIからWebdriverIOへの切り替え
ステップ1:WebdriverIOの依存関係を決定する
ステップ2:環境構成とスクリプト
ステップ3:ローカルでENEテストを実装する
ステップ4:すべてのテストをDocker化する
ステップ5:CICDとの統合
WebdriverIoとのトレードオフ
サイプレスに移動
E2Eテストへの最初の進出:SiteTestUI別名STUI
最初にブラウザ自動化ツールを探すとき、SDET(テスト中のソフトウェア開発エンジニア)は、RubyとSelenium、特にRspecとGridiumと呼ばれるカスタムSeleniumフレームワークで構築された独自のカスタム社内ソリューションの作成に没頭しました。 クロスブラウザーのサポート、QA(品質保証)エンジニアのテストケース用にTestRailとの独自のカスタム統合を構成する機能、およびすべてのフロントエンドチームが1つの場所でE2Eテストを作成してスケジュールに従って実行します。
SDETが構築したツールを使用して初めてE2Eテストを作成することを熱望しているフロントエンド開発者として、すでにリリースしたページのテストの実装を開始し、ユーザーとシードデータを適切に設定してテストしたい機能。 ヘルパー機能を整理するためのページオブジェクトの形成や、ページごとに操作したい要素のセレクターの形成など、いくつかの素晴らしいことを学び、この構造に従った仕様の形成を開始しました。
同様のパターンに従って、同じリポジトリ内の異なるチームにまたがる実質的なテストスイートを徐々に構築しましたが、すぐに多くのフラストレーションを経験し、次のような新しい開発者やSTUIへの一貫した貢献者の進歩を大幅に遅らせました。
- テストスイートを実行する前に、すべてのブラウザードライバー、Ruby Gemの依存関係、および正しいバージョンをインストールするために、起動して実行するにはかなりの時間と労力が必要でした。 自分のマシンと他の人のマシンでテストが実行される理由と、セットアップの違いを理解する必要がある場合がありました。
- テストスイートは急増し、完了するまで何時間も実行されました。 すべてのチームが同じリポジトリに貢献したため、すべてのテストを連続して実行すると、テストスイート全体が実行されるまで数時間待つことになり、複数のチームが新しいコードをプッシュすると、別の場所で別のテストが失敗する可能性がありました。
- 不安定なCSSセレクターと複雑なXPathセレクターに不満を募らせました。 以下のこの図は、XPathを使用すると事態がより複雑になる可能性があることを十分に説明しており、これらはより単純なものの一部でした。
- テストのデバッグは面倒でした。 漠然としたエラー出力のデバッグに問題があり、通常、どこでどのように失敗したのかわかりませんでした。 テストを繰り返し実行し、ブラウザーを観察して、失敗した可能性のある場所と、その原因となったコードを推測することしかできませんでした。 CICDのDocker環境で、コンソール出力以外を確認することなくテストが失敗した場合、ローカルでの再現と問題の解決に苦労しました。
- Seleniumのバグと速度低下に遭遇しました。 サーバーからブラウザに送信されるすべてのリクエストが原因でテストの実行が遅くなり、ページ上の多くの要素を選択しようとしたとき、またはテストの実行中に不明な理由でテストが完全にクラッシュすることがありました。
- テストの修正とスキップに多くの時間が費やされ、スケジュールされたビルドテストの実行の失敗は無視され始めました。 テストでは、システムの真のエラーを実際に示す価値はありませんでした。
- フロントエンドチームは、それぞれのWebアプリケーションのリポジトリとは別のリポジトリに存在していたため、E2Eテストから切り離されていると感じました。 多くの場合、両方のリポジトリを同時に開き、テストの実行時にブラウザのタブに加えてコードベース間を行き来する必要がありました。
- フロントエンドチームは、JavaScriptまたはTypeScriptでのコードの記述からRubyへのコンテキストの切り替えや、STUIに貢献するたびにテストの記述方法を再学習する必要があることを好みませんでした。
- テストに貢献したとき、それは私たちの多くにとって最初のテイクだったので、ログインのためのUIを介した状態の構築、APIを介した十分な分解またはセットアップの実行、および十分なドキュメントの不足など、多くのアンチパターンに陥りました。素晴らしいテストを行うために従うこと。
1つのリポジトリで多くの異なるチームに対してかなりの数のE2Eテストを作成し、役立つパターンをいくつか学習することに向けた進歩にもかかわらず、開発者全体の経験、複数の障害点、および価値のある安定したテストの欠如に悩まされました。スタック全体を検証します。
他のフロントエンド開発者やQAが、テストの再利用、近接性、所有権を促進するために、独自のアプリケーションコードとともに常駐するJavaScriptを使用して独自の安定したE2Eテストスイートを構築できるようにする方法を評価しました。 これにより、カスタムRuby Selenium社内ソリューションであるSTUIの最初の代替として、ブラウザー自動化テスト用のJavaScriptベースのSeleniumフレームワークであるWebdriverIOを調査することになりました。
後でその落ち込みを経験し、最終的にサイプレスに移行します(WebdriverIOのものが魅力的でない場合は、ここでパート2に早送りします)が、各チームのリポジトリに標準化されたインフラストラクチャを確立し、フロントエンドのCICDにE2Eテストを統合することで貴重な経験を積みました。チーム、そして私たちの旅の中で文書化する価値のある技術パターンを採用し、他の人がWebdriverIOまたは他のE2Eテストソリューションに飛び込もうとしている人について学ぶために。
STUIからWebdriverIOへの切り替え
経験したフラストレーションを軽減するためにWebdriverIOに着手するとき、各フロントエンドチームに、Ruby Seleniumアプローチで記述された既存の自動化テストをJavaScriptまたはTypeScriptのWebdriverIOテストに変換させ、安定性、速度、開発者の経験、およびテスト。
E2Eテストをフロントエンドチームのアプリケーションリポジトリに配置し、CICDパイプラインとスケジュールされたパイプラインの両方で実行するという理想的なセットアップを実現するために、同様の目標を持つE2Eテストフレームワークのオンボードを希望するチームに一般的に適用される次の手順を再確認しました。 :
- テストフレームワークに接続するための依存関係のインストールと選択
- 環境構成とスクリプトコマンドの確立
- さまざまな環境に対してローカルで合格するE2Eテストの実装
- テストのDocker化
- DockerizedテストとCICDプロバイダーの統合
ステップ1:WebdriverIOの依存関係を決定する
WebdriverIOは、開発者に、テストランナーを開始するための多くのフレームワーク、レポーター、およびサービスの中から選択する柔軟性を提供します。 これには、チームが開始するために特定のライブラリに落ち着くために、多くの調整と調査が必要でした。
WebdriverIOは何を使用するかについて規範的ではないため、フロントエンドチームがさまざまなライブラリと構成を持つための扉を開きましたが、全体的なコアテストはWebdriverIOAPIの使用で一貫しています。
各フロントエンドチームが好みに基づいてカスタマイズできるようにすることを選択し、通常、テストフレームワークとしてMocha、レポーターとしてMochawesome、Selenium Standaloneサービス、およびTypescriptサポートを使用することにしました。 私たちのチームは以前にモカに精通しており、以前の経験があるため、モカとモカファムを選択しましたが、他のチームも他の選択肢を使用することにしました。
ステップ2:環境構成とスクリプト
WebdriverIOインフラストラクチャを決定した後、環境ごとにさまざまな設定でWebdriverIOテストを実行する方法が必要でした。 これらのテストを実行する方法と、それらをサポートする理由のほとんどのユースケースを示すリストを次に示します。
- ローカルホストで実行されているWebpack開発サーバー(つまりhttp:// localhost:8000)に対して、その開発サーバーはテストやステージングなどの特定の環境API(つまり、https://testing.api.comまたはhttps://)を指します。 staging.api.com)。
なんで? より堅牢な方法で要素と対話するためにテスト用のより具体的なセレクターを追加するなど、ローカルWebアプリに変更を加える必要がある場合や、新しい機能の開発を進めていて、既存の自動化テストを調整および検証する必要がある場合があります。新しいコードの変更に対してローカルで渡されます。 アプリケーションコードが変更され、デプロイされた環境にまだプッシュアップしていない場合は常に、このコマンドを使用して、ローカルWebアプリに対してテストを実行しました。 - テストやステージングなどの特定の環境(つまり、https://testing.app.comまたはhttps://staging.app.com)用にデプロイされたアプリに対して
なんで? また、アプリケーションコードは変更されませんが、テストコードを変更して不安定さを修正する必要がある場合や、フロントエンドを変更せずにテストを完全に追加または削除できる自信がある場合もあります。 このコマンドを多用して、デプロイされたアプリに対してローカルでテストを更新またはデバッグし、CICDパイプラインでのテストの実行方法をより厳密にシミュレートしました。 - テストやステージングなどの特定の環境で、デプロイされたアプリに対してDockerコンテナーで実行する
なんで? これはCICDパイプラインを対象としているため、たとえばステージングされたデプロイ済みアプリに対してDockerコンテナーで実行されるE2Eテストをトリガーし、コードを本番環境にデプロイする前、または専用パイプラインでスケジュールされたテスト実行で合格することを確認できます。 これらのコマンドを最初に設定するとき、さまざまな環境変数値を持つDockerコンテナーを起動し、CICDプロバイダーであるBuildkiteに接続する前に、適切なテストが正常に実行されるかどうかをテストするために、多くの試行錯誤を行いました。
これを実現するために、共有プロパティと多くの環境固有のファイルを使用して一般的な基本構成ファイルを設定し、各環境構成ファイルが基本ファイルとマージされ、実行に必要なプロパティを上書きまたは追加します。 ベースファイルを必要とせずに、環境ごとに1つのファイルを作成することもできますが、それでは一般的な設定で多くの重複が発生します。 deepmerge
のようなライブラリを使用して処理することを選択しましたが、ネストされたオブジェクトや配列ではマージが常に完全であるとは限らないことに注意することが重要です。 正しくマージされなかった重複するプロパティがある場合、未定義の動作につながる可能性があるため、結果の出力構成を常に再確認してください。
次のような共通ベース構成ファイルwdio.conf.js
を作成しました。
環境APIを指すローカルwebpack開発サーバーに対してE2Eテストを実行する最初の主要なユースケースに合わせるために、次の方法でlocalhost構成ファイルwdio.localhost.conf.js
を生成しました。
ベースファイルをマージし、ローカルホスト固有のプロパティをファイルに追加して、ファイルをよりコンパクトで保守しやすくしたことに注意してください。 また、Selenium Standaloneサービスを使用して、さまざまな種類のブラウザー、別名機能を起動します。
デプロイされたウェブアプリに対してE2Eテストを実行する2番目のユースケースでは、テストとステージングのアプリ構成ファイル`wdio.testing.conf.js`とwdio.staging.conf.js
を次のように設定します。
ここでは、ステージングの専用ユーザーへのログインクレデンシャルなど、いくつかの追加の環境変数を構成ファイルに追加し、デプロイされたステージングアプリのURLを指すように`baseUrl`を更新しました。
CICDプロバイダーのレルム内にデプロイされたWebアプリに対してDockerコンテナーでE2Eテストを実行する3番目のユースケースでは、CICD構成ファイルwdio.cicd.testing.conf.js
とwdio.cicd.staging.conf.js
、そのように:
後でSeleniumChrome、Selenium Hub、およびアプリケーションコードをDocker Composeファイルの個別のサービスにインストールするため、SeleniumStandaloneサービスを使用しなくなったことに注意してください。 この構成は、ログイン資格情報や `baseUrl`などのステージング構成と同じ環境変数も示します。これは、デプロイされたステージングアプリに対してテストを実行することを想定しているためです。唯一の違いは、これらのテストがDockerコンテナー内で実行されることを目的としていることです。 。
これらの環境構成ファイルを確立したら、テストの基盤として機能するpackage.json
スクリプトコマンドの概要を説明しました。 この例では、WebdriverIOを使用したUIテストを示すために、コマンドの前に「uitest」を付けました。これは、テストファイルも*.uitest.js
で終了したためです。 ステージング環境のサンプルコマンドは次のとおりです。
ステップ3:ローカルでのE2Eテストの実装
すべてのテストコマンドが手元にあるので、STUIリポジトリ内のテストをスコープアウトして、WebdriverIOテストに変換できるようにしました。 中小規模のページテストに焦点を当て、ページオブジェクトパターンを適用して、各ページのすべてのUIを整理された方法でカプセル化し始めました。
一連のヘルパー関数やオブジェクトリテラル、またはその他の戦略を使用して構造化ファイルを作成することもできますが、重要なのは、保守可能なテストを迅速に提供し、それに固執する一貫した方法を持つことでした。 特定のページのUIフローまたはDOM要素が変更された場合、それに関連するページオブジェクトと、場合によってはテストコードをリファクタリングするだけで、テストに再度合格することができます。
他のすべてのページオブジェクトの拡張元となる共有機能を備えたベースページオブジェクトを使用して、ページオブジェクトパターンを実装しました。 ブラウザでページのURLを「開く」またはアクセスするために、すべてのページオブジェクトにわたって一貫したAPIを提供するopenなどの機能がありました。 これは次のようなものに似ています。
特定のページオブジェクトの実装は、基本のPage
クラスから拡張し、ページ上でアクションを実行するためのヘルパー関数と対話またはアサートしたい特定の要素にセレクターを追加するのと同じパターンに従いました。
この呼び出しSomePage.open()
でページにアクセスできるように、ページの特定のルートでsuper.open(...)
を介してopenの基本クラスをどのように使用したかに注目してください。 また、 SomePage.submitButton
やSomePage.tableRows
などの要素を参照し、WebdriverIOコマンドを使用してこれらの要素と対話したり、それらの要素をアサートしたりできるように、すでに初期化されているクラスをエクスポートしました。 ページオブジェクトがコンストラクター内の独自のメンバープロパティで共有および初期化されることを意図している場合は、クラスを直接エクスポートし、 new SomePage(...constructorArgs)
を使用してテストファイル内のページオブジェクトをインスタンス化することもできます。
セレクターといくつかのヘルパー機能を使用してページオブジェクトをレイアウトした後、E2Eテストを作成し、このテスト式を一般的にモデル化しました。
- 実際のテストを実行する前に、テスト条件を予想される開始点にリセットするために必要なものをAPIでセットアップまたは破棄します。
- テスト専用のユーザーにログインすると、ページに直接アクセスするたびにログインしたままになり、UIを経由する必要がなくなります。 ログインページで使用するのと同じAPI呼び出しを行い、最終的にログインを維持して保護されたAPIリクエストのヘッダーを渡すために必要な認証トークンを返すユーザー名とパスワードを受け取る簡単な
login
ヘルパー関数を作成しました。 他の企業は、シードデータと構成を使用して新しいユーザーをすばやく作成するためのさらに多くのカスタム内部エンドポイントまたはツールを持っている可能性がありますが、残念ながら、十分に具体化されていませんでした。 これは昔ながらの方法で行い、UIを介してさまざまな構成の環境で専用のテストユーザーを作成し、リソースの衝突を回避し、テストが並行して実行されたときに分離されたままになるように、異なるユーザーを持つページのテストを分割することがよくありました。 専用のテストユーザーが他のユーザーに触れられないようにする必要がありました。そうしないと、誰かが無意識のうちにテストユーザーの1人をいじったときにテストが失敗します。 - エンドユーザーが機能/ページを操作するかのように手順を自動化します。 通常、機能フローを保持しているページにアクセスし、入力の入力、ボタンのクリック、モーダルまたはバナーの表示の待機、変更された出力のテーブルの観察など、エンドユーザーが行う高レベルの手順に従います。アクションの結果。 便利なページオブジェクトとセレクターを使用することで、各ステップをすばやく実装し、途中で健全性チェックを行う際に、機能フロー中にユーザーがページに表示する必要があるものと表示しないものについて、特定のものが期待どおりに動作していることを確認します。各ステップの前後。 また、価値の高いハッピーパステストと、場合によっては簡単に再現できる一般的なエラー状態を選択し、残りの下位レベルのテストをユニットテストと統合テストに延期することも検討しました。
E2Eテストの一般的なレイアウトの大まかな例を次に示します(この戦略は、私たちが試した他のテストフレームワークにも適用されました)。
ちなみに、このブログ投稿シリーズでは、WebdriverIOとE2Eのベストプラクティスに関するすべてのヒントと落とし穴を取り上げないことを選択しましたが、これらのトピックについては今後のブログ投稿で説明しますので、ご期待ください。
ステップ4:すべてのテストをDocker化する
クラウド内の新しいAWSマシンでBuildkiteパイプラインの各ステップを実行する場合、これらのマシンには実際にテストを実行するためのノード、ブラウザー、アプリケーションコード、またはその他の依存関係がないため、単に「npm runuitests:staging」を呼び出すことはできません。 。
これを解決するために、Node、Selenium、Chrome、アプリケーションコードなどのすべての依存関係をDockerコンテナーにバンドルして、WebdriverIOテストを正常に実行できるようにしました。 DockerとDockerComposeを利用して、起動して実行するために必要なすべてのサービスをアセンブルしました。これらのサービスは、 Dockerfiles
とdocker docker-compose.yml
ファイルに変換され、Dockerコンテナーをローカルで起動して動作させるための多くの実験が行われました。
より多くのコンテキストを提供するために、私たちはDockerの専門家ではなかったので、すべてをまとめる方法を理解するのにかなりの立ち上げ時間がかかりました。 WebdriverIOテストをDocker化するには複数の方法があり、さまざまなDockerイメージ、作成バージョン、およびチュートリアルをうまく機能するまで、さまざまなサービスを一緒にオーケストレーションしてふるいにかけることは困難であることがわかりました。
チームの構成の1つに一致する、ほとんどが具体化されたファイルを示します。これにより、DockerizingSeleniumベースのテストの一般的な問題に取り組むあなたや誰にとっても洞察が得られることを願っています。
大まかに言えば、私たちのテストでは次のことが要求されました。
- ブラウザに対してコマンドを実行し、ブラウザと通信するためのSelenium 。 Selenium Hubを使用して複数のインスタンスを自由に起動し、docker-composeファイルの
selenium-hub
サービス用のイメージ「selenium/hub」をダウンロードしました。 - 実行するブラウザ。 Selenium Chromeインスタンスを起動し、
docker-compose.yml file
にselenium-chrome
サービスのイメージ「selenium/node-chrome-debug」をインストールしました。 - 他のノードモジュールがインストールされている状態でテストファイルを実行するためのアプリケーションコード。 新しい
Dockerfile
を作成して、ノードでnpmパッケージをインストールしてpackage.json
スクリプトを実行し、テストコードをコピーして、dockerdocker-compose.yml
ファイルでuitests
という名前のテストファイルの実行専用のサービスを割り当てる環境を提供します。
WebdriverIOテストの実行に必要なすべてのアプリケーションとテストコードを含むサービスを起動するために、 Dockerfile
というDockerfile.uitests
を作成し、すべてのnode_modules
をインストールして、ノード環境のイメージの作業ディレクトリにコードをコピーしました。 これは、 uitests
のDocker Composeサービスで使用され、次の方法でDockerfile
のセットアップを実現しました。
Selenium Hub、Chromeブラウザー、およびアプリケーションテストコードをまとめてWebdriverIOテストを実行するために、docker docker-compose.uitests.yml
ファイルでselenium-hub
、 selenium-chrom
e、およびuitest
のサービスの概要を説明しました。 :
Selenium HubとChromeイメージを環境変数、 depends_on
を介して接続し、ポートをサービスに公開しました。 テストアプリケーションのコードイメージは、最終的には、管理しているプライベートDockerレジストリからプッシュアップおよびプルされます。
CICD中にテストコード用のDockerイメージを、 VERSION
やPIPELINE_SUFFIX
などの特定の環境変数を使用して構築し、タグとより具体的な名前でイメージを参照します。 次に、Seleniumサービスを起動し、 uitests
サービスを介してコマンドを実行して、WebdriverIOテストを実行します。
Docker Composeファイルを作成する際に、docker- docker-compose up
やdocker-compose down
-composedownなどの便利なコマンドをマシンにインストールされたMacDockerで活用して、イメージが適切に構成され、Buildkiteと統合する前にスムーズに実行されたことをローカルでテストしました。 タグ付けされたイメージを作成し、レジストリにプッシュし、プルダウンし、環境変数の値に従ってテストを実行するために必要なすべてのコマンドを文書化しました。
ステップ5:CICDとの統合
動作するDockerコマンドを確立し、さまざまな環境に対してDockerコンテナー内でテストを正常に実行した後、CICDプロバイダーであるBuildkiteとの統合を開始しました。
Buildkiteは、AWSマシン上の.yml
ファイルで、コードまたはリポジトリのパイプラインのBuildkite設定UIを介して設定されたBashスクリプトと環境変数を使用してステップを実行する方法を提供しました。
Buildkiteでは、エクスポートされた環境変数を使用してメインのデプロイパイプラインからこのテストパイプラインをトリガーすることもできました。これらのテストステップを、QAが監視および確認するスケジュールで実行される他の分離されたテストパイプラインに再利用します。
大まかに言えば、WebdriverIOおよびそれ以降のCypressパイプライン用のBuildkiteパイプラインのテストでは、次の同様の手順を共有しました。
- Dockerイメージを設定します。 テストに必要なDockerイメージをビルド、タグ付け、およびレジストリにプッシュして、後のステップでプルダウンできるようにします。
- 環境変数の構成に基づいてテストを実行します。 特定のビルドのタグ付きDockerイメージをプルダウンし、デプロイされた環境に対して適切なコマンドを実行して、設定された環境変数から選択したテストスイートを実行します。
これは、「Build UITests DockerImage」ステップでDockerイメージをセットアップし、「Run WebdrivertestsforChrome」ステップでテストを実行することを示すpipeline.uitests.yml
ファイルの近い例です。
注意すべき点の1つは、最初のステップである「UITests Dockerイメージの構築」と、テスト用にDockerイメージをセットアップする方法です。 Docker Compose build
コマンドを使用して、すべてのアプリケーションテストコードでuitests
サービスをビルドし、 latest
の${VERSION}
環境変数でタグ付けして、将来このビルドに適切なタグを付けて同じイメージを最終的にプルダウンできるようにしました。ステップ。
各ステップはAWSクラウドのどこかの異なるマシンで実行される可能性があるため、タグは特定のBuildkite実行のイメージを一意に識別します。 イメージにタグを付けた後、最新のバージョンタグ付きイメージをプライベートDockerレジストリにプッシュして再利用しました。
「Chromeに対してWebdriverテストを実行する」ステップでは、最初のステップで作成、タグ付け、プッシュしたイメージをプルダウンし、Selenium Hub、Chrome、およびテストサービスを起動します。 $UITESTENV
や$UITESTSUITE
などの環境変数に基づいて、 npm run uitest:
のように実行するコマンドのタイプと、 --suite $UITESTSUITE
などのこの特定のBuildkiteビルドに対して実行するテストスイートを選択します。
これらの環境変数は、Buildkiteパイプライン設定を介して設定されるか、Buildkite選択フィールドを解析して実行するテストスイートと環境を決定するBashスクリプトから動的にトリガーされます。
これは、専用のテストパイプラインでトリガーされたWebdriverIOテストの例です。これも同じpipeline.uitests.yml
ファイルを再利用しましたが、パイプラインがトリガーされた場所に環境変数が設定されています。 このビルドは失敗し、[アーティファクト]タブの下の[ Artifacts
]タブと[ Logs
]タブの下のコンソール出力を確認するためのエラースクリーンショットがありました。 pipeline.uitests.yml
(https://gist.github.com/alfredlucero/71032a82f3a72cb2128361c08edbcff2#file-pipeline-uitests-yml-L38)のartifact_paths
、`wdio.conf.jsの`mochawesome`のスクリーンショット設定を覚えておいてください`ファイル(https://gist.github.com/alfredlucero/4ee280be0e0674048974520b79dc993a#file-wdio-conf-js-L39)、および`docker-compose.uitests.yml`の`uitests`サービスでのボリュームのマウント(https://gist.github.com/alfredlucero/d2df4533a4a49d5b2f2c4a0eb5590ff8#file-docker-compose-yml-L32)?
以下に示すように、Buildkite UIからアクセスできるようにスクリーンショットを接続して、直接ダウンロードし、テストのデバッグに役立てることができました。
Buildkiteパイプライン設定ですでに構成されている環境変数を除いて、 pipeline.uitests.yml
ファイルを使用して特定のページのスケジュールで別のパイプラインで実行されるWebdriverIOテストの別の例が下に表示されます。
特定の構文、GUI設定、Bashスクリプト、またはその他の手段を使用する.yml
ファイルを介して新しいコードをマージする場合、CICDプロバイダーごとに異なる機能とステップをある種のデプロイプロセスに統合する方法があることに注意してください。
JenkinsからBuildkiteに切り替えたとき、チームがそれぞれのコードベース内で独自のパイプラインを定義する機能を大幅に改善し、オンデマンドでスケーリングマシン間でステップを並列化し、読みやすいコマンドを利用しました。
使用するCICDプロバイダーに関係なく、テストを統合する戦略は、Dockerイメージをセットアップし、移植性と柔軟性のために環境変数に基づいてテストを実行する場合と同様です。
WebdriverIOとのトレードオフ
かなりの数のカスタムRubySeleniumソリューションテストをWebdriverIOテストに変換し、DockerおよびBuildkiteと統合した後、一部の領域で改善しましたが、それでも古いシステムと同様の苦労を感じ、最終的にサイプレスでの次の最後の停止に至りました。私たちのE2Eテストソリューション。
カスタムRubySeleniumソリューションと比較したWebdriverIOの経験から私たちが見つけたいくつかの長所のリストを次に示します。
- テストは、RubyではなくJavaScriptまたはTypeScriptで純粋に作成されました。 これは、言語間のコンテキスト切り替えが少なくなり、E2Eテストを作成するたびにRubyを再学習するために費やす時間が少なくなることを意味します。
- テストは、Ruby共有リポジトリではなく、アプリケーションコードと同じ場所に配置しました。 他のチームのテストが失敗することに依存することはなくなり、リポジトリ内の機能のE2Eテストをより直接的に所有するようになりました。
- クロスブラウザテストのオプションを高く評価しました。 WebdriverIOを使用すると、Chrome、Firefox、IEなどのさまざまな機能やブラウザーに対してテストを開始できますが、ユーザーの80%以上がChromeを介してアプリにアクセスしたため、主にChromeに対するテストの実行に重点を置きました。
- サードパーティのサービスと統合する可能性を楽しませました。 WebdriverIOのドキュメントでは、BrowserStackやSauceLabsなどのサードパーティサービスと統合して、すべてのデバイスとブラウザーでアプリをカバーする方法について説明しています。
- 独自のテストランナー、レポーター、およびサービスを柔軟に選択できました。 WebdriverIOは何を使用するかを規定していなかったため、各チームはモカとチャイ、ジェストなどのサービスを使用するかどうかを自由に決定しました。 これは、チームがお互いのセットアップから逸脱し始め、私たちが選択する各オプションを試すのにかなりの時間が必要だったため、短所として解釈することもできます。
- WebdriverIO API、CLI、およびドキュメントは、テストを記述し、DockerおよびCIC Dと統合するのに十分なサービスを提供しました。さまざまな構成ファイルを作成し、仕様をグループ化し、コマンドラインからテストを実行し、ページオブジェクトパターンに従ってテストを記述できます。 ただし、ドキュメントはより明確になる可能性があり、多くの奇妙なバグを掘り下げる必要がありました。 それでも、RubySeleniumソリューションからテストを変換することができました。
以前のRubySeleniumソリューションには欠けていた多くの領域で進歩を遂げましたが、次のようなWebdriverIOをすべて使用することを妨げる多くのショートッパーに遭遇しました。
- WebdriverIOはまだSeleniumベースであったため、多くの奇妙なタイムアウト、クラッシュ、バグが発生し、古いRubySeleniumソリューションでのネガティブなフラッシュバックを思い出させました。 ページ上の多くの要素を選択すると、テストが完全にクラッシュし、テストの実行が希望よりも遅くなることがありました。 テストを作成するときは、Githubの多くの問題を回避するか、特定の方法論を回避する必要がありました。
- 全体的な開発者エクスペリエンスは最適ではありませんでした。 ドキュメントには、コマンドの概要がいくつか記載されていますが、コマンドの使用方法をすべて説明するのに十分な例はありません。 RubyでE2Eテストを作成することを避け、最終的にJavaScriptまたはTypeScriptでテストを作成するようになりましたが、WebdriverIOAPIの処理は少し混乱していました。 いくつかの一般的な例は、単数形と複数形の要素に対する
$
対$$
の使用、要素が表示されなくなるのを待つため$('...').waitForVisible(9000, true)
、およびその他の直感的でないコマンドです。 私たちは多くの不安定なセレクターを経験し、すべてに対して明示的に$(...).waitForVisible()
を実行する必要がありました。 - デバッグテストは、開発者とQAにとって非常に面倒で退屈でした。 Whenever tests failed, we only had screenshots, which would often be blank or not capturing the right moment for us to deduce what went wrong, and vague console error messages that did not point us in the right direction of how to solve the problem and even where the issue occurred. We often had to re-run the tests many times and stare closely at the Chrome browser running the tests to hopefully put things together as to where in the code our tests failed. We used things like
browser.debug()
but it often did not work or did not provide enough information. We gradually gathered a bunch of console error messages and mapped them to possible solutions over time but it took lots of pain and headache to get there. - WebdriverIO tests were tough to set up with Docker . We struggled with trying to incorporate it into Docker as there were many tutorials and ways to do things in articles online, but it was hard to figure out a way that worked in general. Hooking up 2 to 3 services together with all these configurations led to long trial and error experiments and the documentation did not guide us enough in how to do that.
- Choosing the test runner, reporter, assertions, and services demanded lots of research time upfront . Since WebdriverIO was flexible enough to allow other options, many teams had to spend plenty of time to even have a solid WebdriverIO infrastructure after experimenting with a lot of different choices and each team can have a completely different setup that doesn't transfer over well for shared knowledge and reuse.
To summarize our WebdriverIO and STUI comparison, we analyzed the overall developer experience (related to tools, writing tests, debugging, API, documentation, etc.), test run times, test passing rates, and maintenance as displayed in this table:
Moving On to Cypress
At the end of the day, our WebdriverIO tests were still flaky and tough to maintain. More time was still spent debugging tests in dealing with weird Selenium issues, vague console errors, and somewhat useful screenshots than actually reaping the benefits of seeing tests fail for when the backend or frontend encountered issues.
We appreciated cross-browser testing and implementing tests in JavaScript, but if our tests could not pass consistently without much headache for even Chrome, then it became no longer worth it and we would then simply have a STUI 2.0.
With WebdriverIO we still strayed from the crucial aspect of providing a way to write consistent, debuggable, maintainable, and valuable E2E automation tests for our frontend applications in our original goal. Overall, we learned a lot about integrating with Buildkite and Docker, using page objects, and outlining tests in a structured way that will transfer over to our final solution with Cypress.
If we felt it was necessary to run our tests in multiple browsers and against various third-party services, we could always circle back to having some tests written with WebdriverIO, or if we needed something fully custom, we would revisit the STUI solution.
Ultimately, neither solution met our main goal for E2E tests, so follow us on our journey in how we migrated from STUI and WebdriverIO to Cypress in part 2 of the blog post series.