Расширения Chrome: путь Eyeo к тестированию приостановки Service Worker

О чем это?

Переход от Манифеста V2 к Манифесту V3 сопряжен с фундаментальными изменениями. В Манифесте V2 расширения располагались на фоновой странице. Фоновые страницы управляли связью между расширениями и веб-страницами. Вместо этого в Manifest V3 используются сервисные работники.

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

Кто мы?

Eyeo — это компания, стремящаяся обеспечить сбалансированный и устойчивый онлайн-обмен ценностями для пользователей, браузеров, рекламодателей и издателей. У нас более 300 миллионов пользователей по всему миру, использующих фильтрацию рекламы, которые разрешают показ приемлемой рекламы — независимого рекламного стандарта, который определяет, является ли реклама приемлемой и ненавязчивой.

Наша команда Extension Engine предоставляет технологию фильтрации рекламы, которая поддерживает некоторые из самых популярных на рынке браузерных расширений для блокировки рекламы, таких как AdBlock и Adblock Plus, с более чем 110 миллионами пользователей по всему миру. Кроме того, мы предлагаем эту технологию в виде библиотеки с открытым исходным кодом , что делает ее доступной для других расширений браузера, фильтрующих рекламу.

Что такое сервисный работник?

Рабочие службы расширений — это центральный обработчик событий расширения браузера. Они работают независимо в фоновом режиме. В целом это нормально. Мы можем делать большую часть того, что нам нужно, на фоновой странице нового сервис-воркера. Но есть несколько изменений по сравнению с фоновыми страницами:

  • Работники службы прекращают работу, когда они не используются . Это требует от нас сохранения состояний приложения вместо того, чтобы полагаться на глобальные переменные. Это означает, что любые точки входа в нашу систему должны быть готовы к вызову до инициализации системы.
  • Прослушиватели событий должны быть подключены до ожидания каких-либо асинхронных обратных вызовов . Приостановленные сервисные работники по-прежнему могут получать события, на которые они подписаны. Если прослушиватель события не зарегистрирован на первом этапе цикла событий, он не получит событие, если это событие разбудило сервисного работника.
  • Завершение простоя может прервать таймеры до их завершения.

Когда работники сферы услуг отстраняются от работы?

В Chrome 119 мы заметили, что сервисные работники приостанавливаются:

  • После отсутствия событий или вызова API расширения в течение 30 секунд.
  • Никогда, если инструменты разработчика открыты или вы используете библиотеку тестирования на основе ChromeDriver ( см. запрос функции ).
  • Если вы нажмете «Стоп» в chrome://serviceworker-internals.

Более свежую информацию см. в разделе Жизненный цикл Service Workers .

Почему тестирование является проблемой?

В идеале было бы полезно иметь официальное руководство о том, «как эффективно тестировать сервисных работников» или примеры рабочих тестов. Во время наших приключений по тестированию сервисных работников мы столкнулись с несколькими проблемами:

  • В нашем тестовом расширении есть состояние. Когда сервис-воркер останавливается, мы теряем его состояние и зарегистрированные события. Как мы будем сохранять данные в ходе тестирования?
  • Если сервис-воркеры могут быть приостановлены в любой момент, нам нужно проверить, что все функции работают, если они прерываются.
  • Даже если бы мы ввели в наши тесты механизм, который случайным образом приостанавливает работу сервис-воркеров, в браузере нет API, позволяющего легко его приостановить. Мы попросили команду W3C добавить эту функцию, но это продолжающийся разговор.

Тестирование приостановки сервисного работника

Во время тестов мы опробовали несколько подходов к запуску приостановки сервисного работника:

Подход Проблемы с подходом
Подождите произвольное время (например, 30 секунд). Это делает тестирование медленным и ненадежным, особенно при запуске нескольких тестов. Это не работает при использовании WebDriver, поскольку WebDriver использует API DevTools Chrome, а сервис-воркер не приостанавливается, когда DevTools открыт. Даже если бы мы могли обойти это, нам все равно пришлось бы проверять, не был ли приостановлен сервис-воркер, а у нас нет способа сделать это.
Запустите бесконечный цикл в сервисном работнике Согласно спецификации, это может привести к завершению работы в зависимости от того, как браузер реализует эту функциональность. В этом случае Chrome не завершает работу сервис-воркера, поэтому мы не можем протестировать сценарий, когда сервис-воркер приостанавливается.
Наличие сообщения в сервисном работнике, чтобы проверить, приостановлено ли оно. Отправка сообщения пробуждает работника службы. Это можно использовать для проверки того, спал ли сервисный работник, но это нарушает результаты тестов, которые должны выполнять проверки сразу после приостановки сервисного работника.
Убейте рабочий процесс службы с помощью chrome.processes.terminate(). Сервисный работник расширения использует общий процесс с другими частями расширения, поэтому завершение этого процесса с помощью chrome.process.terminate() или графического интерфейса диспетчера процессов Chrome приводит к уничтожению не только сервисного работника, но и всех страниц расширения.

В итоге мы получили тест, который проверяет, как наш код реагирует на приостановку сервисного работника, когда Selenium WebDriver открывает chrome://serviceworker-internals/ и нажимает кнопку «Стоп» для сервисного работника.

На данный момент это лучший вариант, но он не идеален, поскольку наши тесты Mocha (которые выполняются на странице расширения) не могут сделать это сами, поэтому им необходимо связаться с нашей программой узла WebDriver. Это означает, что эти тесты нельзя запустить, используя только расширение; их нужно запускать с помощью Selenium WebDriver .

Вот схема того, как мы взаимодействуем с API браузера через различные потоки и как на это влияет добавление механизма «приостановки сервисных работников».

Диаграмма, показывающая ход тестирования
Поток тестирования с приостановкой сервисного работника.

В новом потоке, который приостанавливает сервисных работников (синий), мы добавили Selenium WebDriver для «нажатия» приостановки через пользовательский интерфейс, что запускает действие в API браузера.

Стоит отметить, что в Chrome произошла ошибка , из-за которой выполнение этого действия с помощью Selenium WebDriver приводило к невозможности повторного запуска сервисного работника. Это было исправлено в Chrome 116, и, к счастью, есть обходной путь: настройка Chrome на автоматическое открытие DevTools на каждой вкладке обеспечивает корректный запуск сервисного работника.

Это подход, который мы используем при тестировании, хотя он и не идеален, поскольку нажатие кнопки может быть нестабильным API, а открытие DevTools (для старых браузеров), по-видимому, приводит к снижению производительности.

Как мы охватываем всю функциональность? Фазз-тесты

Когда у нас появился механизм приостановки тестирования, нам нужно было решить, как подключить его к нашим наборам автоматизированных тестов. Мы запускали наши стандартные тесты в среде, где перед каждым взаимодействием с фоновой страницей сервис-воркер приостанавливается , когда WebDriver нажимает кнопку «Стоп» на странице chrome://serviceworker-internals/.

Пример выполнения нечеткого теста
Изображение, показывающее текущую настройку тестов.

Мы проводим большинство, но не все тесты, поскольку механизм подвески не полностью стабилен и иногда вызывает нестабильность. Кроме того, запуск всех наборов тестов в режиме фаззинга занимает много времени. Поэтому вместо того, чтобы охватить все «похожие» случаи, мы выбрали наиболее критичные пути для тестирования в режиме нечеткости. Стоит отметить, что запуск функциональных тестов в режиме «fuzz» означает, что нам пришлось увеличить таймауты тестов, поскольку приостановка и перезапуск сервис-воркеров требует дополнительного времени.

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

Внутри компании мы называем такие тесты «нечеткими тестами». Традиционно фазз-тестирование — это когда вы вводите в программу неверные данные и проверяете, что она реагирует разумно или, по крайней мере, не дает сбоя. В нашем случае «неверный ввод» — это приостановка работы сервисного работника в любой момент, а «разумное поведение», которое мы ожидаем, заключается в том, что наша функция фильтрации рекламы должна продолжать работать как прежде. На самом деле это не недопустимый ввод, поскольку такое поведение является ожидаемым в Манифесте V3, но в Манифесте V2 это было бы недопустимо, поэтому такая терминология кажется разумной.

Краткое содержание

Сервис-воркеры — одно из самых больших изменений в Manifest V3 (помимо правил declarativeNetRequest). Переход на Manifest V3 может потребовать множества изменений кода в расширениях браузера и новых подходов к тестированию. Это также требует от разработчиков расширений с постоянным состоянием подготовить свои расширения к корректной обработке неожиданного приостановки сервисного работника.

К сожалению, не существует API для простой обработки приостановки, подходящей для нашего варианта использования. Поскольку мы хотели на ранней стадии проверить устойчивость кодовой базы нашего расширения к механизмам приостановки, нам пришлось обойти эту проблему. Другие разработчики расширений, которые сталкиваются с аналогичными проблемами, могут использовать этот обходной путь, который, хотя и отнимает много времени на этапе разработки и обслуживания, но того стоит, поскольку мы можем гарантировать, что наши расширения могут успешно работать в среде, в которой работники службы регулярно приостанавливаются.

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