私はSelenium
( python bindings で protractor
を主に使用して)かなり長い間、必要なたびに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固有ですが、言語に依存しません。
2つのAPIの 参照 (これはJavadocですが、機能は同じです)があり、その違いを強調する抜粋があります
[executeAsyncScript]現在選択されているフレームまたはウィンドウのコンテキストでJavaScriptの非同期部分を実行します。同期JavaScriptの実行とは異なり、このメソッドで実行されるスクリプトは、提供されたコールバックを呼び出して、終了したことを明示的に通知する必要があります。このコールバックは、常に最後の引数として実行された関数に注入されます。
基本的に、execSyncはSeleniumブラウザーによって実行される追加のアクションをブロックしますが、execAsyncはcallback
をブロックし、実行時に呼び出します。
分度器で作業したので、これを例として使用します。分度器は executeAsyncScript
と get
の両方で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
を使用しますが、その戻り値はすぐには使用できません。これは、結果をポーリングできないが、コールバックまたはプロミス(自分でコールバックに変換する必要がある)を使用して結果を取得する必要がある場合に特に必要です。
通常の
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
を使用します。