Saturday, March 29, 2014

adbclient: under the hood of AndroidViewClient/culebra

Some of the reasons that made me think about replacing chimpchat, a library that facilitates the use of monkey from Java, used by monkeyrunner, which in turn is the bridge between monkey and Jython to be able to write python language scripts, is its instability and lack of concurrency support.

Long running tests, some lasting more than a day, had the serious problem of failing not because the test failed but because monkeyrunner connection with the device via chimpchat was frozen.

AndroidViewClient/culebra prior to version 4.0 relied on this same connection method as they were using monkeyrunner as the interpreter. This lead to a lot of problems reported against them.

adbclient is the answer to these problems. It's a 100% pure python implementation of an adb client that not only stabilizes the situation but also frees AndroidViewClient/culebra from monkeyrunner and Jython. Furthermore, even though it was not created as a standalone client and its only purpose was to satisfy AndroidViewClient/culebra needs in terms of Android device connections, there's nothing preventing you from using it in python scripts. There are some circumstances where you can even do something which is not even planned for its originator.

Here, I'm introducing an example that I hope spurs your imagination to use adbclient in many other cases. Sometimes, higher level methods supplied by AndroidViewClient/culebra are too high for the case at hands. Let's say you want to do some low-level processing on the screenshots taken and you desire is to do it from inside the same script, perhaps a python unittest, instead of saving the image using viewclient's View.writeImageToFile() or ViewClient.writeImageToFile() depending on your intentions of saving a single View versus a the entire device screen.
Having access to the Python Image Library (PIL) object before it's saved to a file entitles us to use vast PIL set of features, in this example we are calculating the image redness by using PIL's ImageStat.
In such case you can resort to adbclient's AdbClient.takeSnapshot(), as shown here.

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import re
import sys
import os
import time
import math
from PIL import Image, ImageStat

from com.dtmilano.android.adb.adbclient import AdbClient

def redness(image):
    stat = ImageStat.Stat(image)
    return stat.mean[0]/255.0

if len(sys.argv) >= 2:
    serialno = sys.argv[1]
else:
    serialno = '.*'

device = AdbClient(serialno=serialno)
print redness(device.takeSnapshot())


Hope this helps you start using adbclient.

Tuesday, March 18, 2014

culebra's less known features: auto-regexp

This post starts a new series illustrating AndroidViewClient/culebra's less know features. The first instalment is dedicated to an extremely useful feature: auto-regexp.

But before going deeper into this option let's analyze a bit the general use case.
Very often, in the process of writing functional test cases you find yourself having to verify the result of some previous actions on the UI by recognizing some aspects of the device screen.

Traditionally this is done obtaining the screenshot and then applying some computational method to identify elements in the image and being able to determine if it is exactly what we were waiting for or in some cases up to what degree, allowing some parts of the image to vary.
This subject has been treated here before, for example in monkeyrunner: visual image comparison. However, we will be aiming a much simpler method in this post. Instead of using the image representing the screen we will be using the logical representation of it, that is the tree of Views visible at any given time.

Let's also consider the our device or emulator is showing the lock screen and that is the condition we want to detect.



culebra helps us creating the script to achieve this.

$ culebra --find-views-with-text=on --output=lockscreen.py

then we can verify that the device or emulator is showing the lock screen simply by running the script

$ ./lockscreen.py

Nevertheless, when we run the script later, it will miserably fail with a message similar to this one

Traceback (most recent call last):
  File "./lockscreen-0.py", line 44, in
    com_android_keyguard___id_clock_view = vc.findViewWithTextOrRaise(u'5:47')
  File "/usr/local/lib/python2.7/dist-packages/androidviewclient-5.4.2-py2.7.egg/com/dtmilano/android/viewclient.py", line 2220, in findViewWithTextOrRaise
    raise ViewNotFoundException("text", text, root)

com.dtmilano.android.viewclient.ViewNotFoundException: Couldn't find View with text='5:47' in tree with root=ROOT

I'm sure you have already guessed the reason. When we generated the script, the text in the View was used to generate the line

com_android_keyguard___id_clock_view = vc.findViewWithTextOrRaise(u'5:47')

We need a regular expression. We could add it manually but culebra can also help us generating this scripts by identifying some constructions and replacing them by their regular expressions counterpart. The option, as we mentioned before is auto-regexp. This option also has a help sub-option to clarify the possible values

$ culebra --auto-regexp=help
Available auto-regexps options:
    help: prints this help
    all: includes all the available regexps
    date: (Mon|Tue|Wed|Thu|Fri|Sat|Sun), (January|February|March|April|May|June|July|August|September|October|November|December) [0123]\d
    battery: Charging, \d\d%
    clock: [012]?\d:[0-5]\d

Adding this option to the original command line we will have

$ culebra --find-views-with-text=on \
    --auto-regexp=all \
    --output=lockscreen.py

and as a result our generated script contains lines like

com_android_keyguard___id_clock_view = vc.findViewWithTextOrRaise(re.compile(u'[012]?\d:[0-5]\d'))


and the screen identification will succeed even if date and time change.
Hope this helps you with your culebra scripts.