Co oznacza zakończenie ładowania strony?

Przedmowa

Zanim automat zacznie wykonywać jakiekolwiek działania z elementami na stronie aplikacji internetowej, musi najpierw poczekać na zakończenie ładowania strony. Każde porządne narzędzie automatyzacji próbuje to zrobić i Selenium nie jest wyjątkiem. Ono również próbuje, tylko nie zawsze mu wychodzi. Zresztą, nie tylko jemu, inne narzędzia także robią błędy. Teraz postaram się wytłumaczyć, dlaczego tak się dzieje.

Co oznacza z technicznego punktu widzenia zakończenie ładowania strony?

Żeby odpowiedzieć na to pytanie, trzeba najpierw określić, czym jest strona?
Technologie wykorzystywane do tworzenia aplikacji internetowych, dość szybko ewoluują i w trakcie tego rozwoju pojęcie strony również ulega dość silnym zmianom, a razem z nim i odpowiedź na pytanie o zakończenie jej ładowania.

Cofnijmy się o 15 lat. Internet Explorer 5. Netscape Navigator 4. HTML 3.2.

W tamtym czasie aplikacje internetowe rzeczywiście składały się z oddzielnych stron. Każda z nich miała unikatowy adres. Określało się go w przeglądarce i ładowała się strona z tym właśnie adresem.
Każda z nich zawierała tekst w formacie HTML. Ponadto mogły na niej ładować się obrazy i opisy stylów (razem zwane zasobami dodatkowymi).

Tak wyglądała witryna Yahoo w 1996 roku

Gdy przeglądarka ładowała tekst strony w formacie HTML, analizowała go i sporządzała zgodnie z oznaczeniami. Jak tylko zakończyło się renderowanie – strona pokazywała się użytkownikowi i to można było uznać za zakończenie ładowania strony.

Po tym jeszcze kontynuowały ładować się obrazki. Czasem trwało to dość długo, ponieważ Internet w tamtych czasach działał niezbyt żwawo. Do tego strona mogła zostać zniekształcona – wstawiony obrazek przesuwał pozostałe elementy.

Dlatego zaczęto wyodrębniać dwa różne momenty zakończenia ładowania.

  • kiedy załadowała i wyrenderowała się podstawowa zawartość strony,
  • kiedy załadowały i wyrenderowały się wszystkie obrazki i style.

W procesie przetwarzania strony przeglądarka zmienia właściwość document.readyState, która zawiera informację o bieżącym etapie ładowania:

  • loading – oznacza, że strona jeszcze się ładuje,
  • interactive – oznacza, że podstawowa zawartość strony została załadowana i wyrenderowana, użytkownik może już z nią pracować, ale trwa jeszcze ładowanie dodatkowych zasobów,
  • complete – oznacza, że wszystkie dodatkowe zasoby również zostały załadowane.

Tak więc, Selenium wykorzystuje właściwość document.readyState do określenia chwili zakończenia ładowania strony. Zakładam, że większość narzędzi robi to samo.

Jak dokładnie Selenium analizuje właściwość document.readyState, opowiem w kolejnym artykule. Teraz kontynuujmy podróż w przeszłość, ponieważ tylko na samym początku wszystko to było w pełni zrozumiałe.

JavaScrypt

Potem nastąpiła rewolucja. Ktoś wpadł na pomysł, żeby wykorzystać w przeglądarce język programowania, w którym można pisać kod zmieniający już załadowaną stronę.

Umownie będę wykorzystywać nazwę JavaScript w odniesieniu do tego języka. Jest to niezupełnie dokładne, ponieważ języki były różne. Jednak w tym artykule istotne jest tylko to, że pojawił się wbudowany w przeglądarkę język programowania.

Pierwszym zastosowaniem nowej technologii stało się ozdabianie. W aplikacjach internetowych pojawiły się animowane przyciski, mieniący się wszystkimi kolorami tęczy tekst, przewijanie tekstu, rozwijane menu. W ogóle, wszystko błyszczało i kręciło się.

Z początku ta innowacja praktycznie nie miała wpływu na pojęcie strony. Oprócz obrazów mógł na niej być ładowany jeszcze kod, który ją zmieniał. Jednak robił to w niezbyt dużym stopniu.

Natomiast zmieniła się odpowiedź na pytanie kiedy można uznać, że strona jest w całości załadowana.

Kod warunkowo dzielił się na dwie kategorie: ten, który wykonuje się do momentu załadowania strony i ten wykonujący się po jej załadowaniu.

Uważano, że kod, który znajduje się w nagłówku (tag script, umiejscowiony wewnątrz tagu head), jest wykonywany do momentu załadowania strony. Oprócz tego, jeżeli w tagu body wskazano moduł obsługi zdarzeń onLoad, wykonujący jakiś kod – to ten kod także uważano za wykonujący się do momentu załadowania strony.

Tak więc, zasada stała się taka: stronę uważa się za całkowicie załadowaną wtedy, kiedy przeglądarka załadowała jej tekst, wyrenderowała, a także wykonała kod programowy, znajdujący się w nagłówku i w module obsługi zdarzeń onLoad.
Jednak narzędzia wciąż są zorientowane na zmianę właściwości document.readyState, ponieważ ta strategia nadal jest skuteczna.

AJAX

Potem miała miejsce druga rewolucja. Złożoność kodu w języku JavaScript stopniowo wzrastała i w pewnej chwili pojawiła się idea dynamicznego kształtowania stron. To znaczy, że z serwera nie jest ładowana gotowa strona, a tylko jej podstawa, a także kod JavaScript, który powinien ukształtować ostateczną jej zawartość. Kod ten uruchamia się tuż po tym, jak wyrenderowała się podstawa strony, podładowuje on pozostałą zawartość i dobudowuje stronę. Ta technologia otrzymała nazwę AJAX.

Jaki jest tego sens? Wykorzystanie dynamicznego ładowania danych dało możliwość stworzenia u użytkownika wrażenia, że aplikacja działa szybciej, niż jest to w rzeczywistości.

Wyobraźmy sobie, że musimy pokazać użytkownikowi jakieś dane, które dość długo się formułują, ponadto stają się dostępne stopniowo (na przykład, wyniki obliczeń).

Zamiast długiego oczekiwania, aż wszystkie dane staną się dostępne na serwerze, można szybko załadować stronę w przeglądarce. W niej zamiast danych będzie znajdować się zaślepka, czyli informacja o tym, że dane będą dostępne trochę później.

Strona ładuje się, renderuje, użytkownik ją widzi i myśli, że ładowanie zostało ukończone. Niby tak, ale przecież danych na niej jeszcze nie ma.

W tym czasie na stronie działa kod, który periodycznie wysyła zapytania na serwer, aby sprawdzić, czy pojawiły się już dane do wyświetlenia. Jak tylko otrzyma odpowiedź – “dorysowuje” je na stronie. Oczywiście nie wszystkie od razu, a partiami, w miarę jak dane stają się dostępne.

Tu, rzeczywiście jest problem z określeniem tego, kiedy można uznać, że strona załadowała się w całości. Przecież kod doładowujący dane, z punktu widzenia przeglądarki wykonuje się już po załadowaniu strony.

Narzędzia automatyzacji (w tym i Selenium) może określić, kiedy szablon wyrenderował się i przeglądarka wykonała kod, który powinien być wykonany do załadowania strony.

Jednak, niestety, żadne z narzędzi nie potrafi samo zorientować się, że trzeba jeszcze poczekać na pojawienie się danych, które podładowują się później.

Tak, więc czy trzeba czekać? Być może, dla jakiegoś konkretnego testu te dane są całkowicie niepotrzebne, bo na przykład musi po prostu przejść po jakimś linku, który jest już w szablonie strony. Wtedy czekanie, aż załadują się wszystkie dane nie ma żadnego sensu

Selenium nie czeka. Dla niego sygnałem o zakończeniu ładowania strony staje się zmiana właściwości document.readyState. Wszystko, co dzieje się później – to już nasze zmartwienie. Kiedy piszemy swój własny kod, który realizuje potrzebne nam oczekiwania, nikt oprócz nas nie wie, kiedy można uznać ładowanie tych dodatkowych danych za zakończone.

AJAX, ciąg dalszy

Potem ktoś wymyślił, żeby w ogóle nie ładować nowych stron, a zamiast tego odświeżać istniejące.
Skomplikujemy przykład, opisany w poprzedniej części.

Załóżmy, że trzeba przedstawić użytkownikowi dużą ilość danych. Deweloperzy zastanawiali się, jak to zrobić najlepiej i postanowili realizować paginację podobną do tej, którą można zobaczyć w większości wyszukiwarek. Jednak jest ona bardziej złożona: kiedy użytkownik klika w link, który ładuje kolejną stronę z danymi, w rzeczywistości przejście na inną stronę z punktu widzenia przeglądarki się nie odbywa. Zamiast tego za pomocą JavaScript ładuje się nowy fragment danych i odświeża się zawartość bieżącej strony.

Oto przykład takiej tabeli

To jeszcze nie koniec – czasem przy takim fałszywym przejściu zmienia się jeszcze adres strony.
Przykładem tego rodzaju paginacji jest wyszukiwarka Google. Inna zawartość, inny adres, ale z punktu widzenia przeglądarki strona ta sama. To taki paradoks.

GWT, ExtJS, Angular i inne

Potem zaczęła się prawdziwa jazda bez trzymanki. W niektórych aplikacjach internetowych strony jako takie całkiem zniknęły.

Spójrzmy, na przykład na GMail. Tam jest stale otwarta jedna i ta sama strona, nawet adres się nie zmienia. Przechodzimy do innego folderu – odświeża się część bieżącej strony. Otwieramy wiadomość – także odbywa się to na tej samej stronie. Zaczynamy pisać nową wiadomość – otwiera się wyskakujący blok i nie zamyka się nawet już wcześniej otwarta wiadomość. Uruchamiamy czat – i on także pojawia się w wyskakującym bloku na tej samej stronie.

Co możemy powiedzieć o aplikacjach takich jak na przykład “pulpit”.

W tych przypadkach mówienie o stronach (jak i o zakończeniu ich ładowania) w ogóle nie ma sensu.

Podsumowanie

Tu po prostu powtórzę to, co już zostało powiedziane wyżej.

Selenium (i najprawdopodobniej większość narzędzi) wykorzystuje właściwość document.readyState do określenia chwili zakończenia ładowania strony.

Jeżeli nasza aplikacja kontynuuje aktualizowanie zawartości już po tym jak właściwość document.readyState otrzymała wartość complete, to znaczy, że potrzebne są nam własnoręcznie wykonane oczekiwania, które będą sprawdzać jakieś inne kryteria zakończenia ładowania.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn