Distrito Telefónica. Hub de Innovación y Talento

Volver
Development

Pruebas de Captura de Pantalla con Roborazzi en Sistemas de Diseño

En Telefónica debemos tratar con distintas marcas y mantener estilos coherentes en diferentes aplicaciones de diferentes países. Por este motivo, se utiliza un sistema de diseño llamado Mística. Mística proporciona elementos de Interfaz de Usuario con la apariencia esperada para cada marca. 

Para comprobar el aspecto correcto de estos componentes, utilizamos pruebas de capturas de pantalla que revisan su aspecto final cuando se renderizan en un teléfono. Para evitar los fallos y el consumo de tiempo/recursos de los emuladores hicimos uso de la biblioteca Roborazzi que nos da la ventaja de hacer pruebas de captura de pantalla sin necesidad de un emulador. Hemos descubierto esta biblioteca porque la utiliza la aplicación Now in Android y la aplicación Automotive Design for Compose, ambas de Google. 

La principal ventaja de Roborazzi sobre Paparazzi, más comúnmente utilizado, es que Paparazzi no permite la interacción con la aplicación probada (como hacer clic en un botón) y no es compatible con Robolectric, que utilizamos en nuestros proyectos. Roborazzi nos permite realizar capturas de pantalla con Robolectric (utilizando gráficos nativos de Robolectric). 

Ten en cuenta que utilizamos estas pruebas de forma complementaria a las pruebas de extremo a extremo que sí utilizan emuladores. Las pruebas de captura de pantalla se centran exclusivamente en el aspecto, mientras que las pruebas de extremo a extremo comprueban el funcionamiento de los flujos de la aplicación. 

Nuestro enfoque

En lugar de comparar capturas de pantalla completas, que pueden dar lugar a problemas de almacenamiento debido a su tamaño (las capturas de pantalla cambian con el tiempo, pero se mantienen en el historial de Git), hacemos capturas de pantalla de componentes individuales de la interfaz de usuario, como botones, tarjetas, etiquetas, etc. Queremos verificar la interfaz de usuario del componente de cada marca con sus diferentes variaciones (estilos), modo oscuro, etc. Para poder manejar tantas combinaciones utilizamos pruebas parametrizadas que nos permiten definir un conjunto de entradas que se aplicarán a cada prueba de captura de pantalla. 

@JvmStatic 
@ParameterizedRobolectricTestRunner.Parameters(name = " Button {1} brand {0} darkTheme={2} ") 
fun brands(): List<Array<Any>> { 
    return brands.flatMap { brand -> 
        buttonStyles.flatMap { buttonStyle -> 
            themes.map { darkTheme -> 
                arrayOf(brand, buttonStyle, darkTheme) 
            } 
        } 
    } 
} 


En el caso de la implementación de vistas xml, necesitamos un layout que incluya el elemento a probar de forma aislada. Para conseguirlo añadimos una actividad ficticia con un diseño y añadimos el componente mediante programación en tiempo de ejecución: 

composeTestRule.setContent { 
    MisticaTheme(brand = brand, darkTheme = darkTheme) { 
        Surface( 
            color = if (style.isInverse()) { 
                colors.backgroundBrand 
            } else { 
                colors.background 
            } 
        ) { 
            Button( 
                text = "textValue", 
                buttonStyle = style, 
                icon = android.R.drawable.ic_lock_power_off.takeIf { icon }, 
                onClickListener = { }, 
                modifier = Modifier.padding(16.dp) 
            ) 
        } 
    } 
} 
... 
compareScreenshot(  
   composeTestRule.onRoot(),  
   component = "Button",  
   style = style.toString(),  
   brand = brand,  
   darkTheme = darkTheme,  
   extra = iconSuffix  
) 


En el caso de la implementación de vistas xml, necesitamos un layout que incluya el elemento a probar de forma aislada. Para conseguirlo añadimos una actividad ficticia con un diseño y añadimos el componente mediante programación en tiempo de ejecución: 

rule.scenario.onActivity { activity -> 
    val wrapper: FrameLayout = activity.findViewById(R.id.dummy_activity_wrapper) 
    val textInput = TextInput(activity) 
    wrapper.addView(textInput) 
    compareScreenshot( 
        Espresso.onView(ViewMatchers.withId(R.id.dummy_activity_wrapper)), 
        component = "XMLButton", 
        style = style.toString(), 
        brand = brand, 
        darkTheme = darkTheme, 
        extra = iconSuffix 
    ) 
} 

Por último, creamos una función para proporcionar un nombre significativo a las capturas de pantalla utilizando el nombre del componente, el estilo, la marca y el tema oscuro, ya que Roborazzi asigna el nombre de la función al archivo de captura de pantalla, de forma predeterminada.

fun compareScreenshot(...) {  
   node.captureRoboImage(screenshotName(component, style, brand, darkTheme, extra))  
} 

Después de ejecutar las pruebas, tenemos un conjunto de archivos de capturas de pantalla con nombres significativos: 

XMLButton_DANGER_MovistarBrand_dark.png  
XMLButton_DANGER_MovistarBrand_icon_dark.png  
XMLButton_DANGER_MovistarBrand_icon.png  
XMLButton_DANGER_MovistarBrand.png  
Button_DANGER_MovistarBrand_dark.png  
Button_DANGER_MovistarBrand_icon_dark.png  
Button_DANGER_MovistarBrand_icon.png  
Button_DANGER_MovistarBrand.png  
.... 

Forma de trabajar

Actualización de la línea de base 

Para garantizar la coherencia a la hora de generar las capturas de pantalla de la línea de base (aquellas capturas de pantalla con el aspecto esperado con las que comparamos la interfaz de usuario actual) utilizamos Github Actions. De este modo podemos ejecutar la actualización de la línea de base siempre en las mismas condiciones. Esta acción tiene dos pasos principales: primero ejecuta la tarea Gradle que captura capturas de pantalla reemplazando la línea de base actual y luego las consigna en la rama actual. 

Pasos:

 
... 
  - name: Run Roborazzi Record  
   run: 'bash ./gradlew clean recordRoborazziDebug'  
 
- name: Commit and push screenshots baseline  
   id: commitAndPushScreenshotsBaseline  
   uses: EndBug/add-and-commit@v7  
   with:  
     message: 'Updated screenshots baseline'  
     add: './**/screenshots/*' 


Comparar capturas de pantalla 

La comparación de capturas de pantalla se realiza también con una acción de Github por la misma razón. Se activa con cada PR y con cada confirmación del mismo. 

on:  
pull_request:  
 
jobs:  
CompareScreenshots:  
 
   runs-on: ubuntu-latest  
 
   steps:  
     ...  
 
     - name: Verify Screenshots (roborazzi)  
       run: 'bash ./gradlew verifyRoborazziDebug' 

En caso de que una o más comparaciones fallen Roborazzi crea algunas imágenes _compare.png con la captura de pantalla anterior, la actual y los píxeles cambiados entre ellas. Utilizamos estos archivos para crear nuestro propio informe que muestra las pruebas de capturas de pantalla fallidas de forma habitual mostrando el nombre del archivo y los archivos de comparación de lo que ha fallado. 
Informe de errores de capturas de pantalla

Informe de errores de capturas de pantalla

Para ello se ejecuta un script bash en ci obteniendo esos ficheros y creando el HTML correspondiente después de que una tarea de verificación haya fallado. 

La versión actual de Roborazzi también ofrece un informe similar, pero el informe que crea es un poco diferente. 

El informe que genera tiene el archivo html que contiene todas las capturas de pantalla de todas las pruebas más las capturas de pantalla de las pruebas fallidas todas en diferentes directorios. Esto significaría que para guardar ese informe ocuparía mucho más espacio en nuestros servidores en la nube, ya que contendría todas esas capturas de pantalla que siguen siendo las mismas y que no son necesarias para que averigüemos qué ha cambiado. 

Con unas pocas adaptaciones Roborazzi nos permite tener un conjunto de pruebas de capturas de pantalla de muchos componentes de interfaz de usuario y sus variaciones de manera eficiente.