E2Eテストジャーニーパート2:WebdriverIOからサイプレスへ

公開: 2019-11-21

注:これは#frontend@twiliosendgridからの投稿です。 その他のエンジニアリングの投稿については、テクニカルブログロールにアクセスしてください。

すべてのフロントエンドアプリで、次の目標がありました。これは、フロントエンドアプリケーションの一貫性があり、デバッグ可能で、保守可能で、価値のあるE2E(エンドツーエンド)自動化テストを作成し、CICD(継続的インテグレーション)と統合する方法を提供することです。および継続的展開)。

SendGrid全体のすべてのフロントエンドチームで数百のE2Eテストがトリガーされるか、スケジュールどおりに実行される可能性がある現在の状態に到達するには、そのメインを達成するまで、多くの潜在的なソリューションを調査して実験する必要がありました。ゴール。

私たちは、SiteTestUI(別名STUI)と呼ばれる専用のテストエンジニアによって開発された独自のカスタムRuby Seleniumソリューションを展開しようとしました。このソリューションでは、すべてのチームが1つのリポジトリに貢献し、クロスブラウザー自動化テストを実行できます。 悲しいことに、テストの速度が遅く、誤警報、リポジトリと言語間のコンテキストの欠如、1つのバスケットに含まれる手が多すぎる、苦痛なデバッグエクスペリエンス、価値を提供するよりもメンテナンスに多くの時間が費やされました。

次に、WebdriverIOの別の有望なライブラリを試して、各チームのアプリケーションリポジトリと同じ場所にあるJavaScriptでテストを記述しました。 これによりいくつかの問題が解決され、チームの所有権が強化され、CICDプロバイダーであるBuildkiteとテストを統合できるようになりましたが、非常に類似したSeleniumのバグや癖に対処するだけでなく、デバッグが面倒で、作成が難しい不安定なテストがありました。 。

別のSTUI2.0を避けたかったので、他のオプションを検討し始めました。 STUIからWebdriverIOに移行する際に学んだ教訓と発見された戦略について詳しく知りたい場合は、ブログ投稿シリーズのパート1をご覧ください。

ブログ投稿シリーズのこの最後のパート2では、STUIおよびWebdriverIOからCypressへの移行と、インフラストラクチャ全体のセットアップ、組織化されたE2Eテストの作成、Buildkiteパイプラインとの統合、および組織内の他のフロントエンドチーム。

TLDR: STUIおよびWebdriverIOではなくCypressを採用し、CICDと統合するための貴重なE2Eテストを作成するという目標をすべて達成しました。 WebdriverIOとSTUIから学んだ多くの作業と教訓は、今日のサイプレステストの使用方法と統合方法にうまく引き継がれています。

目次

サイプレスの探索と着陸

STUIおよびWebdriverIOからCypressへの切り替え

ステップ1:サイプレスの依存関係をインストールする

ステップ2:環境構成とスクリプト

環境構成とスクリプトの最初のパス

環境構成とスクリプトの進化

ステップ3:ローカルでE2Eテストを実装する

ステップ4:テストをDocker化する

ステップ5:CICDとの統合

ステップ6:サイプレスとWebdriverIO/STUIの比較

ステップ7:他のフロントエンドチームへのスケーリング

サイプレスで楽しみにしていること

サイプレスを未来に採用

サイプレスの探索と着陸

WebdriverIOの代替品を検索したところ、WebdriverIOと同様の機能が設定されたProtractorやNightwatchなどの他のSeleniumラッパーが見つかりましたが、長いセットアップ、不安定なテスト、面倒なデバッグとメンテナンスに遭遇する可能性が高いと感じました。

ありがたいことに、Cypressと呼ばれる新しいE2Eテストフレームワークに出くわしました。このフレームワークは、迅速なセットアップ、ブラウザーで実行される高速でデバッグ可能なテスト、ネットワーク層のリクエストスタブ、そして最も重要なことに、内部でSeleniumを使用しないことを示しました。

ビデオ録画、サイプレスGUI、有料ダッシュボードサービス、並列化などのすばらしい機能を試してみました。 開発者とQAのテストを実装、デバッグ、維持するためのツールのレパートリーを使用して、Chromeに対して一貫して合格する貴重なテストを優先して、クロスブラウザーのサポートについて妥協することをいとわなかった。

また、テストフレームワーク、アサーションライブラリ、およびすべてのフロントエンドチームにわたるテストへのより標準化されたアプローチを持つために選択された他のすべてのツールに感謝します。 以下に、WebdriverIOとCypressのようなソリューションの違いのスクリーンショットを示しました。CypressとSeleniumのソリューションの違いをもっと知りたい場合は、その仕組みに関するドキュメントを確認してください。

そのことを念頭に置いて、CICD中にBuildkiteと統合される、高速で一貫性のあるデバッグ可能なE2Eテストを作成するための別のソリューションとしてCypressをテストすることを約束しました。 また、サイプレスを以前のSeleniumベースのソリューションと比較するという別の目標を意図的に設定し、今後もサイプレスを使用してテストスイートを探し続けるか、構築するかをデータポイントで最終的に判断します。 既存のWebdriverIOテストおよびSTUIに残っているその他の優先度の高いテストをCypressテストに変換し、開発者の経験、速度、安定性、テストの実行時間、およびテストのメンテナンスを比較することを計画しました。

STUIおよびWebdriverIOからCypressへの切り替え

STUIとWebdriverIOからCypressに切り替えるときは、フロントエンドアプリケーションリポジトリでSTUIからWebdriverIOに移行しようとしたときに使用したのと同じ高レベルの戦略を通じて、体系的に取り組みました。 WebdriverIOでこのような手順を実行した方法の詳細については、ブログ投稿シリーズのパート1を参照してください。 サイプレスに移行するための一般的な手順は次のとおりです。

  1. サイプレスと接続するための依存関係のインストールと設定
  2. 環境設定およびスクリプトコマンドの確立
  3. さまざまな環境に対してローカルで合格するE2Eテストの実装
  4. テストのDocker化
  5. DockerizedテストをCICDプロバイダーであるBuildkiteと統合する

二次的な目標を達成するために、サイプレスを以前のSeleniumソリューションと比較し、最終的に組織内のすべてのフロントエンドチームにわたってサイプレスを拡張するための追加の手順も追加しました。

6.開発者の経験、速度、およびテストの安定性とWebdriverIOおよびSTUIの観点からサイプレスを比較する
7.他のフロントエンドチームへのスケーリング

ステップ1:サイプレスの依存関係をインストールする

Cypressをすばやく起動して実行するには、プロジェクトに `npm install cypress`をインストールし、Cypressを初めて起動して、`cypress.json`構成ファイルとcypressフォルダーで自動的にレイアウトするだけでした。スターターフィクスチャ、テスト、およびコマンドとプラグイン用の他のセットアップファイルを使用します。 サイプレスがテストランナーとしてMochaにバンドルされ、アサーションにChaiがバンドルされ、さらに多くのアサーションを使用してチェーンオフするためにChai-jQueryとSinon-Chaiがバンドルされたことを高く評価しました。 WebdriverIOまたはSTUIを最初に使用したときと比較して、インストールして使用するテストランナー、レポーター、アサーション、およびサービスライブラリについて調査するためにかなりの時間を費やす必要がなくなりました。 生成されたテストのいくつかをCypressGUIですぐに実行し、タイムトラベルデバッグ、セレクタープレイグラウンド、録画ビデオ、スクリーンショット、コマンドログ、ブラウザー開発ツールなど、自由に使用できる多くのデバッグ機能を調査しました。

また、後でEslintとTypeScriptを使用して設定し、新しいサイプレステストコードをコミットするときに従う追加の静的型チェックとフォーマットルールを設定します。 最初はTypeScriptのサポートに問題があり、プラグインファイルを中心としたJavaScriptファイルである必要があるファイルもありましたが、ほとんどの場合、テスト、ページオブジェクト、コマンドのファイルの大部分をタイプチェックできました。

これは、フロントエンドチームの1つがページオブジェクト、プラグイン、およびコマンドを組み込むために従ったフォルダー構造の例です。

ステップ2:環境構成とスクリプト

Cypressをすばやくインストールしてローカルで実行するように設定した後、Cypressテストを環境ごとに異なる設定で実行する方法が必要であり、WebdriverIOコマンドで実行できるのと同じユースケースをサポートしたいと考えました。 これらのテストを実行する方法と、それらをサポートする理由のほとんどのユースケースを示すリストを次に示します。

  • localhost(つまり、http:// localhost:8000)で実行されているWebpack開発サーバーに対して、その開発サーバーは特定の環境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に接続する前に、適切なテストが正常に実行されるかどうかをテストするために、多くの試行錯誤を行いました。

環境構成とスクリプトの最初のパス

サイプレスのセットアップを最初に実験したとき、送信者認証、電子メールアクティビティ、電子メール検証などの機能ページを含むWebアプリhttps://app.sendgrid.comをカバーするリポジトリでそれを行い、必然的に共有しましたhttps://mc.sendgrid.comドメインを含むMarketingCampaignsWebアプリの背後にあるチームとの発見と学習。 ステージング環境に対してE2Eテストを実行することを望み、Cypressのコマンドラインインターフェイスと--config--envなどのオプションを利用してユースケースを達成しました。

http://127.0.0.1:8000などのローカルでWebアプリに対して、またはデプロイされたステージングアプリのURLに対してサイプレステストを実行するために、コマンドのbaseUrl configフラグを調整し、 testEnvなどの追加の環境変数を追加して支援しましたサイプレステストで特定のフィクスチャまたは環境固有のテストデータをロードします。 たとえば、使用されるAPIキー、作成されたユーザー、およびその他のリソースは、環境によって異なる場合があります。 testEnvを使用して、これらのフィクスチャを切り替えたり、一部の機能が環境でサポートされていない場合やテストセットアップが異なる場合は特別な条件付きロジックを追加し、仕様のCypress.env(“testEnv”)などの呼び出しを通じて環境にアクセスしました。

次に、コマンドcypress:open:*を整理して、ローカルで開発したときにUIを介して実行するテストを選択するためのCypress GUIを開き、 cypress:run:*は、より調整されたヘッドレスモードでのテストの実行を示します。 CICD中にDockerコンテナで実行するため。 openまたはrunの後に来るのは環境なので、コマンドはnpm run cypress:open:localhost:stagingのように簡単に読み取ってGUIを開き、ステージングAPIまたはnpm run cypress:run:stagingを指すローカルWebpack開発サーバーに対してテストを実行します。 npm run cypress:run:stagingは、デプロイされたステージングアプリとAPIに対してヘッドレスモードでテストを実行します。 package.jsonサイプレススクリプトは次のようになりました。

環境構成とスクリプトの進化

別のプロジェクトでは、サイプレスのコマンドと構成を進化させて、 cypress/plugins/index.jsファイルのノードロジックを利用して、ベースのcypress.jsonファイルと、次の環境変数に基づいて読み取られる個別の構成ファイルを作成しました。 configFileを使用して、特定の構成ファイルをロードします。 次に、ロードされた構成ファイルがベースファイルとマージされ、最終的にステージングサーバーまたはモックバックエンドサーバーを指すようになります。

モックバックエンドサーバーについて詳しく知りたい場合は、リクエストで渡されたクエリパラメーターに応じて静的JSONデータとステータスコード(200、4XX、5XXなど)のさまざまな応答を返すだけのバックエンドエンドポイントを備えたExpressサーバーを開発しました。 これにより、フロントエンドのブロックが解除され、モックバックエンドサーバーへの実際のネットワーク呼び出しでページフローを開発し続け、将来利用可能になったときに実際のAPIがどのようになるかをエミュレートする応答が得られます。 また、本番環境では再現が難しいさまざまなUI状態について、さまざまなレベルの成功とエラー応答を簡単にシミュレートできます。また、確定的なネットワーク呼び出しを行うため、同じネットワークを起動する際のサイプレステストの不安定さが軽減されます。毎回リクエストとレスポンス。

一般的なタイムアウトの共有プロパティ、後で説明するサイプレスダッシュボードサービスに接続するプロジェクトID、および以下に示すその他の設定を含むベースcypress.jsonファイルがありました。

cypressフォルダーにconfigフォルダーを作成して、ローカルのモックAPIサーバーに対してローカルのWebpack開発サーバーを実行するlocalhostMock.jsonや、デプロイされたステージングアプリとAPIに対して実行するstaging.jsonなどの各構成ファイルを保持します。 差分を取り、基本構成とマージするこれらの構成ファイルは、次のようになります。

CICD構成ファイルにはさらに単純なJSONファイルが含まれていました。これは、後で掘り下げるさまざまなDockerサービスフロントエンドベースURLとモックサーバーAPIホストを考慮して環境変数を動的に設定する必要があるためです。

cypress/plugins/index.jsファイルに、サイプレスコマンドからconfigFileセットと呼ばれる環境変数を読み取るロジックを追加しました。この変数は、最終的にconfigフォルダー内の対応するファイルを読み取り、以下のようにベースcypress.jsonとマージします。

ユースケースに設定された環境変数を使用して適切なサイプレスコマンドを作成するために、次のようなMakefileを利用しました。

これらのコマンドをMakefileに適切に配置すると、` make cypress_run_stagingスクリプトでmake cypress_open_stagingすばやく実行できます。

以前は、エラーなしで編集するのが難しいいくつかのコマンドを1つの長い行に配置していました。 ありがたいことに、Makefileは、複数行にわたるサイプレスコマンドへの環境変数の読み取り可能な補間により、物事をより適切に分散させるのに役立ちました。 ロードする環境構成ファイルのconfigFile 、ページにアクセスするBASE_URL 、さまざまなバックエンド環境のAPI_HOST 、Makefileコマンドを開始する前に実行するテストを決定するSPECSなどの環境変数をすばやく設定またはエクスポートできます。

また、Webpackアセットの構築、依存関係のインストール、他のコマンドの実行など、他の長いnpmスクリプトやDockerコマンドにもMakefileコマンドを使用しました。 最後に、いくつかのMakefileコマンドをpackage.jsonスクリプトセクションに変換しますが、誰かがMakefileのみを使用したい場合はこれは必要ではなく、次のようになります。

サイプレスCICDコマンドの多くは、日常の開発で使用されるコマンドではなく、結果としてpackage.jsonをより合理化したものであったため、意図的に除外しました。 最も重要なことは、モックサーバーとローカルWebpack開発サーバーに関連するすべてのサイプレスコマンドとステージング環境、およびヘッドレスモードで「実行」するのではなくGUIを「開く」コマンドを一目で確認できることです。

ステップ3:ローカルでE2Eテストを実装する

CypressでE2Eテストの実装を開始したとき、WebdriverIOとSTUIの既存のテストを参照して変換し、単純なヘルスチェックから複雑なハッピーパスフローに至るまで、他の優先度の高い機能の新しいテストを追加しました。 既存のページオブジェクトとテストファイルをWebdriverIOまたはSTUIから同等のページオブジェクトとサイプレスの仕様に変換するのは簡単であることがわかりました。 実際には、要素の明示的な待機が少なくなり、アサーションやその他のサイプレスコマンドの連鎖性が向上し、以前よりもはるかにクリーンなコードが得られました。

たとえば、テストの一般的な手順はエンドユーザーの観点からは同じままだったため、変換作業には、次の方法でWebdriverIOまたはSTUIAPIをCypressAPIにマッピングすることが含まれていました。

  • 多くのコマンドは基本的に、 $またはbrowsercyまたはCypressに置き換えたところと同じように表示され、機能しました。つまり、 $(“.button”).click()からcy.get(“.button”).click() ")を介してページにアクセスします。 cy.get(“.button”).click()browser.url()からcy.visit() 、または$(“.input”).setValue()からcy.get(“.input”).type()
  • $または$$を使用すると、通常はcy.get(...)またはcy.contains(...) 、つまり$$(“.multiple-elements-selector”)または$(“.single-element-selector”)なります。 $(“.single-element-selector”) cy.get(“.any-element-selector”)cy.contains(“text”) 、またはcy.contains(“.any-selector”) 。any-selector")に変換されます
  • 無関係な$(“.selector”).waitForVisible(timeoutInMs)$(“.selector”).waitUntil(...) 、または$(“.selector”).waitForExist()呼び出しを削除して、デフォルトでサイプレスを許可するcy.get('.selector')およびcy.contains(textInElement)を使用して、要素の再試行と取得を繰り返し処理します。 デフォルトよりも長いタイムアウトが必要な場合は、 cy.get('.selector', { timeout: longTimeoutInMs })をすべて使用し、要素を取得した後、次のアクションコマンドをチェーンして、要素、つまりcy.get(“.selector”).click()で何かを実行します。 cy.get(“.selector”).click()
  • ブラウザを使用したカスタムコマンド。 addCommand('customCommand, () => {})` turned into `Cypress.Commands.add('customCommand', () => {})に変わり、 `cy.customCommand()`を実行します
  • node-fetchと呼ばれるライブラリを使用してAPIを介してセットアップまたはティアダウンのネットワーク要求を行い、それをbrowser.call(() => return fetch(...))および/またはbrowser.waitUntil(...)でラップします。 cy.request(endpoint)またはcy.task(taskToHitAPIOrService)のような定義および呼び出しを行ったカスタムプラグインを介してサイプレスノードサーバーでHTTPリクエストを作成します。
  • 重要なネットワークリクエストがUIの顕著な変更なしに終了するのを待たなければならない前は、 browser.pause(timeoutInMs)を使用する必要がありましたが、Cypressでは、ネットワークスタブ機能でそれを改善し、リッスンできました。そして、特定のリクエストがcy.server()cy.route(“method”, “/endpoint/we/are/waiting/for).as(“endpoint”)`, and `cy.wait(“@endpoint”)リクエストをトリガーするアクションを開始する前。

多くのWebdriverIO構文とコマンドをCypressコマンドに変換した後、共通の共有機能用のベースページオブジェクトと、テストに必要な各ページ用の拡張ページオブジェクトを持つという同じ概念を導入しました。 これは、すべてのページで共有される共通のopen()機能を備えたベースページオブジェクトの例です。

拡張ページオブジェクトは、要素セレクターのゲッターを追加し、そのページルートでopen()機能を実装し、以下に示すようにヘルパー機能を提供します。

実際の拡張ページオブジェクトは、単純なオブジェクトマップを利用して、要素のすべてのCSSセレクターを1つの場所に維持し、データ属性としてReactコンポーネントにプラグインし、単体テストで参照し、サイプレスページオブジェクトのセレクターとして使用します。 また、ページオブジェクトがSuppressionsページのような見た目も機能も似ているページの束に再利用され、ルートや特定のプロパティを変更するための引数を渡す場合、ページオブジェクトクラスはクラスコンストラクターを利用する際に変化することがありました。

ちなみに、チームはページオブジェクトを使用する必要はありませんでしたが、すべてのページで共通の機能を共有するために、ページ機能とDOM要素セレクター参照を標準クラスオブジェクト構造とともに維持するためのパターンの一貫性を高く評価しました。 他のチームは、ユーティリティ関数がほとんどなく、ES6クラスを使用せずに多くの異なるファイルを作成することを好みましたが、重要なことは、すべてをカプセル化し、開発者の効率と保守性を向上させるためのテストを作成するための体系的で予測可能な方法を提供することでした。

以前のWebdriverIOテストで使用されていたのと同じ一般的なテスト戦略を順守し、APIを介して可能な限りテストをセットアップしようとしました。 特に、UIを介してセットアップ状態を構築することを避け、テストすることを目的としていないパーツにフレークネスと時間を浪費しないようにしたかったのです。 ほとんどのテストには、この戦略が含まれていました。

  • APIを介したセットアップまたは破棄– UIを介したエンティティの作成をテストする必要がある場合は、最初にAPIを介してエンティティを削除するようにしてください。 前回のテストの実行がどのように成功または失敗したかに関係なく、テストが一貫した方法で動作し、適切な条件で開始されるように、APIを介してテストを適切に設定または破棄する必要がありました。
  • APIを介して専用のテストユーザーにログインする–ページごと、または自動化テストごとに専用のテストユーザーを作成したため、テストを分離し、並行して実行したときに互いのリソースを踏みにじることはありません。 APIを介してログインページと同じリクエストを行い、テストを開始する前にCookieを保存して、認証されたページに直接アクセスして実際のテスト手順を開始できるようにしました。
  • エンドユーザーの観点からの手順の自動化– APIを介してユーザーにログインした後、ページに直接アクセスし、エンドユーザーが機能フローを終了し、ユーザーが適切なものを表示して操作することを確認するために実行する手順を自動化しました。途中で。

テストを期待される元の状態にリセットするには、グローバルcy.loginコマンドを使用してAPIを介して専用のテストユーザーにログインし、ユーザーのログインを維持するためにCookieを設定し、API呼び出しを行ってユーザーは、 cy.request(“endpoint”)またはcy.task(“pluginAction”)呼び出しを介して目的の開始状態に移動し、直接テストしようとした認証済みページにアクセスします。 次に、以下のテストレイアウトに示すように、ユーザー機能フローを実現するための手順を自動化します。

ログイン、 cy.login() 、およびログアウト、 cy.logout() ()について説明したカスタムコマンドを覚えていますか? この方法でサイプレスに簡単に実装したため、すべてのテストで同じ方法でAPIを介してユーザーにログインできます。

さらに、WebdriverIOやSTUIではこれまでうまくできなかった、電子メールに関連する特定の複雑なフローを自動化して検証したいと考えていました。 いくつかの例には、電子メールアクティビティをCSVにエクスポートする、送信者認証のために同僚に送信フローを実行する、または電子メール検証結果をCSVにエクスポートすることが含まれます。 サイプレスは、1回のテストで複数のスーパードメインにアクセスできないようにするため、所有していないUIを介して電子メールクライアントに移動することは不安定であり、オプションではありませんでした。

代わりに、 cy.task(“pluginAction”)コマンドを使用してサイプレスプラグインを開発し、サイプレスノードサーバー内の一部のライブラリを使用して、SquirrelMailなどのテストメールIMAPクライアント/受信トレイに接続し、アクションを促した後、受信トレイで一致するメールをチェックしました。 UIで、これらの電子メールからWebアプリドメインにリダイレクトリンクをたどって、特定のダウンロードページが表示され、顧客フロー全体を効果的に完了していることを確認します。 特定の件名を指定してメールがSquirrelMailの受信トレイに到着するのを待つプラグインを実装し、メールを削除し、メールを送信し、メールイベントをトリガーし、バックエンドサービスをポーリングし、テストで使用するAPIを介してはるかに便利なセットアップとティアダウンを実行します。

サイプレスで実際にテストした内容をより深く理解するために、次のような価値の高いケースを多数取り上げました。

  • アプリのツアーとも呼ばれるすべてのページのヘルスチェック–コンテンツが読み込まれたページによって、特定のバックエンドサービスやフロントエンドホスティングがダウンすることがあることを確認したかったのです。 また、最初にこれらのテストを実行して、セレクターとヘルパー関数を使用してページオブジェクトを構築するという精神的な記憶を構築し、環境に対して実行される迅速で実用的なテストを取得することをお勧めします。
  • ページでのCRUD操作– APIを介して常にテストをリセットし、UIで作成、読み取り、更新、または削除を具体的にテストします。 たとえば、UIを介してドメイン認証を作成できることをテストした場合、最後のテスト実行がどのように終了したかに関係なく、続行する前に、UIを介して作成しようとしているドメインが最初にAPIを介して削除されたことを確認する必要がありました。ドメインを作成し、衝突を回避するための自動化されたUIステップを使用します。 UIを介して抑制を削除できることをテストした場合は、最初にAPIを介して抑制を作成してから、手順を続行するようにしました。
  • ページでの検索フィルターのテスト–電子メールアクティビティを使用して一連の高度な検索フィルターを設定し、クエリパラメーターを使用してページにアクセスして、フィルターが自動入力されていることを確認しました。 また、電子メール検証用のAPIを介してデータを追加し、もう一度さまざまな検索フィルターを開始して、テーブルがそのページの検索フィルターと一致することを検証しました。
  • さまざまなユーザーアクセス– Twilio SendGridには、さまざまなスコープまたはアクセス許可を持つチームメイトを持つことができる親アカウント、またはさまざまなアクセス度を持ち、親アカウントと多少似た動作をするサブユーザーがあります。 特定のページとサブユーザーの読み取り専用アクセスと管理者アクセスを持つチームメイトは、ページ上の特定のものを表示または非表示にするため、これらのタイプのユーザーへのログインと、サイプレステストで表示されるものと表示されないものの確認を簡単に自動化できます。
  • さまざまなユーザーパッケージ–ユーザーは、Essentials、Pro、Premierなどの無料から有料のパッケージの種類もさまざまであり、これらのパッケージはページ上で特定のものを表示することも表示しないこともできます。 さまざまなパッケージを使用してユーザーにログインし、サイプレステストでユーザーがアクセスした機能、コピー、またはページをすばやく確認しました。

ステップ4:テストをDocker化する

クラウド内の新しいAWSマシンでBuildkiteパイプラインの各ステップを実行する場合、これらのマシンには実際にサイプレスを実行するためのノード、ブラウザー、アプリケーションコード、またはその他の依存関係がないため、 npm run cypress:run:staging呼び出すことはできません。テスト。 以前にWebdriverIOをセットアップしたとき、テストを実行するために適切なSelenium、Chrome、およびアプリケーションコードサービスを一緒に動作させるために、DockerComposeファイルに3つの別々のサービスをアセンブルする必要がありました。

サイプレスでは、サイプレスベースのDockerイメージであるcypress/baseのみを必要とし、 Dockerfileで環境をセットアップし、docker docker-compose.ymlファイルで1つのサービスのみを使用して、サイプレスを実行するため、はるかに簡単でした。テスト。 使用する他のサイプレスDockerイメージや、Dockerでサイプレステストを設定する他の方法があるため、これを行う1つの方法について説明します。 代わりにサイプレスのドキュメントを参照することをお勧めします

サイプレステストの実行に必要なすべてのアプリケーションとテストコードを含むサービスを起動するために、 DockerfileというDockerfile.cypressを作成し、すべてのnode_modulesをインストールして、ノード環境のイメージの作業ディレクトリにコードをコピーしました。 これは、 cypressのDocker Composeサービスで使用され、次の方法でDockerfileのセットアップを実現しました。

このDockerfile.cypressを使用すると、Cypressを統合して、特定の環境APIに対して選択した仕様を実行し、 cypressと呼ばれる1つのDockerComposeサービスを介してアプリをデプロイできます。 SPECSBASE_URLなどの環境変数を補間して、 npm run cypress:run:cicd:stagingコマンドを使用して特定のベースURLに対して選択したサイプレステストを実行するだけでした。 ”cypress:run:cicd:staging”: “cypress run --record --key --config baseUrl=$BASE_URL --env testEnv=staging”

これらの環境変数は、Buildkiteパイプラインの設定/構成ファイルを介して設定されるか、Cypressテストをトリガーしてデプロイメントパイプラインから実行するときに動的にエクスポートされます。 docker-compose.cypress.ymlファイルの例は次のようになります。

他にも注意すべきことがいくつかあります。 たとえば、特定のタグ付きDockerイメージを参照できるVERSION環境変数を確認できます。 後で、Dockerイメージにタグを付けてから、そのビルドの同じDockerイメージをプルダウンして、サイプレステストの正しいコードに対して実行する方法を示します。

さらに、 BUILDKITE_BUILD_IDが渡されたことにも気付くでしょう。これは、開始するすべてのビルドの他のBuildkite環境変数、およびci-build-idフラグとともに無料で提供されます。 これにより、サイプレスの並列化機能が有効になり、サイプレステストに割り当てられた特定の数のマシンを設定すると、それらのマシンを起動し、テストを分離してすべてのマシンノードで実行し、テストを最適化および高速化する方法を自動的に認識します。実行時間。

最後に、ボリュームマウントとBuildkiteのアーティファクト機能も利用しました。 その月の有料の割り当てられたテスト記録が不足した場合、または何らかの理由でダッシュボードサービスにアクセスできない場合に備えて、BuildkiteUIの[アーティファクト]タブから直接アクセスできるようにビデオとスクリーンショットをアップロードします。 サイプレスの「実行」コマンドをヘッドレスモードで実行すると、 cypress/videosおよびcypress/screenshotsフォルダーに出力が出力され、ローカルで確認できます。これらのフォルダーをマウントして、フェイルセーフとしてBuildkiteにアップロードします。

ステップ5:CICDとの統合

さまざまな環境に対してDockerコンテナーでCypressテストを正常に実行できるようになると、CICDプロバイダーであるBuildkiteとの統合を開始しました。 Buildkiteは、コードまたはWeb UIのリポジトリのBuildkiteパイプライン設定のいずれかで設定されたBashスクリプトと環境変数を使用して、AWSマシン上の.ymlファイルでステップを実行する方法を提供しました。 Buildkiteでは、エクスポートされた環境変数を使用してメインのデプロイパイプラインからこのテストパイプラインをトリガーすることもできました。これらのテストステップを、QAが監視および確認するスケジュールで実行される他の分離されたテストパイプラインに再利用します。

大まかに言えば、サイプレス用のBuildkiteパイプラインのテストと、以前のWebdriverIOパイプラインは、次の同様の手順を共有していました。

  • Dockerイメージを設定します。 テストに必要なDockerイメージをビルド、タグ付け、およびレジストリにプッシュして、後のステップでプルダウンできるようにします。
  • 環境変数の構成に基づいてテストを実行します。 特定のビルドのタグ付きDockerイメージをプルダウンし、デプロイされた環境に対して適切なコマンドを実行して、設定された環境変数から選択したテストスイートを実行します。

これは、「サイプレスDockerイメージのビルド」ステップでのDockerイメージのセットアップと、「サイプレステストの実行」ステップでのテストの実行を示すpipeline.cypress.ymlファイルの例です。

注意すべき点の1つは、最初のステップである「サイプレスDockerイメージの構築」と、テスト用にDockerイメージをセットアップする方法です。 Docker Compose buildコマンドを使用して、すべてのアプリケーションテストコードでcypressサービスをビルドし、 latest${VERSION}環境変数でタグ付けして、最終的にこのビルドの適切なタグで同じイメージをプルダウンできるようにしました。将来のステップ。 各ステップはAWSクラウドのどこかの異なるマシンで実行される可能性があるため、タグは特定のBuildkite実行のイメージを一意に識別します。 イメージにタグを付けた後、最新のバージョンタグ付きイメージをプライベートDockerレジストリにプッシュして再利用しました。

「サイプレステストの実行」ステップでは、最初のステップで作成、タグ付け、プッシュしたイメージをプルダウンし、サイプレスサービスを起動してテストを実行します。 SPECSBASE_URLなどの環境変数に基づいて、この特定のBuildkiteビルドの特定のデプロイ済みアプリ環境に対して特定のテストファイルを実行します。 これらの環境変数は、Buildkiteパイプライン設定を介して設定されるか、Buildkite選択フィールドを解析して実行するテストスイートと環境を決定するBashスクリプトから動的にトリガーされます。

Buildkite CICDデプロイパイプライン中に実行するテストを選択し、特定のエクスポートされた環境変数を使用して専用のトリガーされたテストパイプラインをトリガーする場合、 pipeline.cypress.ymlファイルの手順に従って実行します。 デプロイパイプラインから機能ブランチ環境に新しいコードをデプロイした後にテストをトリガーする例は、次のようになります。

トリガーされたテストは別のパイプラインで実行され、「ビルド#639」リンクをたどった後、次のようにトリガーされたテスト実行のビルド手順に進みます。

下の写真に示すように、スケジュールに従って実行されている専用のCypress Buildkiteパイプラインに同じpipeline.cypress.ymlファイルを再利用すると、最も優先度の高いE2Eテストである「P1」を実行するビルドなどがあります。

実行する仕様や、Buildkiteパイプラインの設定でヒットするバックエンド環境など、適切な環境変数を設定するだけです。 次に、パイプライン設定にもあるCronスケジュールビルドを構成して、特定の時間数ごとに開始することができます。これで準備完了です。 We would then create many other separate pipelines for specific feature pages as needed to run on a schedule in a similar way and we would only vary the Cron schedule and environment variables while once again uploading the same `pipeline.cypress.yml` file to execute.

In each of those “Run Cypress tests” steps, we can see the console output with a link to the recorded test run in the paid Dashboard Service, the central place to manage your team's test recordings, billing, and other Cypress stats. Following the Dashboard Service link would take us to a results view for developers and QAs to take a look at the console output, screenshots, video recordings, and other metadata if required such as this:

Step 6: Comparing Cypress vs. WebdriverIO/STUI

After diving into our own custom Ruby Selenium solution in STUI, WebdriverIO, and finally Cypress tests, we recorded our tradeoffs between Cypress and Selenium wrapper solutions.

長所

  • It's not another Selenium wrapper – Our previous solutions came with a lot of Selenium quirks, bugs, and crashes to work around and resolve, whereas Cypress arrived without the same baggage and troubles to deal with in allowing us full access to the browser.
  • More resilient selectors – We no longer had to explicitly wait for everything like in WebdriverIO with all the $(.selector).waitForVisible() calls and now rely on cy.get(...) and c y.contains(...) commands with their default timeout. It will automatically keep on retrying to retrieve the DOM elements and if the test demanded a longer timeout, it is also configurable per command. With less worrying about the waiting logic, our tests became way more readable and easier to chain.
  • Vastly improved developer experience – Cypress provides a large toolkit with better and more extensive documentation for assertions, commands, and setup. We loved the options of using the Cypress GUI, running in headless mode, executing in the command-line, and chaining more intuitive Cypress commands.
  • Significantly better developer efficiency and debugging – When running the Cypress GUI, one has access to all of the browser console to see some helpful output, time travel debug and pause at certain commands in the command log to see before and after screenshots, inspect the DOM with the selector playground, and discern right away at which command the test failed. In WebdriverIO or STUI we struggled with observing the tests run over and over in a browser and then the console errors would not point us toward and would sometimes even lead us astray from where the test really failed in the code. When we opted to run the Cypress tests in headless mode, we got console errors, screenshots, and video recordings. With WebdriverIO we only had some screenshots and confusing console errors. These benefits resulted in us cranking out E2E tests much faster and with less overall time spent wondering why things went wrong. We recorded it took less developers and often around 2 to 3 times less days to write the same level of complicated tests with Cypress than with WebdriverIO or STUI.
  • Network stubbing and mocking – With WebdriverIO or STUI, there was no such thing as network stubbing or mocking in comparison to Cypress. Now we can have endpoints return certain values or we can wait for certain endpoints to finish through cy.server() and cy.route() .
  • Less time to set up locally – With WebdriverIO or STUI, there was a lot of time spent up front researching which reporters, test runners, assertions, and services to use, but with Cypress, it came bundled with everything and started working after just doing an npm install cypress.
  • Less time to set up with Docker – There are a bunch of ways to set up WebdriverIO with Selenium, browser, and application images that took us considerably more time and frustration to figure out in comparison to Cypress's Docker images to use right out of the gate.
  • Parallelization with various CICD providers – We were able to configure our Buildkite pipelines to spin up a certain number of AWS machines to run our Cypress tests in parallel to dramatically speed up the overall test run time and uncover any flakiness in tests using the same resources. The Dashboard Service would also recommend to us the optimal number of machines to spin up in parallel for the best test run times.
  • Paid Dashboard Service – When we run our Cypress tests in a Docker container in a Buildkite pipeline during CICD, our tests are recorded and stored for us to look at within the past month through a paid Dashboard Service. We have a parent organization for billing and separate projects for each frontend application to check out console output, screenshots, and recordings of all of our test runs.
  • Tests are way more consistent and maintainable – Tests passed way more consistently with Cypress in comparison to WebdriverIO and STUI where the tests kept on failing so much to the point where they were often ignored. Cypress tests failing more often signaled actual issues and bugs to look into or suggested better ways to refactor our tests to be less flaky. With WebdriverIO and STUI, we wasted a lot more time in maintaining those tests to be somewhat useful, whereas with Cypress, we would every now and then adjust the tests in response to changes in the backend services or minor changes in the UI.
  • Tests are faster – Builds passed way more consistently and overall test run times would be around 2 to 3 times faster when run serially without parallelization. We used to have overall test runs that would take hours with STUI and around 40 minutes with WebdriverIO, but now with way more tests and with the help of parallelization across many machine nodes, we can run over 200 tests in under 5 minutes .
  • Room to grow with added features in the future – With a steady open-source presence and dedicated Cypress team working towards releasing way more features and improvements to the Cypress infrastructure, we viewed Cypress as a safer bet to invest in rather than STUI, which would require us to engineer and solve a lot of the headaches ourselves, and WebdriverIO, which appeared to feel more stagnant in new features added but with the same baggage as other Selenium wrappers.

短所

  • Lack of cross-browser support – As of this writing, we can only run our tests against Chrome. With WebdriverIO, we could run tests against Chrome, Firefox, Safari, and Opera. STUI also provided some cross-browser testing, though in a much limited form since we created a custom in-house solution
  • Cannot integrate with some third-party services – With WebdriverIO, we had the option to integrate with services like BrowserStack and Sauce Labs for cross-browser and device testing. However, with Cypress there are no such third-party integrations but there are some plugins with services like Applitools for visual regression testing available. STUI, on the other hand, also had some small integrations with TestRail , but as a compromise, we log out the TestRail links in our Cypress tests so we can refer back to them if we needed to.
  • Requires workarounds to test with iframes – There are some issues around handling iframes with Cypress. We ended up creating a global Cypress command to wrap how to deal with retrieving an iframe's contents as there is no specific API to deal with iframes like how WebdriverIO does.

To summarize our STUI, WebdriverIO, and Cypress 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:

Following our analysis of the pros and cons of Cypress versus our previous solutions, it was pretty clear Cypress would be our best bet to accomplish our goal of writing fast, valuable, maintainable, and debuggable E2E tests we could integrate with CICD.

Though Cypress lacked features such as cross-browser testing and other integrations with third-party services that we could have had with STUI or WebdriverIO, we most importantly need tests that work more often than not and with the right tools to confidently fix broken ones. If we ever needed cross-browser testing or other integrations we could always still circle back and use our knowledge from our trials and experiences with WebdriverIO and STUI to still run a subset of tests with those frameworks.

We finally presented our findings to the rest of the frontend organization, engineering management, architects, and product. Upon demoing the Cypress test tools and showcasing our results between WebdriverIO/STUI and Cypress, we eventually received approval to standardize and adopt Cypress as our E2E testing library of choice for our frontend teams.

Step 7: Scaling to Other Frontend Teams

After successfully proving that using Cypress was the way to go for our use cases, we then focused on scaling it across all of our frontend teams' repos. We shared lessons learned and patterns of how to get up and running, how to write consistent, maintainable Cypress tests, and of how to hook those tests up during CICD or in scheduled Cypress Buildkite pipelines.

To promote greater visibility of test runs and gain access to a private monthly history of recordings, we established our own organization to be under one billing method to pay for the Dashboard Service with a certain recorded test run limit and maximum number of users in the organization to suit our needs.

Once we set up an umbrella organization, we invited developers and QAs from different frontend teams and each team would install Cypress, open up the Cypress GUI, and inspect the “Runs” and “Settings” tab to get the “Project ID” to place in their `cypress.json` configuration and “Record Key” to provide in their command options to start recording tests to the Dashboard Service. Finally, upon successfully setting up the project and recording tests to the Dashboard Service for the first time, logging into the Dashboard Service would show that team's repo under the “Projects” tab like this:

When we clicked a project like “mako”, we then had access to all of the test runs for that team's repo with quick access to console output, screenshots, and video recordings per test run upon clicking each row as shown below:

For more insights into our integration, we set up many separate dedicated test pipelines to run specific, crucial page tests on a schedule like say every couple hours to once per day. We also added functionality in our main Buildkite CICD deploy pipeline to select and trigger some tests against our feature branch environment and staging environment.

予想通り、これは、特に複数のチームがさまざまな方法でテストに貢献し、トリガーしているため、その月に割り当てられた記録されたテスト実行をすばやく通過しました。 経験から、スケジュールで実行されているテストの数、それらのテストが実行される頻度、およびCICD中に実行されるテストに注意することをお勧めします。 いくつかの冗長なテスト実行や、スケジュールされたテスト実行の頻度をダイヤルバックしたり、CICD中にのみテストをトリガーするために一部を完全に削除したりするなど、より質素な他の領域が存在する場合があります。

質素であるという同じルールがユーザーの追加にも適用されます。これは、ダッシュボードサービスを頻繁に使用するフロントエンドチームの開発者とQAにのみアクセスを提供することを強調したためです。これらの限られた場所を埋めるために、上級管理職やチーム外の他の人々ではありません。

サイプレスで楽しみにしていること

前に述べたように、サイプレスは、オープンソースコミュニティと、E2Eテストで使用するためのより役立つ機能の提供を担当する専任チームで成長の可能性を秘めています。 私たちが強調した多くの欠点は現在対処されており、次のようなことを楽しみにしています。

  • クロスブラウザのサポート– Firefox、Chrome、SafariなどのブラウザをサポートするSeleniumベースのソリューションと比較して、Cypressを採用することからの多くの反発は、Chromeのみの使用によるものであるためです。 ありがたいことに、より信頼性が高く、保守が容易で、デバッグ可能なテストが私たちの組織に勝ちました。将来、サイプレスチームがそのようなクロスブラウザーのサポートをリリースするときに、より多くのクロスブラウザーテストでテストスイートを強化したいと考えています。
  • ネットワーク層の書き換え–これは、新しいReact領域と古いBackbone / Marionetteアプリケーション領域でFetchAPIを頻繁に使用する傾向があるため、これも大きな問題です。jQueryAJAXと通常のXHRベースの呼び出しを引き続き使用しました。 XHR領域でリクエストを簡単にスタブアウトまたはリッスンできますが、同じ効果を実現するには、フェッチポリフィルを使用していくつかのハッキーな回避策を実行する必要がありました。 ネットワーク層の書き換えは、これらの問題を軽減するのに役立つはずです。
  • ダッシュボードサービスの段階的な改善–最近ダッシュボードサービスにいくつかの新しいUIの変更があり、統計の視覚化と有用なデータの内訳が増えるにつれて、ダッシュボードサービスが成長し続けることを期待しています。 また、並列化機能を多用し、ダッシュボードサービスで失敗したテスト記録を頻繁にチェックするため、レイアウトや機能の反復的な改善を確認してください。

サイプレスを未来に採用

私たちの組織では、Chromeブラウザーで実行した場合のサイプレステストの開発者の効率、デバッグ、および安定性を評価しました。 将来のメンテナンスを減らし、新しいテストを開発して既存のテストを修正するための多くのツールを使用して、一貫性のある価値のあるテストを実現しました。

全体として、私たちが自由に使えるドキュメント、API、およびツールは、短所をはるかに上回りました。 サイプレスGUIと有料ダッシュボードサービスを体験した後、WebdriverIOやカスタムRubySeleniumソリューションに戻りたくありませんでした。

テストをBuildkiteに接続し、フロントエンドアプリケーションがCICDと統合するための、一貫性があり、デバッグ可能で、保守可能で、価値のあるE2E自動化テストを作成する方法を提供するという目標を達成しました。 サイプレスを採用し、WebdriverIOとSTUIを廃止することの利点について、残りのフロントエンドチーム、エンジニアリングの上位層、および製品所有者に証拠を示しました。

その後、Twilio SendGridのフロントエンドチーム全体で数百のテストが行​​われ、ステージング環境で多くのバグが検出され、これまで以上に自信を持って、不安定なテストを迅速に修正することができました。 開発者とQAは、E2Eテストを作成することを恐れなくなりましたが、リリースするすべての新機能、またはより多くのカバレッジを使用できるすべての古い機能について作成することを楽しみにしています。