SeleniumとNode.js:信頼できるブラウザーテストの作成

Node.js用に設計されたSeleniumのバージョンを使用して自動ブラウザーテストの作成を開始する方法に関する多くの優れた記事があります。







MochaまたはJasmineでテストをラップする方法について説明する資料もあれば、npm、Grunt、またはGulpを使用してすべてを自動化する資料もあります。 そのようなすべての出版物には、必要なものをすべてインストールして構成する方法に関する情報があります。 作業コードの簡単な例を見ることができます。 初心者にとっては、多くのコンポーネントで構成される動作テスト環境を構築するのはそれほど簡単ではないため、これはすべて非常に便利です。



ただし、セレンの落とし穴に関する限り、テスト開発の最良の実用的な方法の分析に関する限り、これらの記事は通常期待を満たしていません。



今日は、Selenium for Node.jsでブラウザーテストを自動化するための他の資料が通常何で終わるかから始めます。 つまり、テストの信頼性を高めて、ブラウザーやWebアプリケーションでいっぱいの予測不可能な現象から「アンティ」する方法について説明します。



睡眠は悪



Selenium driver.sleep



メソッドは、テスト開発者driver.sleep



最悪の敵です。 ただし、これにもかかわらず、それはどこでも使用されます。 おそらく、これはSeleniumのNodeバージョンのドキュメントの簡潔さによるものであり、API構文のみを扱っているためです。 彼女には実生活の例が欠けています。



おそらく、この方法は、ブログやStackOverflowのようなQ&Aサイトの多くのコードサンプルで使用されているためです。



driver.sleep



メソッドの機能を理解するために、例を考えてみましょう。 画面に表示されるときに、サイズと位置を変更するアニメーションパネルがあるとします。 彼女を見てください。









パネルアニメーション



これは非常に迅速に行われるため、パネル内のボタンとコントロールのサイズと位置の変更にも気付かない場合があります。

同じプロセスのスローモーションバージョンを次に示します。 パネルで緑色のClose



ボタンがどのように変化するかに注目してください。









スローモーションパネルアニメーション



アニメーションは非常に高速であるため、このようなパネルの動作が実際のユーザーの通常の操作を妨げる可能性はほとんどありません。 2番目の例のように十分に遅く、アニメーションのプロセスで[ Close



]ボタンをクリックしようとすると、非常にうまく入らない可能性があります。



通常、これらのアニメーションは非常に高速で発生するため、ユーザーは変化するボタンを「キャッチ」したくありません。 アニメーションが完了するのを待つだけです。 ただし、これはSeleniumには適用されません。 彼は非常に速いので、まだアニメーション化されている要素をクリックすることができます。 その結果、次のようなエラーメッセージが表示される場合があります。



 System.InvalidOperationException : Element is not clickable at point (326, 792.5)
      
      





同様の状況で、多くのプログラマーは「うん、アニメーションが完了するのを待つ必要があるので、 driver.sleep(1000)



を使用してパネルを通常に戻します」と言います。 問題が解決したように見えますか? ただし、すべてがそれほど単純ではありません。



Driver.sleepの問題



driver.sleep(1000)



コマンドは、期待どおりの動作を行います。 テストを1000ミリ秒間停止し、ブラウザーの動作を継続します。ページの読み込み、ページ上のドキュメントのフラグメントの配置、画面上の要素のアニメーション化またはスムーズな表示、その他の操作を行います。



例に戻ると、パネルが800ミリ秒で通常の状態に達すると仮定すると、 driver.sleep(1000)



コマンドは通常、それが求められていることを達成するのに役立ちます。 それでは、なぜそれを利用しないのですか?



主な理由は、この動作が非決定的であることです。 これは、そのようなコードが動作する場合と動作しない場合があることを示しています。 これは常に機能するとは限らないため、特定の条件下で失敗する信頼性の低いテストになります。 したがって、自動化されたブラウザテストの評判は悪い。



driver.sleep



を含む構造が常に機能しないのはなぜですか? 言い換えれば、なぜこれが非決定的なメカニズムなのでしょうか?



Webページは、見ることができるものをはるかに超えています。 そして、要素アニメーションは素晴らしい例です。 ただし、すべてが正常に機能している間は、特別なことを確認する必要はありません。



Webページは、人々が作業することを期待して設計されていることに言及する価値があります。 Seleniumを使用したテスト中、人がページを操作するよりもはるかに高速なプログラム。 たとえば、最初にアイテムを見つけてそれをクリックするようにSeleniumにコマンドを実行すると、これらの操作の間に数ミリ秒しか経過できません。



人がウェブサイトで作業しているとき、クリックする前に要素が完全に表示されるまで待機します。 また、要素の外観が1秒未満である場合、この「期待」に気付かないでしょう。 Seleniumは、平均的なユーザーよりも高速で要求が厳しいだけではありません。 テストは、ページを操作しながら、さまざまな予測不可能な要因に直面することを余儀なくされます。 それらのいくつかを考えてみましょう:



  1. デザイナーは、アニメーション時間を800ミリ秒から1200ミリ秒に変更できます。 その結果、 driver.sleep(1000)



    を使用したテストは失敗します。

  2. ブラウザーは、必ずしも必要なことを正確に行うとは限りません。 システムの負荷により、アニメーションが遅くなり、800ミリ秒以上かかる場合があります。 おそらく、待機時間は1000ミリ秒に設定されています。 その結果、テストは再び失敗します。

  3. ブラウザごとに異なるデータ視覚化メカニズムがあり、要素を画面に配置する操作に異なる優先順位を割り当てます。 新しいブラウザをテストスイートに追加すると、テストが再度クラッシュします。

  4. ページを制御するブラウザー、コンテンツを変更するJavaScript呼び出しは、本質的に非同期です。 この例のアニメーションがサーバーからの情報を必要とするブロックに適用される場合、アニメーションを開始する前に、AJAX呼び出しの結果のようなものを待つ必要があります。 今、とりわけ、ネットワークの遅延に対処しています。 その結果、パネルの表示に必要な時間を正確に推定することはできません。 テストは再び失敗します。

  5. もちろん、テストが失敗する理由は他にもありますが、私にはわかりません。 ブラウザ自体でさえ、外部要因を考慮に入れずに、さらにエラーがある複雑なシステムです。 ブラウザーごとに異なるエラー。 その結果、信頼性の高いテストを記述しようとして、異なるバージョンの異なるブラウザーおよび異なるリリースの複数のオペレーティングシステムで動作するように努めています。 そのような条件での非決定論的テストは遅かれ早かれ失敗します。 これらすべてを考慮すると、プログラマーが自動化されたテストを拒否し、それらがどれほど信頼できないかについて不平を言う理由が明らかになります。



上記の問題の1つを修正するためにプログラマは何をしますか? 彼は失敗の原因を探し始め、アニメーションが明白な解決策を思いつくのに要する時間であることがわかりますdriver.sleep



呼び出しの待ち時間を増やします。 次に、運に頼って、プログラマーはこの改善がすべての可能なテストシナリオで機能すること、システムのさまざまな負荷に対処すること、さまざまなブラウザーの視覚化システムの違いを滑らかにすることなどを期待します。 しかし、まだ非決定的なアプローチがあります。 したがって、これを行うことはできません。



多くの場合、 driver.sleep



が有害なコマンドであると確信していない場合は、これについて考えてください。 driver.sleep



なければdriver.sleep



テストははるかに高速に実行されます。 たとえば、この例のアニメーションには800ミリ秒しかかからないことを願っています。 実際のテストケースでは、このような仮定はdriver.sleep(2000)



ようなものの使用につながります。これは、影響する追加要因が何であれ、アニメーションが正常に完了するのに2秒で十分であることを望みます。ブラウザとページに。



これは、自動テストの1つのステップで1秒以上失われます。 そのようなステップが多数ある場合、そのような「スペア」秒の多くは非常に速く来ます。 たとえば、 driver.sleep



過度の使用により数分かかったWebページの1つだけに対して最近再設計されたテストは、15秒未満で実行されます。



driver.sleep



を取り除き、テストを信頼性の高い完全に決定的な構造に変換する特定の例を紹介します。



約束について一言



SeleniumのJavaScript APIは、Promiseを多用します。 同時に、Promiseの組み込みマネージャーを使用しているため、詳細はプログラマーから隠されています。 この機能は削除されることが予想されるため、将来的には、Promiseを独立してチェーンに結合する方法、または新しいJavaScript async / awaitメカニズムを使用する方法を理解する必要があります。



この資料では、例では従来の組み込みのSelenium Promise ManagerとPromiseをチェーンする機能を使用しています。 約束がどのように機能するかを理解すれば、これは以下のコード例を分析するときに大きなプラスになります。 ただし、約束をまだ適切に理解していない場合にも、この資料の恩恵を受けることができます。



テストを書く



アニメーションパネルにあるボタンを使用して例を続けましょう。 このボタンをクリックします。 テストを中断する可能性のある特定の機能をいくつか見てみましょう。



ページに動的に追加され、ページの読み込みが完了した直後には存在しない要素についてはどうですか?



DOMに要素が表示されるのを待っています



次のコードは、ページのロード後にCSS id my-button



要素がDOMに追加された場合は機能しません。



 //   Selenium    //  . driver.get('https:/foobar.baz'); //  . const button = driver.findElement(By.id('my-button')); button.click();
      
      





driver.findElement



メソッドは、アイテムがDOMに既に存在することを想定しています。 アイテムがすぐに見つからない場合、エラーがスローされます。 この場合、 driver.get



呼び出しにより「すぐに」は「ページの読み込みが完了した後」を意味します。



Selenium for JavaScriptの現在のバージョンは、個別にプロミスを管理することに注意してください。 したがって、各式は次の式に進む前に完全に完了します。



上記のシナリオは必ずしも望ましいとは限らないことに注意してください。 要素がすでにDOMに存在することが確実な場合は、 driver.findElement



を単独で呼び出すと便利です。



まず、このエラーを修正してはならない方法を見てください。 DOMに要素を追加するのに数秒かかることがわかっていると仮定します。



 driver.get('https:/foobar.baz'); //  ,     driver.sleep(3000); // ,      ,            . const button = driver.findElement(By.id('my-button')); button.click();
      
      





上記の理由により、このような設計は失敗につながる可能性があり、その可能性があります。 要素がDOMに表示されるのを待つ方法を理解する必要があります。 これは非常に簡単です。これは、インターネットで見られる例でよく見られます。 十分に文書化された driver.wait



メソッドを使用して、DOMに要素が表示される瞬間を20秒以内待機します。



 const button = driver.wait( until.elementLocated(By.id('my-button')), 20000 ); button.click();
      
      





このアプローチはすぐに多くの利点をもたらします。 たとえば、アイテムが1秒以内にDOMに追加された場合、 driver.wait



メソッドdriver.wait



1秒で完了します。 彼は彼に割り当てられているすべての20秒を待つことはありません。



この動作により、テストが遅くなることを心配することなく、大きなマージンでタイムアウトを設定できます。 この動作モデルは、指定されたすべての時間を常に待機するdriver.sleep



と比べて有利です。



これは多くの状況で機能します。 しかし、このアプローチが役に立たない唯一のケースは、DOMに存在するがまだ画面に表示されていない要素をクリックすることです。



Seleniumは、不可視の要素をクリックしようとしないほどスマートです。 ユーザーはそのような要素をクリックできないため、これは良いことですが、信頼できる自動テストを作成する作業が複雑になります。



画面にアイテムが表示されるのを待つ



要素が表示されるのを待つ前に、DOMに追加されるまで待つのが理にかなっているため、前の例に基づきます。 さらに、以下のコードでは、一連の約束の使用の最初の例を見ることができます。



 const button = driver.wait( until.elementLocated(By.id('my-button')), 20000 ) .then(element => {  return driver.wait(    until.elementIsVisible(element),    20000  ); }); button.click();
      
      





一般に、テストを大幅に改善するのに十分な調査を既に行っているため、これで停止できます。 このコードを使用すると、ページの読み込みが完了した直後に要素がDOMにないためにテストが失敗する多くの状況を回避できます。 または、アニメーションのようなものが原因で、ページを読み込んだ直後に表示されないという事実が原因です。 または、これらの両方の理由があります。



上記のアプローチを習得した場合、Seleniumの非決定的なコードを作成する理由はないはずです。 ただし、このようなコードを書くことは、常にいつもとは程遠いものです。



タスクの複雑さが増すと、開発者は地面を失い、 driver.sleep



ます。 より複雑な状況でdriver.sleep



を使用せずに実行できるいくつかの例をdriver.sleep



ましょう。



自身の状態の説明



until



メソッドのおかげで、SeleniumのJavaScript APIにはすでにdriver.wait



使用できる多くのヘルパーメソッドがあります。 さらに、アイテムが存在しなくなるまで待機したり、特定のテキストを含むアイテムが表示されるまで待機したり、通知が表示されるまで待機したり、他の多くの条件を使用したりできます。



標準的な方法の中から必要なものが見つからない場合は、独自の条件を記述する必要があります。 これは実際には非常に簡単です。 ここでの主な問題は、そのような条件の例を見つけることが難しいことです。 ここで対処する必要がある別の落とし穴があります。



ドキュメントによると、 true



またはfalse



を返すメソッドをdriver.wait



提供できfalse







特定の要素のopacity



プロパティが1に等しくなるまで待つ必要があるとしましょう。



 //  . const element = driver.wait( until.elementLocated(By.id('some-id')), 20000 ); // driver.wait    ,   true  false. driver.wait(() => { return element.getCssValue('opacity')        .then(opacity => opacity === '1'); });
      
      





この設計は便利で再利用に適しているように見えるので、関数に入れてください。



 const waitForOpacity = function(element) { return driver.wait(element => element.getCssValue('opacity')        .then(opacity => opacity === '1'); ); };
      
      





次に、この関数を使用します。



 driver.wait( until.elementLocated(By.id('some-id')), 20000 ) .then(waitForOpacity);
      
      





そして、ここで私たちは問題に直面しています。 完全に不透明になった要素をクリックしたい場合はどうしますか? 上記のコードによって返された値を使用しようとすると、何も良い結果が得られません。



 const element = driver.wait( until.elementLocated(By.id('some-id')), 20000 ) .then(waitForOpacity); //  .  element   true  false,   ,     click(). element.click();
      
      





同じ理由で、同様の構造で約束の連鎖を使用することはできません。



 const element = driver.wait( until.elementLocated(By.id('some-id')), 20000 ) .then(waitForOpacity) .then(element => { //    , element     . element.click(); });
      
      





ただし、これはすべて簡単に修正できます。 改善された方法は次のとおりです。



 const waitForOpacity = function(element) { return driver.wait(element => element.getCssValue('opacity')        .then(opacity => {     if (opacity === '1') {       return element;     } else {       return false;   }); ); };
      
      





このコードは、条件がtrueの場合に要素を返し、そうでない場合はfalse



返しfalse



。 このようなテンプレートは再利用に適しており、独自の条件を記述するときに使用できます。



チェーンの約束とともにこれを適用する方法は次のとおりです。



 driver.wait( until.elementLocated(By.id('some-id')), 20000 ) .then(waitForOpacity) .then(element => element.click());
      
      





またはこのように:



 const element = driver.wait( until.elementLocated(By.id('some-id')), 20000 ) .then(waitForOpacity); element.click();
      
      





独自の条件を作成すると、テストの機能を拡張し、それらを確定的にすることができます。 ただし、これだけでは十分ではありません。



マイナスに行く



これは事実であり、ネガティブになり、プラスになろうとしないことがあります。 ここでは、存在しないもの、または画面に表示されなくなったものをチェックします。



要素がすでにDOMに存在しているが、一部のデータがAJAXを介してロードされる前に要素と対話しないでください。 要素には、「読み込み中...」というパネルが重なる場合があります。



until



メソッドが提供する条件に細心の注意を払うと、 elementIsNotVisible



elementIsDisabled



などのメソッド、またはあまり目立たないstalenessOf



メソッドに気付くかもしれません。



これらの方法のいずれかを使用すると、ロードインジケーターでパネルを非表示にするためのチェックを整理できます。



 //     DOM,     . const desiredElement = driver.wait( until.elementLocated(By.id('some-id')), 20000 ); //        ,      //  . driver.wait( until.elementIsNotVisible(By.id('loading-panel')), 20000 ); //        ,     ,   . desiredElement.click();
      
      





上記のメソッドを調べると、 stalenessOf



メソッドstalenessOf



特に役立つことがstalenessOf



ました。 要素がDOMから削除されるまで待機します。これは、他の理由の中でも特に、ページの更新により発生する可能性があります。



iframe



コンテンツが更新されるのを待機する例を次に示します。



 let iframeElem = driver.wait( until.elementLocated(By.className('result-iframe')), 20000 ); //   ,     iframe. someElement.click(); //    iframe  : driver.wait( until.stalenessOf(iframeElem), 20000 ); //    iframe. driver.wait( until.ableToSwitchToFrame(By.className('result-iframe')), 20000 ); // ,    ,     iframe.
      
      





まとめ



Seleniumで信頼性の高いテストを作成しようとする人に与えることができる主な推奨事項は、常に決定論を求めてsleep



メソッドを放棄するsleep



です。 sleep



メソッドに依存することは、任意の仮定に基づいています。 そして、これは遅かれ早かれ、失敗につながります。



ここに挙げた例が、Seleniumでの品質テストの作成への道を進む助けになることを願っています。



親愛なる読者! Seleniumを使用してテストを自動化していますか?



All Articles