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

Z tego przewodnika dowiesz się, jak tworzyć bardziej niezawodne rozszerzenia przez testowanie zakończenia działania skryptu service worker za pomocą Puppeteer. Przygotowanie się do obsługi zakończenia w dowolnym momencie jest ważne, ponieważ może się to zdarzyć bez ostrzeżenia, co spowoduje utratę nietrwałego stanu w instancji roboczej usługi. Dlatego rozszerzenia muszą zapisywać ważny stan i być w stanie obsługiwać żądania natychmiast po ponownym uruchomieniu, gdy pojawi się zdarzenie do przetworzenia.

Zanim zaczniesz

Skopiuj lub pobierz repozytorium chrome-extensions-samples. Wykorzystamy rozszerzenie testowe w komponencie /functional-samples/tutorial.terminate-sw/test-extension, które wysyła wiadomość do skryptu service worker po każdym kliknięciu przycisku oraz dodaje tekst do strony po otrzymaniu odpowiedzi.

Musisz też zainstalować Node.JS – środowisko wykonawcze, na którym działa Puppeteer.

Krok 1. Uruchom projekt Node.js

Utwórz następujące pliki w nowym katalogu. Wspólnie tworzą nowy projekt Node.js i udostępniają podstawową strukturę pakietu testowego Puppeteer przy użyciu Jest w trybie uruchamiania testów. Więcej informacji o tej konfiguracji znajdziesz w artykule Testowanie rozszerzeń do Chrome przy użyciu Puppeteer.

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

Zwróć uwagę, że nasz test wczytuje test-extension z repozytorium przykładów. Moduł obsługi zdarzenia chrome.runtime.onMessage korzysta ze stanu ustawionego w module obsługi zdarzenia chrome.runtime.onInstalled. W związku z tym zawartość pliku data zostanie utracona po zamknięciu skryptu service worker, a odpowiedzi na kolejne wiadomości zakończą się niepowodzeniem. Naprawimy to po napisaniu testu.

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

Krok 2. Zainstaluj zależności

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

Krok 3. Napisz test podstawowy

Dodaj poniższy test na końcu elementu index.test.js. Otwiera to stronę testową z naszego rozszerzenia testowego, klika przycisk i oczekuje na odpowiedź od 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 przeprowadzić test z użyciem instancji npm start i sprawdzić, czy wszystko się uda.

Krok 4. Zakończ skrypt service worker

Dodaj następującą funkcję pomocniczą, która zamyka skrypt 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 skrypt service worker 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. Uruchom test

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

Krok 6. Napraw skrypt service worker

Następnie napraw skrypt service worker, usuwając jego zależność od stanu tymczasowego. Zaktualizuj rozszerzenie testowe, aby używać podanego niżej kodu, który jest przechowywany w repozytorium service-worker-fixed.js.

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

Tutaj zapisujemy wersję w tabeli chrome.storage.local, a nie w zmiennej globalnej, aby zachować stan między kolejnymi okresami działania mechanizmu Service Worker. Dostęp do pamięci masowej jest możliwy tylko asynchronicznie, dlatego zwracamy też wartość „prawda” z detektora onMessage, aby zapewnić aktywność wywołania zwrotnego sendResponse.

Krok 7. Ponownie uruchom test

Ponownie uruchom test, używając pakietu npm start. Powinno teraz przejść weryfikację.

Dalsze kroki

Teraz możesz zastosować takie samo podejście do własnego rozszerzenia. Weź pod uwagę te kwestie:

  • Utwórz pakiet testowy tak, aby umożliwić działanie z nieoczekiwanym zamknięciem skryptu service worker lub bez niego. Następnie możesz uruchomić oba tryby pojedynczo, aby łatwiej było zrozumieć, co spowodowało awarię.
  • Napisz kod powodujący zakończenie działania skryptu service worker w losowych punktach testu. Może to być dobry sposób na wykrywanie problemów, których trudno jest przewidzieć.
  • Ucz się na błędach testów i rozwijaj kod defensywny w przyszłości. Na przykład dodaj regułę lintowania, która odradza używanie zmiennych globalnych i spróbuj przenieść dane do bardziej trwałego stanu.