Tester l'arrêt d'un service worker avec Puppeteer

Ce guide explique comment créer des extensions plus robustes en testant l'arrêt d'un service worker à l'aide de Puppeteer. Il est important de vous préparer à gérer l'arrêt à tout moment, car cela peut se produire sans avertissement préalable, ce qui entraîne la perte d'un état non persistant dans le service worker. Par conséquent, les extensions doivent enregistrer un état important et pouvoir traiter les requêtes dès qu'elles sont redémarrées en cas d'événement à gérer.

Avant de commencer

Clonez ou téléchargez le dépôt chrome-extensions-samples. Nous allons utiliser l'extension de test dans /functional-samples/tutorial.terminate-sw/test-extension, qui envoie un message au service worker chaque fois que l'utilisateur clique sur un bouton et ajoute du texte à la page si une réponse est reçue.

Vous devez également installer Node.JS, l'environnement d'exécution sur lequel Puppeteer repose.

Étape 1: Démarrez votre projet Node.js

Créez les fichiers suivants dans un nouveau répertoire. Ensemble, ils créent un projet Node.js et fournissent la structure de base d'une suite de tests Puppeteer en utilisant Jest comme exécuteur de test. Pour en savoir plus sur cette configuration, consultez Tester les extensions Chrome avec Puppeteer.

package.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"
  }
}

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,
    args: [
      `--disable-extensions-except=${EXTENSION_PATH}`,
      `--load-extension=${EXTENSION_PATH}`
    ]
  });
});

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

Notez que notre test charge test-extension à partir du dépôt d'exemples. Le gestionnaire de chrome.runtime.onMessage repose sur l'état défini dans celui-ci pour l'événement chrome.runtime.onInstalled. Par conséquent, le contenu de data sera perdu lorsque le service worker sera arrêté, et la réponse aux futurs messages échouera. Nous allons résoudre ce problème après avoir écrit notre test.

service-worker-broken.js:

let data;

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

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

Étape 2: Installez les dépendances

Exécutez npm install pour installer les dépendances requises.

Étape 3: Écrivez un test de base

Ajoutez le test suivant en bas de index.test.js. La page de test de notre extension de test s'ouvre, clique sur l'élément de bouton et attend la réponse du service worker.

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');
});

Vous pouvez exécuter votre test avec npm start. Il devrait se terminer correctement.

Étape 4: Arrêter le service worker

Ajoutez la fonction d'assistance suivante pour arrêter votre service worker:

/**
 * 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();
}

Enfin, mettez à jour votre test avec le code suivant. À présent, arrêtez le service worker et cliquez à nouveau sur le bouton pour vérifier que vous avez reçu une réponse.

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');
});

Étape 5: Exécutez le test

Exécutez npm start. Votre test doit échouer, ce qui indique que le service worker n'a pas répondu après l'arrêt.

Étape 6: Corrigez le service worker

Ensuite, corrigez le service worker en supprimant la dépendance à l'état temporaire. Mettez à jour l'extension de test pour utiliser le code suivant, stocké dans service-worker-fixed.js du dépôt.

service-worker-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;
});

Ici, nous enregistrons la version dans chrome.storage.local au lieu d'une variable globale pour conserver l'état entre la durée de vie des service workers. Comme le stockage n'est accessible que de manière asynchrone, nous renvoyons également la valeur "true" à partir de l'écouteur onMessage pour nous assurer que le rappel sendResponse reste actif.

Étape 7: Exécutez à nouveau le test

Exécutez à nouveau le test avec npm start. L'opération devrait maintenant s'effectuer.

Étapes suivantes

Vous pouvez désormais appliquer la même approche à votre propre extension. Réfléchissez aux éléments suivants :

  • Créez votre suite de tests pour permettre l'exécution avec ou sans arrêt inattendu d'un service worker. Vous pouvez ensuite exécuter les deux modes individuellement pour déterminer plus clairement la cause d'une défaillance.
  • Écrivez le code permettant d'arrêter le service worker à des moments aléatoires dans un test. Cela peut être un bon moyen de découvrir les problèmes qui peuvent être difficiles à prévoir.
  • Tirez des enseignements des échecs des tests et essayez de coder de manière défensive à l'avenir. Par exemple, ajoutez une règle d'analyse lint pour décourager l'utilisation de variables globales et essayer de faire passer les données à un état plus persistant.