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: 1.861 ]

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

Sincronizando Dados do PostgreSQL no Elasticsearch

Sockets em Python

paramiko - Python + SSH

Vault: SSH com OneTimePassword

MongoDB Aggregation

Leitura recomendada

Threads - Importância dentro de um software

Sockets em Python

Arduino com Bluetooth e pySerial

Desenvolvendo aplicações GUI simples em Python & Glade (PyGTK) com banco de dados SQLite

Python para pesquisadores: material didático

  
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 HostGator.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Viva o Android

Tópicos

Top 10 do mês

Scripts