...что делать в Selenium, если страница загружается слишком долго?

...что делать в Selenium, если страница загружается слишком долго?

Суть проблемы

В предыдущей статье я рассказал, как Selenium ожидает завершения загрузки страницы.

Вкратце повторю: перед выполнением каждой команды он проверяет значение свойства document.readyState и приостанавливает выполнение команды до тех пор, пока это свойство не приобретёт значение complete.

Однако иногда эта стратегия приводит к провалу. Бывают такие ситуации, когда свойство document.readyState либо очень долго не может попасть в состояние complete, либо вообще никогда не достигает этого состояния.

Представьте себе, что на странице приложения есть большая картинка, которая загружается с ооооочень медленного сервера. Вся страница уже давным-давно загрузилась, с ней можно работать, но из-за этой дурацкой картинки браузер продолжает крутить колёсико загрузки, а Selenium продолжает ждать…

Вот реальный пример, который демонстрирует эту проблему:

// инициализируем драйвер
WebDriver driver = new FirefoxDriver();
WebDriverWait wait = new WebDriverWait(driver, 10);
// открываем сайт, ждём загрузки страницы
driver.get("http://www.sazehgostar.com/SitePages/HomePage.aspx");
// кликаем кнопку
driver.findElement(By.id("en")).click();
// ждём загрузки следующей страницы
wait.until(visibilityOfElementLocated(By.id("menu")));

На моей машине выполнение этого фрагмента кода занимает от 20 до 40 секунд (без учёта времени на запуск браузера). Причина как раз в том, что на страницу грузится большая картинка (~7 мегабайт). При этом нужная кнопка для переключения на английскую версию сайта становится доступна уже через несколько секунд, но Selenium ждёт, пока загрузится вся страница целиком.

Можно ли что-нибудь сделать, чтобы Selenium не ждал так долго?

Да, можно.

Есть два способа:

  • установить таймаут ожидания загрузки
  • изменить стратегию завершения загрузки

Таймаут ожидания загрузки

Установка таймаута ожидания загрузки приводит к тому, что операция get выбрасывает исключение TimeoutException, если страница не успела загрузиться в течение заданного количества времени. При этом после возникновения исключения загрузка страницы не прерывается, но зато появляется возможность “досрочно” выполнять с ней какие-нибудь действия. Однако следует помнить, что на такой “недозагруженной” странице нужные для дальнейших действий элементы ещё могли не появиться, поэтому требуются дополнительные ожидания появления элементов:

// инициализируем драйвер
WebDriver driver = new FirefoxDriver();
WebDriverWait wait = new WebDriverWait(driver, 10);
// устанавливаем таймаут ожидания загрузки
driver.manage().timeouts().pageLoadTimeout(1, TimeUnit.SECONDS);
// открываем сайт, но ждём недолго
try {
  driver.get("http://www.sazehgostar.com/SitePages/HomePage.aspx");
} catch (TimeoutException ignore) {
}
// ждём появления кнопки на "недозагруженной" странице
WebElement button = wait.until(visibilityOfElementLocated(By.id("en")));
// кликаем
try {
  button.click();
} catch (TimeoutException ignore) {
}
// ждём загрузки следующей страницы
wait.until(visibilityOfElementLocated(By.id("menu")));

В таком варианте код выполняется примерно за 4 секунды (без учёта времени на запуск браузера).

Хорошо?

Нет!

Быстро – ещё не значит правильно :)

Дело в том, что элемент с идентификатором menu есть как на первой странице, так и на второй. В тот момент, когда выполняется клик по кнопке с идентификатором en (переключение на английскую версию сайта), элемент с идентификатором menu тоже присутствует на странице. И Selenium, вместо того, чтобы после клика ждать загрузки второй страницы, немедленно “находит” этот элемент на первой странице.

Причина этого в том, что, как я уже упоминал, после возникновения TimeoutException загрузка страницы не прерывается, она продолжает загружаться, в том числе и в момент выполнения команды click. Это сбивает Selenium с толку, он не понимает, что должна появиться другая страница, и вместо этого ищет элементы на текущей странице.

Что же делать?

Ну, раз уж мы отобрали у Selenium и взяли на себя ответственность за ожидание загрузки страницы, надо брать ответственность и за “выгрузку” страницы тоже. То есть перед ожиданием появления элемента, который должен найтись на следующей странице, нужно сначала подождать, пока исчезнет элемент, находящийся на текущей странице. Например, исчезнет та самая кнопка, по которой кликали:

// инициализируем драйвер
WebDriver driver = new FirefoxDriver();
WebDriverWait wait = new WebDriverWait(driver, 10);
// устанавливаем таймаут ожидания загрузки
driver.manage().timeouts().pageLoadTimeout(1, TimeUnit.SECONDS);
// открываем сайт, но ждём недолго
try {
  driver.get("http://www.sazehgostar.com/SitePages/HomePage.aspx");
} catch (TimeoutException ignore) {
}
// ждём появления кнопки на "недозагруженной" странице
WebElement button = wait.until(visibilityOfElementLocated(By.id("en")));
// кликаем
try {
  button.click();
} catch (TimeoutException ignore) {
}
// ждём исчезновения кнопки, то есть "выгрузки" страницы
wait.until(stalenessOf(button));
// ждём загрузки следующей страницы
wait.until(visibilityOfElementLocated(By.id("menu")));

Теперь этот фрагмент кода выполняется примерно 10 секунд (без учёта времени на запуск браузера), и это правильно – около 5 секунд на каждую страницу.

Недостаток описанного выше способа заключается в том, что приходится оборачивать в блок try-catch все вызовы команд, которые могут привести к началу загрузки новой страницы. А это может быть вообще говоря любая команда. То есть исключение TimeoutException нужно ожидать буквально везде. Это ужасно!

К счастью, есть другой способ.

Стратегия ожидания загрузки

Если вы ещё помните, Selenium перед выполнением каждой команды он проверяет значение свойства document.readyState и приостанавливает выполнение команды до тех пор, пока это свойство не приобретёт значение complete.

В процессе обработки страницы браузер меняет это свойство, отражая информацию о текущем этапе загрузки:

  • loading означает, что страница ещё загружается,
  • interactive означает, что основное содержимое страницы загрузилось и отрисовалось, пользователь уже может с ней взаимодействовать, но ещё продолжается загрузка дополнительных ресурсов,
  • complete означает, что все дополнительные ресурсы тоже загружены.

Так вот, можно изменить настройки Selenium так, чтобы он ждал не значения complete, а значения interactive, или даже вообще не ждал ничего.

Для этого при инициализации драйвера надо установить подходящее значение для capability с названием pageLoadStrategy.

Допустимые значения:

  • normal (установлено по умолчанию) – ждать, пока свойство document.readyState примет значение complete
  • eager – ждать, пока свойство document.readyState примет значение interactive
  • none – вообще не ждать

Конечно, и в этом случае тоже приходится брать на себя ответственность за ожидание “выгрузки” страниц.

Вот тот же самый пример, на этот раз без таймаутов, но с изменённой стратегией ожидания:

// инициализируем драйвер
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(CapabilityType.PAGE_LOAD_STRATEGY, "eager");
WebDriver driver = new FirefoxDriver(capabilities);
WebDriverWait wait = new WebDriverWait(driver, 10);
// открываем сайт
driver.get("http://www.sazehgostar.com/SitePages/HomePage.aspx");
// ждём появления кнопки на "недозагруженной" странице
WebElement button = wait.until(visibilityOfElementLocated(By.id("en")));
// кликаем
button.click();
// ждём исчезновения кнопки, то есть "выгрузки" страницы
wait.until(stalenessOf(button));
// ждём загрузки следующей страницы
wait.until(visibilityOfElementLocated(By.id("menu")));

В этом варианте сценарий тоже отрабатывает примерно за 10 секунд (без учёта времени на запуск браузера).

Вот и всё.

P.S. Я думаю, вы поняли, что с использованием только неявных (implicit) ожиданий описанные выше трюки сделать не получится.


Алексей Баранцев

Автор:

Если вам понравилась эта статья, вы можете поделиться ею в социальных сетях (кнопочки ниже), а потом вернуться на главную страницу блога и почитать другие мои статьи.
Ну а если вы не согласны с чем-то или хотите что-нибудь дополнить – оставьте комментарий ниже, может быть это послужит поводом для написания новой интересной статьи.

Мои тренинги
А ещё есть? Конечно!