Prueba la finalización de service worker con Puppeteer

En esta guía, se explica cómo crear extensiones más sólidas mediante la prueba del servicio y la finalización de los trabajadores con Puppeteer. Es importante estar preparado para controlar la finalización en cualquier momento, ya que esto puede ocurrir sin advertencia, lo que provocará la pérdida de cualquier estado no persistente en el trabajador de servicio. Por lo tanto, las extensiones deben guardar el estado importante y las solicitudes se inician de nuevo cuando evento que manejar.

Antes de comenzar

Clona o descarga el repositorio chrome-extensions-samples. Usaremos la extensión de prueba en /functional-samples/tutorial.terminate-sw/test-extension, que envía un mensaje al trabajador de servicio cada vez que se hace clic en un botón y agrega texto a la página si se recibe una respuesta.

También deberás instalar Node.JS, que es el entorno de ejecución en el que se basa Puppeteer.

Paso 1: Inicia tu proyecto de Node.js

Crea los siguientes archivos en un directorio nuevo. Juntos, crean un proyecto de Node.js nuevo y proporcionan la estructura básica de un paquete de pruebas de Puppeteer con Jest como ejecutor de pruebas. Consulta Cómo probar extensiones de Chrome con Puppeteer para obtener más información sobre esta configuración.

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

Ten en cuenta que nuestra prueba carga test-extension desde el repositorio de muestras. El controlador de chrome.runtime.onMessage se basa en el estado establecido en el controlador para el evento chrome.runtime.onInstalled. Como resultado, el contenido de data se perderán cuando el service worker finalice y responda los mensajes fallarán. Corregiremos esto después de escribir la prueba.

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

Paso 2: Instala las dependencias

Ejecuta npm install para instalar las dependencias requeridas.

Paso 3: Escribe una prueba básica

Agrega la siguiente prueba a la parte inferior de index.test.js. Se abrirá la prueba de nuestra extensión de prueba, hace clic en el elemento del botón y espera una respuesta. del 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');
});

Puedes ejecutar tu prueba con npm start y deberías ver que se completa correctamente.

Paso 4: Finaliza el trabajador de servicio

Agrega la siguiente función auxiliar que finaliza tu trabajador de servicio:

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

Por último, actualiza la prueba con el siguiente código. Ahora finaliza el servicio y vuelve a hacer clic en el botón para comprobar que recibiste una respuesta.

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

Paso 5: Ejecuta la prueba

Ejecuta npm start. La prueba debería fallar, lo que indica que el trabajador de servicio no respondió después de que se cerró.

Paso 6: Corrige el service worker

A continuación, corrige el trabajador de servicio quitando su dependencia del estado temporal. Actualiza la extensión de prueba para usar el siguiente código, que se almacena en service-worker-fixed.js en el repositorio.

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

Aquí, guardamos la versión en chrome.storage.local en lugar de una variable global para conservar el estado entre los ciclos de vida del trabajador de servicio. Como solo se puede acceder al almacenamiento de forma asíncrona, también mostramos "true" desde el objeto de escucha onMessage para asegurarnos de que la devolución de llamada de sendResponse permanezca activa.

Paso 7: Vuelve a ejecutar la prueba

Vuelve a ejecutar la prueba con npm start. Ahora debería aprobarse.

Próximos pasos

Ahora puedes aplicar el mismo enfoque a tu propia extensión. Ten en cuenta lo siguiente:

  • Compila tu suite de pruebas para admitir la ejecución con o sin el cierre inesperado de un trabajador del servicio. Luego, puedes ejecutar ambos modos de forma individual para aclarar cuál fue la causa de la falla.
  • Escribir código para finalizar el service worker en puntos aleatorios dentro de una prueba. Esta puede ser una buena manera de descubrir problemas que pueden ser difíciles de predecir.
  • Aprende de las pruebas fallidas e intenta programar de forma defensiva en el futuro. Para ejemplo, agrega una regla de análisis con lint para desalentar el uso de variables globales y trata de mover los datos a un estado más persistente.