Rozszerzenia do Chrome: droga Eyeo do testowania zawieszenia skryptu service worker

O czym to jest?

Przejście z Manifestu V2 na Manifest V3 wiąże się z zasadnicza zmianą. W pliku manifestu w wersji 2 rozszerzenia były dostępne na stronie w tle. Strony w tle zarządzały komunikacją między rozszerzeniami a stronami internetowymi. Manifest V3 używa zamiast tego usług.

W tym poście omawiamy problem testowania pracowników obsługi rozszerzeń. W szczególności przyjrzymy się, jak zapewnić prawidłowe działanie usługi w przypadku zawieszenia usługi na żądanie.

Kim jesteśmy?

eyeo to firma, która specjalizuje się w ułatwianiu zrównoważonej i zrównoważonej wymiany wartości online dla użytkowników, przeglądarek, reklamodawców i wydawców. Mamy ponad 300 milionów użytkowników na całym świecie, którzy korzystają z filtrowania reklam i zgadzają się na wyświetlanie Akceptowalnych reklam, czyli reklam zgodnych z niezależnym standardem określającym, czy reklama jest akceptowalna i nieinwazyjna.

Nasz zespół ds. silnika rozszerzeń udostępnia technologię filtrowania reklam, która obsługuje niektóre z najpopularniejszych rozszerzeń do blokowania reklam na rynku, np. AdBlock i Adblock Plus, które mają ponad 110 milionów użytkowników na całym świecie. Dodatkowo udostępniamy tę technologię jako bibliotekę open source, dzięki czemu jest ona dostępna dla innych rozszerzeń przeglądarki filtrujących reklamy.

Co to jest serwis worker?

Workery rozszerzenia to centralny moduł obsługi zdarzeń w rozszerzeniu do przeglądarki. działają niezależnie w tle. Ogólnie jest to w porządku. Większość czynności, które musimy wykonać na stronie w tle, możemy wykonać w nowym serwisie workera. Wprowadziliśmy jednak kilka zmian w porównaniu z tradycyjnymi stronami z tłem:

  • Prace serwisowe zawieszają się, gdy nie są używane. Wymaga to zapisania stanów aplikacji zamiast polegania na zmiennych globalnych. Oznacza to, że wszystkie punkty wejścia do naszego systemu muszą być gotowe do wywołania przed zainicjowaniem systemu.
  • Detektorzy zdarzeń muszą być dołączeni przed oczekiwaniem na wywołania asynchroniczne. Zawieszone serwisy workerów mogą nadal otrzymywać zdarzenia, do których się subskrybują. Jeśli odbiorca zdarzenia nie zostanie zarejestrowany w pierwszym przebiegu pętli zdarzeń, nie otrzyma tego zdarzenia, nawet jeśli to zdarzenie obudziło usługę.
  • Zakończenie bezczynności może przerwać liczniki czasu przed ich zakończeniem.

Kiedy zawiesza się usługa robocza?

W Chrome 119 usługa service worker została zawieszona:

  • po 30 sekundach bez otrzymywania zdarzeń lub wywoływania interfejsów API rozszerzeń.
  • Nigdy, jeśli otwarte są narzędzia dla deweloperów lub używasz biblioteki testów opartej na ChromeDriver (zobacz prośbę o dodanie funkcji).
  • Jeśli klikniesz Zatrzymaj na stronie chrome://serviceworker-internals.

Najnowsze informacje znajdziesz w artykule Cykl życia skryptów service worker.

Dlaczego testowanie tego jest problemem?

Najlepiej byłoby mieć oficjalne wskazówki dotyczące „skutecznego testowania usług” lub przykłady działających testów. Podczas testowania interfejsów usługowych napotkaliśmy kilka problemów:

  • Mamy stan w naszym rozszerzeniu testowym. Gdy usługa przestanie działać, utracimy jej stan i zapisywane zdarzenia. Jak dane są przechowywane w ramach procesu testowania?
  • Jeśli w dowolnym momencie można zawiesić działanie service workerów, musimy sprawdzić, czy wszystkie funkcje działają prawidłowo po przerwaniu ich działania.
  • Nawet jeśli wprowadzimy w naszych testach mechanizm, który losowo zawiesza usługę, w przeglądarce nie ma interfejsu API, który umożliwiałby łatwe zawieszenie. Poprosiliśmy zespół W3C o dodanie tej funkcji, ale rozmowy w tej sprawie wciąż trwają.

Testowanie zawieszenia skryptu service worker

Podczas testów mieliśmy do czynienia z kilkoma sposobami na zawieszenie działania usługi:

Podejście Problemy z podejściem
Zaczekaj dowolny czas (np. 30 sekund) Sprawia to, że testowanie jest powolne i niewiarygodne, zwłaszcza w przypadku przeprowadzania wielu testów. Nie działa ona w przypadku WebDrivera, ponieważ WebDriver korzysta z interfejsu DevTools API w Chrome, a worker usługi nie jest zawieszony, gdy otwarte są Narzędzia deweloperskie. Nawet jeśli uda nam się ominąć ten problem, nadal będziemy musieli sprawdzić, czy skrypt service worker został zawieszony, a nie mamy na to sposobu.
Uruchamianie nieskończonego pętli w skrypcie service worker Zgodnie ze specyfikacją może to prowadzić do zakończenia umowy w zależności od tego, jak przeglądarka wdraża tę funkcję. W tym przypadku Chrome nie kończy działania skryptu service worker, więc nie możemy przetestować scenariusza, w którym skrypt jest zawieszony.
Sprawdzanie, czy w usługach w tle jest wyświetlany komunikat o zawieszeniu Wysłanie wiadomości uruchamia usługę. Można go użyć do sprawdzenia, czy usługa robocza była uśpiona, ale powoduje on przerwanie działania testów, które muszą zostać sprawdzone natychmiast po zawieszeniu usługi.
Zatrzymaj proces service workera za pomocą funkcji chrome.processes.terminate(). Skrypt service worker rozszerzenia współdzieli proces z innymi częściami rozszerzenia, więc jego zakończenie za pomocą funkcji chrome.process.terminate() lub menedżera procesów w Chrome powoduje nie tylko zakończenie skryptu service worker, ale też wszystkich stron rozszerzenia.

Ostatecznie stworzyliśmy test, który sprawdza, jak nasz kod reaguje na zawieszenie pracownika usługi. W tym celu użyliśmy Selenium WebDriver, aby otworzyć chrome://serviceworker-internals/ i kliknąć przycisk „stop” dla pracownika usługi.

Jest to najlepsza opcja, ale nie jest idealna, ponieważ nasze testy Mocha (które są uruchamiane na stronie rozszerzenia) nie mogą tego zrobić samodzielnie, więc muszą się komunikować z programem węzła WebDriver. Oznacza to, że te testy nie mogą być uruchamiane tylko za pomocą rozszerzenia. Muszą być wywoływane za pomocą Selenium WebDriver.

Oto diagram pokazujący, jak komunikujemy się z interfejsem API przeglądarki w ramach różnych procesów oraz jak wpływa na to dodanie mechanizmu „wstrzymywania usług działających w tle”.

Diagram przedstawiający przepływ testowania
Testowanie przepływu z zawieszeniem skryptu service worker.

W nowym procesie zawieszania usług (niebieski) dodaliśmy Selenium WebDriver, aby „kliknąć” zawieszenie w interfejsie użytkownika, co powoduje wykonanie działania w interfejsie API przeglądarki.

Warto wspomnieć, że w Chrome wystąpił problem, w którym wykonanie tej czynności za pomocą Selenium WebDriver powodowało, że nie można było ponownie uruchomić pracownika usługi. Ten problem został rozwiązany w wersji 116 Chrome, ale na szczęście istnieje też obejście: ustawienie w Chrome automatycznego otwierania Narzędzi deweloperskich na każdej karcie powoduje prawidłowe uruchamianie pracownika usługi.

Stosujemy to podejście podczas testowania, mimo że nie jest ono idealne, ponieważ kliknięcie przycisku może nie być stabilnym interfejsem API, a otwieranie narzędzi deweloperskich (w starszych przeglądarkach) wydaje się spowalniać działanie.

Jak uwzględnić wszystkie funkcje? Testy rozmywania

Gdy mieliśmy już mechanizm testowania zawieszenia, musieliśmy zdecydować, jak go wdrożyć w naszych zestawach testów automatyzacji. Standardowe testy przeprowadzaliśmy w takim środowisku, w którym przed każdą interakcją z tą stroną w tle serwis worker jest zawieszony przez WebDrivera, który klika Zatrzymaj na stronie chrome://serviceworker-internals/.

Przykładowe wykonanie testu losowego
Ilustracja przedstawiająca bieżącą konfigurację testów.

Przeprowadzamy większość testów, ale nie wszystkie, ponieważ mechanizm zawieszenia nie jest w pełni stabilny i czasami powoduje niestabilność. Uruchomienie wszystkich zestawów testów w trybie fuzz zajmuje też dużo czasu. Zamiast uwzględniać wszystkie „podobne” przypadki, wybraliśmy najbardziej krytyczne ścieżki do przetestowania w trybie fuzz. Warto wspomnieć, że ze względu na to, że testy funkcjonalne są wykonywane w trybie „fuzz”, musieliśmy wydłużyć czas oczekiwania na wyniki testów, ponieważ zawieszanie i ponowne uruchamianie usług wtyczki wymaga dodatkowego czasu.

Te testy są przydatne jako pierwszy etap, który pozwala zidentyfikować wiele miejsc, w których kod zawodzi, ale niekoniecznie ujawnia wszystkie subtelne sposoby, w jakie zawieszenie usługi może spowodować awarię.

Wewnętrznie nazywamy te testy „testami podatności”. Testowanie fuzz polega na podawaniu nieprawidłowego wejścia do programu i sprawdzeniu, czy reaguje on w odpowiednim sposób lub czy przynajmniej nie ulega awarii. W naszym przypadku „nieprawidłowe dane wejściowe” to zawieszenie usługi w dowolnym momencie, a „rozsądne działanie” to działanie funkcji filtrowania reklam w taki sam sposób jak wcześniej. Nie jest to nieprawidłowy tekst wejściowy, ponieważ jest to oczekiwane zachowanie w przypadku Manifestu V3, ale nie byłoby to możliwe w przypadku Manifestu V2, więc wydaje się, że jest to odpowiednia terminologia.

Podsumowanie

Serwisy to jedna z największych zmian w pliku manifestu w wersji 3 (oprócz reguł declarativeNetRequest). Migracja do Manifestu V3 może wymagać wielu zmian kodu w rozszerzeniach przeglądarki i nowych podejść do testowania. Wymaga też, aby deweloperzy rozszerzeń z trwałym stanem przygotowali swoje rozszerzenia do radzenia sobie z nieoczekiwanym zawieszeniem usługi.

Niestety nie ma interfejsu API, który umożliwiałby łatwe przetwarzanie zawieszenia w sposób odpowiedni do naszego przypadku użycia. Ponieważ chcieliśmy już na wczesnym etapie przetestować odporność kodu źródłowego rozszerzenia na mechanizmy zawieszenia, musieliśmy znaleźć sposób na obejście tego problemu. Inni deweloperzy rozszerzeń, którzy napotykają podobne problemy, mogą skorzystać z tego obejścia. Chociaż jest ono czasochłonne na etapie tworzenia i utrzymania, warto je zastosować, aby zapewnić prawidłowe działanie naszych rozszerzeń w środowisku, w którym usługowe workery są regularnie zawieszane.

Chociaż podstawowe testowanie zawieszania usług robota jest już dostępne, chcielibyśmy w przyszłości uzyskać lepsze wsparcie platformy dla testowania usług robota w ramach rozszerzeń, ponieważ znacznie skróciłoby to czas wykonywania testów i wysiłek związany z konserwacją.