Service-Worker-Beendigung mit Puppeteer testen

In diesem Leitfaden wird erläutert, wie Sie robustere Erweiterungen erstellen, indem Sie die Beendigung von Service Workern mit Puppeteer testen. Es ist wichtig, jederzeit auf die Beendigung vorbereitet zu sein, da dies ohne Vorwarnung geschehen kann. In diesem Fall geht jeder nicht persistente Status im Service Worker verloren. Daher müssen Erweiterungen wichtige Statusinformationen speichern und Anfragen verarbeiten können, sobald sie wieder gestartet werden, wenn ein Ereignis zu verarbeiten ist.

Vorbereitung

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

Außerdem müssen Sie Node.JS installieren, 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 grundlegende Struktur einer Puppeteer-Testsuite mit Jest als Testrunner bereit. 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": "^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;
});

Beachten Sie, dass in unserem Test die test-extension aus dem Beispiel-Repository geladen wird. Der Handler für browser.runtime.onMessage basiert auf dem Status, der im Handler für das browser.runtime.onInstalled-Ereignis festgelegt ist. Daher gehen die Inhalte von data verloren, wenn der Service Worker beendet wird, und das Beantworten zukünftiger Nachrichten schlägt fehl. Wir werden das nach dem Schreiben unseres Tests beheben.

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

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 den folgenden Test unten in index.test.js ein. Dadurch wird die Testseite unserer Test-Erweiterung geöffnet, auf das Schaltflächen-Element 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. Er sollte erfolgreich abgeschlossen werden.

Schritt 4: Service Worker beenden

Fügen Sie die folgende Hilfsfunktion hinzu, die den 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 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. Ihr Test sollte fehlschlagen, was darauf hindeutet, dass der Service Worker nach dem Beenden nicht reagiert hat.

Schritt 6: Service Worker korrigieren

Als Nächstes müssen Sie den Service Worker korrigieren, indem Sie die Abhängigkeit vom temporären Status entfernen. Aktualisieren Sie die Test-Extension mit dem folgenden Code, der im Repository unter service-worker-fixed.js gespeichert ist.

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 speichern wir die Version in browser.storage.local anstelle einer globalen Variablen, um den Status zwischen den Lebenszyklen des Service Workers beizubehalten. Da auf den Speicher nur asynchron zugegriffen werden kann, geben wir auch „true“ vom onMessage-Listener zurück, damit der sendResponse-Callback aktiv bleibt.

Schritt 7: Test noch einmal ausführen

Führen Sie den Test noch einmal mit npm start aus. Der Test sollte jetzt bestanden werden.

Nächste Schritte

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

  • Erstellen Sie Ihre Testsuite so, dass sie mit oder ohne unerwarteten Service Worker-Beendigung ausgeführt werden kann. Anschließend können Sie beide Modi einzeln ausführen, um besser nachvollziehen zu können, was zu einem Fehler geführt hat.
  • Code schreiben, um den Service Worker an zufälligen Stellen in einem Test zu beenden. So lassen sich Probleme erkennen, die schwer vorherzusagen sind.
  • Lernen Sie aus Testfehlern und versuchen Sie, in Zukunft defensiver zu programmieren. Fügen Sie beispielsweise eine Linting-Regel hinzu, um die Verwendung globaler Variablen zu vermeiden, und versuchen Sie, Daten in einen persistenten Status zu verschieben.