Testar o encerramento do service worker com o Puppeteer

Neste guia, explicamos como criar extensões mais robustas por meio do teste do encerramento do service worker usando o Puppeteer. É importante estar preparado para lidar com o encerramento a qualquer momento, porque isso pode acontecer sem aviso e resulta na perda de qualquer estado não persistente no service worker. Consequentemente, as extensões precisam salvar um estado importante e ser capaz de processar solicitações assim que são iniciadas novamente quando há um evento para processar.

Antes de começar

Clone ou faça o download do repositório chrome-extensions-samples. Usaremos a extensão de teste em /functional-samples/tutorial.terminate-sw/test-extension, que envia uma mensagem ao service worker sempre que um botão é clicado e adiciona texto à página se uma resposta é recebida.

Também será preciso instalar o Node.JS, que é o ambiente de execução em que o Puppeteer foi criado.

Etapa 1: iniciar o projeto Node.js

Crie os arquivos a seguir em um novo diretório. Juntos, eles criam um novo projeto Node.js e fornecem a estrutura básica de um conjunto de testes do Puppeteer usando o Jest como um executor de testes. Consulte Testar extensões do Chrome com o Puppeteer para saber mais sobre essa configuração.

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

O teste carrega o test-extension do repositório de exemplos. O gerenciador de chrome.runtime.onMessage depende do estado definido no gerenciador para o evento chrome.runtime.onInstalled. Como resultado, o conteúdo de data será perdido quando o service worker for encerrado e a resposta a qualquer mensagem futura falhar. Vamos corrigir isso depois de criar o teste.

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

Etapa 2: instalar dependências

Execute npm install para instalar as dependências necessárias.

Etapa 3: criar um teste básico

Adicione o teste abaixo à parte de baixo de index.test.js. Isso abre a página de teste da nossa extensão de teste, clica no elemento de botão e aguarda uma resposta do 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');
});

Execute o teste com npm start e confira se ele foi concluído.

Etapa 4: encerrar o service worker

Adicione a seguinte função auxiliar que encerra seu 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 fim, atualize o teste com o código a seguir. Encerre o service worker e clique no botão novamente para verificar se você recebeu uma resposta.

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

Etapa 5: executar o teste

Execute npm start. O teste falhará, o que indica que o service worker não respondeu após o encerramento.

Etapa 6: corrigir o service worker

Em seguida, corrija o service worker removendo a dependência dele no estado temporário. Atualize a extensão de teste para usar o código a seguir, que é armazenado em service-worker-fixed.js no repositório.

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

Aqui, salvamos a versão em chrome.storage.local em vez de uma variável global para manter o estado entre os ciclos de vida do service worker. Como o armazenamento só pode ser acessado de forma assíncrona, também retornamos verdadeiro do listener onMessage para garantir que o callback sendResponse permaneça ativo.

Etapa 7: executar o teste novamente

Execute o teste novamente com npm start. Agora ele deve passar.

Próximas etapas

Agora você pode aplicar a mesma abordagem à sua própria extensão. Considere o seguinte:

  • Crie seu pacote de testes para que seja compatível com a execução com ou sem o encerramento inesperado do service worker. É possível executar os dois modos individualmente para deixar mais claro o que causou uma falha.
  • Escreva o código para encerrar o service worker em pontos aleatórios dentro de um teste. Essa pode ser uma boa maneira de descobrir problemas que podem ser difíceis de prever.
  • Aprenda com falhas de teste e tente programar de maneira defensiva no futuro. Por exemplo, adicione uma regra de inspeção para desencorajar o uso de variáveis globais e tente mover os dados para um estado mais persistente.