Подтвердить что ты не робот

Понимание выполнения async script в Selenium

Я использовал selenium (привязки python и через 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)

Но в API WebDriver также есть execute_async_script(), который я лично не использовал.

В каких случаях он распространяется? Когда следует использовать execute_async_script() вместо обычного execute_script()?

Вопрос - селен-специфический, но язык-агностик.

4b9b3361

Ответ 1

Здесь ссылка на два API (ну, это Javadoc, но функции одинаковы), и здесь выдержка из него, которая подчеркивает разницу

[executeAsyncScript] Выполните асинхронную часть JavaScript в контекст выбранного кадра или окна. В отличие от выполнение синхронного JavaScript, скрипты, выполненные с помощью этого метода должны явно сигнализировать, что они завершены, вызвав предоставленную Перезвони. Этот обратный вызов всегда вводится в выполняемую функцию как последний аргумент.

В принципе, execSync блокирует дальнейшие действия, выполняемые браузером selenium, а execAsync не блокирует и вызывает callback, когда это делается.


Поскольку вы работали с транспортиром, я буду использовать его в качестве примера. Транспортир использует executeAsyncScript в get и waitForAngular

В waitForAngular транспортир должен ждать, пока angular не объявит, что все события установлены. Вы не можете использовать executeScript, потому что в конце нужно вернуть значение (хотя, я думаю, вы можете реализовать цикл занятости, который постоянно проверяет angular, пока он не будет выполнен). Способ, которым он работает, заключается в том, что protractor обеспечивает обратный вызов, который angular вызывает один раз, когда все события установлены, и для этого требуется executeAsyncScript. Код здесь

В get транспортир должен опросить страницу до тех пор, пока глобальный window.angular не будет установлен на Angular. Один из способов сделать это - 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, когда вам нужно вернуть возвращаемое значение в вызывающем script, но это возвращаемое значение будет недоступно немедленно. Это особенно необходимо, если вы не можете опросить результат, но должны получить результат с помощью обратного вызова или обещания (которое вы должны перевести на обратный вызов самостоятельно).

Ответ 2

Когда следует использовать execute_async_script() вместо обычного execute_script()?

Когда дело доходит до условий проверки на стороне браузера, все проверки, которые вы можете выполнить с помощью execute_async_script, можно выполнить с помощью execute_script. Даже если то, что вы проверяете, является асинхронным. Я знаю, потому что когда-то была ошибка с execute_async_script, которая заставила мои тесты потерпеть неудачу, если script вернул результаты слишком быстро. Насколько я могу судить, ошибка исчезла, поэтому я использовал execute_async_script, но за несколько месяцев до этого я использовал execute_script для задач, где execute_async_script было бы более естественным. Например, выполнение проверки, требующей загрузки модуля с RequireJS для выполнения проверки:

driver.execute_script("""
// Reset in case it 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 работает путем опроса: он запускает тест, пока возвращаемое значение не будет истинным. Это означает, что один сетевой запрос на проверку, выполняемый wait (в коде выше).

Когда вы тестируете локально, это не имеет большого значения. Если вам нужно пройти через сеть, потому что у вас есть браузеры, предоставляемые службой, такой как 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.