Завершение работы тестового сервиса с помощью Puppeteer

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

Прежде чем ты начнешь

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

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

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

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

пакет.json:

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

индекс.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,
    args: [
      `--disable-extensions-except=${EXTENSION_PATH}`,
      `--load-extension=${EXTENSION_PATH}`
    ]
  });
});

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

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

сервис-работник-сломанный.js:

let data;

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

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

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

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

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

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

test('can message service worker', async () => {
  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_worker' && t.url().startsWith(host);
  });

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

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

test('can message service worker when terminated', async () => {
  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 в репозитории.

сервис-работник-fixed.js:

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

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

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

Шаг 7. Запустите тест еще раз

Запустите тест еще раз с помощью npm start . Теперь это должно пройти.

Следующие шаги

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

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