Python + ADB

Recentemente, fiz um projeto para efetuar testes em dispositivos Android utilizando a linguagem Python. Esses testes, feitos em celular Samsung conectados via USB a um computador com Windows, onde os dispositivos eram testados utilizando comando chamado adb (Android Debug Bridge).

[ Hits: 8.885 ]

Por: Alisson Machado em 30/03/2017


Introdução



Recentemente, fiz um projeto para efetuar testes em dispositivos Android utilizando a linguagem Python. Esses testes eram feitos em um celular da Samsung conectado via USB a um computador com Windows. Os dispositivos era testados utilizando um comando chamado adb (Android Debug Bridge).

Todos os meus outros posts foram feitos utilizando Linux, esse eu vou fazer utilizando o Windows, pois quero replicar o mesmo ambiente do cliente e mostrar que o Python também roda muito bem em ambientes Windows.

O primeiro passo é instalar um "cara" chamado Android Studio, pois nele já tem o SDKmanager, o adb e todos os requisitos necessários para o desenvolvimento Android.

Para instalar, é só baixar no site oficial:
Após fazer a instalação eu coloquei o caminho do adb dentro da variável PATH do Windows:

→ C:\Users\alisson\AppData\Local\Android\sdk\platform-tools

Não entendo nada de Windows, mas na versão 10 é só abrir o iniciar e digitar variáveis, ele vai aparecer uma opção chamada: "Editar variáveis de ambiente do sistema".

E lá vai ter a PATH, é só adicionar esse caminho lá, lembrando que tem que mudar o nome do usuário. Feito isso, abra o CMD e digite o comando:

C:\Users\alisson adb devices

A saída será:

List of devices attached

Então, conecte o seu celular no computador e, no gerenciamento de conexão USB, marque a opção: "Depuração USB".
Desconecte o USB e conecte de novo, no seu celular irá aparecer uma mensagem para você aceitar a chave do computador. Aceite, e no celular já irá aparecer na lista de devices.

C:\Users\alisson adb devices

List of devices attached

4012e40b        device


O meu celular é esse: 4012e40b.

Caso eu queira entrar dentro do dispositivo, é só digitar o seguinte comando:

adb shell
shell@A6020l36:/ $

E aí, já era. Você está dentro do sistema Android.

Pra quem conhece Linux, muitos dos comandos vão funcionar, como o ls, cd, pwd e entre outros comandos básicos.

Pra voltar ao CMD do Windows, é só pressionar Ctrl+d.

Bom, isso é o básico que é necessário saber por enquanto, pois isso permite saber que o seu celular está corretamente configurado para que sejam efetuados os testes.

Nos testes que foram feitos na empresa, utilizamos basicamente 3 módulos do Python, sendo eles:
  • subprocess ( Executa comandos do sistema )
  • ElementTree ( Modulo para trabalhar com XML )
  • threading ( Modulo de threads para executar testes em paralelo )

No exemplo abaixo, não vou usar threads, mas caso vocês precisem, eu tenho um post específico falando de threads neste link: Threads em Python

O script completo é esse:

import subprocess
import xml.etree.ElementTree as ET
import time

def get_devices():
    output = subprocess.Popen('adb devices', shell=True, stdout=subprocess.PIPE).communicate()[0]
    output = str(output).split('attached')[1]
    output = output.split('device')[0]
    output = output.replace("\\n", "").replace("\\r", "")
    output = output.replace("\\t", "")
    return output

def get_screen(serial):
    output = subprocess.Popen('adb -s %s shell uiautomator dump'%serial,shell=True,stdout=subprocess.PIPE).communicate()[0]
    print(output)
    output = subprocess.Popen('adb -s %s pull /storage/sdcard0/window_dump.xml'%serial,shell=True,stdout=subprocess.PIPE).communicate()[0]
    print(output)

def tap_screen(app_name,x,y):
    print("Abrindo %s"%app_name)
    output = subprocess.Popen("adb shell input tap %s %s"%(x,y),shell=True,stdout=subprocess.PIPE)
    time.sleep(5)
    print("Voltando a tela anterior")
    output = subprocess.Popen("adb shell input keyevent 4",shell=True,stdout=subprocess.PIPE)

def test_app(node,app_name):
    if node.attrib.get('text') == app_name:
        position = node.attrib.get('bounds').split(']')[0]
        position = position.replace("[","").split(",")
        print("Testando APP")
        tap_screen(app_name, position[0], position[1])
    else:
        for n in node.findall('node'):
            test_app(n,app_name)

def get_apps():
    xml = ET.parse('window_dump.xml')
    root = xml.getroot()
    app = test_app(root,'Play Store')

device = get_devices()
get_screen(device)
get_apps()

Agora, explicando o que isso faz, a função "get_devices" abaixo:

def get_devices():
    output = subprocess.Popen('adb devices', shell=True, stdout=subprocess.PIPE).communicate()[0]
    output = str(output).split('attached')[1]
    output = output.split('device')[0]
    output = output.replace("\\n", "").replace("\\r", "")
    output = output.replace("\\t", "")
    return output

Ela executa um comando dentro do sistema operacional, o comando "adb devices" vai listar todos os seus dispositivos conectados, então, a saída é armazenada dentro da variável output e, na sequência, vão sendo aplicados alguns comandos para remover caracteres especiais, como quebra de linha e Tabs, para que no final venha só o SERIAL do dispositivo conectado.

Uma vez que tenho esse serial, é possível pegar um dump da tela para saber a posição dos ícones dentro dela.

A tela que eu fiz um dump foi a seguinte:
O script criado vai procurar o botão da "play store", clicar nele e depois voltar para essa mesma tela.

Para que isso aconteça, foi criada a função "get_screen":

def get_screen(serial):
    output = subprocess.Popen('adb -s %s shell uiautomator dump'%serial,shell=True,stdout=subprocess.PIPE).communicate()[0]
    print(output)
    output = subprocess.Popen('adb -s %s pull /storage/sdcard0/window_dump.xml'%serial,shell=True,stdout=subprocess.PIPE).communicate()[0]
    print(output)

Essa função roda o comando "adb shell uiatomator dump", que gera um XML com as posições do ícones na tela, uma vez que esse XML foi gerado, ele é copiado para o diretório onde está sendo executado o script, com o comando "adb pull".

Logo na sequência, é executada a função "get_apps()":

def get_apps():
    xml = ET.parse('window_dump.xml')
    root = xml.getroot()
    app = test_app(root,'Play Store')

Essa função, basicamente, lê o arquivo XML gerado pelo "adb", faz o parse dele de string para XML e manda para a função "test_app":

def test_app(node,app_name):
    if node.attrib.get('text') == app_name:
        position = node.attrib.get('bounds').split(']')[0]
        position = position.replace("[","").split(",")
        print("Testando APP")
        tap_screen(app_name, position[0], position[1])
    else:
        for n in node.findall('node'):
            test_app(n,app_name)

Essa função verifica se o elemento atual do XML possui um atributo chamado "text", e se esse atributo é igual ao nome do APP procurado, que no caso é Play Store. Caso esse APP não seja encontrado, é realizado um "for", buscando os elementos filhos do elemento atual, fazendo assim uma recursividade, até que sejam percorridos todos os elementos filhos desse XML.

Quando o APP é encontrado, é pegado o valor do atributo "bounds" que guarda a posição do item na tela, essa posição é guarda na variável "position" e depois é chamada a função "tap_screen" que clica no botão.

def tap_screen(app_name,x,y):
    print("Abrindo %s"%app_name)
    output = subprocess.Popen("adb shell input tap %s %s"%(x,y),shell=True,stdout=subprocess.PIPE)
    time.sleep(5)
    print("Voltando a tela anterior")
    output = subprocess.Popen("adb shell input keyevent 4",shell=True,stdout=subprocess.PIPE)

Essa função recebe como parâmetros, o nome do aplicativo somente para que seja informado o nome na tela e as posições x e y do botão, essas posições são passadas para o comando "adb shell input tap" que vai clicar no Play Store, como esse APP demora para abrir, é aguardado o tempo de 5 segundos e depois é executado o comando "adb shell input keyvent 4", que é o equivalente ao botão voltar do Android, então é voltado para a tela anterior.

Alguns dos eventos possíveis são:
  • 0 → "KEYCODE_UNKNOWN"
  • 1 → "KEYCODE_MENU"
  • 2 → "KEYCODE_SOFT_RIGHT"
  • 3 → "KEYCODE_HOME"
  • 4 → "KEYCODE_BACK"
  • 5 → "KEYCODE_CALL"
  • 6 → "KEYCODE_ENDCALL"
  • 7 → "KEYCODE_0"
  • 8 → "KEYCODE_1"
  • 9 → "KEYCODE_2"
  • 10 → "KEYCODE_3"
  • 11 → "KEYCODE_4"
  • 12 → "KEYCODE_5"
  • 13 → "KEYCODE_6"
  • 14 → "KEYCODE_7"
  • 15 → "KEYCODE_8"
  • 16 → "KEYCODE_9"
  • 17 → "KEYCODE_STAR"
  • 18 → "KEYCODE_POUND"
  • 19 → "KEYCODE_DPAD_UP"
  • 20 → "KEYCODE_DPAD_DOWN"
  • 21 → "KEYCODE_DPAD_LEFT"
  • 22 → "KEYCODE_DPAD_RIGHT"
  • 23 → "KEYCODE_DPAD_CENTER"
  • 24 → "KEYCODE_VOLUME_UP"
  • 25 → "KEYCODE_VOLUME_DOWN"
  • 26 → "KEYCODE_POWER"
  • 27 → "KEYCODE_CAMERA"
  • 28 → "KEYCODE_CLEAR"
  • 29 → "KEYCODE_A"
  • 30 → "KEYCODE_B"
  • 31 → "KEYCODE_C"
  • 32 → "KEYCODE_D"
  • 33 → "KEYCODE_E"
  • 34 → "KEYCODE_F"
  • 35 → "KEYCODE_G"

Basicamente, é isso aí, qualquer dúvida é só em dar um salve.

   

Páginas do artigo
   1. Introdução
Outros artigos deste autor

paramiko - Python + SSH

MongoDB Aggregation

Python - Threads

Python Flask Básico

Sincronizando Dados do PostgreSQL no Elasticsearch

Leitura recomendada

Criando um leitor de RSS com Python

Trabalhando com permutações em ordem lexicográfica crescente

Interagindo com servidores HTTP com Python

Breve Estudo Sobre Ransomwares e Análise Estática/Dinâmica do WannaCry

paramiko - Python + SSH

  
Comentários
[1] Comentário enviado por podscrer em 31/03/2017 - 07:23h

Primeiramente, muito bom o artigo. Não conhecia essas capacidades do ADB.

Segundo, porque instalar o Android Studio completo? Já que é pra usar só ADB, não é melhor baixar só ele e suas ferramentas?

https://developer.android.com/studio/releases/platform-tools.html


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts