← Nesim Apps

Capturas de App Store sin teléfono: Robolectric, un cargador de imágenes falso y un navegador sin interfaz

Por Nesim Razon, desarrollador Android independiente · 2026-11-16

#Notas de desarrollo#Herramientas y calculadoras

Mis apps de iPhone comparten sus pantallas con las versiones de Android, escritas una sola vez en Compose. App Store Connect pide capturas de 1206x2622 píxeles, y yo trabajo en un portátil con Windows, sin un iPhone libre para esto y con una cantidad limitada de minutos de compilación en la nube que prefiero gastar en compilaciones de verdad. Así que dejo que las pruebas unitarias dibujen las pantallas. Así funciona, qué se rompió y qué son realmente estas imágenes.

La idea: que una prueba unitaria dibuje la pantalla

Robolectric ejecuta código Android en la JVM de escritorio y, con el modo gráfico nativo, puede dibujar Compose en un mapa de bits real. Roborazzi guarda ese mapa de bits como PNG. Así, una captura se convierte en una prueba: preparo la pantalla, hago clic hasta el estado que quiero, capturo el nodo raíz y listo. Sin emulador, sin teléfono, sin servicio de compilación.

Un solo calificador fija el tamaño: w402dp-h874dp-xxhdpi. Es una pantalla de 402 por 874 dp con tres píxeles por dp, lo que da exactamente 1206x2622 píxeles, el tamaño que pide App Store Connect. Comprobé los archivos de salida en lugar de fiarme de la aritmética.

Las áreas seguras, a mano

La ventana de una prueba de Android no tiene muesca, así que la parte superior de cada pantalla quedaría bajo el lugar de la Dynamic Island. En la prueba envuelvo la app en una caja con 47 dp de relleno superior, que representa el área segura del iPhone. En una app añadí además 34 dp abajo para el indicador de inicio, porque esa app tiene una barra de navegación inferior.

Es un sustituto aproximado, no una medición. Solo tiene que hacer que el diseño se vea como se vería en el teléfono, sin nada oculto bajo la isla.

Imágenes de red que nunca cargan

En estas pruebas Robolectric no tiene una red útil, así que las portadas de artículos y las imágenes de satélite saldrían como cuadros vacíos. Coil tiene un FakeImageLoaderEngine para esto: registro imágenes reales, guardadas como recursos de prueba, para las URL o patrones de URL que pediría la app. Así la captura muestra imágenes reales y no marcadores.

Los datos reciben el mismo trato. En el lector de noticias, el feed de la prueba es una copia del feed real tomada del sitio en vivo. En la app del espacio, los datos diarios son una copia del archivo diario real. No invento contenido para una ficha de tienda; congelo contenido real.

Contenido en vivo y bucles infinitos

Algunas pantallas muestran cosas que cambian, como la posición actual de la estación espacial. Para ellas la prueba pasa un objeto de ejemplo fijo, y la pantalla lo dibuja como si acabara de llegar.

Eso exigió un cambio en el código de la app. La pantalla en vivo consultaba en un bucle, y un bucle while(true) con una espera dentro puede impedir que waitForIdle del marco de pruebas llegue a volver, porque la app nunca queda inactiva. No esperé a comprobar si se colgaba; añadí de antemano un parámetro de vista previa: si se da un ejemplo, la pantalla lo muestra y no inicia el bucle. Es una pequeña costura de pruebas que además facilita previsualizar la pantalla.

Pantallas con WebView

Robolectric no dibuja un WebView. La vista de artículo y las páginas estáticas de mis apps de noticias son WebViews, así que la captura salía con un hueco donde debía estar la página.

La solución tiene dos mitades. La prueba escribe el HTML de la página, construido con la misma función compartida que usa la app, más una captura de la pantalla sin la página: solo la barra superior, de 111 dp o 333 píxeles de alto. Luego un script de Python dibuja ese HTML en Edge sin interfaz dentro de un iframe de 402 píxeles de ancho con escala 3x y pega el resultado bajo la barra. La página la dibuja un motor de navegador real, la barra la dibuja Compose, y se encuentran en una línea fija.

Las trampas

Cada una me costó una tarde o parte de una.

Qué son estas imágenes y qué no

Son renderizados de Android con el tamaño de un iPhone. Las fuentes, la sensación del desplazamiento, las barras del sistema y algunos controles pequeños no son lo que dibujaría iOS. Lo acepto para la ficha de la tienda porque la interfaz compartida es el mismo código, pero no las llamo capturas de iOS.

El comportamiento real en iPhone de esas apps lo comprobé aparte, en un iPhone 7 con la app instalada mediante TestFlight. Las capturas muestran las pantallas; el dispositivo real me dijo si las pantallas funcionan.

Preguntas frecuentes

¿Esto produce capturas reales de iOS?

No. Son renderizados de Android de la interfaz Compose compartida, con tamaño 1206x2622. El comportamiento real en iPhone se comprobó aparte en un dispositivo mediante TestFlight.

¿Por qué el tamaño es exactamente 1206x2622?

La prueba usa una ventana de 402 por 874 dp en xxhdpi, que son tres píxeles por dp, así que el resultado es de 1206 por 2622 píxeles.

¿Por qué no sacar las capturas en un iPhone real o en un simulador?

No tengo un dispositivo libre para eso ni un Mac, y quiero reservar mis minutos de compilación en la nube para compilaciones de verdad. Las pruebas unitarias se ejecutan gratis en mi portátil y se pueden repetir cuando quiera.

¿Por qué las pantallas con WebView se tratan aparte?

Robolectric no dibuja WebViews, así que el HTML de la página se dibuja en Edge sin interfaz y se pega bajo una barra superior dibujada por Compose.

Más del blog