サイプレステストを構成、整理、統合するためのアイデア#frontend @ twiliosendgrid
公開: 2020-12-15ここTwilioSendGridでは、さまざまなWebアプリケーションとフロントエンドチームにまたがる多くのサイプレステストを作成しました。 テストが多くの機能とページに拡大するにつれて、いくつかの便利な構成オプションに出くわし、増え続けるファイル、ページの要素へのセレクター、およびタイムアウト値をより適切に維持する方法を開発しました。
サイプレス関連のものを構成、整理、統合するためのこれらのヒントやアイデアをお見せすることを目的としています。そのため、あなたとあなたのチームに最適なものを自由に使ってください。
このブログ投稿は、サイプレステストの実用的な知識があり、サイプレステストの保守と改善に関するアイデアを探していることを前提としています。 ただし、個別の環境に対してサイプレステストを作成するのに役立つ一般的な関数、アサーション、およびパターンについて詳しく知りたい場合は、代わりにこの1,000フィートの概要ブログ投稿を確認してください。
それ以外の場合は、続けて、最初にサイプレスの構成のヒントをいくつか見ていきましょう。
cypress.jsonの構成
cypress.json
ファイルには、サイプレスコマンドのベースタイムアウト、環境変数、その他のプロパティなど、サイプレステストのすべての構成を設定できます。
構成で試すためのヒントを次に示します。
- 基本コマンドのタイムアウトを微調整して、サイプレスコマンド全体で常に
{ timeout: timeoutInMs }
を追加する必要がないようにします。 「defaultCommandTimeout」、「requestTimeout」、「responseTimeout」などの設定の適切なバランスが見つかるまで、数値をいじくりまわします。 - テストにiframeが含まれる場合は、アプリケーションでクロスオリジンiframeにアクセスできるように、「chromeWebSecurity」をfalseに設定する必要があります。
- サイプレステストを実行するときに、サードパーティのマーケティング、分析、およびロギングスクリプトをブロックして、速度を上げ、不要なイベントをトリガーしないようにしてください。 サードパーティのホストパスに一致する文字列グロブの配列を取り込んで、「blacklistHosts」プロパティ(またはCypress 5.0.0では「blockHosts」プロパティ)を使用して拒否リストを簡単に設定できます。
- サイプレスGUIを開いたときのデフォルトのビューポートのサイズを「viewportWidth」と「viewportHeight」で調整して、見やすくします。
- 重いテストのためにDockerにメモリの懸念がある場合、または物事をより効率的にしたい場合は、「numTestsKeptInMemory」をデフォルトよりも小さい数に変更し、「videoUploadOnPasses」をfalseに設定して、失敗したテスト用のビデオのアップロードに集中できます。のみ実行されます。
別の注意点として、サイプレスの構成を微調整した後、TypeScriptを追加して、このブログ投稿で行ったようにサイプレスのテストをより適切に入力することもできます。 これは、関数( cy.task('someTaskPluginFunction)
、 Cypress.env('someEnvVariable')
、 cy.customCommand()
など)を呼び出してチェーンするときのオートコンプリート、タイプ警告、またはエラーに特に役立ちます。
また、 package.json
でさまざまなCypressスクリプトを実行する場合は、ベースcypress.json
ファイルを設定し、テスト環境ごとに個別のCypress構成ファイル( staging.json
など)をロードしてみることもできます。 サイプレスのドキュメントは、このアプローチを順を追って説明するのに最適です。
サイプレスフォルダを整理する
Cypressは、コードベースでCypressを最初に起動したときに、ガイド付き構造の最上位のcypress
フォルダーを既にセットアップしています。 これには、スペックファイルのintegration
、JSONデータファイルのfixtures
、 cy.task(...)
関数やその他の構成のplugins
、カスタムコマンドとタイプのsupport
フォルダーなどの他のフォルダーが含まれます。
サイプレスフォルダー、Reactコンポーネント、または一般的なコード内で整理するときに従うべき経験則は、一緒に変更される可能性のあるものを一緒に配置することです。 この場合、ブラウザーでWebアプリケーションを処理しているため、拡張性に優れた1つのアプローチは、フォルダー内の項目をページまたは包括的な機能ごとにグループ化することです。
「DomainAuthentication」(/ settings / sender_auth / domains / ** / *)や「LinkBranding」などの/settings/sender_auth
ルートの下にあるすべてのページオブジェクトを保持するために、「SenderAuthentication」などの機能名でパーティション化された別のpages
フォルダーを作成しました(/ settings / sender_auth /links / ** / *)。 plugins
フォルダーでは、送信者認証用に同じフォルダー内の特定の機能またはページのすべてのcy.task(...)
プラグインファイルを整理することでも同じことを行い、 integration
フォルダーでも同じアプローチに従いました。スペックファイル。 テストを、コードベースの1つにある数百のスペックファイル、ページオブジェクト、プラグイン、およびその他のファイルにスケーリングしました。ナビゲートするのは簡単で便利です。
cypress
のフォルダーを整理する方法の概要は次のとおりです。
integration
フォルダー(すべての仕様が存在する場所)を整理するときに考慮すべきもう1つのことは、テストの優先度に基づいて仕様ファイルを分割する可能性があることです。 たとえば、「P1」フォルダにすべての最高の優先度と値のテストがあり、「P2」フォルダに2番目の優先度のテストがある場合、次のよう--spec 'cypress/integration/P1/**/*'
--spec
オプションを設定することで、すべての「P1」テストを簡単に実行できます。 --spec 'cypress/integration/P1/**/*'
。
自分に合ったスペックフォルダ階層を作成して、ページや--spec 'cypress/integration/SomePage/**/*'
などの機能だけでなく、優先度、製品などの他の基準によってもスペックを簡単にグループ化できるようにします。 、または環境。
要素セレクターの統合
ページのReactコンポーネントを開発するときは、通常、ある程度のユニットテストとJestおよびEnzymeとの統合テストを追加します。 機能開発の終わりに向けて、サイプレスE2Eテストの別のレイヤーを追加して、すべてがバックエンドで機能することを確認します。 Reactコンポーネントの単体テストとCypressE2Eテストのページオブジェクトの両方で、対話してアサートするページ上のコンポーネント/DOM要素へのセレクターが必要です。
これらのページとコンポーネントを更新すると、単体テストセレクターからサイプレスページオブジェクト、実際のコンポーネントコード自体まで、複数の場所を同期する必要があるため、ドリフトやエラーが発生する可能性があります。 スタイルに関連するクラス名のみに依存している場合、破損する可能性のあるすべての場所を更新することを忘れないでください。 代わりに、「data-hook」、「data-testid」、またはその他の一貫した名前の「data- *」属性を、覚えておきたいページの特定のコンポーネントと要素に追加し、テストレイヤーにセレクターを記述します。
多くの要素に「データフック」属性を追加できますが、それでも、他のファイルで更新して再利用するために、それらをすべて1か所にグループ化する方法が必要でした。 これらすべての「データフック」セレクターを管理して、Reactコンポーネントに分散し、ユニットテストとページオブジェクトセレクターで使用して、エクスポートされたオブジェクトの再利用とメンテナンスを容易にする方法を考え出しました。
各ページのトップレベルフォルダーに対して、要素の読み取り可能なキー名と要素の実際の文字列CSSセレクターを値として持つオブジェクトを管理およびエクスポートするhooks.ts
ファイルを作成します。 単体テストやサイプレスのEnzymeのwrapper.find(“[data-hook='selector']”)
などの呼び出しで要素のCSSセレクターフォームを読み取って使用する必要があるため、これらを「読み取りセレクター」と呼びます。ページオブジェクトcy.get(“[data-hook='selector']”)
。 これらの呼び出しは、 wrapper.find(Selectors.someElement)
またはcy.get(Selectors.someElement)
)のようにきれいに見えます。
次のコードスニペットでは、これらの読み取りセレクターを実際に使用する理由と方法について詳しく説明しています。
同様に、要素の読み取り可能なキー名を持ち、値として{ “data-hook”: “selector” }
ようなオブジェクトを持つオブジェクトもエクスポートします。 これらのオブジェクトを小道具としてReactコンポーネントに書き込みまたは拡散して、基になる要素に「データフック」属性を正常に追加する必要があるため、これらを「書き込みセレクター」と呼びます。 目標はこのようなことをすることですまた、下にある実際のDOM要素(小道具がJSX要素に正しく渡されると仮定)にも「data-hook=」属性が設定されます。
次のコードスニペットでは、これらの書き込みセレクターを実際に使用する理由と方法について詳しく説明しています。
オブジェクトを作成して読み取りセレクターと書き込みセレクターを統合し、更新する場所を減らすことができますが、より複雑なページのいくつかに多くのセレクターを書き込む必要がある場合はどうでしょうか。 これはエラーが発生しやすいため、これらの読み取りセレクターと書き込みセレクターを簡単に生成して、最終的に特定のページにエクスポートする関数を作成しましょう。
読み取りセレクタージェネレーター関数では、入力オブジェクトのプロパティをループして、要素名ごとに[data-hook=”selector”]
CSSセレクター文字列を作成します。 キーの対応する値がnull
の場合、入力オブジェクトの要素名は、 { someElement: null } => { someElement: '[data-hook=”someElement”] }
などの「data-hook」値と同じであると想定します。 { someElement: null } => { someElement: '[data-hook=”someElement”] }
。 それ以外の場合、キーの対応する値がnullでない場合は、 { someElement: “newSelector” } => { someElement: '[data-hook=”newSelector”]' }
などの「data-hook」値をオーバーライドすることを選択できます。
書き込みセレクタージェネレーター関数の場合、入力オブジェクトのプロパティをループして、要素名ごとに{ “data-hook”: “selector” }
オブジェクトを形成します。 キーの対応する値がnull
の場合、入力オブジェクトの要素名は、 { someElement: null } => { someElement: { “data-hook”: “someElement” } }
などの「data-hook」値と同じであると想定します。 { someElement: null } => { someElement: { “data-hook”: “someElement” } }
。 それ以外の場合、キーの対応する値がnull
でない場合は、 { someElement: “newSelector” } => { someElement: { “data-hook”: “newSelector” }' }
などの「data-hook」値をオーバーライドすることを選択できます。 。
これらの2つのジェネレーター関数を使用して、ページの読み取りセレクターオブジェクトと書き込みセレクターオブジェクトを作成し、それらをエクスポートして、単体テストとサイプレスページオブジェクトで再利用します。 もう1つの利点は、これらのオブジェクトが、TypeScriptファイルでも誤ってSelectors.unknownElement
またはWriteSelectors.unknownElement
を実行できないように入力されることです。 読み取りセレクターをエクスポートする前に、制御できないサードパーティコンポーネントの要素とCSSセレクターのマッピングを追加することもできます。 場合によっては、特定の要素に「データフック」属性を追加できないため、以下に示すように、他の属性、ID、クラスで選択し、読み取りセレクターオブジェクトにさらにプロパティを追加する必要があります。
このパターンは、ページのすべてのセレクターを整理し、更新が必要な場合に役立ちます。 将来の変更時に触れる必要のあるファイルの数を最小限に抑えるために、ある種のオブジェクトまたはその他の手段でこれらすべてのセレクターを管理する方法を調査することをお勧めします。
タイムアウトの統合
多くのサイプレステストを作成した後に気付いたのは、特定の要素のセレクターがタイムアウトし、「defaultCommandTimeout」や「responseTimeout」などのcypress.json
で設定されたデフォルトのタイムアウト値よりも時間がかかることです。 戻って、より長いタイムアウトが必要な特定のページを調整しましたが、時間の経過とともに、任意のタイムアウト値の数が増え、大規模な変更に対してそれを維持することが難しくなりました。
その結果、「defaultCommandTimeout」より上から始まるオブジェクトにタイムアウトを統合しました。これは、 cy.get(...)
やcy.contains(...)
。 デフォルトのタイムアウトを超えて、タイムアウトオブジェクト内で「short」、「medium」、「long」、「xlong」、および「xxlong」にスケールアップし、ファイル内の任意の場所にインポートして、 cy.get(“someElement”, { timeout: timeouts.short })
などのサイプレスコマンドで使用できます。 cy.get(“someElement”, { timeout: timeouts.short })
またはcy.task('pluginName', {}, { timeout: timeouts.xlong })
。 タイムアウトをインポートされたオブジェクトのこれらの値に置き換えた後、特定のタイムアウトにかかる時間をスケールアップまたはスケールダウンするために更新する場所が1つあります。
これを簡単に開始する方法の例を以下に示します。
まとめ
サイプレステストスイートが成長するにつれて、サイプレステストを最適にスケーリングおよび維持する方法を理解するときに行ったのと同じ問題のいくつかに遭遇する可能性があります。 ページ、機能、またはその他のグループ化規則に従ってファイルを整理することを選択できるため、既存のテストファイルを探す場所と、コードベースに貢献する開発者が増えるにつれて新しいファイルを追加する場所を常に把握できます。
UIが変更されると、ある種の型指定されたオブジェクト(読み取りおよび書き込みセレクターなど)を使用して、単体テストおよびサイプレス内でアサートまたは対話する各ページの主要な要素に対する「データ」属性セレクターを維持できます。テスト。 サイプレスコマンドのタイムアウト値などに任意の値を適用し始めた場合は、スケーリングされた値で満たされたオブジェクトを設定して、それらの値を1か所で更新できるようにする必要があります。
フロントエンドUI、バックエンドAPI、およびサイプレステスト全体で状況が変化するため、これらのテストをより簡単に維持する方法を常に検討する必要があります。 多くの開発者が新しいページ、機能、および(必然的に)サイプレステストを追加するので、更新して間違いを犯す場所が減り、物を置く場所に関するあいまいさが減り、大きな違いが生まれます。
サイプレスに関するその他の投稿に興味がありますか? 次の記事をご覧ください。
- E2Eテストを書くときに考慮すべきこと
- サイプレステストの作成の1,000フィートの概要
- TypeScriptサイプレステストのすべてのもの
- サイプレステストでの電子メールフローの処理
- サイプレステストとDocker、Buildkite、およびCICDの統合