Zakończenie działania skryptu service worker za pomocą Puppeteer

Z tego przewodnika dowiesz się, jak tworzyć bardziej niezawodne rozszerzenia, testując zakończenie działania service workera za pomocą Puppeteer. Przygotowanie na zakończenie w dowolnym momencie jest ważne, ponieważ może się to zdarzyć bez ostrzeżenia, co spowoduje utratę każdego nietrwałego stanu w usłudze Service Worker. W związku z tym rozszerzenia muszą zapisywać ważne stany i być w stanie obsługiwać żądania od razu po ponownym uruchomieniu, gdy wystąpi zdarzenie do obsłużenia.

Zanim rozpoczniesz

Sklonuj lub pobierz repozytorium chrome-extensions-samples. Użyjemy rozszerzenia testowego w /functional-samples/tutorial.terminate-sw/test-extension, które wysyła wiadomość do skryptu service worker za każdym razem, gdy klikniesz przycisk, i dodaje tekst do strony, jeśli otrzyma odpowiedź.

Musisz też zainstalować Node.JS, czyli środowisko wykonawcze, na którym opiera się Puppeteer.

Krok 1. Rozpocznij projekt Node.js

Utwórz w nowym katalogu te pliki: Razem tworzą nowy projekt Node.js i zapewniają podstawową strukturę pakietu testów Puppeteer, używając Jest jako narzędzia do uruchamiania testów. Więcej informacji o tej konfiguracji znajdziesz w artykule Testowanie rozszerzeń Chrome za pomocą Puppeteer.

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

Zwróć uwagę, że nasz test wczytuje test-extension z repozytorium z przykładami. Moduł obsługi browser.runtime.onMessage korzysta ze stanu ustawionego w module obsługi zdarzenia browser.runtime.onInstalled. W związku z tym zawartość data zostanie utracona po zakończeniu działania skryptu service worker, a odpowiadanie na przyszłe wiadomości nie będzie możliwe. Naprawimy to po napisaniu testu.

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

Krok 2. Zainstaluj zależności

Uruchom npm install, aby zainstalować wymagane zależności.

Krok 3. Napisz podstawowy test

Dodaj ten test na końcu pliku index.test.js. Otwiera to stronę testową z naszego rozszerzenia testowego, klika element przycisku i czeka na odpowiedź ze skryptu 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');
});

Możesz uruchomić test za pomocą polecenia npm start. Powinien on zakończyć się pomyślnie.

Krok 4. Zakończ działanie skryptu service worker

Dodaj tę funkcję pomocniczą, która kończy działanie skryptu 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();
}

Na koniec zaktualizuj test za pomocą tego kodu. Teraz zakończ działanie service workera i ponownie kliknij przycisk, aby sprawdzić, czy otrzymano odpowiedź.

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

Krok 5. Przeprowadź test

Uruchom npm start. Test powinien zakończyć się niepowodzeniem, co oznacza, że po zakończeniu działania skrypt service worker nie odpowiedział.

Krok 6. Napraw skrypt service worker

Następnie napraw usługę Service Worker, usuwając jej zależność od stanu tymczasowego. Zaktualizuj rozszerzenie testowe, aby używało tego kodu, który jest przechowywany w pliku service-worker-fixed.js w repozytorium.

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

W tym przypadku zapisujemy wersję w browser.storage.local zamiast w zmiennej globalnej, aby zachować stan między okresami istnienia skryptu service worker. Dostęp do pamięci może być tylko asynchroniczny, więc zwracamy też wartość „true” z detektora onMessage, aby mieć pewność, że wywołanie zwrotne sendResponse pozostanie aktywne.

Krok 7. Ponownie uruchom test

Uruchom test ponownie za pomocą polecenia npm start. Powinien teraz przejść.

Dalsze kroki

Możesz teraz zastosować to samo podejście w przypadku własnego rozszerzenia. Weź pod uwagę te kwestie:

  • Zbuduj zestaw testów, który będzie obsługiwał uruchamianie z nieoczekiwanym zakończeniem działania service workera lub bez niego. Możesz wtedy uruchomić oba tryby osobno, aby lepiej zrozumieć, co spowodowało błąd.
  • Napisz kod, który będzie losowo kończyć działanie skryptu service worker w trakcie testu. Może to być dobry sposób na wykrycie problemów, które trudno przewidzieć.
  • Wyciągaj wnioski z nieudanych testów i w przyszłości staraj się pisać kod w sposób defensywny. Możesz na przykład dodać regułę lintingu, która będzie zniechęcać do używania zmiennych globalnych i przenosić dane do bardziej trwałego stanu.