Testa la terminazione del service worker con Puppeteer

Questa guida spiega come creare estensioni più efficaci testando la terminazione dei worker del servizio tramite Puppeteer. È importante essere preparati a gestire la terminazione in qualsiasi momento, perché questo può accadere senza avviso, con la conseguente perdita di uno stato non persistente del service worker. Di conseguenza, le estensioni devono salvare lo stato importante ed essere in grado di gestire le richieste non appena vengono riavviate quando c'è un evento da gestire.

Prima di iniziare

Clona o scarica il repository chrome-extensions-samples. Utilizzeremo l'estensione di test in /functional-samples/tutorial.terminate-sw/test-extension, che invia un messaggio al service worker ogni volta che viene fatto clic su un pulsante e aggiunge testo alla pagina se viene ricevuta una risposta.

Dovrai anche installare Node.JS, che è il runtime su cui si basa Puppeteer.

Passaggio 1: avvia il progetto Node.js

Crea i seguenti file in una nuova directory. Insieme creano un nuovo progetto Node.js e forniscono la struttura di base di una suite di test Puppeteer utilizzando Jest come esecutore del test. Per scoprire di più su questa configurazione in modo più dettagliato, consulta la pagina Testare le estensioni di Chrome con 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;
});

Tieni presente che il nostro test carica test-extension dal repository di esempio. Il gestore per chrome.runtime.onMessage si basa sullo stato impostato nel gestore per l'evento chrome.runtime.onInstalled. Di conseguenza, i contenuti di data andranno persi all'arresto del service worker e a eventuali messaggi futuri non andrà a buon fine. Provvederemo a risolvere il problema dopo aver scritto il 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);
});

Passaggio 2: installa le dipendenze

Esegui npm install per installare le dipendenze richieste.

Passaggio 3: scrivi un test di base

Aggiungi il seguente test in fondo a index.test.js. Si apre la pagina di test dalla nostra estensione di test, fa clic sull'elemento pulsante e attende una risposta dal 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');
});

Puoi eseguire il test con npm start e dovresti vedere che è stato completato correttamente.

Passaggio 4: termina il service worker

Aggiungi la seguente funzione helper che termina il 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();
}

Infine, aggiorna il test con il codice seguente. Ora interrompi il worker del servizio e fai di nuovo clic sul pulsante per verificare di aver ricevuto una risposta.

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

Passaggio 5: esegui il test

Esegui npm start. Il test dovrebbe avere esito negativo, a indicare che il service worker non ha risposto dopo l'arresto.

Passaggio 6: correggi il service worker

A questo punto, correggi il service worker eliminando il suo ricorso allo stato temporaneo. Aggiorna l'estensione di test in modo da utilizzare il codice seguente, archiviato in service-worker-fixed.js nel repository.

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

Qui salviamo la versione in chrome.storage.local anziché in una variabile globale per mantenere in modo permanente lo stato tra le durate dei service worker. Poiché lo spazio di archiviazione è accessibile solo in modo asincrono, restituiamo anche true dal listener onMessage per assicurarci che il callback sendResponse rimanga attivo.

Passaggio 7: esegui di nuovo il test

Esegui di nuovo il test con npm start. Ora dovrebbe passare.

Passaggi successivi

Ora puoi applicare lo stesso approccio alla tua estensione. Considera quanto segue:

  • Crea la tua suite di test per supportare l'esecuzione con o senza chiusura imprevista dei Service worker. Puoi quindi eseguire entrambe le modalità singolarmente per chiarire meglio cosa ha causato un errore.
  • Scrivi il codice per terminare il service worker in punti casuali all'interno di un test. Questo può essere un buon modo per scoprire problemi difficili da prevedere.
  • Impara dagli errori di test e prova a programmare in modo difensivo in futuro. Ad esempio, aggiungi una regola di analisi tramite lint per scoraggiare l'uso di variabili globali e prova a spostare i dati in uno stato più permanente.