Service-Worker-Beendigung mit Puppeteer testen

In dieser Anleitung wird erläutert, wie Sie robustere Erweiterungen erstellen, indem Sie die Beendigung von Service-Workern mit Puppeteer testen. Es ist wichtig, jederzeit auf eine Beendigung vorbereitet zu sein, da dies ohne Vorwarnung passieren kann, was dazu führt, dass ein nicht dauerhafter Zustand im Service Worker verloren geht. Erweiterungen müssen daher wichtige Status speichern und Anfragen verarbeiten können, sobald sie neu gestartet werden, wenn ein Ereignis verarbeitet werden muss.

Vorbereitung

Klonen Sie das Repository chrome-extensions-sample oder laden Sie es herunter. Wir verwenden die Testerweiterung in /functional-samples/tutorial.terminate-sw/test-extension, die bei jedem Klick auf eine Schaltfläche eine Nachricht an den Service Worker sendet und der Seite Text hinzufügt, wenn eine Antwort eingeht.

Außerdem müssen Sie Node.JS installieren. Das ist die Laufzeit, auf der Puppeteer basiert.

Schritt 1: Node.js-Projekt starten

Erstellen Sie die folgenden Dateien in einem neuen Verzeichnis. Gemeinsam erstellen sie ein neues Node.js-Projekt und stellen die Grundstruktur einer Puppeteer-Testsuite bereit, die Jest als Test-Runner verwendet. Weitere Informationen zu dieser Einrichtung finden Sie unter Chrome-Erweiterungen mit Puppeteer testen.

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

Beachten Sie, dass bei unserem Test das test-extension aus dem Beispiel-Repository geladen wird. Der Handler für chrome.runtime.onMessage stützt sich auf den Status, der im Handler für das chrome.runtime.onInstalled-Ereignis festgelegt wurde. Daher geht der Inhalt von data verloren, wenn der Service Worker beendet wird und Sie keine weiteren Nachrichten mehr beantworten können. Wir werden dieses Problem beheben, nachdem wir unseren Test geschrieben haben.

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

Schritt 2: Abhängigkeiten installieren

Führen Sie npm install aus, um die erforderlichen Abhängigkeiten zu installieren.

Schritt 3: Einfachen Test schreiben

Fügen Sie am Ende von index.test.js den folgenden Test hinzu. Daraufhin wird die Testseite der Testerweiterung geöffnet, auf das Schaltflächenelement geklickt und auf eine Antwort des Service Workers gewartet.

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

Sie können den Test mit npm start ausführen. Sie sollten sehen, dass er erfolgreich abgeschlossen wurde.

Schritt 4: Service Worker beenden

Fügen Sie die folgende Hilfsfunktion hinzu, die Ihren Service Worker beendet:

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

Aktualisieren Sie Ihren Test schließlich mit dem folgenden Code. Beenden Sie nun den Service Worker und klicken Sie noch einmal auf die Schaltfläche, um zu prüfen, ob Sie eine Antwort erhalten haben.

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

Schritt 5: Test ausführen

Führen Sie npm start aus. Der Test sollte fehlschlagen. Dies weist darauf hin, dass der Service Worker nach der Beendigung nicht reagiert hat.

Schritt 6: Fehler beim Service Worker beheben

Beheben Sie als Nächstes den Fehler des Service Workers, indem Sie seine Abhängigkeit vom temporären Status entfernen. Aktualisieren Sie die Testerweiterung mit dem folgenden Code, der im Repository in service-worker-fixed.js gespeichert ist.

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

Hier wird die Version unter chrome.storage.local und nicht in einer globalen Variablen gespeichert, um den Status zwischen den Service-Worker-Lebensdauer beizubehalten. Da der Zugriff auf den Speicher nur asynchron möglich ist, wird auch „true“ vom Listener onMessage zurückgegeben, damit der sendResponse-Callback aktiv bleibt.

Schritt 7: Test noch einmal ausführen

Führe den Test mit npm start noch einmal aus. Sie sollte nun bestehen.

Nächste Schritte

Sie können diesen Ansatz jetzt auch auf Ihre eigene Erweiterung anwenden. Beachten Sie dabei Folgendes:

  • Erstellen Sie Ihre Testsuite, um die Ausführung mit oder ohne unerwartete Beendigung von Service-Workern zu unterstützen. Sie können dann beide Modi einzeln ausführen, um die Fehlerursache zu verdeutlichen.
  • Schreiben Sie Code, um den Service Worker an zufälligen Punkten innerhalb eines Tests zu beenden. Dies ist eine gute Möglichkeit, Probleme zu erkennen, die sich nur schwer vorhersagen lassen.
  • Lernen Sie aus Testfehlern und versuchen Sie, in Zukunft defensiv zu programmieren. Fügen Sie beispielsweise eine Linting-Regel hinzu, um die Verwendung globaler Variablen zu verhindern und Daten in einen persistenten Zustand zu verschieben.