web-dev-qa-db-ja.com

Seleniumの非同期実行スクリプトを理解する

私はSeleniumpython bindingsprotractor を主に使用して)かなり長い間、必要なたびにJavaScriptコードを実行し、execute_script()メソッドを使用しました。たとえば、 ページのスクロール用 (python):

_driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
_

または、 別の要素内での無限スクロール (分度器)の場合:

_var div = element(by.css('div.table-scroll'));
var lastRow = element(by.css('table#myid tr:last-of-type'));

browser.executeScript("return arguments[0].offsetTop;", lastRow.getWebElement()).then(function (offset) {
    browser.executeScript('arguments[0].scrollTop = arguments[1];', div.getWebElement(), offset).then(function() {
        // assertions

    });
});
_

または、 すべての要素属性の辞書 (python)を取得するには:

_driver.execute_script('var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;', element)
_

しかし、WebDriver APIには execute_async_script() もありますが、私は個人的に使用していません。

どのようなユースケースに対応していますか?通常のexecute_async_script()の代わりにexecute_script()を使用する場合

質問はSelenium固有ですが、言語に依存しません。

22
alecxe

2つのAPIの 参照 (これはJavadocですが、機能は同じです)があり、その違いを強調する抜粋があります

[executeAsyncScript]現在選択されているフレームまたはウィンドウのコンテキストでJavaScriptの非同期部分を実行します。同期JavaScriptの実行とは異なり、このメソッドで実行されるスクリプトは、提供されたコールバックを呼び出して、終了したことを明示的に通知する必要があります。このコールバックは、常に最後の引数として実行された関数に注入されます。

基本的に、execSyncはSeleniumブラウザーによって実行される追加のアクションをブロックしますが、execAsyncはcallbackをブロックし、実行時に呼び出します。


分度器で作業したので、これを例として使用します。分度器は executeAsyncScriptget の両方でwaitForAngularを使用します

waitForAngularでは、分度器はangularがすべてのイベントが確定したことを通知するまで待つ必要があります。最後に値を返す必要があるため、executeScriptは使用できません。 (ただし、angularが完了するまで絶えずポーリングするビジーループを実装できると思います。)動作する方法は、分度器がコールバックを提供することですAngularすべてのイベントが落ち着いたら、executeAsyncScriptが必要です。コード here

getでは、分度器は、Angularによってグローバル_window.angular_が設定されるまでページをポーリングする必要があります。その方法の1つはdriver.wait(function() {driver.executeScript('return window.angular')}, 5000)ですが、その方法では分度器は数ミリ秒ごとにブラウザーを叩きます。代わりに、これを行います(簡略化)。

_functions.testForAngular = function(attempts, callback) {
  var check = function(n) {
    if (window.angular) {
      callback('good');
    } else if (n < 1) {
      callback('timedout');
    } else {
      setTimeout(function() {check(n - 1);}, 1000);
    }
  };
  check(attempts);
};
_

繰り返しますが、すぐに戻り値がないため、executeAsyncScriptが必要です。コード ここ


全体として、呼び出し元のスクリプトで戻り値が必要な場合はexecuteAsyncScriptを使用しますが、その戻り値はすぐには使用できません。これは、結果をポーリングできないが、コールバックまたはプロミス(自分でコールバックに変換する必要がある)を使用して結果を取得する必要がある場合に特に必要です。

16
hankduan

通常のexecute_async_script()の代わりにexecute_script()を使用する場合

ブラウザ側の条件のチェックに関しては、execute_async_scriptで実行できるすべてのチェックはexecute_scriptで実行できます。チェック対象が非同期の場合でもかつてexecute_async_scriptにバグがあり、スクリプトから結果があまりにも早く返された場合にテストが失敗したためです。私の知る限り、バグはなくなっているので、私はexecute_async_scriptを使用していましたが、数か月前からexecute_scriptを使用して、execute_async_scriptがより自然になりました。たとえば、チェックを実行するためにRequireJSでモジュールをロードする必要があるチェックを実行します。

driver.execute_script("""
// Reset in case it's been used already.
window.__Selenium_test_check = undefined;
require(["foo"], function (foo) {
    window.__Selenium_test_check = foo.computeSomething();
});
""")

result = driver.wait(lambda driver: 
    driver.execute_script("return window.__Selenium_test_check;"))

require呼び出しは非同期です。ただし、これに関する問題は、変数をグローバルスペースにリークすることに加えて、ネットワーク要求が増加することです。各execute_script呼び出しはネットワーク要求です。 waitメソッドはポーリングによって機能します。戻り値がtrueになるまでテストを実行します。これは、上記のコードでwaitが実行するチェックごとに1つのネットワーク要求を意味します。

ローカルでテストする場合、大したことではありません。 Sauce Labsのようなサービス(私が使用しているので、経験から話しています)でブラウザーをプロビジョニングしているためにネットワークを経由する必要がある場合、各ネットワーク要求によりテストスイートの速度が低下します。 したがって、execute_async_scriptを使用すると、より自然に見えるテストを作成できるだけでなく(グローバルスペースにリークするのではなく、通常非同期コードで行うようにコールバックを呼び出す)、テストのパフォーマンスに役立ちます。

result = driver.execute_async_script("""
var done = arguments[0];
require(["foo"], function (foo) {
    done(foo.computeSomething());
});
""")

私が今見ている方法は、テストがブラウザ側の非同期コードにフックして結果を得る場合、execute_async_scriptを使用することです。利用可能な非同期メソッドがないものを実行する場合は、execute_scriptを使用します。

25
Louis