Extensões do Chrome: a jornada da Eo' para testar a suspensão do service worker

Qual é o motivo do contato?

A transição do Manifest V2 para o Manifest V3 traz uma mudança fundamental. No Manifest V2, as extensões ficavam em uma página em segundo plano. As páginas em segundo plano gerenciavam a comunicação entre extensões e páginas da Web. O Manifest V3 usa service workers.

Neste post, abordamos o problema de testar workers de serviço de extensão. Em particular, vamos conferir como garantir que nosso produto funcione corretamente caso um worker de serviço seja suspenso.

Quem somos?

A eyeo é uma empresa dedicada a promover uma troca de valor on-line equilibrada e sustentável para usuários, navegadores, anunciantes e editores. Temos mais de 300 milhões de usuários globais que filtram anúncios e permitem a exibição de anúncios aceitáveis, um padrão de anúncio derivado de forma independente que determina se um anúncio é aceitável e não intrusivo.

Nossa equipe do Extension Engine oferece tecnologia de filtragem de anúncios que alimenta algumas das extensões de navegador de bloqueio de anúncios mais conhecidas do mercado, como o AdBlock e o Adblock Plus, com mais de 110 milhões de usuários em todo o mundo. Além disso, oferecemos essa tecnologia como uma biblioteca de código aberto, disponibilizando-a para outras extensões de navegador de filtragem de anúncios.

O que é um worker de serviço?

Os service workers de extensão são o manipulador de eventos central de uma extensão do navegador. Elas são executadas de forma independente em segundo plano. Em geral, isso não é um problema. Podemos fazer a maioria das coisas que precisamos fazer em uma página em segundo plano no novo service worker. No entanto, há algumas mudanças em comparação com as páginas de segundo plano:

  • Os workers de serviço são encerrados quando não estão em uso. Isso exige que permaneçam os estados do aplicativo em vez de depender de variáveis globais. Isso significa que todos os pontos de entrada no sistema precisam ser preparados para serem chamados antes que o sistema seja inicializado.
  • Os listeners de eventos precisam ser anexados antes de esperar por callbacks assíncronos. Os service workers suspensos ainda podem receber eventos aos quais estão inscritos. Se o listener do evento não estiver registrado na primeira vez do loop de eventos, ele não vai receber o evento se ele tiver ativado o worker de serviço.
  • A interrupção de inatividade pode interromper os timers antes da conclusão.

Quando os service workers são suspensos?

No Chrome 119, os service workers foram suspensos:

  • Depois de não receber eventos ou chamar APIs de extensão por 30 segundos.
  • Nunca, se as ferramentas para desenvolvedores estiverem abertas ou se você estiver usando uma biblioteca de testes baseada no ChromeDriver (consulte a solicitação de recurso).
  • Se você clicar em Parar em chrome://serviceworker-internals.

Para informações mais recentes, consulte Ciclo de vida dos service workers.

Por que testar isso é um problema?

Seria ideal ter orientações oficiais sobre "como testar service workers de maneira eficiente" ou exemplos de testes em funcionamento. Durante nossas aventuras no teste de service workers, enfrentamos alguns desafios:

  • Temos o estado na extensão de teste. Quando o service worker é interrompido, perdemos o estado e os eventos registrados dele. Como podemos manter os dados no fluxo de teste?
  • Se os workers de serviço puderem ser suspensos a qualquer momento, precisamos testar se todos os recursos funcionam se forem interrompidos.
  • Mesmo que apresentássemos um mecanismo nos testes que suspenda os workers de serviço aleatoriamente, não há uma API no navegador para suspender com facilidade. Pedimos à equipe do W3C para adicionar esse recurso, mas a conversa ainda está em andamento.

Como testar a suspensão do worker do serviço

Tentamos várias abordagens para acionar a suspensão do worker do serviço durante os testes:

Abordagem Problemas com a abordagem
Aguarde um período arbitrário (por exemplo, 30 segundos) Isso torna o teste lento e não confiável, especialmente ao executar vários testes. Ele não funciona ao usar o WebDriver, já que ele usa a API DevTools do Chrome, e o service worker não é suspenso quando o DevTools está aberto. Mesmo que pudéssemos ignorar isso, ainda precisaríamos verificar se o worker do serviço foi suspenso, e não temos uma maneira de fazer isso.
Executar um loop infinito no worker de serviço De acordo com a especificação, isso pode levar à rescisão, dependendo de como o navegador implementa essa funcionalidade. O Chrome não encerra o service worker nesse caso, então não podemos testar o cenário em que o service worker é suspenso.
Uma mensagem no worker de serviço para verificar se ele foi suspenso O envio de uma mensagem ativa o service worker. Isso pode ser usado para verificar se o service worker estava inativo, mas ele interrompe os resultados de testes que precisam fazer verificações imediatamente após suspender o service worker.
Encerrar o processo do worker do serviço usando chrome.processes.terminate() O service worker da extensão compartilha um processo com outras partes dela. Portanto, encerrar esse processo usando chrome.process.terminate() ou a GUI do gerenciador de processos do Chrome não mata apenas o service worker, mas também todas as páginas da extensão.

Fizemos um teste que verifica como o código responde à suspensão do worker de serviço, fazendo com que o Selenium WebDriver abra chrome://serviceworker-internals/ e clique no botão "stop" do worker de serviço.

Essa é a melhor opção até agora, mas não é ideal porque nossos testes Mocha (que são executados em uma página de extensão) não podem fazer isso por conta própria. Por isso, eles precisam se comunicar com nosso programa de nó do WebDriver. Isso significa que esses testes não podem ser executados usando apenas a extensão. Eles precisam ser acionados usando o Selenium WebDriver.

Este é um diagrama de como nos comunicamos com a API do navegador em diferentes fluxos e como a adição do mecanismo "suspender service workers" afeta isso.

Diagrama mostrando o fluxo de teste
Fluxo de teste com suspensão de service worker.

Em um novo fluxo que suspende os workers de serviço (azul), adicionamos o Selenium WebDriver para "clicar" na suspensão pela interface, o que aciona uma ação na API do navegador.

Vale a pena mencionar que houve um bug do Chrome em que fazer isso com o Selenium WebDriver impedia a inicialização do worker de serviço novamente. Isso foi corrigido no Chrome 116 e, felizmente, há uma solução alternativa: definir o Chrome para abrir o DevTools automaticamente em todas as guias faz com que o service worker seja iniciado corretamente.

Essa é a abordagem que estamos usando nos testes, embora não seja ideal, já que clicar no botão pode não ser uma API estável, e abrir as Ferramentas do desenvolvedor (para navegadores mais antigos) parece ter um custo de desempenho.

Como cobrimos toda a funcionalidade? Testes de fuzz

Depois de criar um mecanismo para testar a suspensão, tivemos que decidir como conectá-lo aos nossos conjuntos de testes de automação. Realizamos nossos testes padrão em um ambiente em que, antes de cada interação com a página em segundo plano, o service worker é suspenso pelo WebDriver clicando em Stop na página chrome://serviceworker-internals/.

Um exemplo de execução de teste de fuzz
Imagem apresentando a configuração atual dos testes.

Executamos a maioria dos testes, mas não todos, porque o mecanismo de suspensão não é totalmente estável e às vezes causa instabilidade. Além disso, a execução de todos os conjuntos de testes no modo de fuzz leva muito tempo. Em vez de cobrir todos os casos "semelhantes", escolhemos os caminhos mais importantes para testar no modo de fuzz. Vale a pena mencionar que, para executar testes funcionais no modo "fuzz", precisamos aumentar os tempos limite dos testes porque suspender e reiniciar os workers de serviço leva mais tempo.

Esses testes são valiosos como uma primeira passagem grosseira, que destaca muitos lugares em que o código falha, mas não necessariamente revela todas as maneiras sutis em que a suspensão do service worker pode causar falhas.

Esses tipos de testes são chamados de "testes de fuzz" internamente. Tradicionalmente, o teste de fuzz é quando você lança uma entrada inválida no programa e verifica se ele responde de maneira razoável ou, pelo menos, não falha. No nosso caso, a "entrada inválida" é o service worker suspenso a qualquer momento, e o "comportamento razoável" que esperamos é que a funcionalidade de filtragem de anúncios continue funcionando como antes. Essa não é uma entrada inválida, já que é um comportamento esperado no Manifest V3, mas seria inválido no Manifest V2.

Resumo

Os service workers são uma das maiores mudanças no Manifest V3 (além das regras declarativeNetRequest). A migração para o Manifesto V3 pode exigir muitas mudanças de código nas extensões do navegador e novas abordagens de teste. Além disso, os desenvolvedores de extensões com estado persistente precisam preparar as extensões para lidar com a suspensão inesperada de service workers de maneira adequada.

Infelizmente, não há uma API para processar a suspensão de uma maneira fácil que se encaixe no nosso caso de uso. Como queríamos testar a robustez da base de código da nossa extensão em relação aos mecanismos de suspensão em uma fase inicial, tivemos que trabalhar em torno disso. Outros desenvolvedores de extensões que enfrentam desafios semelhantes podem usar essa solução alternativa, que, embora demorada na fase de desenvolvimento e manutenção, vale a pena para garantir que nossas extensões funcionem em um ambiente em que os service workers são suspensos regularmente.

Embora já exista suporte básico para testar a suspensão de service workers, gostaríamos de ter um melhor suporte de plataforma para testar service workers nas extensões no futuro, já que isso poderia reduzir bastante os tempos de execução do teste e o esforço de manutenção.