Una Device Farm Android con ADB + FFmpeg: Test UI automatizzati su 10–50 dispositivi

@ridark_eth
INGLESE1 giorno fa · 07 lug 2026
209K
104
10
22
175

TL;DR

Questa guida fornisce una pipeline Python per automatizzare l'installazione di APK, i test UI e la creazione di report video su decine di dispositivi Android utilizzando ADB e FFmpeg, includendo un'analisi dettagliata costi-benefici.

Quando la tua app deve funzionare su un vero zoo di decine di telefoni reali, con diversi produttori, versioni Android e risoluzioni dello schermo, il test manuale si trasforma rapidamente in un incubo. Qui sotto trovi una pipeline Python che scopre autonomamente tutti i dispositivi connessi, installa l'APK su tutti in parallelo, esegue test strumentati, registra un video di ogni esecuzione e unisce quelle registrazioni in un unico report video con FFmpeg.

L'intero stack > Python + ADB + FFmpeg < è il toolkit standard per il QA. Niente di magico, solo automazione della routine.

Ridark - inline image

Architettura della pipeline

text
1adb devices ──► elenco dei seriali
2
3
4Installazione APK (in parallelo su tutti i dispositivi, ThreadPoolExecutor)
5
6
7per ogni dispositivo:
8 screenrecord (in background) → am instrument (esegue test) → stop + estrai video
9
10
11FFmpeg: sovrapponi seriale su ogni clip + concatena ──► test_report.mp4

Cosa ti servirà

  • ADB (Android Debug Bridge) da Android Platform Tools -> controllo del dispositivo.
  • Python 3.10+ -> orchestrazione (uso list[str], tuple[...] senza from __future__).
  • FFmpeg -> elaborazione e assemblaggio video.
  • Dispositivi con debug USB abilitato, collegati via USB (o via Wi-Fi con adb tcpip).

Un principio che attraversa tutto il codice: passo gli argomenti a subprocess come lista e senza shell=True. È più sicuro (nessuna iniezione tramite nomi di file) e non si rompe con spazi o caratteri speciali nei percorsi.

1. Scoperta dei dispositivi

Ridark - inline image

adb devices elenca anche i dispositivi nello stato non autorizzato / offline. Teniamo solo quelli effettivamente nello stato "device".

python
1import subprocess
2
3def get_devices() -> list[str]:
4 """Restituisce i seriali di tutti i dispositivi nello stato '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 prima riga è l'intestazione "List of devices"
12 line = line.strip()
13 if line.endswith("\tdevice"): # esclude non autorizzati / offline
14 serials.append(line.split("\t")[0])
15 return serials

2. Installazione APK in parallelo

Installare su 50 dispositivi uno per uno è lento. Distribuiamo il lavoro su un pool di thread: ogni adb install è un processo separato, quindi i thread funzionano benissimo qui (stiamo aspettando I/O, non bruciando 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}")

Flag: -r -> reinstallazione mantenendo i dati, -g -> concede immediatamente tutti i permessi runtime (utile per evitare che i test si blocchino sui dialoghi dei permessi).

3. Esecuzione di test strumentati

am instrument esegue test Espresso/JUnit sul dispositivo. Stampa OK in caso di successo e FAILURES!!! in caso di fallimento su stdout -> è così che determiniamo il risultato.

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 è l'ID del pacchetto di test, di solito com.example.app.test.

4. Registrazione dello schermo durante un test

screenrecord registra video direttamente sul dispositivo. Limitazioni da tenere a mente: un limite di circa 3 minuti per file e nessun audio. Avviamo la registrazione in background, eseguiamo il test, poi la fermiamo correttamente e estraiamo il file sull'host.

Il modo più affidabile per fermare la registrazione non è inviare un segnale al adb locale, ma con pkill sul dispositivo stesso -> in questo modo screenrecord finalizza correttamente il contenitore MP4.

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 sul dispositivo fa chiudere correttamente il file a screenrecord
15 subprocess.run(["adb", "-s", serial, "shell", "pkill", "-SIGINT", "screenrecord"])
16 proc.wait(timeout=10)
17 time.sleep(1) # concedi un momento al dispositivo per finalizzare il contenitore
18 subprocess.run(["adb", "-s", serial, "pull", remote, local], check=True)

5. Assemblaggio del report video con FFmpeg

Dispositivi diversi hanno risoluzioni dello schermo diverse, quindi non puoi semplicemente concatenarli con -c copy. Normalizziamo ogni clip a un formato comune (1080×1920) e sovrapponiamo il seriale tramite drawtext lungo il percorso. Dopo di che tutte le clip sono identiche e l'assemblaggio finale è una concat veloce senza re-encoding.

python
1def label_clip(src: str, dst: str, label: str) -> None:
2 """Ridimensiona a 1080x1920 e sovrapponi un'etichetta (il seriale 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. Mettere tutto insieme

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("Nessun dispositivo trovato. Controlla USB e l'output di 'adb devices'.")
9 return
10
11 print(f"Dispositivi trovati: {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'}] test su {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("Fatto: test_report.mp4")
27
28if __name__ == "__main__":
29 main()

Qui il ciclo "registra + test" viene eseguito in sequenza sui dispositivi -> è più leggibile così. Per una vera farm vorresti avvolgere questo blocco in un ThreadPoolExecutor anche tu, in modo che tutti i dispositivi vengano testati contemporaneamente; la logica è la stessa dell'installazione nella sezione 2.

Non reinventare la ruota: strumenti già pronti

Ridark - inline image
  • scrcpy -> mirroring e controllo in tempo reale di un dispositivo dal tuo PC. Indispensabile quando si esegue il debug di test falliti.
  • Appium / Espresso / UI Automator -> framework di test UI completi; il comando am instrument sopra è il loro motore.
  • Gradle Managed Devices -> esegui test su emulatori direttamente dalla build, senza dover gestire ADB manualmente.
  • Firebase Test Lab / AWS Device Farm -> una flotta cloud di dispositivi reali se preferisci non tenere il tuo hardware.
  • GNU parallel -> se preferisci orchestrare da bash piuttosto che da Python.

L'economia: cosa costa e cosa risparmia

L'automazione dei test non riguarda "fare soldi dal nulla" -> si tratta di tagliare le due voci di costo più costose: ore-persona e minuti cloud. Qui sotto c'è una stima su tre scale tipiche. I numeri sono indicativi e dipendono dalla regione, dal produttore del dispositivo e dai prezzi del fornitore; controlla le tariffe attuali prima di acquistare.

La tua farm -> investimento una tantum

Voce

Per 10 dispositivi

Per 30 dispositivi

Per 50 dispositivi

Telefoni Android usati (~$60 ciascuno)

~$600

~$1.800

~$3.000

Hub USB alimentati

~$100

~$250

~$400

Mini-PC / host

~$400

~$400

~$500

Cavi, rack, accessori vari

~$80

~$150

~$250

Totale una tantum

~$1.200

~$2.600

~$4.150

Elettricità al mese

pochi centesimi

~$10–20

~$20–40

Questa è una spesa in conto capitale: paghi una volta e la farm funziona per anni a costo quasi zero.

Cloud -> paghi al minuto

Firebase Test Lab, AWS Device Farm, BrowserStack e simili fanno pagare per minuto-dispositivo -> circa $0,05–0,20 per minuto-dispositivo. Una singola esecuzione di regressione su 30 dispositivi a 5 minuti ciascuno sono 150 minuti-dispositivo, cioè ~$7,5–30 per esecuzione.

Ora moltiplica per l'intensità CI:

Frequenza di esecuzione

Esecuzioni al mese

Costo (a ~$15/esecuzione)

2× al giorno

~44

~$660/mese

10× al giorno

~220

~$3.300/mese

Ad ogni push (team attivo)

500+

$7.500+/mese

Punto di pareggio: una farm da 30 dispositivi (~$2.600) si ripaga da sola rispetto al cloud in circa 4 mesi con una modesta frequenza di 2 esecuzioni al giorno; con CI attivo, in meno di un mese. Dopodiché la bolletta del cloud continua a gocciolare ogni mese, mentre la farm no.

Lavoro manuale -> cosa viene liberato

Un'esecuzione manuale di un singolo scenario di regressione su 30 dispositivi equivale all'incirca a una giornata lavorativa di un ingegnere QA. Eseguire la regressione due volte a settimana si traduce in circa 8 giorni-persona al mese. Con un costo QA di circa $1.600–2.700/mese, è una fetta significativa di stipendio che la pipeline libera per lavoro significativo invece della routine "collega–installa–tocca–registra" ×30.

Come si traduce in denaro

Non c'è un reddito diretto "dallo script" qui -> ci sono tre meccanismi indiretti ma molto reali:

  • Rilasci più veloci. Regressione in minuti invece che in un giorno → pubblichi funzionalità più spesso → rispondi più velocemente al mercato. Per un prodotto in abbonamento, questo si lega direttamente a fidelizzazione e fatturato.
  • Meno bug in produzione. Catturare un crash su un Samsung specifico prima del rilascio costa pochi centesimi; lo stesso crash che raggiunge gli utenti significa recensioni negative, abbandono e rimborsi. Ogni bug catturato presto è una manciata di recensioni negative che non vengono mai scritte.
  • Si vende come servizio. Configurare una farm di dispositivi e test CI è un ruolo molto richiesto nel freelancing e nell'outsourcing. La pipeline sopra è un nucleo già pronto per tale offerta.

La differenza fondamentale dagli "schemi di engagement-farming": lì, i soldi arrivano ingannando gli algoritmi e finiscono in un ban. Qui, arrivano da ore risparmiate e perdite evitate. Il primo crolla; il secondo è un caso di business sostenibile di cui non ti vergogni di mostrare a un cliente.

Conclusione

Il risultato è una pipeline riproducibile: un comando e l'app viene testata sull'intera flotta di dispositivi, con un report video alla fine che mostra il comportamento su ogni singolo dispositivo.

Aggiungi ai preferiti e iscriviti per non perdere articoli utili che ti faranno risparmiare denaro 📝

Salva con un clic

Leggi in profondità gli articoli virali con l’AI di YouMind

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

Explore YouMind
Per i creator

Trasforma il tuo Markdown in un articolo 𝕏 pulito

Quando pubblichi i tuoi testi lunghi, formattare immagini, tabelle e blocchi di codice per 𝕏 è una seccatura. YouMind trasforma un'intera bozza Markdown in un articolo 𝕏 pulito e pronto da pubblicare.

Prova Markdown verso 𝕏

Altri pattern da decodificare

Articoli virali recenti

Esplora altri articoli virali