サイプレステストの作成に関する1,000フィートの概要#frontend @ twiliosendgrid

公開: 2020-10-03

Twilio SendGridでは、何百ものCypressエンドツーエンド(E2E)テストを作成し、さまざまなWebアプリケーションやチームで新機能がリリースされるにつれてさらに多くのテストを作成し続けています。 これらのテストはスタック全体をカバーし、お客様が経験する最も一般的なユースケースが、アプリケーションに新しいコード変更をプッシュした後も機能することを確認します。

最初に一歩下がって、E2Eテスト全般について考える方法について詳しく知りたい場合は、このブログ投稿をチェックして、準備ができたらここに戻ってください。 このブログ投稿では、E2Eテストの専門家である必要はありませんが、テストで特定の方法で実行した理由がわかるので、正しい心構えを得るのに役立ちます。 サイプレステストを紹介するよりステップバイステップのチュートリアルを探している場合は、サイプレスのドキュメントを確認することをお勧めします。 このブログ投稿では、以前に多くのサイプレステストを見たり書いたりしたことがあると想定しており、他の人が自分のアプリケーション用にサイプレステストをどのように書いているかを知りたいと思っています。

サイプレスのテストをたくさん書いた後、必要なことを達成するために同様のサイプレスの関数、アサーション、およびパターンを使用していることに気付くでしょう。 開発やステージングなどの個別の環境に対してテストを作成するために、サイプレスでこれまでに使用または実行した最も一般的な部分と戦略を示します。 サイプレステストの作成方法に関するこの1,000フィートの概要が、自分のテストと比較するためのアイデアを提供し、サイプレステストへのアプローチ方法を改善するのに役立つことを願っています。

概要:

  1. サイプレスAPIのまとめ
  2. 要素との相互作用
  3. 要素を主張する
  4. APIとサービスの取り扱い
  5. cy.request(…)を使用してHTTPリクエストを作成する
  6. cy.task()を使用した再利用可能なプラグインの作成
  7. cy.server()およびcy.route()を使用したネットワーク要求のモック
  8. カスタムコマンド
  9. ページオブジェクトについて
  10. window.Cypressチェックでクライアント側コードを実行しないことを選択する
  11. iframeの取り扱い
  12. テスト環境全体での標準化

サイプレスAPIのまとめ

サイプレスAPIで最も一般的に使用されている部分から始めましょう。

要素の選択

DOM要素を選択する方法はたくさんありますが、これらのCypressコマンドを使用して必要な作業のほとんどを実行でき、通常、これらの後にさらに多くのアクションとアサーションをチェーンできます。

  • cy.get(“[data-hook='someSelector']”)またはcy.find(“.selector”)を使用してCSSセレクターに基づいて要素を取得します。
  • cy.contains(“someText”)などのテキストに基づいて要素を選択するか、cy.contains( "。selector" cy.contains(“.selector”, “someText”) ")などのテキストを含む特定のセレクターで要素を取得します。
  • 親要素を「内」に見えるようにするため、今後のすべてのクエリは、 cy.get(“.selector”).within(() => { cy.get(“.child”) })
  • 要素のリストを検索し、「各」要素を調べて、 cy.get(“tr”).each(($tableRow) => { cy.wrap($tableRow).find('td').eq(1).should(“contain”, “someText” })
  • 場合によっては、要素がページに表示されないことがあるため、最初にcy.get(“.buttonFarBelow”).scrollIntoView()などの要素をスクロールして表示する必要があります。
  • デフォルトのコマンドタイムアウトよりも長いタイムアウトが必要になる場合があるため、オプションでcy.get(“.someElement”, { timeout: 10000 })のような{ timeout: timeoutInMs }を追加できます。

要素との相互作用

これらは、サイプレステスト全体で最もよく使用される相互作用です。 場合によっては、これらの関数呼び出しで{ force: true }プロパティをスローして、要素のチェックをバイパスする必要があります。 これは、要素が何らかの方法でカバーされている場合、または要素のレンダリング方法に関してあまり制御できない外部ライブラリから派生している場合によく発生します。

  • モーダルやテーブルなどのボタンなど、多くのものをクリックする必要があるため、 cy.get(“.button”).click()などを実行します。
  • フォームは、ユーザーの詳細やその他のデータフィールドに入力するために、Webアプリケーションのいたるところにあります。 これらの入力をcy.get(“input”).type(“somekeyboardtyping”)で入力し、最初にcy.get(“input”).clear().type(“somenewinput”)のように入力をクリアして、入力のデフォルト値をクリアする必要がある場合があります。 cy.get(“input”).clear().type(“somenewinput”) cy.get(“input”).type(“text{enter}”)を実行するときに、Enterキーに{enter}のような他のキーを入力するクールな方法もあります。
  • cy.get(“select”).select(“value”)などの選択オプションやcy.get( " cy.get(“.checkbox”).check()などのチェックボックスを操作できます。

要素を主張する

これらは、サイプレステストで使用できる典型的なアサーションであり、適切なコンテンツを含むページに物事が存在するかどうかを判断します。

  • ページに表示されるかどうかを確認するには、 cy.get(“.selector”).should(“be.visible”)cy.get(“.selector”).should(“not.be.visible”)
  • DOM要素がマークアップのどこかに存在するかどうか、および要素が表示されているかどうかを必ずしも気にしない場合は、 cy.get(“.element”).should(“exist”)またはcy.get(“.element”).should(“not.exist”)を使用できます。 cy.get(“.element”).should(“not.exist”)
  • 要素にテキストが含まれているかどうかを確認するには、 cy.get(“button”).should(“contain”, “someText”)cy.get(“button”).should(“not.contain”, “someText”)
  • 入力またはボタンが無効または有効になっていることを確認するには、次のようにアサートします: cy.get(“button”).should(“be.disabled”)
  • 何かがチェックされているかどうかをアサートするには、 cy.get(“.checkbox”).should(“be.checked”)のようにテストできます。
  • 通常、より具体的なテキストと可視性のチェックに依存できますが、 cy.get(“element”).should(“have.class”, “class-name”)などのクラスチェックに依存する必要がある場合もあります。 .should(“have.attr”, “attribute”)を使用して属性をテストする他の同様の方法があります。
  • cy.get(“div”).should(“be.visible”).and(“contain”, “text”)のように、アサーションを連鎖させると便利なことがよくあります。

APIとサービスの取り扱い

電子メールに関連する独自のAPIおよびサービスを処理する場合、 cy.request(...)を使用して、認証ヘッダーを使用してバックエンドエンドポイントにHTTPリクエストを送信できます。 もう1つの方法は、任意のスペックファイルから呼び出すことができるcy.task(...)プラグインを構築して、メールの受信トレイへの接続やメールの受信トレイの検索など、他のライブラリを使用してノードサーバーで最適に処理できる他の機能をカバーすることです。使用するテストの値を返す前に、電子メールを照合するか、特定のAPI呼び出しの応答とポーリングをより詳細に制御します。

cy.request(…)を使用してHTTPリクエストを作成する

cy.request()を使用してバックエンドAPIにHTTPリクエストを送信し、テストケースを実行する前にデータを設定または破棄できます。 通常、エンドポイントURL、「GET」や「POST」などのHTTPメソッド、ヘッダー、場合によってはバックエンドAPIに送信するリクエスト本文を渡します。 次に、これを.then((response) => { })でチェーンして、「status」や「body」などのプロパティを介してネットワーク応答にアクセスできます。 cy.request()呼び出しを行う例をここに示します。

テストを実行する前のクリーンアップ中に、 cy.request(...)が4xxまたは5xxステータスコードで失敗するかどうかを気にしない場合があります。 失敗したステータスコードを無視することを選択できるシナリオの1つは、テストがGET要求を行って、アイテムがまだ存在し、すでに削除されているかどうかを確認する場合です。 アイテムはすでにクリーンアップされている可能性があり、GETリクエストは404notfoundステータスコードで失敗します。 この場合、 failOnStatusCode: falseに設定して、テストステップを実行する前にサイプレステストが失敗しないようにします。

cy.task()を使用した再利用可能なプラグインの作成

ノードサーバーを介して電子メール受信ボックスプロバイダーなどの別のサービスと通信するための再利用可能な機能をより柔軟に制御したい場合(この例については後のブログ投稿で説明します)、独自の追加機能を提供します。 API呼び出しへのカスタム応答では、サイプレステストにチェーンして適用する必要があります。 または、ノードサーバーで他のコードを実行するのが好きです。多くの場合、そのためのcy.task()プラグインを作成します。 モジュールファイルにプラグイン関数を作成し、 plugins/index.tsにインポートします。ここで、以下に示すように、関数を実行するために必要な引数を使用してタスクプラグインを定義します。

これらのプラグインは、スペックファイルの任意の場所でcy.task(“pluginName”, { ...args })を使用して呼び出すことができ、同じ機能が実行されることを期待できます。 一方、 cy.request()を使用した場合、それらの呼び出し自体をページオブジェクトまたはヘルパーファイルでラップしてどこにでもインポートしない限り、再利用性は低くなります。

もう1つの注意点は、プラグインタスクコードはノードサーバーで実行されることを意図しているため、 Cypress.env(“apiHost”)cy.getCookie('auth_token')などの関数内で通常のCypressコマンドを呼び出すことができないことです。 バックエンドAPIと通信する必要がある場合は、リクエスト本文に必要なものに加えて、認証トークン文字列やバックエンドAPIホストなどをプラグイン関数の引数オブ​​ジェクトに渡します。

cy.server()およびcy.route()を使用したネットワーク要求のモック

再現が難しいデータを必要とするサイプレステスト(ページ上の重要なUI状態のバリエーションや低速のAPI呼び出しの処理など)の場合、考慮すべきサイプレスの機能の1つは、ネットワーク要求をスタブ化することです。 これは、バニラXMLHttpRequest、axiosライブラリ、またはjQuery AJAXを使用している場合、XmlHttpRequest(XHR)ベースのリクエストでうまく機能します。 次に、 cy.server()cy.route()を使用して、必要な状態の応答をモックアウトするルートをリッスンします。 次に例を示します。

もう1つの使用例は、 cy.server()cy.route() 、およびcy.wait()を一緒に使用して、ネットワーク要求が終了するのをリッスンして待機してから、次の手順を実行することです。 通常、ページを読み込んだ後、またはページ上で何らかのアクションを実行した後、直感的な視覚的な合図は、何かが完了したか、主張して​​行動する準備ができていることを示します。 このような目に見える手がかりがない場合は、このようにAPI呼び出しが終了するのを明示的に待つことができます。

大きな落とし穴の1つは、ネットワークリクエストにフェッチを使用している場合、ネットワークリクエストをモックアウトしたり、同じように完了するのを待ったりすることができないことです。 これらの問題に記録されているように、テストを実行する前に、通常のwindow.fetchをXHRポリフィルに置き換え、いくつかのセットアップとクリーンアップの手順を実行する回避策が必要になります Cypress 4.9.0の時点でexperimentalFetchPolyfillプロパティも機能する可能性がありますが、全体として、アプリケーションでフェッチとXHRの使用全体でネットワークスタブを処理するためのより良い方法を探しています。 Cypress 5.1.0の時点で、XHRとフェッチリクエストの両方の実験的なネットワークスタブのための有望な新しいcy.route2()関数(Cypressのドキュメントを参照)があるため、Cypressバージョンをアップグレードし、それを試してみてください。それは私たちの問題を解決します。

カスタムコマンド

WebdriverIOなどのライブラリと同様に、テストケースを実行する前にAPIを介してログインを処理するカスタムコマンドなど、スペックファイル全体で再利用およびチェーンできるグローバルカスタムコマンドを作成できます。 support/commands.tsなどのファイルで開発すると、 cy.customCommand()cy.login() )などの関数にアクセスできるようになります。 ログイン用のカスタムコマンドの作成は次のようになります。

ページオブジェクトについて

ページオブジェクトは、ページを操作するのに役立つセレクターと関数のラッパーです。 テストを作成するためにページオブジェクトを作成する必要はありませんが、UIへの変更をカプセル化する方法を検討することをお勧めします。 セレクターの更新や、1つの場所ではなく複数のファイルでの対話を回避するために、物事をグループ化するという点で、生活を楽にしたいのです。

継承されたページクラスを共有および拡張するためのopen()などの一般的な機能を使用して、基本の「ページ」クラスを定義できます。 派生ページクラスは、ここに示すように、 super.open()などの呼び出しを通じて基本クラスの機能を再利用しながら、セレクターやその他のヘルパー関数用に独自のゲッター関数を定義します。

window.Cypressチェックでクライアント側コードを実行しないことを選択する

CSVなどの自動ダウンロードファイルを使用してフローをテストした場合、ダウンロードによってテストの実行がフリーズし、サイプレスのテストが失敗することがよくありました。 妥協案として、主に、ユーザーがダウンロードの適切な成功状態に到達できるかどうかをテストし、ウィンドウを追加してテスト実行でファイルを実際にダウンロードしないようにしました。 window.Cypressチェック。

サイプレスのテスト実行中に、 window.Cypressプロパティがブラウザに追加されます。 クライアント側のコードでは、ウィンドウオブジェクトにサイプレスプロパティがないかどうかを確認してから、通常どおりダウンロードを実行することを選択できます。 ただし、サイプレステストで実行されている場合は、実際にファイルをダウンロードしないでください。 また、Webアプリで実行されているA/B実験のwindow.Cypressプロパティを確認することも利用しました。 テストユーザーにさまざまなエクスペリエンスを示す可能性のあるA/B実験から、フレーク感や非決定論的な動作を追加したくなかったため、以下で強調表示されているように、実験ロジックを実行する前に、まずプロパティが存在しないことを確認しました。

iframeの取り扱い

組み込みのiframeサポートがないため、サイプレスではiframeの処理が難しい場合があります。 実行中の[issue]( https://github.com/cypress-io/cypress/issues/136 )には、単一のiframeとネストされたiframeを処理するための回避策が満載されています。これは、現在のバージョンのCypressによっては機能する場合と機能しない場合があります。または操作するiframe。 このユースケースでは、ステージング環境でZuora課金iframeを処理して、EメールAPIとマーケティングキャンペーンAPIのアップグレードフローを検証する方法が必要でした。 テストでは、アプリの新しいサービスへのアップグレードを完了する前に、サンプルの請求情報を入力します。

iframeの処理をカプセル化するcy.iframe(iframeSelector)カスタムコマンドを作成しました。 セレクターをiframeに渡すと、iframeの本体の内容が空でなくなるまでチェックされ、次に示すように、本体の内容が返され、さらに多くのサイプレスコマンドでチェーンされます。

TypeScriptを使用する場合、 index.d.tsファイルに次のようにiframeカスタムコマンドを入力できます。

テストの課金部分を実行するために、iframeカスタムコマンドを使用してZuora iframeの本体コンテンツを取得し、iframe内の要素を選択して、それらの値を直接変更しました。 以前はcy.find(...).type(...)やその他の代替手段が機能しないという問題がありましたが、ありがたいことに、入力の値を変更し、invokeコマンド( cy.get(selector).invoke('val', 'some value') クロスオリジンエラーをバイパスできるようにするには、 cypress.json構成ファイルに”chromeWebSecurity”: falseも必要です。 フィラーセレクターでの使用例のスニペットを以下に示します。

テスト環境全体での標準化

前に強調した最も一般的なアサーション、関数、およびアプローチを使用してCypressでテストを作成した後、テストを実行して1つの環境に合格させることができます。 これは素晴らしい最初のステップですが、新しいコードをデプロイし、変更をテストするための複数の環境があります。 各環境には独自のデータベース、サーバー、およびユーザーのセットがありますが、同じ一般的な手順で機能するように、サイプレステストを1回だけ作成する必要があります。

変更を最終的に本番環境にデプロイする前に、開発、テスト、ステージングなどの複数のテスト環境に対してサイプレステストを実行するには、環境変数を追加し、構成値を変更してこれらのユースケースをサポートするサイプレスの機能を利用する必要があります。

さまざまなフロントエンド環境に対してテストを実行するには

Cypress.config(“baseUrl”)からアクセスする「baseUrl」値を、https://staging.app.comhttps://testing.app.comなどのURLと一致するように変更する必要があります これにより、パスを追加するすべてのcy.visit(...)呼び出しのベースURLが変更されます。 サイプレスコマンドを実行する前にCYPRESS_BASE_URL=<frontend_url>を設定したり、-config --config baseUrl=<frontend_url> = <frontend_url>を設定したりするなど、これを設定する方法は複数あります。

さまざまなバックエンド環境に対してテストを実行するには

「apiHost」などの環境変数に設定し、 Cypress.env(“apiHost”)などの呼び出しを介してアクセスするには、 https ://staging.api.comhttps://testing.api.comなどのAPIホスト名を知っている必要がありますCypress.env(“apiHost”) これらは、「<apiHost> / some / endpoint」などの特定のパスにHTTPリクエストを送信するためのcy.request(...)呼び出しに使用されるか、別の引数としてcy.task(...)関数呼び出しに渡されます。ヒットするバックエンドを知るためのプロパティ。 これらの認証された呼び出しは、localStorageに保存している可能性が最も高い認証トークンまたはcy.getCookie(“auth_token”)を介したCookieも知っている必要があります。 この認証トークンが最終的に「Authorization」ヘッダーの一部として、またはリクエストの一部として他の手段を介して渡されることを確認してください。 これらの環境変数を設定する方法は多数あります。たとえば、 cypress.jsonファイルで直接設定したり、Cypressのドキュメントで参照できる--envコマンドラインオプションで設定したりできます

さまざまなユーザーにログインするか、さまざまなメタデータを使用する方法:

複数のフロントエンドURLとバックエンドAPIホストを処理する方法がわかったので、さまざまなユーザーへのログインをどのように処理しますか? ドメイン、APIキー、およびテスト環境全体で一意である可能性が高いその他のリソースに関連するものなど、環境に基づいてさまざまなメタデータをどのように使用しますか?

「testing」と「staging」の可能な値を使用して「testEnv」と呼ばれる別の環境変数を作成することから始めましょう。これを使用して、テストに適用する環境のユーザーとメタデータを判断できます。 「testEnv」環境変数を使用すると、いくつかの方法でこれにアプローチできます。

fixturesフォルダの下に個別の「staging.json」、「testing.json」、およびその他の環境JSONファイルを作成し、それらをインポートして、 cy.fixture(`${testEnv}.json`).then(...)などの「testEnv」値に基づいて使用できます。 cy.fixture(`${testEnv}.json`).then(...) ただし、JSONファイルを適切に入力することはできず、構文やテストごとに必要なすべてのプロパティを書き出す際に間違いを犯す可能性がはるかに高くなります。 JSONファイルもテストコードから遠く離れているため、テストを編集するときに少なくとも2つのファイルを管理する必要があります。 すべての環境テストデータがcypress.jsonの環境変数に直接設定されていて、多数のテストで管理するには多すぎる場合にも、同様のメンテナンスの問題が発生します。

別のオプションは、テストまたはステージングに基づくプロパティを使用してスペックファイル内にテストフィクスチャオブジェクトを作成し、特定の環境のテストのユーザーとメタデータをロードすることです。 これらはオブジェクトであるため、すべてのスペックファイルを再利用してメタデータタイプを定義するために、テストフィクスチャオブジェクトの周囲に、より一般的なTypeScriptタイプを定義することもできます。 Cypress.env(“testEnv”)を呼び出して、実行しているテスト環境を確認し、その値を使用して、テストフィクスチャオブジェクト全体から対応する環境のテストフィクスチャを抽出し、それらの値をテストで使用します。 テストフィクスチャオブジェクトの一般的な考え方は、下のコードスニペットに要約されています。

「baseUrl」サイプレス構成値、「apiHost」バックエンド環境変数、および「testEnv」環境変数を一緒に適用すると、以下に示すように、複数の条件や個別のロジックフローを追加することなく、複数の環境に対して機能するサイプレステストを実行できます。

一歩下がって、独自のCypressコマンドを作成してnpmを実行する方法を見てみましょう。 同様の概念を、アプリケーションで使用している可能性のあるyarn、Makefile、およびその他のスクリプトに適用できます。 「open」および「run」コマンドのバリエーションを定義して、サイプレスがGUIを「開き」、パッケージ内のさまざまなフロントエンドおよびバックエンド環境に対してヘッドレスモードで「実行」するようにすることができpackage.json 環境の構成ごとに複数のJSONファイルを設定することもできますが、簡単にするために、オプションと値がインラインで含まれているコマンドが表示されます。

package.jsonスクリプトで、フロントエンドの「baseUrl」の範囲が「http:// localhost:9001」であり、アプリをローカルで起動した場合から、デプロイされたアプリケーションのURL(「 https://staging.app」など)までの範囲であることがわかります。 com 」。 バックエンドの「apiHost」変数と「testEnv」変数を設定して、バックエンドエンドポイントにリクエストを送信し、特定のテストフィクスチャオブジェクトをロードするのに役立てることができます。 記録キーを使用してDockerコンテナでテストを実行する必要がある場合に備えて、特別な「cicd」コマンドを作成することもできます。

いくつかのポイント

要素の選択、要素との相互作用、およびページ上の要素についての主張に関しては、 cy.get()cy.contains() 、などのサイプレスコマンドの小さなリストを使用して多くのサイプレステストを作成することでかなり遠くまで行くことができます。 .click() .type() .should('be.visible')

cy.request()を使用してバックエンドAPIにHTTPリクエストを送信し、 cy.task()を使用してノードサーバーで任意のコードを実行し、 cy.server()cy.route() )を使用してネットワークリクエストをスタブアウトする方法もあります。 。 cy.login()のような独自のカスタムコマンドを作成して、APIを介してユーザーにログインするのに役立てることもできます。 これらはすべて、テストを実行する前にユーザーを適切な開始点にリセットするのに役立ちます。 これらのセレクターと関数をすべてファイルにラップすると、仕様で使用する再利用可能なページオブジェクトが作成されます。

複数の環境に合格するテストを作成するには、環境変数と環境固有のメタデータを保持するオブジェクトを利用してください。

これは、サイプレス仕様の個別のデータリソースを使用してさまざまなユーザーセットを実行するのに役立ちます。 パッケージ内のpackage.json npm run cypress:open:stagingのような個別のCypress npmコマンドは、適切な環境変数値をロードし、実行することを選択した環境のテストを実行します。

これで、サイプレステストの作成に関する1,000フィートの概要をまとめました。 これにより、独自のサイプレステストに適用および改善するための実用的な例とパターンが提供されることを願っています。

サイプレステストについてもっと知りたいですか? 次のリソースを確認してください。

  • E2Eテストを書くときに考慮すべきこと
  • TypeScriptサイプレステストのすべてのもの
  • サイプレステストでの電子メールフローの処理
  • サイプレステストを構成、整理、統合するためのアイデア
  • サイプレステストとDocker、Buildkite、およびCICDの統合