En esta guía, se explica cómo compilar extensiones más sólidas probando la finalización del service worker con Puppeteer. Es importante estar preparado para controlar la finalización en cualquier momento, ya que esto puede ocurrir sin previo aviso, lo que provoca que se pierda cualquier estado no persistente en el service worker. Por lo tanto, las extensiones deben guardar el estado importante y poder controlar las solicitudes en cuanto se reinicien cuando haya un evento que controlar.
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 service worker 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 tiempo 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 nuevo proyecto de Node.js 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 detalles sobre esta configuración.
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;
});
Ten en cuenta que nuestra prueba carga el test-extension desde el repositorio de muestras.
El controlador de browser.runtime.onMessage depende del estado establecido en el controlador del evento browser.runtime.onInstalled. Como resultado, el contenido de data se perderá cuando finalice el service worker y fallará la respuesta a cualquier mensaje futuro. Lo corregiremos después de escribir nuestra prueba.
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);
});
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. Esto abre la página de prueba de nuestra extensión de prueba, hace clic en el elemento de 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 service worker
Agrega la siguiente función auxiliar que finaliza tu 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();
}
Por último, actualiza tu prueba con el siguiente código. Ahora, finaliza el service worker y vuelve a hacer clic en el botón para verificar 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 service worker no respondió después de que se detuvo.
Paso 6: Corrige el service worker
A continuación, corrige el service worker quitándole 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:
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;
});
Aquí, guardamos la versión en browser.storage.local en lugar de una variable global para conservar el estado entre los ciclos de vida del service worker. Dado que solo se puede acceder al almacenamiento de forma asíncrona, también devolvemos verdadero desde el objeto de escucha onMessage para asegurarnos de que la devolución de llamada sendResponse permanezca activa.
Paso 7: Vuelve a ejecutar la prueba
Vuelve a ejecutar la prueba con npm start. Ahora debería pasar.
Próximos pasos
Ahora puedes aplicar el mismo enfoque a tu propia extensión. Ten en cuenta lo siguiente:
- Crea tu paquete de pruebas para que admita la ejecución con o sin la finalización inesperada del service worker. Luego, puedes ejecutar cada modo de forma individual para que quede más claro qué causó la falla.
- Escribe 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 los errores de las pruebas y trata de programar de forma defensiva en el futuro. Por ejemplo, agrega una regla de análisis estático para desalentar el uso de variables globales y trata de trasladar los datos a un estado más persistente.