Testa la terminazione del service worker con Puppeteer

Questa guida spiega come creare estensioni più affidabili utilizzando il servizio di test risoluzione dei lavoratori tramite Puppeteer. Essere preparati a gestire la risoluzione in qualsiasi l'ora è importante perché ciò può verificarsi senza preavviso, generando uno stato non persistente del Service worker che viene perso. Di conseguenza, le estensioni devono salvare lo stato importante e 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 del testo alla pagina se viene ricevuta una risposta.

Dovrai anche installare Node.JS, che è il runtime su cui è basato 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 test runner. Consulta Testa le estensioni di Chrome con Puppeteer per scoprire di più su questa configurazione in modo più dettagliato.

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 esempi. 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 quando il servizio worker verrà terminato e la risposta a eventuali messaggi futuri non andrà a buon fine. Lo risolveremo 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 della nostra estensione di test, fa clic sull'elemento del 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 viene completato correttamente.

Passaggio 4: termina il service worker

Aggiungi la seguente funzione helper che termina il tuo 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 termina il servizio worker 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, il che indica che il service worker non ha risposto dopo la chiusura.

Passaggio 6: correggi il service worker

Poi, correggi il worker del servizio rimuovendo la dipendenza dallo stato temporaneo. Aggiorna l'estensione di test in modo da utilizzare il seguente codice, memorizzato 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 lo stato tra le durate dei service worker. Poiché l'archiviazione può accessibile in modo asincrono, viene restituito anche true dal listener onMessage a assicurati 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 le seguenti:

  • Crea la suite di test in modo che supporti l'esecuzione con o senza interruzione imprevista dei worker di servizio. Puoi quindi eseguire entrambe le modalità singolarmente per renderlo più chiaro cosa ha causato l'errore.
  • Scrivi codice per terminare il servizio 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 scrivere codice in modo più sicuro in futuro. Ad esempio, aggiungi una regola di linting per scoraggiare l'uso di variabili globali e prova a spostare i dati in uno stato più persistente.