Beëindiging van servicemedewerkers testen met Puppeteer

Deze handleiding legt uit hoe je robuustere extensies kunt bouwen door het beëindigen van service workers te testen met Puppeteer. Het is belangrijk om voorbereid te zijn op beëindiging op elk moment, omdat dit zonder waarschuwing kan gebeuren en daardoor alle niet-persistente statusinformatie in de service worker verloren kan gaan. Daarom moeten extensies belangrijke statusinformatie opslaan en in staat zijn om verzoeken af ​​te handelen zodra ze opnieuw worden opgestart en er een gebeurtenis is om te verwerken.

Voordat je begint

Kloon of download de chrome-extensions-samples repository. We gebruiken de testextensie in /functional-samples/tutorial.terminate-sw/test-extension die een bericht naar de service worker stuurt telkens wanneer er op een knop wordt geklikt en tekst aan de pagina toevoegt als er een reactie wordt ontvangen.

Je moet ook Node.JS installeren, de runtime waarop Puppeteer is gebouwd.

Stap 1: Start je Node.js-project

Maak de volgende bestanden aan in een nieuwe map. Samen vormen ze een nieuw Node.js-project en bieden ze de basisstructuur van een Puppeteer-testsuite met Jest als testrunner. Zie Test Chrome-extensies met Puppeteer voor meer informatie over deze configuratie.

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

Merk op dat onze test de test-extension laadt vanuit de voorbeeldrepository. De handler voor browser.runtime.onMessage is afhankelijk van de status die is ingesteld in de handler voor de browser.runtime.onInstalled gebeurtenis. Hierdoor gaat de inhoud van data verloren wanneer de service worker wordt beëindigd en zal het reageren op toekomstige berichten mislukken. We zullen dit oplossen nadat we onze test hebben geschreven.

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

Stap 2: Installeer de afhankelijkheden

Voer npm install om de benodigde afhankelijkheden te installeren.

Stap 3: Schrijf een eenvoudige test

Voeg de volgende test toe aan het einde van index.test.js . Deze test opent de testpagina vanuit onze testextensie, klikt op het knopelement en wacht op een reactie van de 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');
});

Je kunt je test uitvoeren met npm start en zou moeten zien dat deze succesvol wordt afgerond.

Stap 4: Beëindig de servicemedewerker

Voeg de volgende hulpfunctie toe die uw service worker beëindigt:

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

Werk tot slot je test bij met de volgende code. Beëindig nu de service worker en klik opnieuw op de knop om te controleren of je een reactie hebt ontvangen.

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

Stap 5: Voer uw test uit

Voer npm start uit. Je test zou moeten mislukken, wat aangeeft dat de service worker niet reageerde nadat deze was beëindigd.

Stap 6: Herstel de service worker

Vervolgens moet de service worker worden gecorrigeerd door de afhankelijkheid van tijdelijke status te verwijderen. Werk de test-extensie bij met de volgende code, die te vinden is in het bestand service-worker-fixed.js in de repository.

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

Hier slaan we de versie op in browser.storage.local in plaats van een globale variabele, zodat de status behouden blijft tussen de levenscycli van de service workers. Omdat de opslag alleen asynchroon toegankelijk is, retourneren we ook `true` vanuit de onMessage listener om ervoor te zorgen dat de sendResponse callback actief blijft.

Stap 7: Voer uw test opnieuw uit.

Voer de test opnieuw uit met npm start . Deze zou nu moeten slagen.

Volgende stappen

Je kunt nu dezelfde aanpak toepassen op je eigen extensie. Overweeg het volgende:

  • Bouw je testsuite zo dat deze zowel met als zonder onverwachte beëindiging van de service worker kan worden uitgevoerd. Je kunt vervolgens beide modi afzonderlijk uitvoeren om duidelijker te krijgen wat de oorzaak van een storing is.
  • Schrijf code om de service worker op willekeurige momenten tijdens een test te beëindigen. Dit kan een goede manier zijn om problemen te ontdekken die moeilijk te voorspellen zijn.
  • Leer van testfouten en probeer in de toekomst defensiever te programmeren. Voeg bijvoorbeeld een linterregel toe om het gebruik van globale variabelen te ontmoedigen en probeer gegevens in een meer permanente opslag te bewaren.