...что означает
Image source: Micro Focus

...что означает "окончание загрузки страницы"?

Предисловие

Прежде чем автотест начнёт выполнять какие-либо действия с элементами на странице веб-приложения, ему нужно сначала дождаться окончания загрузки страницы. Всякий порядочный инструмент автоматизации пытается это сделать, и Selenium не исключение. Он тоже пытается. Но у него не всегда получается. Впрочем, не только у него, другие инструменты автоматизации тоже “косячат”. И сейчас я постараюсь объяснить, почему это происходит.

Что означает с технической точки зрения “окончание загрузки страницы”?

Чтобы ответить на этот вопрос, надо сначала определить, что такое “страница”?

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

Начало

Вернёмся лет на пятнадцать назад. Internet Explorer 5. Netscape Navigator 4. HTML 3.2.

В то время веб-приложения, действительно, состояли из отдельных “страниц”. У каждой страницы был уникальный адрес. Указываешь в браузере адрес – загружается страница с этим адресом.

Каждая страница содержала размеченный текст в формате HTML. Кроме этого на страницу могли подгружаться картинки и описания стилей (вместе они назывались “дополнительные ресурсы”).

Вот так выглядел сайт Yahoo в 1996 году Вот так выглядел сайт Yahoo в 1996 году

После того, как браузер загружал текст страницы в формате HTML, он его анализировал и отрисовывал в соответствии с разметкой. Как только завершалась отрисовка – страница показывалась пользователю, и это можно считать “окончанием загрузки страницы”.

Да, после этого ещё продолжали подгружаться картинки, иногда достаточно долго, потому что интернет в те времена работал не слишком шустро. Страница при этом могла перерисовываться – вставленная картинка куда-нибудь сдвигала остальные элементы.

Поэтому стали выделять два различных момента “окончания загрузки”

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

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

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

Так вот, Selenium использует именно это свойство document.readyState для определения момента окончания загрузки страницы. Я предполагаю, что большинство других инструментов делает то же самое.

Как именно Selenium анализирует свойство document.readyState – я расскажу в следующей статье. А сейчас давайте продолжим путешествие в прошлое, потому что это только в самом начале было всё так хорошо и понятно.

JavaScript

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

Я условно буду использовать название JavaScript для обозначения этого языка. Да, это не совсем точно, языки были разные, но для данной статьи важно лишь то, что появился встроенный в браузер язык программирования.

Первым применением новой технологии стало “украшательство”. В веб-приложениях появились анимированные кнопки, переливающийся всеми цветами радуги текст, бегущая строка, выпадающие меню. В общем, всё сверкало и крутилось.

Это учебный сайт, где учат делать как раз такие украшательства Это учебный сайт, где учат делать как раз такие украшательства

Поначалу это нововведение практически не повлияло на понятие “страницы”. Теперь на страницу помимо картинок мог ещё подгружаться программный код, который её менял. Но менял не очень сильно.

Зато поменялся ответ на вопрос, когда можно считать, что страница “полностью загружена”.

Программный код условно делился на две категории: тот, который выполняется “до загрузки страницы” и “после загрузки страницы”.

Считалось, что код, который находится в заголовке страницы (тег script, находящийся внутри тега head), выполняется до загрузки страницы. Кроме того, если в теге body указан обработчик события onLoad, который выполняет какой-то код – этот код тоже считается выполняющимся до загрузки страницы.

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

Но инструменты по прежнему ориентировались на изменение свойства document.readyState, потому что эта стратегия всё ещё успешно работала.

AJAX

А потом случилась вторая революция. Сложность кода на языке JavaScript постепенно возрастала, и в какой-то момент появилась идея динамического формирования страниц. То есть с сервера загружается не сразу готовая страница, а только шаблон, основа, а также JavaScript-код, который должен сформировать окончательное содержимое страницы. Этот код запускается уже после того, как основа страницы отрисовалась, подгружает оставшееся содержимое и достраивает страницу. Эта технология получила название AJAX.

В чём смысл фокуса? Использование динамической загрузки данных дало возможность создать у пользователя впечатление, что приложение работает быстрее, чем это есть на самом деле.

Действительно, представьте себе, что вам нужно показать пользователю какие-то данные, которые достаточно долго формируются, причём становятся доступны постепенно (например, результаты каких-нибудь вычислений).

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

Страница загружается, отрисовывается, пользователь её видит… Загрузка страницы завершена? Ну, вроде бы да. Но ведь данных на ней ещё нет? И это верно.

А в это время на странице работает программный код, который периодически отправляет запросы на сервер, чтобы узнать, не появились ли данные для отображения. И как только получает ответ – “дорисовывает” эти данные на станице. Причём может быть не все сразу, а по частям, по мере поступления.

Можете посмотреть на живой пример такой динамически обновляющейся страницы

И вот тут в полный рост встаёт проблема с определением того, когда можно считать, что “страница загрузилась полностью”. Ведь код, подгружающий данные, выполняется уже “после загрузки страницы” с точки зрения браузера.

Инструмент автоматизации (в том числе и Selenium) может определить момент, когда шаблон отрисовался, браузер выполнил код, который должен быть выполнен “до загрузки страницы”.

Но, увы, никакой инструмент не может сам сообразить, что нужно ещё дождаться появления данных, которые подгружаются позже.

Да и нужно ли дожидаться? Может быть для конкретного теста эти данные вовсе и не нужны, может быть ему надо просто пройти по какой-то ссылке, которая есть уже в шаблоне страницы. Тогда было бы просто глупо ждать, пока загрузятся все данные.

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

AJAX, продолжение

А потом кто-то придумал вообще не загружать новые страницы, а вместо этого обновлять существующие.

Усложним пример, описанный в предыдущей части.

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

Вот живой пример такой таблицы

Мало этого обмана – иногда при таком мнимом переходе ещё и адрес страницы меняется!

Примером такого рода является поисковая система Google. Содержимое другое, адрес другой, а с точки зрения браузера станица та же самая. Вот такой парадокс.

GWT, ExtJS, Angular и другие

А потом начался настоящий трэш и угар. В некоторых веб-приложениях страницы как таковые исчезли совсем.

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

Что уж говорить про такие приложения, как, например, “рабочий стол”.

В этих случаях о “страницах” (и об окончании их загрузки) вообще говорить бессмысленно.

Подведём итог

Здесь я просто повторю то, что уже было сказано выше.

Selenium (и, я думаю, большинство других инструментов тоже) использует свойство document.readyState для определения момента окончания загрузки страницы.

Если ваше приложение продолжает обновлять содержимое уже после того, как свойство document.readyState приобрело значение complete – значит, вам нужны самодельные ожидания, которые будут проверять какие-то другие критерии “завершения загрузки”.


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

Автор:

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

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