Eine Android Device Farm mit ADB + FFmpeg: Automatisierte UI-Tests auf 10–50 Geräten

@ridark_eth
ENGLISCHvor 1 Tag · 07. Juli 2026
209K
104
10
22
175

TL;DR

Dieser Leitfaden bietet eine Python-Pipeline zur Automatisierung von APK-Installationen, UI-Tests und Video-Reporting auf Dutzenden von Android-Geräten mittels ADB und FFmpeg, inklusive einer detaillierten Kosten-Nutzen-Analyse.

Wenn Ihre App auf einem ganzen Zoo von Dutzenden echten Smartphones, verschiedenen Herstellern, Android-Versionen und Bildschirmauflösungen funktionieren muss, wird manuelles Testen schnell zum Albtraum. Im Folgenden finden Sie eine Python-Pipeline, die selbstständig alle angeschlossenen Geräte erkennt, die APK parallel auf allen installiert, instrumentierte Tests ausführt, einen Video-Mitschnitt jedes Durchlaufs erstellt und diese Aufnahmen mit FFmpeg zu einem einzigen Video-Report zusammenfügt.

Der gesamte Stack > Python + ADB + FFmpeg < ist Standard-QA-Tooling. Keine Magie, nur die Automatisierung der lästigen Routinearbeit.

Ridark - inline image

Pipeline-Architektur

text
1adb devices ──► Liste der Seriennummern
2
3
4APK-Installation (parallel auf allen Geräten, ThreadPoolExecutor)
5
6
7für jedes Gerät:
8 screenrecord (im Hintergrund) → am instrument (Tests ausführen) → stoppen + Video holen
9
10
11FFmpeg: Seriennummer auf jeden Clip einblenden + zusammenfügen ──► test_report.mp4

Was Sie brauchen

  • ADB (Android Debug Bridge) aus den Android Platform Tools -> Gerätesteuerung.
  • Python 3.10+ -> Orchestrierung (ich verwende list[str], tuple[...] ohne from __future__).
  • FFmpeg -> Videoverarbeitung und -zusammenstellung.
  • Geräte mit aktiviertem USB-Debugging, verbunden über USB (oder über Wi-Fi via adb tcpip).

Ein Prinzip, das sich durch den gesamten Code zieht: Ich übergebe Argumente an subprocess als Liste und ohne shell=True. Das ist sicherer (keine Injection über Dateinamen) und funktioniert auch mit Leerzeichen oder Sonderzeichen in Pfaden.

1. Geräteerkennung

Ridark - inline image

adb devices listet auch Geräte im Status unauthorized / offline auf. Wir behalten nur die, die tatsächlich im Status device sind.

python
1import subprocess
2
3def get_devices() -> list[str]:
4 """Gibt die Seriennummern aller Geräte im Status 'device' zurück."""
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:]: # erste Zeile ist der "List of devices"-Header
12 line = line.strip()
13 if line.endswith("\tdevice"): # unauthorized / offline aussortieren
14 serials.append(line.split("\t")[0])
15 return serials

2. Parallele APK-Installation

Die Installation auf 50 Geräten nacheinander ist langsam. Wir verteilen die Arbeit auf einen Thread-Pool: Jede adb install ist ein eigener Prozess, daher funktionieren Threads hier hervorragend (wir warten auf I/O, nicht auf CPU-Leistung).

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 'FEHLER'}] {serial}")
17 if not ok:
18 print(f" {log}")

Flags: -r -> Neuinstallation unter Beibehaltung der Daten, -g -> alle Laufzeitberechtigungen sofort erteilen (praktisch, damit Tests nicht an Berechtigungsdialogen scheitern).

3. Ausführen instrumentierter Tests

am instrument führt Espresso/JUnit-Tests auf dem Gerät aus. Bei Erfolg gibt es OK aus, bei Fehlschlag FAILURES!!! auf stdout -> daran erkennen wir das Ergebnis.

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 ist die Test-Package-ID, normalerweise com.example.app.test.

4. Bildschirmaufnahme während eines Tests

screenrecord zeichnet Videos direkt auf dem Gerät auf. Zu beachtende Einschränkungen: ein ~3-Minuten-Limit pro Datei und kein Ton. Wir starten die Aufnahme im Hintergrund, führen den Test aus, stoppen sie dann sauber und holen die Datei auf den Host.

Die zuverlässigste Methode, die Aufnahme zu stoppen, ist nicht das Signalisieren des lokalen adb, sondern pkill auf dem Gerät selbst -> so finalisiert screenrecord den MP4-Container korrekt.

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 auf dem Gerät bewirkt, dass screenrecord die Datei ordentlich schließt
15 subprocess.run(["adb", "-s", serial, "shell", "pkill", "-SIGINT", "screenrecord"])
16 proc.wait(timeout=10)
17 time.sleep(1) # dem Gerät einen Moment geben, den Container zu finalisieren
18 subprocess.run(["adb", "-s", serial, "pull", remote, local], check=True)

5. Zusammenstellen des Video-Reports mit FFmpeg

Verschiedene Geräte haben unterschiedliche Bildschirmauflösungen, daher kann man sie nicht einfach mit -c copy aneinanderhängen. Wir normalisieren jeden Clip auf ein einheitliches Format (1080×1920) und blenden die Seriennummer per drawtext ein. Danach sind alle Clips identisch und die finale Zusammenstellung ist eine schnelle Concat ohne erneute Kodierung.

python
1def label_clip(src: str, dst: str, label: str) -> None:
2 """Skaliert auf 1080x1920 und blendet ein Label (die Geräteseriennummer) ein."""
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. Alles zusammenfügen

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("Keine Geräte gefunden. Überprüfen Sie USB und die Ausgabe von 'adb devices'.")
9 return
10
11 print(f"Gefundene Geräte: {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"[{'BESTANDEN' if ok else 'FEHLGESCHLAGEN'}] Tests auf {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("Fertig: test_report.mp4")
27
28if __name__ == "__main__":
29 main()

Hier läuft die "Aufnahme + Test"-Schleife sequenziell über die Geräte -> das ist lesbarer. Für eine echte Farm sollten Sie diesen Block ebenfalls in einen ThreadPoolExecutor packen, damit alle Geräte gleichzeitig getestet werden; die Logik ist dieselbe wie bei der Installation in Abschnitt 2.

Das Rad nicht neu erfinden: Fertige Werkzeuge

Ridark - inline image
  • scrcpy -> Echtzeit-Spiegelung und Steuerung eines Geräts vom PC. Unverzichtbar beim Debuggen fehlschlagender Tests.
  • Appium / Espresso / UI Automator -> vollwertige UI-Test-Frameworks; das am instrument oben ist ihre Engine.
  • Gradle Managed Devices -> Tests auf Emulatoren direkt aus dem Build heraus, ohne manuelles ADB-Gefummel.
  • Firebase Test Lab / AWS Device Farm -> eine Cloud-Flotte echter Geräte, falls Sie keine eigene Hardware vorhalten möchten.
  • GNU parallel -> falls Sie die Orchestrierung lieber in Bash als in Python vornehmen.

Die Wirtschaftlichkeit: Was es kostet und was es spart

Testautomatisierung geht nicht darum, "Geld aus dem Nichts zu zaubern" -> es geht darum, die beiden teuersten Posten zu reduzieren: Personenstunden und Cloud-Minuten. Nachfolgend eine Schätzung für drei typische Größenordnungen. Die Zahlen sind illustrativ und hängen von Region, Gerätehersteller und Anbieterpreisen ab; prüfen Sie vor dem Kauf die aktuellen Tarife.

Eigene Farm -> einmalige Investition

Posten

Für 10 Geräte

Für 30 Geräte

Für 50 Geräte

Gebrauchte Android-Smartphones (~60 € pro Stück)

~600 €

~1.800 €

~3.000 €

Aktive USB-Hubs

~100 €

~250 €

~400 €

Mini-PC / Host

~400 €

~400 €

~500 €

Kabel, Rack, Kleinteile

~80 €

~150 €

~250 €

Einmalige Gesamtkosten

~1.200 €

~2.600 €

~4.150 €

Strom pro Monat

Centbeträge

~10–20 €

~20–40 €

Dies sind Investitionsausgaben: einmal zahlen, und die Farm läuft dann jahrelang nahezu kostenlos.

Cloud -> Sie zahlen pro Minute

Firebase Test Lab, AWS Device Farm, BrowserStack und ähnliche Anbieter berechnen pro Geräteminute – etwa 0,05–0,20 € pro Geräteminute. Ein einzelner Regressionstestlauf auf 30 Geräten mit je 5 Minuten ergibt 150 Geräteminuten, also etwa 7,50–30 € pro Lauf.

Multiplizieren Sie das mit der CI-Intensität:

Lauffrequenz

Läufe pro Monat

Kosten (bei ~15 €/Lauf)

2× pro Tag

~44

~660 €/Monat

10× pro Tag

~220

~3.300 €/Monat

Bei jedem Push (aktives Team)

500+

7.500+ €/Monat

Amortisation: Eine 30-Geräte-Farm (~2.600 €) amortisiert sich gegenüber der Cloud in etwa 4 Monaten bei bescheidenen 2 Läufen pro Tag; bei aktiver CI in unter einem Monat. Danach läuft die Cloud-Rechnung jeden Monat weiter, die Farm nicht.

Manuelle Arbeit -> was frei wird

Ein manueller Durchlauf eines einzelnen Regressionstestszenarios auf 30 Geräten entspricht etwa einem Arbeitstag eines QA-Ingenieurs. Regression zweimal pro Woche summiert sich auf etwa 8 Personentage pro Monat. Bei QA-Kosten von irgendwo um die 1.600–2.700 €/Monat ist das ein beträchtlicher Teil eines Gehalts, den die Pipeline für sinnvolle Arbeit freigibt – anstelle der "Verbinden–Installieren–Klicken–Aufnehmen"-Routine ×30.

Wie sich das in Geld umwandelt

Es gibt hier keine direkten Einnahmen "aus dem Skript" – aber drei indirekte, sehr reale Mechanismen:

  • Schnellere Releases. Regression in Minuten statt an einem Tag → Sie liefern Funktionen häufiger aus → Sie reagieren schneller auf den Markt. Bei einem Abonnementprodukt wirkt sich das direkt auf Kundenbindung und Umsatz aus.
  • Weniger Fehler in der Produktion. Einen Absturz auf einem bestimmten Samsung vor der Veröffentlichung zu finden, kostet Centbeträge; derselbe Absturz, der die Nutzer erreicht, bedeutet schlechtere Store-Bewertungen, Abwanderung und Rückerstattungen. Jeder früh gefundene Fehler ist eine Handvoll negativer Rezensionen, die nie geschrieben werden.
  • Es lässt sich als Dienstleistung verkaufen. Der Aufbau einer Gerätefarm und von CI-Tests ist eine gefragte Rolle in der Freiberuflichkeit und im Outsourcing. Die obige Pipeline ist ein fertiger Kern für ein solches Angebot.

Der entscheidende Unterschied zu "Engagement-Farming-Systemen": Dort kommt das Geld aus der Täuschung von Algorithmen und endet in einer Sperre. Hier kommt es aus eingesparten Stunden und verhinderten Verlusten. Das erste bricht zusammen; das zweite ist ein nachhaltiges Geschäftsmodell, das Sie einem Kunden nicht peinlich präsentieren müssen.

Zusammenfassung

Das Ergebnis ist eine reproduzierbare Pipeline: Ein Befehl, und die App wird auf der gesamten Geräteflotte getestet, mit einem Video-Report am Ende, der das Verhalten auf jedem einzelnen Gerät zeigt.

Setzen Sie ein Lesezeichen und abonnieren Sie, damit Sie keine nützlichen Artikel verpassen, die Geld sparen 📝

Mit einem Klick speichern

Virale Artikel mit YouMind per KI tief lesen

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

Explore YouMind
Für Creator

Verwandle dein Markdown in einen sauberen 𝕏-Artikel

Wenn du eigene Langtexte veröffentlichst, wird die 𝕏-Formatierung von Bildern, Tabellen und Codeblöcken mühsam. YouMind macht aus einem ganzen Markdown-Entwurf einen sauberen, sofort postbaren 𝕏-Artikel.

Markdown zu 𝕏 testen

Mehr Muster zum Entschlüsseln

Aktuelle virale Artikel

Mehr virale Artikel entdecken