Uma fazenda de dispositivos Android com ADB + FFmpeg: Testes de UI automatizados em 10 a 50 dispositivos

@ridark_eth
INGLÊShá 1 dia · 07/07/2026
209K
104
10
22
175

TL;DR

Este guia fornece um pipeline em Python para automatizar a instalação de APKs, testes de UI e relatórios em vídeo em dezenas de dispositivos Android usando ADB e FFmpeg, incluindo uma análise detalhada de custo-benefício.

Quando seu aplicativo precisa funcionar em um zoológico de dezenas de telefones reais, diferentes fabricantes, versões do Android e resoluções de tela, os testes manuais rapidamente se transformam em um pesadelo. Abaixo está um pipeline em Python que descobre todos os dispositivos conectados automaticamente, instala o APK em todos eles em paralelo, executa testes instrumentados, grava um vídeo de cada execução e une essas gravações em um único relatório em vídeo com o FFmpeg.

Toda a pilha > Python + ADB + FFmpeg < é o kit de ferramentas padrão de QA. Nada de mágica, apenas automatizando o trabalho braçal.

Ridark - inline image

Arquitetura do pipeline

text
1adb devices ──► lista de seriais
2
3
4Instalação do APK (em paralelo em todos os dispositivos, ThreadPoolExecutor)
5
6
7para cada dispositivo:
8 screenrecord (em segundo plano) → am instrument (executar testes) → parar + puxar vídeo
9
10
11FFmpeg: sobrepor serial em cada clipe + concatenar ──► test_report.mp4

O que você vai precisar

  • ADB (Android Debug Bridge) do Android Platform Tools -> controle de dispositivos.
  • Python 3.10+ -> orquestração (uso list[str], tuple[...] sem from __future__).
  • FFmpeg -> processamento e montagem de vídeo.
  • Dispositivos com depuração USB ativada, conectados via USB (ou via Wi-Fi com adb tcpip).

Um princípio que percorre todo o código: passo argumentos para subprocess como uma lista e sem shell=True. É mais seguro (sem injeção por nomes de arquivos) e não quebra com espaços ou caracteres especiais nos caminhos.

1. Descoberta de dispositivos

Ridark - inline image

O comando adb devices também lista dispositivos no estado não autorizado / offline. Mantemos apenas aqueles que estão realmente no estado "device".

python
1import subprocess
2
3def get_devices() -> list[str]:
4 """Retorna os seriais de todos os dispositivos no 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:]: # primeira linha é o cabeçalho "List of devices"
12 line = line.strip()
13 if line.endswith("\tdevice"): # descarta não autorizados / offline
14 serials.append(line.split("\t")[0])
15 return serials

2. Instalação paralela do APK

Instalar em 50 dispositivos um por um é lento. Distribuímos o trabalho em um pool de threads: cada adb install é um processo separado, então as threads funcionam muito bem aqui (estamos esperando por E/S, não queimando 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}")

Flags: -r -> reinstala mantendo os dados, -g -> concede todas as permissões de tempo de execução imediatamente (útil para que os testes não esbarrem em diálogos de permissão).

3. Executando testes instrumentados

O comando am instrument executa testes Espresso/JUnit no dispositivo. Ele imprime OK em caso de sucesso e FAILURES!!! em caso de falha no stdout -> é assim que determinamos o 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 é o ID do pacote de teste, geralmente com.example.app.test.

4. Gravando a tela durante um teste

O screenrecord grava vídeo diretamente no dispositivo. Limitações a serem lembradas: um limite de ~3 minutos por arquivo e sem áudio. Iniciamos a gravação em segundo plano, executamos o teste, depois a paramos de forma limpa e puxamos o arquivo para o host.

A maneira mais confiável de parar a gravação não é sinalizando o adb local, mas com pkill no próprio dispositivo -> assim o screenrecord finaliza o contêiner MP4 corretamente.

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 no dispositivo faz o screenrecord fechar o arquivo corretamente
15 subprocess.run(["adb", "-s", serial, "shell", "pkill", "-SIGINT", "screenrecord"])
16 proc.wait(timeout=10)
17 time.sleep(1) # dá um momento para o dispositivo finalizar o contêiner
18 subprocess.run(["adb", "-s", serial, "pull", remote, local], check=True)

5. Montando o relatório em vídeo com FFmpeg

Dispositivos diferentes têm resoluções de tela diferentes, então você não pode simplesmente concatená-los com -c copy. Normalizamos cada clipe para um formato comum (1080×1920) e sobrepomos o serial via drawtext ao longo do caminho. Depois disso, todos os clipes são idênticos e a montagem final é uma concatenação rápida sem re-codificação.

python
1def label_clip(src: str, dst: str, label: str) -> None:
2 """Redimensiona para 1080x1920 e sobrepõe um rótulo (o serial do 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. Juntando tudo

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("Nenhum dispositivo encontrado. Verifique o USB e a saída 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'}] testes em {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("Pronto: test_report.mp4")
27
28if __name__ == "__main__":
29 main()

Aqui, o loop "gravar + testar" é executado sequencialmente entre os dispositivos -> é mais legível dessa forma. Para um farm real, você vai querer envolver este bloco em um ThreadPoolExecutor também, para que todos os dispositivos sejam testados de uma vez; a lógica é a mesma da instalação na seção 2.

Não reinvente a roda: ferramentas prontas

Ridark - inline image
  • scrcpy -> espelhamento e controle em tempo real de um dispositivo a partir do seu PC. Indispensável ao depurar testes com falha.
  • Appium / Espresso / UI Automator -> frameworks de teste de UI completos; o am instrument acima é o motor deles.
  • Gradle Managed Devices -> execute testes em emuladores diretamente da compilação, sem manipulação manual de ADB.
  • Firebase Test Lab / AWS Device Farm -> uma frota em nuvem de dispositivos reais se você preferir não manter seu próprio hardware.
  • GNU parallel -> se você preferir orquestrar a partir do bash em vez do Python.

A economia: o que custa e o que economiza

A automação de testes não se trata de "ganhar dinheiro do nada" -> trata-se de cortar os dois itens de custo mais caros: horas-pessoa e minutos em nuvem. Abaixo está uma estimativa em três escalas típicas. Os números são ilustrativos e dependem da região, do fabricante do dispositivo e dos preços do provedor; verifique as taxas atuais antes de comprar.

Seu próprio farm -> investimento único

Item

Para 10 dispositivos

Para 30 dispositivos

Para 50 dispositivos

Telefones Android usados (~$60 cada)

~$600

~$1.800

~$3.000

Hubs USB alimentados

~$100

~$250

~$400

Mini-PC / host

~$400

~$400

~$500

Cabos, rack, extras

~$80

~$150

~$250

Total único

~$1.200

~$2.600

~$4.150

Eletricidade por mês

centavos

~$10–20

~$20–40

Isso é despesa de capital: pague uma vez, e o farm funciona por anos a um custo quase zero.

Nuvem -> você paga por minuto

Firebase Test Lab, AWS Device Farm, BrowserStack e similares cobram por dispositivo-minuto -> aproximadamente $0,05–0,20 por dispositivo-minuto. Uma única execução de regressão em 30 dispositivos com 5 minutos cada são 150 dispositivo-minutos, ou seja, ~$7,5–30 por execução.

Agora multiplique pela intensidade de CI:

Frequência de execução

Execuções por mês

Custo (a ~$15/execução)

2× por dia

~44

~$660/mês

10× por dia

~220

~$3.300/mês

Em cada push (equipe ativa)

500+

$7.500+/mês

Ponto de equilíbrio: um farm de 30 dispositivos (~$2.600) se paga em relação à nuvem em aproximadamente 4 meses com modestas 2 execuções por dia; com CI ativo, em menos de um mês. Depois disso, a conta da nuvem continua pingando todo mês, e o farm não.

Trabalho manual -> o que é liberado

Uma execução manual de um único cenário de regressão em 30 dispositivos equivale aproximadamente a um dia de trabalho de um engenheiro de QA. Executar a regressão duas vezes por semana soma cerca de 8 dias-pessoa por mês. Com um custo de QA em torno de $1.600–2.700/mês, isso é uma parte significativa de um salário que o pipeline libera para trabalho significativo, em vez da rotina de "conectar–instalar–clicar–gravar" ×30.

Como isso se converte em dinheiro

Não há renda direta "do script" aqui -> existem três mecanismos indiretos, mas muito reais:

  • Lançamentos mais rápidos. Regressão em minutos em vez de um dia → você entrega funcionalidades com mais frequência → responde ao mercado mais rápido. Para um produto por assinatura, isso se traduz diretamente em retenção e receita.
  • Menos bugs em produção. Pegar uma falha em um Samsung específico antes do lançamento custa centavos; a mesma falha chegando aos usuários significa classificações na loja em queda, churn e reembolsos. Cada bug detectado cedo é um punhado de avaliações negativas que nunca são escritas.
  • Isso se vende como serviço. Montar um farm de dispositivos e testes de CI é uma função muito procurada em freelancing e terceirização. O pipeline acima é um núcleo pronto para tal oferta.

A principal diferença dos "esquemas de engajamento": lá, o dinheiro vem de enganar algoritmos e termina em banimento. Aqui, vem de horas economizadas e perdas evitadas. O primeiro desmorona; o segundo é um caso de negócio sustentável do qual você não se envergonha de mostrar a um cliente.

Conclusão

O resultado é um pipeline reproduzível: um comando, e o aplicativo é exercitado em toda a frota de dispositivos, com um relatório em vídeo no final que mostra o comportamento em cada dispositivo específico.

Salve nos favoritos e inscreva-se para não perder artigos úteis que vão economizar dinheiro 📝

Guardar com um clique

Faça leitura aprofundada de artigos virais com IA no 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 criadores

Transforme o seu Markdown num artigo 𝕏 impecável

Quando publica os seus próprios textos longos, formatar imagens, tabelas e blocos de código para o 𝕏 é uma dor de cabeça. O YouMind transforma um rascunho completo em Markdown num artigo 𝕏 impecável e pronto a publicar.

Experimente Markdown para 𝕏

Mais padrões para decifrar

Artigos virais recentes

Explorar mais artigos virais