Una granja de dispositivos Android con ADB + FFmpeg: Pruebas de interfaz automatizadas en 10–50 dispositivos

@ridark_eth
INGLÉShace 1 día · 07 jul 2026
209K
104
10
22
175

TL;DR

Esta guía ofrece un pipeline de Python para automatizar la instalación de APK, las pruebas de interfaz y la generación de informes en video en decenas de dispositivos Android utilizando ADB y FFmpeg, incluyendo un análisis detallado de costo-beneficio.

Cuando tu aplicación tiene que funcionar en un zoológico de docenas de teléfonos reales, diferentes fabricantes, versiones de Android y resoluciones de pantalla, las pruebas manuales se convierten rápidamente en una pesadilla. A continuación, te presento un pipeline en Python que descubre todos los dispositivos conectados por sí solo, instala el APK en todos ellos en paralelo, ejecuta pruebas instrumentadas, graba un video de cada ejecución y une esas grabaciones en un único informe de video con FFmpeg.

Todo el stack > Python + ADB + FFmpeg < es el conjunto de herramientas estándar de control de calidad. Sin magia, solo automatizando el trabajo pesado.

Ridark - inline image

Arquitectura del pipeline

text
1adb devices ──► lista de seriales
2
3
4Instalación del APK (en paralelo en todos los dispositivos, ThreadPoolExecutor)
5
6
7para cada dispositivo:
8 screenrecord (en segundo plano) → am instrument (ejecutar pruebas) → detener + extraer video
9
10
11FFmpeg: superponer serial en cada clip + concatenar ──► test_report.mp4

Lo que necesitarás

  • ADB (Android Debug Bridge) de Android Platform Tools -> control de dispositivos.
  • Python 3.10+ -> orquestación (uso list[str], tuple[...] sin from __future__).
  • FFmpeg -> procesamiento y ensamblaje de video.
  • Dispositivos con depuración USB habilitada, conectados por USB (o por Wi-Fi mediante adb tcpip).

Un principio que recorre todo el código: paso los argumentos a subprocess como una lista y sin shell=True. Es más seguro (sin inyección a través de nombres de archivo) y no se rompe con espacios o caracteres especiales en las rutas.

1. Descubrimiento de dispositivos

Ridark - inline image

adb devices también lista dispositivos en estado no autorizado / fuera de línea. Solo conservamos aquellos que están realmente en estado de dispositivo.

python
1import subprocess
2
3def get_devices() -> list[str]:
4 """Devuelve los seriales de todos los dispositivos en estado 'device'."""
5 out = subprocess.run(
6 ["adb", "devices"],
7 capture_output=True, text=True, check=True,
8 ).stdout
9
10 serials: list[str] = []
11 for line in out.splitlines()[1:]: # la primera línea es el encabezado "List of devices"
12 line = line.strip()
13 if line.endswith("\tdevice"): # descartar no autorizados / fuera de línea
14 serials.append(line.split("\t")[0])
15 return serials

2. Instalación paralela del APK

Instalar en 50 dispositivos uno por uno es lento. Distribuimos el trabajo en un grupo de hilos: cada adb install es un proceso separado, por lo que los hilos funcionan muy bien aquí (estamos esperando E/S, no quemando CPU).

python
1from concurrent.futures import ThreadPoolExecutor, as_completed
2
3def install_apk(serial: str, apk_path: str) -> tuple[str, bool, str]:
4 r = subprocess.run(
5 ["adb", "-s", serial, "install", "-r", "-g", apk_path],
6 capture_output=True, text=True,
7 )
8 ok = r.returncode == 0 and "Success" in r.stdout
9 return serial, ok, (r.stdout + r.stderr).strip()
10
11def install_on_all(apk_path: str, serials: list[str]) -> None:
12 with ThreadPoolExecutor(max_workers=len(serials) or 1) as pool:
13 futures = [pool.submit(install_apk, s, apk_path) for s in serials]
14 for f in as_completed(futures):
15 serial, ok, log = f.result()
16 print(f"[{'OK' if ok else 'FAIL'}] {serial}")
17 if not ok:
18 print(f" {log}")

Banderas: -r -> reinstalar conservando datos, -g -> otorgar todos los permisos de ejecución de inmediato (útil para que las pruebas no tropiecen con diálogos de permisos).

3. Ejecución de pruebas instrumentadas

am instrument ejecuta pruebas Espresso/JUnit en el dispositivo. Imprime OK en caso de éxito y FAILURES!!! en caso de fallo en stdout -> así determinamos el resultado.

python
1def run_instrumented_tests(
2 serial: str,
3 package: str,
4 runner: str = "androidx.test.runner.AndroidJUnitRunner",
5) -> tuple[str, bool]:
6 r = subprocess.run(
7 ["adb", "-s", serial, "shell", "am", "instrument", "-w",
8 f"{package}/{runner}"],
9 capture_output=True, text=True,
10 )
11 ok = "FAILURES!!!" not in r.stdout and r.returncode == 0
12 return serial, ok

package es el ID del paquete de prueba, normalmente com.example.app.test.

4. Grabación de la pantalla durante una prueba

screenrecord graba video directamente en el dispositivo. Limitaciones a tener en cuenta: un límite de ~3 minutos por archivo y sin audio. Iniciamos la grabación en segundo plano, ejecutamos la prueba, luego la detenemos limpiamente y extraemos el archivo al host.

La forma más confiable de detener la grabación no es señalizando el adb local, sino con pkill en el propio dispositivo -> así screenrecord finaliza el contenedor MP4 correctamente.

python
1import time
2
3def start_recording(serial: str, remote: str = "/sdcard/run.mp4") -> subprocess.Popen:
4 return subprocess.Popen(
5 ["adb", "-s", serial, "shell", "screenrecord", remote]
6 )
7
8def stop_recording(
9 serial: str,
10 proc: subprocess.Popen,
11 remote: str = "/sdcard/run.mp4",
12 local: str = "run.mp4",
13) -> None:
14 # SIGINT en el dispositivo hace que screenrecord cierre el archivo correctamente
15 subprocess.run(["adb", "-s", serial, "shell", "pkill", "-SIGINT", "screenrecord"])
16 proc.wait(timeout=10)
17 time.sleep(1) # darle un momento al dispositivo para finalizar el contenedor
18 subprocess.run(["adb", "-s", serial, "pull", remote, local], check=True)

5. Ensamblaje del informe de video con FFmpeg

Diferentes dispositivos tienen diferentes resoluciones de pantalla, por lo que no se pueden concatenar simplemente con -c copy. Normalizamos cada clip a un formato común (1080×1920) y superponemos el serial mediante drawtext durante el proceso. Después de eso, todos los clips son idénticos y el ensamblaje final es una concatenación rápida sin necesidad de recodificación.

python
1def label_clip(src: str, dst: str, label: str) -> None:
2 """Escalar a 1080x1920 y superponer una etiqueta (el serial del dispositivo)."""
3 vf = (
4 "scale=1080:1920:force_original_aspect_ratio=decrease,"
5 "pad=1080:1920:(ow-iw)/2:(oh-ih)/2,"
6 f"drawtext=text='{label}':x=20:y=20:fontsize=42:"
7 "fontcolor=white:box=1:[email protected]"
8 )
9 subprocess.run(
10 ["ffmpeg", "-y", "-i", src, "-vf", vf,
11 "-an", "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", dst],
12 check=True,
13 )
14
15def concat_report(clips: list[str], out: str = "test_report.mp4") -> None:
16 with open("concat_list.txt", "w") as f:
17 for c in clips:
18 f.write(f"file '{c}'\n")
19 subprocess.run(
20 ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
21 "-i", "concat_list.txt", "-c", "copy", out],
22 check=True,
23 )

6. Poniéndolo todo junto

python
1def main() -> None:
2 apk = "app-debug.apk"
3 package = "com.example.app.test"
4 runner = "androidx.test.runner.AndroidJUnitRunner"
5
6 serials = get_devices()
7 if not serials:
8 print("No se encontraron dispositivos. Verifica el USB y la salida de 'adb devices'.")
9 return
10
11 print(f"Dispositivos encontrados: {len(serials)}")
12 install_on_all(apk, serials)
13
14 labeled: list[str] = []
15 for serial in serials:
16 proc = start_recording(serial)
17 _, ok = run_instrumented_tests(serial, package, runner)
18 stop_recording(serial, proc, local=f"{serial}.mp4")
19 print(f"[{'PASS' if ok else 'FAIL'}] pruebas en {serial}")
20
21 out = f"{serial}_labeled.mp4"
22 label_clip(f"{serial}.mp4", out, serial)
23 labeled.append(out)
24
25 concat_report(labeled, "test_report.mp4")
26 print("Listo: test_report.mp4")
27
28if __name__ == "__main__":
29 main()

Aquí el bucle "grabar + probar" se ejecuta secuencialmente en todos los dispositivos -> es más legible así. Para un parque real, querrías envolver este bloque en un ThreadPoolExecutor también, para que todos los dispositivos se prueben a la vez; la lógica es la misma que la instalación en la sección 2.

No reinventes la rueda: herramientas listas para usar

Ridark - inline image
  • scrcpy -> duplicación y control en tiempo real de un dispositivo desde tu PC. Indispensable al depurar pruebas fallidas.
  • Appium / Espresso / UI Automator -> marcos de pruebas de interfaz de usuario completos; el am instrument anterior es su motor.
  • Gradle Managed Devices -> ejecuta pruebas en emuladores directamente desde la compilación, sin necesidad de manejar ADB manualmente.
  • Firebase Test Lab / AWS Device Farm -> una flota en la nube de dispositivos reales si prefieres no mantener tu propio hardware.
  • GNU parallel -> si prefieres orquestar desde bash en lugar de Python.

La economía: qué cuesta y qué ahorra

La automatización de pruebas no se trata de "hacer dinero de la nada" -> se trata de reducir los dos elementos más costosos: horas-persona y minutos en la nube. A continuación, una estimación en tres escalas típicas. Las cifras son ilustrativas y dependen de la región, el fabricante del dispositivo y los precios del proveedor; verifica las tarifas actuales antes de comprar.

Tu propio parque -> inversión única

Elemento

Para 10 dispositivos

Para 30 dispositivos

Para 50 dispositivos

Teléfonos Android usados (~$60 cada uno)

~$600

~$1,800

~$3,000

Hubs USB con alimentación

~$100

~$250

~$400

Mini-PC / host

~$400

~$400

~$500

Cables, rack, varios

~$80

~$150

~$250

Total único

~$1,200

~$2,600

~$4,150

Electricidad por mes

centavos

~$10–20

~$20–40

Esto es gasto de capital: pagas una vez, y el parque funciona durante años casi sin costo.

Nube -> pagas por minuto

Firebase Test Lab, AWS Device Farm, BrowserStack y similares cobran por minuto-dispositivo -> aproximadamente $0.05–0.20 por minuto-dispositivo. Una sola ejecución de regresión en 30 dispositivos a 5 minutos cada uno son 150 minutos-dispositivo, es decir, ~$7.5–30 por ejecución.

Ahora multiplica por la intensidad de CI:

Frecuencia de ejecución

Ejecuciones por mes

Costo (a ~$15/ejecución)

2× al día

~44

~$660/mes

10× al día

~220

~$3,300/mes

En cada push (equipo activo)

500+

$7,500+/mes

Punto de equilibrio: un parque de 30 dispositivos (~$2,600) se amortiza frente a la nube en aproximadamente 4 meses con una modesta frecuencia de 2 ejecuciones al día; con CI activo, en menos de un mes. Después de eso, la factura de la nube sigue goteando cada mes, y el parque no.

Trabajo manual -> lo que se libera

Una ejecución manual de un solo escenario de regresión en 30 dispositivos equivale aproximadamente a un día laboral de un ingeniero de QA. Ejecutar la regresión dos veces por semana suma ~8 días-persona al mes. Con un costo de QA de alrededor de $1,600–2,700/mes, eso es una parte significativa de un salario que el pipeline libera para trabajo significativo en lugar del tedioso "conectar–instalar–probar–grabar" ×30.

Cómo se convierte esto en dinero

No hay ingresos directos "del script" aquí -> hay tres mecanismos indirectos pero muy reales:

  • Lanzamientos más rápidos. Regresión en minutos en lugar de un día → lanzas funciones con más frecuencia → respondes al mercado más rápido. Para un producto de suscripción, esto se vincula directamente con la retención y los ingresos.
  • Menos errores en producción. Detectar un bloqueo en un Samsung específico antes del lanzamiento cuesta centavos; el mismo bloqueo que llega a los usuarios significa calificaciones bajas en la tienda, pérdida de clientes y reembolsos. Cada error detectado temprano son un puñado de reseñas negativas que nunca se escriben.
  • Se vende como servicio. Configurar un parque de dispositivos y pruebas de CI es un rol demandado en freelancing y subcontratación. El pipeline anterior es un núcleo listo para tal oferta.

La diferencia clave con los "esquemas de generación de engagement": allí, el dinero proviene de engañar a los algoritmos y termina en una prohibición. Aquí, proviene de horas ahorradas y pérdidas evitadas. El primero se desmorona; el segundo es un caso de negocio sostenible del que no te avergüenzas mostrar a un cliente.

Resumen

El resultado es un pipeline reproducible: un comando, y la aplicación se prueba en toda la flota de dispositivos, con un informe de video al final que muestra el comportamiento en cada dispositivo específico.

Guarda y suscríbete para no perderte artículos útiles que te ahorrarán dinero 📝

Guardar con un clic

Lee artículos virales en profundidad con IA en YouMind

Save the source, ask focused questions, summarize the argument, and turn a viral article into reusable notes in one AI workspace.

Explore YouMind
Para creadores

Convierte tu Markdown en un artículo de 𝕏 impecable

Cuando publicas tus propios textos largos, dar formato en 𝕏 a imágenes, tablas y bloques de código es un fastidio. YouMind convierte un borrador completo en Markdown en un artículo de 𝕏 impecable y listo para publicar.

Prueba Markdown a 𝕏

Más patrones por descifrar

Artículos virales recientes

Explorar más artículos virales