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

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

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

Клонируйте или скачайте репозиторий chrome-extensions-samples . Мы будем использовать расширение test из /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', 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 в репозитории.

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 . Теперь он должен пройти успешно.

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

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

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