Extensiones de Chrome: El recorrido de Eyeo para probar la suspensión de los service worker

¿De qué se trata?

La transición de Manifest V2 a Manifest V3 incluye un cambio fundamental. En Manifest V2, las extensiones se encontraban en una página en segundo plano. Las páginas en segundo plano administraban la comunicación entre las extensiones y las páginas web. En su lugar, Manifest V3 usa service workers.

En esta publicación, analizamos el problema de probar los trabajadores de servicios de extensión. En particular, analizamos cómo asegurarnos de que nuestro producto funcione correctamente en caso de que se suspenda un trabajador de servicio.

¿Quiénes somos?

eyeo es una empresa dedicada a potenciar un intercambio de valor en línea equilibrado y sostenible para usuarios, navegadores, anunciantes y publicadores. Tenemos más de 300 millones de usuarios globales que filtran anuncios y permiten la publicación de Anuncios Aceptables, un estándar de anuncios independiente que determina si un anuncio es aceptable y no intrusivo.

Nuestro equipo de Extension Engine proporciona la tecnología de filtrado de anuncios que potencia algunas de las extensiones de navegador de bloqueo de anuncios más populares del mercado, como AdBlock y Adblock Plus, con más de 110 millones de usuarios en todo el mundo. Además, ofrecemos esta tecnología como una biblioteca de código abierto, lo que la pone a disposición de otras extensiones de navegadores de filtrado de anuncios.

¿Qué es un trabajador de servicio?

Los service workers de extensión son el controlador de eventos central de una extensión del navegador. Se ejecutan de forma independiente en segundo plano. En términos generales, esto está bien. Podemos hacer la mayoría de las tareas que necesitamos realizar en una página en segundo plano en el nuevo trabajador de servicio. Sin embargo, hay algunos cambios en comparación con las páginas en segundo plano:

  • Los trabajadores de servicio finalizan cuando no están en uso. Esto requiere que persistamos los estados de la aplicación en lugar de depender de variables globales. Esto significa que cualquier punto de entrada a nuestro sistema debe estar preparado para que se le llame antes de que se inicialice el sistema.
  • Los objetos de escucha de eventos deben adjuntarse antes de esperar a las devoluciones de llamada asíncronas. Los trabajadores del servicio suspendidos aún pueden recibir los eventos a los que se suscribieron. Si el objeto de escucha del evento no está registrado en el primer turno del bucle de eventos, no lo recibirá si ese evento activó el trabajador de servicio.
  • La finalización inactiva puede interrumpir los temporizadores antes de que se completen.

¿Cuándo se suspenden los trabajadores del servicio?

En el caso de Chrome 119, detectamos que los trabajadores del servicio están suspendidos:

  • Después de no recibir eventos ni llamar a las APIs de extensión durante 30 segundos.
  • Nunca si las herramientas para desarrolladores están abiertas o si usas una biblioteca de pruebas basada en ChromeDriver (consulta la solicitud de función).
  • Si haces clic en Detener en chrome://serviceworker-internals.

Para obtener información más reciente, consulta Ciclo de vida de los trabajadores de servicio.

¿Por qué es un problema probar esto?

Lo ideal sería tener una guía oficial sobre “cómo probar los trabajadores del servicio de forma eficiente” o ejemplos de pruebas que funcionen. Durante nuestras aventuras para probar los trabajadores del servicio, nos enfrentamos a algunos desafíos:

  • Tenemos estado en nuestra extensión de prueba. Cuando se detiene el trabajador de servicio, perdemos su estado y sus eventos registrados. ¿Cómo persistiríamos los datos en nuestro flujo de pruebas?
  • Si los trabajadores del servicio se pueden suspender en cualquier momento, debemos probar que todas las funciones funcionen si se interrumpen.
  • Incluso si introdujéramos un mecanismo en nuestras pruebas que suspendiera los trabajadores del servicio de forma aleatoria, no hay una API en el navegador para suspenderlo fácilmente. Le pedimos al equipo del W3C que agregue esta función, pero esa es una conversación en curso.

Cómo probar la suspensión del trabajador de servicio

Probamos varios enfoques para activar la suspensión del trabajador de servicio durante las pruebas:

Enfoque Problemas con el enfoque
Espera una cantidad de tiempo arbitraria (por ejemplo, 30 segundos). Esto hace que las pruebas sean lentas y poco confiables, especialmente cuando se ejecutan varias pruebas. No funciona cuando se usa WebDriver, ya que este usa la API de DevTools de Chrome, y el servicio trabajador no se suspende cuando DevTools está abierto. Incluso si pudiéramos omitirla, aún tendríamos que verificar si el trabajador de servicio se suspendió, y no tenemos forma de hacerlo.
Ejecuta un bucle infinito en el trabajador del servicio Según las especificaciones, esto puede provocar la cancelación, según cómo el navegador implemente esta funcionalidad. En este caso, Chrome no finaliza el servicio trabajador, por lo que no podemos probar la situación en la que se suspende.
Tener un mensaje en el trabajador del servicio para verificar si se suspendió El envío de un mensaje activa el trabajador de servicio. Esto se puede usar para comprobar si el trabajador de servicio estaba inactivo, pero interrumpe los resultados de las pruebas que deben realizar verificaciones inmediatamente después de suspender el trabajador de servicio.
Finaliza el proceso del trabajador de servicio con chrome.processes.terminate(). El service worker de la extensión comparte un proceso con otras partes de la extensión, por lo que finalizar este proceso con chrome.process.terminate() o la GUI del administrador de procesos de Chrome no solo finaliza el service worker, sino también las páginas de la extensión.

Terminamos con una prueba que verifica cómo nuestro código responde a la suspensión del trabajador de servicio haciendo que Selenium WebDriver abra chrome://serviceworker-internals/ y haga clic en el botón "detener" del trabajador de servicio.

Esta es la mejor opción hasta el momento, pero no es ideal porque nuestras pruebas de Mocha (que se ejecutan en una página de extensión) no pueden hacerlo por sí mismas, por lo que deben volver a comunicarse con nuestro programa de nodos de WebDriver. Esto significa que estas pruebas no se pueden ejecutar solo con la extensión; deben activarse con Selenium WebDriver.

Este es un diagrama de cómo nos comunicamos con la API del navegador a través de diferentes flujos y cómo la afecta agregar el mecanismo de "suspensión de trabajadores del servicio".

Diagrama que muestra el flujo de pruebas
Flujo de prueba con suspensión del servicio de trabajo.

En un nuevo flujo que suspende los service workers (en azul), agregamos Selenium WebDriver para hacer clic en la suspensión a través de la IU, lo que activa una acción en la API del navegador.

Vale la pena mencionar que hubo un error de Chrome en el que hacer esto con Selenium WebDriver causaba que el trabajador del servicio no pudiera reiniciarse. Este problema se corrigió en Chrome 116 y, por fortuna, también hay una solución alternativa: configurar Chrome para que abra DevTools automáticamente en cada pestaña hace que el trabajador de servicio se inicie correctamente.

Este es el enfoque que usamos durante las pruebas, aunque no es ideal, ya que hacer clic en el botón puede no ser una API estable y abrir DevTools (para navegadores más antiguos) parece tener un costo de rendimiento.

¿Cómo cubrimos toda la funcionalidad? Pruebas de fuzz

Una vez que tuvimos un mecanismo para probar la suspensión, tuvimos que decidir cómo conectarlo a nuestros paquetes de pruebas de automatización. Ejecutamos nuestras pruebas estándar en un entorno en el que, antes de cada interacción con la página en segundo plano, WebDriver hace clic en Stop en la página chrome://serviceworker-internals/ para suspender el trabajador de servicio.

Ejecución de prueba de fuzz de muestra
Imagen que presenta la configuración actual de las pruebas.

Ejecutamos la mayoría de las pruebas, pero no todas, porque el mecanismo de suspensión no es del todo estable y, a veces, causa inestabilidad. Además, ejecutar todos los paquetes de pruebas en modo de fuzz lleva mucho tiempo. Por lo tanto, en lugar de abarcar todos los casos "similares", elegimos las rutas más críticas para realizar pruebas en el modo de fuzz. Vale la pena mencionar que ejecutar pruebas funcionales en modo "fuzz" significa que tuvimos que aumentar los tiempos de espera de las pruebas porque suspender y reiniciar los trabajadores del servicio lleva tiempo adicional.

Estas pruebas son valiosas como un primer pase de grano grueso, que destaca muchos lugares en los que falla el código, pero es posible que no descubran todas las formas sutiles en que la suspensión del trabajador de servicio podría causar fallas.

De forma interna, llamamos a este tipo de pruebas "pruebas de fuzz". Tradicionalmente, las pruebas de fuzz consisten en enviar entradas no válidas al programa y asegurarse de que responda de forma razonable o, al menos, no falle. En nuestro caso, la "entrada no válida" es que el trabajador del servicio se suspenda en cualquier momento, y el "comportamiento razonable" que esperamos es que nuestra funcionalidad de filtrado de anuncios siga funcionando como antes. En realidad, no es una entrada no válida, ya que este es el comportamiento esperado en el manifiesto V3, pero no sería válido en el manifiesto V2, por lo que parece una terminología razonable.

Resumen

Los trabajadores del servicio son uno de los cambios más importantes en el manifiesto V3 (además de las reglas de declarativeNetRequest). La migración a Manifest V3 puede requerir muchos cambios de código en las extensiones del navegador y nuevos enfoques para las pruebas. También requiere que los desarrolladores de extensiones con estado persistente preparen sus extensiones para controlar la suspensión inesperada del trabajador de servicio de forma fluida.

Lamentablemente, no hay una API para controlar la suspensión de una manera sencilla que se adapte a nuestro caso de uso. Como queríamos probar la solidez de la base de código de nuestra extensión frente a los mecanismos de suspensión en una fase inicial, tuvimos que solucionar el problema. Otros desarrolladores de extensiones que enfrentan desafíos similares pueden usar esta solución alternativa, que, si bien requiere tiempo en la fase de desarrollo y mantenimiento, vale la pena para garantizar que nuestras extensiones puedan funcionar correctamente en un entorno en el que los trabajadores del servicio se suspenden con frecuencia.

Si bien ya existe una compatibilidad básica para probar la suspensión de los trabajadores del servicio, nos gustaría ver en el futuro una mejor compatibilidad de la plataforma para probar los trabajadores del servicio desde las extensiones, ya que podría reducir en gran medida los tiempos de ejecución de las pruebas y el esfuerzo de mantenimiento.