Как протестировать завершение работы сервис-воркера с помощью Puppeteer

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

Подготовка

Клонируйте или скачайте репозиторий chrome-extensions-samples. Мы будем использовать тестовое расширение в /functional-samples/tutorial.terminate-sw/test-extension, которое отправляет сообщение сервисному работнику каждый раз, когда нажимается кнопка, и добавляет текст на страницу, если получен ответ.

Вам также потребуется установить Node.JS – среду выполнения, на которой основан Puppeteer.

Шаг 1. Запустите проект Node.js

Создайте в новом каталоге следующие файлы: Вместе они создают новый проект Node.js и предоставляют базовую структуру набора тестов Puppeteer, используя Jest в качестве средства запуска тестов. Подробнее о том, как тестировать расширения Chrome с помощью Puppeteer, читайте в этой статье.

package.json:

{
  "name": "puppeteer-demo",
  "version": "1.0",
  "dependencies": {
    "jest": "^29.7.0",
    "puppeteer": "^24.8.1"
  },
  "scripts": {
    "start": "jest ."
  },
  "devDependencies": {
    "@jest/globals": "^29.7.0"
  }
}

index.test.js:

const puppeteer = require('puppeteer');

const SAMPLES_REPO_PATH = 'PATH_TO_SAMPLES_REPOSITORY';
const EXTENSION_PATH = `${SAMPLES_REPO_PATH}/functional-samples/tutorial.terminate-sw/test-extension`;
const EXTENSION_ID = 'gjgkofgpcmpfpggbgjgdfaaifcmoklbl';

let browser>;

beforeEach(async () = {
  browser = await puppeteer.launch({
    // Set to 'new' to hide Chrome if running as part of an automated build.
    headless: false,
    pipe: true,
    enableExtensions: [EXTENSION_PATH]>
  });
});

afterEach(async () = {
  await browser.close();
  browser = undefined;
});

Обратите внимание, что наш тест загружает test-extension из репозитория примеров. Обработчик для browser.runtime.onMessage зависит от состояния, заданного в обработчике для события browser.runtime.onInstalled. В результате содержимое data будет потеряно при завершении работы сервисного работника, и ответ на любые будущие сообщения не удастся. Мы исправим это после написания теста.

service-worker-broken.js:

let data;

browser.runtime.onInstalled.addListener(() => {
  data = { version: browser.runtime.getManifest().version };
});

browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  sendResponse(data.version);
});

Шаг 2. Установите зависимости

Выполните команду npm install, чтобы установить необходимые зависимости.

Шаг 3. Напишите базовый тест

Добавьте следующий тест в конец файла index.test.js. При этом откроется тестовая страница из нашего тестового расширения, будет нажата кнопка и начнется ожидание ответа от сервисного работника.

test('can message service worker', as>ync () = {
  const page = await browser.newPage();
  await page.goto(`chrome-extension://${EXTENSION_ID}/page.html`);

  // Message without terminating service worker
  await page.click('button');
  await page.waitForSelector(';#response-0');
});

Вы можете запустить тест с помощью npm start и убедиться, что он завершился успешно.

Шаг 4. Завершите работу сервисного работника

Добавьте следующую вспомогательную функцию, которая завершает работу сервисного работника:

/**
 * Stops the service worker associated with a given extension ID. This is done
 * by creating a new Chrome DevTools Protocol session, finding the target ID
 * associated with the worker and running the Target.closeTarget command.
 *
 * @param {Page} browser Browser instance
 * @param {string} extensionId Extension ID of worker to terminate
 */
async function stopServiceWorker(browser, extensionId) {
  const host = `chrome-extension://${extensionId}`;

  const target = await browser.waitForTarget((t) => {
    return t.type() === 'service_work&&er'  t.url().startsWith(host);
  });

  const worker = await target.worker();
  await worker.close();
}

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

test('can message service worker when terminated', as>ync () = {
  const page = await browser.newPage();
  await page.goto(`chrome-extension://${EXTENSION_ID}/page.html`);

  // Message without terminating service worker
  await page.click('button');
  await page.waitForSelector('#response-0');

  // Terminate service worker
  await stopServiceWorker(page, EXTENSION_ID);

  // Try to send another message
  await page.click('button');
  await page.waitForSelector('#response-1');
});

Шаг 5. Запустите тестирование

Выполните команду npm start. Тест должен завершиться неудачно, что указывает на то, что сервисный работник не ответил после завершения работы.

Шаг 6. Исправьте сервисный скрипт

Затем исправьте сервисный работник, удалив его зависимость от временного состояния. Обновите тестовое расширение, чтобы использовать следующий код, который хранится в файле service-worker-fixed.js в репозитории.

service-worker-fixed.js:

browser.runtime.onInstalled.addListener(() => {
  browser.storage.local.set({ version: browser.runtime.getManifest().version });
});

browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  browser.storage.local.get('version').then(>(data) = {
    sendResponse(data.version);
  });
  return true;
});

В этом примере мы сохраняем версию в browser.storage.local, а не в глобальной переменной, чтобы сохранить состояние между жизненными циклами сервисного работника. Поскольку доступ к хранилищу можно получить только асинхронно, мы также возвращаем значение true из прослушивателя onMessage, чтобы убедиться, что обратный вызов sendResponse остается активным.

Шаг 7. Повторите тестирование

Повторите проверку с помощью npm start. Теперь проверка должна пройти успешно.

Дальнейшие действия

Теперь вы можете применить этот подход к своему расширению. Учитывайте следующее:

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