Friday, May 29, 2015

Google Cloud Test Lab

Yesterday, at Google I/O 2015, Cloud Test Lab was announced. One of its promoted features is: "you can run all of your tests across all devices, all at the same time--giving you access to massively parallel testing, bringing you deep and scaled insight".
Wow, that's pretty much the same I described in my previous post,
android: culebra multi-device capabilities, minus the devices of course. You have to provide them.

Wouldn't it be great if Cloud Test Lab allows you to create your personalized tests, in addition to the automatically generated ones, using something like culebra GUI, where you can just point and click?

Tuesday, May 19, 2015

android: culebra multi-device capabilities

culebra and its GUI both include multi-device capabilities in auto-generated test cases and scripts.

The test case or script is created as usual, but when the --multi-device option is present in the command line, the statements generated will be slightly different, still you can easily recognize them. These statements will include support for several devices using python's list comprehension.

For example, a typical self.vc.dump(window=-1) will become

 [vc.dump(window=-1) for vc in self.allVcs()]

that is, for all the ViewClient's dump the content of the default window. You may wonder where the list of all ViewClient's obtained by self.allVcs() comes from. CulebraTestCase class handles multiple devices in its setUp() method and it's there where the lists of all devices, serial numbers and ViewClient's are handled

self.devices.append(ConnectedDevice(serialno=serialno, device=device, vc=vc))

Everything is handled automatically, so there's nothing to worry about. If only one device is connected to adb or a single serial number is passed to the test case as an argument, then the test case behaves exactly as single-device. However, if more than one device is connected to adb and they are specified on the command line the multi-device capabilities kicks-in and the test is run on all devices at the same time.
The special command line keyword all can be used to mean all the connected devices.

This trivial test for Calculator generated using

$ culebra --gui --multi-device \
    --unit-test-class \
    --do-not-verify-screen-dump \
    -o ~/tmp/multi.py

shows multi-device capabilities.


#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
Copyright (C) 2013-2014  Diego Torres Milano
Created on 2015-05-19 by Culebra v10.5.1
                      __    __    __    __
                     /  \  /  \  /  \  /  \ 
____________________/  __\/  __\/  __\/  __\_____________________________
___________________/  /__/  /__/  /__/  /________________________________
                   | / \   / \   / \   / \   \___
                   |/   \_/   \_/   \_/   \    o \ 
                                           \_____/--<
@author: Diego Torres Milano
@author: Jennifer E. Swofford (ascii art snake)
'''


import re
import sys
import os


import unittest


try:
    sys.path.insert(0, os.path.join(os.environ['ANDROID_VIEW_CLIENT_HOME'], 'src'))
except:
    pass

from com.dtmilano.android.viewclient import ViewClient, CulebraTestCase

TAG = 'CULEBRA'


class CulebraTests(CulebraTestCase):

    @classmethod
    def setUpClass(cls):
        cls.kwargs1 = {'ignoreversioncheck': False, 'verbose': False, 'ignoresecuredevice': False}
        cls.kwargs2 = {'compresseddump': True, 'startviewserver': True, 'forceviewserveruse': False, 'autodump': False, 'ignoreuiautomatorkilled': True}
        cls.options = {'start-activity': None, 'device-art': None, 'use-jar': False, 'multi-device': True, 'unit-test-class': True, 'save-screenshot': None, 'use-dictionary': False, 'glare': False, 'dictionary-keys-from': 'id', 'scale': 1, 'find-views-with-content-description': True, 'window': -1, 'orientation-locked': None, 'save-view-screenshots': None, 'find-views-by-id': True, 'log-actions': False, 'use-regexps': False, 'null-back-end': False, 'auto-regexps': None, 'do-not-verify-screen-dump': True, 'verbose-comments': False, 'gui': True, 'find-views-with-text': True, 'prepend-to-sys-path': True, 'drop-shadow': False, 'output': '/Users/diego/tmp/multi2.py', 'unit-test-method': None, 'interactive': False}
        cls.sleep = 5

    def setUp(self):
        super(CulebraTests, self).setUp()

    def tearDown(self):
        super(CulebraTests, self).tearDown()

    def preconditions(self):
        if not super(CulebraTests, self).preconditions():
            return False
        return True

    def testSomething(self):
        if not self.preconditions():
            self.fail('Preconditions failed')

        _s = CulebraTests.sleep
        _v = CulebraTests.verbose

        [_vc.dump(window=-1) for _vc in self.allVcs()]
        [_vc.findViewWithTextOrRaise(u'2').touch() for _vc in self.allVcs()]
        [_vc.sleep(_s) for _vc in self.allVcs()]
        [_vc.dump(window=-1) for _vc in self.allVcs()]
        [_vc.findViewWithContentDescriptionOrRaise(u'''plus''').touch() for _vc in self.allVcs()]
        [_vc.sleep(_s) for _vc in self.allVcs()]
        [_vc.dump(window=-1) for _vc in self.allVcs()]
        [_vc.findViewWithTextOrRaise(u'1').touch() for _vc in self.allVcs()]
        [_vc.sleep(_s) for _vc in self.allVcs()]
        [_vc.dump(window=-1) for _vc in self.allVcs()]
        [_vc.findViewWithContentDescriptionOrRaise(u'''equals''').touch() for _vc in self.allVcs()]
        [_vc.sleep(_s) for _vc in self.allVcs()]
        [_vc.dump(window=-1) for _vc in self.allVcs()]


if __name__ == '__main__':
    CulebraTests.main()


Then, when you run the test with several devices connected as

$ ~/tmp/multi.py -s all

you will obtain something like this



Wednesday, May 13, 2015

android: beautiful screenshots, take 2

In my previous post, android: obtaining beautiful screenshots automatically, I introduced an automated way of obtaining screenshots of your application perhaps after doing some actions that would take considerably time to do manually over time.

We described how culebra intuitive UI could help you create the automation script even if you don't know anything about programing or python.

However, for simpler cases or when you just want to take one screenshot and an automation script is not worth the effort, dump, another tool in AndroidViewClient's toolbox can be used.

Simply, to take a screenshot and add device art, shadow and glare:


$ dump -f /path/to/image -ZBA auto

or, with the long option which are easier to remember

$ dump --save-screenshot=/path/to/file \
    --drop-shadow \
    --glare \
    --device-art=auto

Now taking advantage of this opportunity. I would like to introduce some new device art support added in the latest version, like this Samsung Galaxy S4 (this screenshot has been taken with the previous command)




Sunday, April 26, 2015

android: obtaining beautiful screenshots automatically

How much time have you invested obtaining screenshots for your app and then making them look nicer framing them in the corresponding device artwork?

You have to install the app, manually do the steps needed to get to the point where you want to take the screenshot to produce the most effective communication of the idea you are trying to convey, actually taking the screenshot, saving it to a file and then probably using Device Art Generator to frame it using the corresponding artwork and orientation that you have to decide and finally downloading the result.

Android Studio may simplify these steps a bit, but still is annoyingly slow and time consuming to have to check the desired options every time.



The model (i.e. Nexus 5) has to be selected every time as well as Drop Shadow and Screen Glare. The orientation is not detected and you have to Rotate it manually if incorrect.

Wouldn't it be great if you can automate all of these steps and get the screenshots without human intervention?

I have wondered the same many times and finally support for device artwork was added to AndroidViewClient/culebra.

Task automation using culebra

As an example, just let's pretend we want to prepare some screenshots to be included in the documentation or presentation about our app, which in this case would be Calculator.
Every time we create a new version of the app we want to reproduce the same scenario an take the beautiful screenshot. For the sake of simplicity, this scenario is the simple calculation "2+1=3".

Let's run

$ culebra --gui --do-not-verify-screen-dump --scale=0.5 \
    --device-art=auto --drop-shadow --glare \
    -o ~/tmp/calculator.py

This runs `culebra` GUI.
Once the window reproducing your device screen is shown, do:

  1. Press HOME, to start from a known state
  2. Touch on All Apps
  3. Touch on Calculator
  4. Click with the secondary button to open the context menu and select Long touch point using PX (the shortcut for this action is ^L)
  5. Touch on Calculator's DEL, to completely clear the screen
  6. Touch 2
  7. Touch +
  8. Touch 1
  9. Click with the secondary button to open the context menu and select Take snapshot and save to file (the shortcut is ^F)
  10. Close the window
Now we have the `calculator.py` screen generated, that every time we run it will reproduce the steps, detect the device and orientation, take the screenshot and frame it with the corresponding device artwork and will add the drop shadow and glare.



Analyzing the generated script

Let's take a closer look at the script generated by `culebra`.


#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
Copyright (C) 2013-2014  Diego Torres Milano
Created on 2015-04-26 by Culebra v10.3.0
                      __    __    __    __
                     /  \  /  \  /  \  /  \ 
____________________/  __\/  __\/  __\/  __\_____________________________
___________________/  /__/  /__/  /__/  /________________________________
                   | / \   / \   / \   / \   \___
                   |/   \_/   \_/   \_/   \    o \ 
                                           \_____/--<
@author: Diego Torres Milano
@author: Jennifer E. Swofford (ascii art snake)
'''


import re
import sys
import os


try:
    sys.path.insert(0, os.path.join(os.environ['ANDROID_VIEW_CLIENT_HOME'], 'src'))
except:
    pass

from com.dtmilano.android.viewclient import ViewClient

TAG = 'CULEBRA'

_s = 5
_v = '--verbose' in sys.argv


kwargs1 = {'ignoreversioncheck': False, 'verbose': False, 'ignoresecuredevice': False}
device, serialno = ViewClient.connectToDeviceOrExit(**kwargs1)
kwargs2 = {'compresseddump': True, 'startviewserver': True, 'forceviewserveruse': False, 'autodump': False, 'ignoreuiautomatorkilled': True}
vc = ViewClient(device, serialno, **kwargs2)
#vc.dump(window='-1') # FIXME: seems not needed

vc.dump(window=-1)
device.press('HOME')
vc.dump(window=-1)
vc.findViewWithContentDescriptionOrRaise(u'''Apps''').touch()
vc.sleep(_s)
vc.dump(window=-1)
vc.findViewWithContentDescriptionOrRaise(u'''Calculator''').touch()
vc.sleep(_s)
vc.dump(window=-1)
device.longTouch(912.0, 838.0, 2000, 0)
vc.sleep(5)
vc.dump(window=-1)
vc.findViewWithTextOrRaise(u'2').touch()
vc.sleep(_s)
vc.dump(window=-1)
vc.findViewWithContentDescriptionOrRaise(u'''plus''').touch()
vc.sleep(_s)
vc.dump(window=-1)
vc.findViewWithTextOrRaise(u'1').touch()
vc.sleep(_s)
vc.dump(window=-1)
vc.writeImageToFile('/tmp/${serialno}-${focusedwindowname}-${timestamp}.png', 'PNG', 'auto', True, True)


The first part is the same for all culebra scripts. Some global variables, like TAG, are defined and can lately be used as parameters for some method calls like `Log.d()`.
Then the ViewClient object is created connecting to the selected device. If you don't specify any `serialno` the default device will be selected.
Once the ViewClient object is created we see the method invocations that correspond to the action we did on the GUI, closed by the invocation to `vc.writeImageToFile()` which will be creating the screenshot. In this case, and determined by the command line arguments used when we launched `culebra`, device art will be `auto` selected, and drop shadow (True) and glare (True) will be added.
The filename generation for the taken screenshot uses some keywords, replaced at save time, to avoid overwriting old ones if any.

Here is where the magic begins

As a proof-of-concept, let's physically rotate the device and run the generated script again

$ ~/tmp/calculator.py

once it runs we will obtain the correct screenshot for landscape orientation



No human intervention!



Monday, January 26, 2015

New culebra logo

culebra logo
Dean Britto of deanbritto.com has graciously created this awesome culebra logo and release it under a Creative Commons Attribution 4.0 International License.

This will be incorporated in future culebra versions and documentation.


Thanks Dean for your fine work!

Thursday, January 22, 2015

AndroidViewClient/culebra 10K/month download mark

I proudly announce that last month we achieved the 10K/month download.




Thank you all collaborator, contributors, testers and users that helped in the creation and evolution of one of the best and most versatile Android automated testing tool.

Since culebra's GUI inception some months ago, we closed the gap between programmers and testers. Even though the auto-generated test scripts are the same as the ones that could be manually created using AndroidViewClient as a library  with the GUI we lowered the bar and the tool can be used even you don't know how to program in python and even if you don't program at all.

In retrospective last months we have seen several fundamental additions, like as I mentioned

  • culebra GUI
  • multi-device test generation and run capability (more coming soon)
  • orientation locked tests
  • log test steps to file, screen and adb logcat
  • screen and View snapshots with filename generated from pattern
  • added WiFiManager to mimic Android device's
  • calculation of distance between different View trees
  • drag using PX or DIP
  • and many, many more

Stay in touch. You comments and suggestions are welcome, and http://stackoverflow.com/questions/tagged/androidviewclient is a great way of getting your questions answered.
Install from pypi, clone from github and read the documentation from the wiki (yes, it's been updated and cleaned up lately, however some still needs some love, anyone?).

See you at the 100K/month post ;-)

Friday, November 07, 2014

culebra: the magical drag

This time we are demonstrating how to include drag() method calls in the generated test script.

The little secret, and what adds some "magic" is that units of the generated method call can be selected between DIP (density-independent pixels, also known as dp) or PX (pixels). The former, allows to give the generated test some independence of the physical screen size it is running on by applying the inverse relationship between pixels and density. That is, when a point it's touched on the screen, the actual density it used to calculate the touched points in DIPs and used to provide the arguments for the drag() method.

This is the culebra auto-generated script after the interactions showed in the screencast are performed.

If you inspect the script you will notice that now the drag() method includes another argument, which is 0 in this case and indicates the orientation of the device, but we will be covering this in a future post.


#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
Copyright (C) 2013-2014  Diego Torres Milano
Created on 2014-11-07 by Culebra v8.14.3
                      __    __    __    __
                     /  \  /  \  /  \  /  \ 
____________________/  __\/  __\/  __\/  __\_____________________________
___________________/  /__/  /__/  /__/  /________________________________
                   | / \   / \   / \   / \   \___
                   |/   \_/   \_/   \_/   \    o \ 
                                           \_____/--<
@author: Diego Torres Milano
@author: Jennifer E. Swofford (ascii art snake)
'''


import re
import sys
import os


import unittest

from com.dtmilano.android.viewclient import ViewClient, CulebraTestCase


class CulebraTests(CulebraTestCase):

    @classmethod
    def setUpClass(cls):
        cls.kwargs1 = {'ignoreversioncheck': False, 'verbose': False, 'ignoresecuredevice': False}
        cls.kwargs2 = {'startviewserver': True, 'forceviewserveruse': False, 'autodump': False, 'ignoreuiautomatorkilled': True}
        cls.options = {'start-activity': None, 'unit-test-class': True, 'save-screenshot': None, 'use-dictionary': False, 'dictionary-keys-from': 'id', 'scale': 0.5, 'find-views-with-content-description': True, 'window': -1, 'orientation-locked': None, 'save-view-screenshots': None, 'find-views-by-id': True, 'do-not-verify-initial-screen-dump': True, 'use-regexps': False, 'auto-regexps': None, 'use-jar': False, 'verbose-comments': False, 'gui': True, 'find-views-with-text': True, 'output': '/home/user/tmp/grab-1.py', 'unit-test-method': None, 'append-to-sys-path': False, 'interactive': False}
        cls.sleep = 5

    def setUp(self):
        super(CulebraTests, self).setUp()

    def tearDown(self):
        super(CulebraTests, self).tearDown()

    def preconditions(self):
        if not super(CulebraTests, self).preconditions():
            return False
        return True

    def testSomething(self):
        if not self.preconditions():
            self.fail('Preconditions failed')

        self.vc.dump(window=-1)
        self.vc.device.dragDip((289.0, 485.0), (46.0, 484.0), 1000, 20, 0)
        self.vc.sleep(1)
        self.vc.dump(window=-1)
        self.vc.device.dragDip((44.0, 468.0), (339.0, 473.0), 1000, 20, 0)
        self.vc.sleep(1)
        self.vc.dump(window=-1)
        self.vc.device.dragDip((69.0, 478.0), (335.0, 485.0), 1000, 20, 0)
        self.vc.sleep(1)
        self.vc.dump(window=-1)
        self.vc.device.dragDip((345.0, 491.0), (31.0, 485.0), 1000, 20, 0)
        self.vc.sleep(1)
        self.vc.dump(window=-1)


if __name__ == '__main__':
    CulebraTests.main()


And this is the screencast showing the emulator window (I used an emulator to easily capture the content but you can use any device), a terminal window where I run culebra and the culebra window where the interaction takes place.



Wednesday, November 05, 2014

culebra: cross-device application tests

In the previous post, we demonstrated how a real python unit test can be generated using the new culebra GUI to test any application or set of applications in any Android device by just interacting with the representation of the device screen, that looks pretty much as the emulator.

We also mentioned that the generated script does not store screen coordinates to interact with the Views but its logical attributes like content description, text or ids.

Well, now we will be demonstrating how the generated test that launches Calculator, enter some operation and verifies the actual result can be run unaffected on a completely different device. Remember that we used a Nexus 4 emulator to interact and generate the test and now we will be using a Nexus 7 tablet to run it.

Without further ado, here is the recorded screen of this Nexus 7 tablet running the test.


Monday, November 03, 2014

culebra: GUI to automatically generate Android application tests

We have mentioned AndroidViewClient/culebra in many recent post and we reviewed many of the most useful features, however what I'm introducing today is certainly a breakthrough in automatic generation of Android application tests.

The following tests was automatically generated by running culebra and interacting with the window that mimics the content of the device or emulator.

#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
Copyright (C) 2013-2014  Diego Torres Milano
Created on 2014-11-02 by Culebra v8.11.1
                      __    __    __    __
                     /  \  /  \  /  \  /  \ 
____________________/  __\/  __\/  __\/  __\_____________________________
___________________/  /__/  /__/  /__/  /________________________________
                   | / \   / \   / \   / \   \___
                   |/   \_/   \_/   \_/   \    o \ 
                                           \_____/--<
@author: Diego Torres Milano
@author: Jennifer E. Swofford (ascii art snake)
'''


import re
import sys
import os


import unittest

from com.dtmilano.android.viewclient import ViewClient, CulebraTestCase


class CulebraTests(CulebraTestCase):

    @classmethod
    def setUpClass(cls):
        cls.kwargs1 = {'ignoreversioncheck': False, 'verbose': False, 'ignoresecuredevice': False}
        cls.kwargs2 = {'startviewserver': True, 'forceviewserveruse': False, 'autodump': False, 'ignoreuiautomatorkilled': True}
        cls.options = {'find-views-by-id': True, 'verbose-comments': False, 'find-views-with-content-description': True, 'save-view-screenshots': None, 'do-not-verify-initial-screen-dump': True, 'use-regexps': False, 'start-activity': None, 'find-views-with-text': True, 'unit-test-class': True, 'window': -1, 'save-screenshot': None, 'use-dictionary': False, 'scale': 0.5, 'use-jar': False, 'output': '/home/brldmila/tmp/calc-1.py', 'auto-regexps': None, 'interactive': False, 'append-to-sys-path': False, 'gui': True, 'dictionary-keys-from': 'id', 'unit-test-method': None}
        cls.sleep = 5

    def setUp(self):
        super(CulebraTests, self).setUp()

    def tearDown(self):
        super(CulebraTests, self).tearDown()

    def preconditions(self):
        return True

    def testSomething(self):
        if not self.preconditions():
            self.fail('Preconditions failed')

        self.vc.dump(window=-1)
        self.vc.device.press("HOME")
        self.vc.dump(window=-1)
        self.vc.findViewWithContentDescriptionOrRaise(u'''Apps''').touch()
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)
        self.vc.findViewWithTextOrRaise(u'Calculator').touch()
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)
        self.vc.findViewWithContentDescriptionOrRaise(u'''clear''').touch()
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)
        self.vc.findViewWithTextOrRaise(u'2').touch()
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)
        self.vc.findViewWithContentDescriptionOrRaise(u'''plus''').touch()
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)
        self.vc.findViewWithTextOrRaise(u'1').touch()
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)
        self.vc.findViewWithContentDescriptionOrRaise(u'''equals''').touch()
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)
        self.assertEquals(self.vc.findViewWithContentDescriptionOrRaise(u'''3''').getText(), u'''3''')
        self.vc.device.touchDip(168.262910798, 932.957746479)
        self.vc.sleep(CulebraTests.sleep)
        self.vc.dump(window=-1)


if __name__ == '__main__':
    CulebraTests.main()

To really appreciate how this test has been automatically generated I uploaded a simple (and crude) video of the steps. In the screencast you can see the emulator window (I used an emulator to easily capture the content but you can use any device), a terminal window where I run culebra and the culebra window where the interaction takes place.


Enjoy the video.
One really (or I should say extremely) cool feature of theses tests is that you can run exactly the same tests on a completely different device, let's say a tablet, and the tests would run exactly the same because culebra is not storing screen coordinates to interact with the Views but its logical attributes, like content description, text or ids. That way the test is device agnostic.
I'm explaining it step-by-step in the next post.

Friday, May 30, 2014

culebra: new unit tests facilities added

AndroidViewClient/culebra 7.0.2 was released recently. Besides a number of bug fixes it includes some new command line options mainly to help in the creation of unit tests.

$ culebra --help
usage: culebra [OPTION]... [serialno]

Options:
  -H, --help                       prints this help                             
  -V, --verbose                    verbose comments                             
  -v, --version
  -I, --ignore-secure-device
  -F, --force-view-server-use
  -S, --do-not-start-view-server
  -k, --do-not-ignore-uiautomator-killed don't ignore UiAutomator killed              
  -w, --window=WINDOW              use WINDOW content (default: -1, all windows)
  -i, --find-views-by-id=BOOL      whether to use findViewById() in script      
  -t, --find-views-with-text=BOOL  whether to use findViewWithText() in script  
  -d, --find-views-with-content-description=BOOL whether to use findViewWithContentDescription
  -r, --use-regexps                use regexps in matches                       
  -C, --verbose-comments
  -U, --unit-test-class            generates unit test class and script         
  -M, --unit-test-method=NAME      generates unit test method. Can be used with or without -U
  -j, --use-jar=BOOL               use jar and appropriate shebang to run script (deprecated)
  -D, --use-dictionary=BOOL        use a dictionary to store the Views found    
  -K, --dictionary-keys-from=VALUE dictionary keys from: id, text, content-description
  -R, --auto-regexps=LIST          auto regexps (i.e. clock), implies -r. help list options
  -a, --start-activity=COMPONENT   starts Activity before dump                  
  -o, --output=FILENAME            output filename                              
  -A, --interactive                interactive                                  
  -p, --append-to-sys-path         append environment variables values to sys.path
  -s, --save-screenshot=FILENAME   save screenshot to file                      

  -W, --save-view-screenshots=DIR  save View screenshots to files in directory  

These new options are:

-U, --unit-test-class this tells culebra to generate a script containing a unit test. If you just save the script and run it, the test containing in it will verify that the same Views are in the screen. This test case is a kind of logical screenshot where no bitmaps are involved.

-M, --unit-test-method=NAME this option has two different uses. If it's specified when --unit-test-case was also specified then the unit test case generated will have a test method called NAME. It's advisable to name the method testSomething, where Something should describe what you are testing. On the other hand, if --unit-test-case is not specified then only the test method is generated. This option is useful if you are generating test methods that you are planning to add to an existing class.

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.

Friday, January 31, 2014

Installing AndroidViewClient using easy_install

There are a lof of ways of installing or upgrading AndroidViewClient but this is perhaps the easiest one, no wonder why the command name just mentions it.

If you don't have easy_install installed install the package python-setuptools.

$ sudo easy_install --upgrade androidviewclient
Searching for androidviewclient
Reading http://pypi.python.org/simple/androidviewclient/
Best match: androidviewclient 4.10.1
Downloading https://pypi.python.org/packages/2.7/a/androidviewclient/androidviewclient-4.10.1-py2.7.egg#md5=190e29a946b44a651b37fcdcf1a8d2a8
Processing androidviewclient-4.10.1-py2.7.egg
creating /usr/local/lib/python2.7/dist-packages/androidviewclient-4.10.1-py2.7.egg
Extracting androidviewclient-4.10.1-py2.7.egg to /usr/local/lib/python2.7/dist-packages
Removing androidviewclient 4.10.0 from easy-install.pth file
Adding androidviewclient 4.10.1 to easy-install.pth file

Installed /usr/local/lib/python2.7/dist-packages/androidviewclient-4.10.1-py2.7.egg
Processing dependencies for androidviewclient

Finished processing dependencies for androidviewclient

Monday, November 25, 2013

Android Continuous Integration Guides: Ebook I



 This book helps you explored Continuous Integration in practice providing valuable information to start applying it soon to your Android projects.

Employs Ant to automate the building process, git to create a simple version control system repository to store our source code and manage the changes, and finally installs and configures Jenkins as the Continuous Integration of choice. In this journey we detail the creation of jobs for automating the building process of TemperatureConverter, its dependency library LocalViewServer and its tests and we emphasized on the relationship between the projects.

Finally, we analyze a way of getting XML results from Android tests and implement this to obtain an attractive interface to monitor the running of tests, their results, and the existing trends and using and showing EMMA code coverage reports.

This will save you precious time and experimentation leading you through a step-by-step guide.

Visit Google Play Books to find more.

Thursday, October 10, 2013

AndroidViewClient/culebra version 4.6.0: now 100% pure python

AndroidViewClient/culebra v4.6.0 has been released bringing major improvements closing the gap with the existing APIs. This is demonstrated by the example scripts migrated to use AdbClient now, instead of MonkeyDevice.
monkeyrunner seems much farther in the rearview mirror.

changelog

  • AdbClient: Added isLocked() method to check if screen is locked
  • Fixed #55: drag doesn't work for old Android versions
  • Removed sys.path manipulation not needed for python
  • Read ANDROID_ADB_SERVER_PORT environment variable to set adb PORT. This variable is set by Jenkins Android Emulator plugin.
  • Merge pull request #56 from knorrium/clean-whitespaces
  • Check for sys.executable that may be not available on Windows
  • Examples modified to use python
  • Marked jar file creation as deprecated
  • Improved UTF-8 treatment in messages
  • AdbClient.startActivity() raises exception on errors
  • Warns if modules are imported using 'monkeyrunner'
  • Added image comparison
  • Decode used only on python's 8-bit strings
  • Restored obtainAdbPath() used for android API <= 16
  • Trap exception generated by integer division by zero in takeSnapshot()
  • Fixed famebuffer exception



Monday, September 09, 2013

AndroidViewClient/culebra takeSnapshot() improvements

The latest release of AndroidViewClient/culebra v.4.2.1 includes now the implementation of AdbClient.takeSnapshot(), replacing MonkeyDevice.takeSnapshot() used in previous releases.

Starting from AndroidViewClient v4.0.0, monkeyrunner was ditched in favor of plain good old python. This change brought massive speed improvements in running tests as it was described in AndroidViewClient/culebra version 4.0.0: now 100% pure python, and takeSnapshot() is not an exception.

In order to measure the improvement, 2 scripts were added to the examples, one taking a screenshot using MonkeyDevice.takeSnapshot() and the orher using the alternative AdbClient.takeSnapshot(). As with other functionality, the API is maintained so changes to existing scripts are unnecessary or just minimum.


screenshot-monkeyrunner.py

The script taking the screenshot of the device or emulator using monkeyrunner.


#! /usr/bin/env monkeyrunner
'''
Copyright (C) 2012  Diego Torres Milano
Created on Set 5, 2013

@author: diego
'''


import sys
import os

from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

if len(sys.argv) < 2:
    print >> sys.stderr, "usage: %s filename.png" % sys.argv[0]
    sys.exit(1)

filename = sys.argv.pop(1)
device = MonkeyRunner.waitForConnection()
device.takeSnapshot().writeToFile(filename, 'PNG')

screenshot.py

The script taking the screenshot of the device or emulator using python.


#! /usr/bin/env python
'''
Copyright (C) 2012  Diego Torres Milano
Created on Aug 31, 2013

@author: diego
'''


import sys
import os

from com.dtmilano.android.viewclient import ViewClient

if len(sys.argv) < 2:
    sys.exit("usage: %s filename.png [serialno]" % sys.argv[0])

filename = sys.argv.pop(1)
device, serialno = ViewClient.connectToDeviceOrExit(verbose=False)
device.takeSnapshot().save(filename, 'PNG')

results

Both scripts were run to take the screenshot of a Nexus 4 (android api-18) home screen.

As you may have been anticipating, the python script run much faster and this is illustrated in the following chart.



Something to take into account is that both script are almost the same, except for some improvements that are supported only by AndroidViewClient, like the possibility of requesting a verbose connection or specifying the device serial number in the command line.
Also, notice that instead of writeToFile() like MonkeyImage does, save() is used. This is because AdbClient.takeSnapshot() returns a real PIL Image and not a wrapper.

Hope you enjoyed the changes.
If you have any question or comments just post it on Google+ or Stackoverflow.

Tuesday, August 27, 2013

AndroidViewClient/culebra version 4.0.0: now 100% pure python

AndroidViewClient/culebra v4.0.0 has been released bringing major improvements and an incredible speed gain by removing the dependency on monkeyrunner and now using python as the interpreter.

I have been thinking about this change for a very long time but never had the time to do it. In one way or another I found workaround after workaround for monkeyrunner and ChimpChat problems. If monkeyrunner was not detecting that the device was not actually connected, AndroidViewClient was forcing a wake() after waitForConnection() returns to verify that everything was right or catching the Exception and showing a more meaningful message (code). If the connection attempt didn't time out and hangs AndroidViewClient was also providing a workaround (code).
Lately, on some Android devices, uiautomator is killed before finishing and the Killed message is also included in the output, and then AndroidViewClient also provided a workaround (code).
I could name several more, but I think you got the idea.

However, it wasn't until the introduction of this bug:

Issue 58912:UiAutomator and UiAutomation-based tests fail to run when chimpchat connection is present on 4.3

when things were really screaming for a long term solution and not just another workaround.

Then, I took adbclient module I have been developing some time ago to be able to run some stability tests (did I mention ChimpChat was not stable enough to run them?), modify it a bit and now AndroidViewClient/culebra and dump are not  dependant on monkeyrunner or ChimpChat and can use any compatible python interpreter.

The inclusion of adbclient also brought some tremendous speed improvement, something you would thank if you have to run hundreds or thousands of tests.

These charts demonstrates the improvements:



This is just the beginning. Stay tuned and you will see more improvements coming.

Friday, August 09, 2013

AndroidViewClient/culebra version 3.2.0 released

The focus of this new release is to improve UTF-8 support.
Latest AndroidViewClient/culebra release (v3.2.0) can now be found at https://github.com/dtmilano/AndroidViewClient/releases/tag/v3.2.0

These are the release notes:

  • Added UTF-8 support for str* methods
  • Explicit error message when received XML is not valid
  • Added Chinese text tests



Wednesday, July 31, 2013

Android 4.3 (Jellybean) update breaks some things

It is quite annoying.
I guess this should not happen for a major release.

UiAutomator is kind of broken. I've found this problem on a Nexus 4 and also on emulator, so it seems generic. Now when Chimpchat opens a connection (i.e. monkeyrunner) and you try to run uiautomator from a different adb shell you'll get

07-30 23:49:51.821: D/AndroidRuntime(2951): >>>>>> AndroidRuntime START com.android.internal.os.RuntimeInit <<<<<<
07-30 23:49:51.821: D/AndroidRuntime(2951): CheckJNI is OFF
07-30 23:49:51.831: D/dalvikvm(2951): Trying to load lib libjavacore.so 0x0
07-30 23:49:51.841: D/dalvikvm(2951): Added shared lib libjavacore.so 0x0
07-30 23:49:51.841: D/dalvikvm(2951): Trying to load lib libnativehelper.so 0x0
07-30 23:49:51.841: D/dalvikvm(2951): Added shared lib libnativehelper.so 0x0
07-30 23:49:51.981: D/AndroidRuntime(2951): Calling main entry com.android.commands.uiautomator.Launcher
07-30 23:49:51.991: D/AndroidRuntime(2951): Shutting down VM
07-30 23:49:51.991: W/dalvikvm(2951): threadid=1: thread exiting with uncaught exception (group=0x415b9700)
07-30 23:49:51.991: D/dalvikvm(2951): Note: class Landroid/app/ActivityManagerNative; has 163 unimplemented (abstract) methods
07-30 23:49:51.991: E/JavaBinder(2951): Unknown binder error code. 0xfffffff7
07-30 23:49:51.991: E/ServiceManager(2951): error in getService
07-30 23:49:51.991: E/ServiceManager(2951): android.os.RemoteException: Unknown binder error code. 0xfffffff7
07-30 23:49:51.991: E/ServiceManager(2951): at android.os.BinderProxy.transact(Native Method)
07-30 23:49:51.991: E/ServiceManager(2951): at android.os.ServiceManagerProxy.getService(ServiceManagerNative.java:123)
07-30 23:49:51.991: E/ServiceManager(2951): at android.os.ServiceManager.getService(ServiceManager.java:55)
07-30 23:49:51.991: E/ServiceManager(2951): at android.app.ActivityManagerNative$1.create(ActivityManagerNative.java:1893)
07-30 23:49:51.991: E/ServiceManager(2951): at android.app.ActivityManagerNative$1.create(ActivityManagerNative.java:1891)
07-30 23:49:51.991: E/ServiceManager(2951): at android.util.Singleton.get(Singleton.java:34)
07-30 23:49:51.991: E/ServiceManager(2951): at android.app.ActivityManagerNative.getDefault(ActivityManagerNative.java:73)
07-30 23:49:51.991: E/ServiceManager(2951): at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:76)
07-30 23:49:51.991: E/ServiceManager(2951): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693)
07-30 23:49:51.991: E/ServiceManager(2951): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
07-30 23:49:51.991: E/ServiceManager(2951): at dalvik.system.NativeStart.main(Native Method)
07-30 23:49:51.991: I/Process(2951): Sending signal. PID: 2951 SIG: 9
07-30 23:49:51.991: E/AndroidRuntime(2951): *** FATAL EXCEPTION IN SYSTEM PROCESS: main
07-30 23:49:51.991: E/AndroidRuntime(2951): java.lang.IllegalStateException: UiAutomationService android.accessibilityservice.IAccessibilityServiceClient$Stub$Proxy@427adc60already registered!
07-30 23:49:51.991: E/AndroidRuntime(2951): at android.os.Parcel.readException(Parcel.java:1439)
07-30 23:49:51.991: E/AndroidRuntime(2951): at android.os.Parcel.readException(Parcel.java:1385)
07-30 23:49:51.991: E/AndroidRuntime(2951): at android.view.accessibility.IAccessibilityManager$Stub$Proxy.registerUiTestAutomationService(IAccessibilityManager.java:342)
07-30 23:49:51.991: E/AndroidRuntime(2951): at android.app.UiAutomationConnection.registerUiTestAutomationServiceLocked(UiAutomationConnection.java:173)
07-30 23:49:51.991: E/AndroidRuntime(2951): at android.app.UiAutomationConnection.connect(UiAutomationConnection.java:72)
07-30 23:49:51.991: E/AndroidRuntime(2951): at android.app.UiAutomation.connect(UiAutomation.java:188)
07-30 23:49:51.991: E/AndroidRuntime(2951): at com.android.uiautomator.core.UiAutomationShellWrapper.connect(UiAutomationShellWrapper.java:32)
07-30 23:49:51.991: E/AndroidRuntime(2951): at com.android.commands.uiautomator.DumpCommand.run(DumpCommand.java:74)
07-30 23:49:51.991: E/AndroidRuntime(2951): at com.android.commands.uiautomator.Launcher.main(Launcher.java:83)
07-30 23:49:51.991: E/AndroidRuntime(2951): at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
07-30 23:49:51.991: E/AndroidRuntime(2951): at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:235)
07-30 23:49:51.991: E/AndroidRuntime(2951): at dalvik.system.NativeStart.main(Native Method)
07-30 23:49:51.991: E/AndroidRuntime(2951): Error reporting crash
07-30 23:49:51.991: E/AndroidRuntime(2951): java.lang.NullPointerException
07-30 23:49:51.991: E/AndroidRuntime(2951): at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:76)
07-30 23:49:51.991: E/AndroidRuntime(2951): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693)
07-30 23:49:51.991: E/AndroidRuntime(2951): at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
07-30 23:49:51.991: E/AndroidRuntime(2951): at dalvik.system.NativeStart.main(Native Method)

This was perfectly valid and correct on Android 4.2.x. It still requires some investigation, but I wanted to post it here just in case you find the same, at least you won't feel alone.

UPDATE: 2013-AUG-27:
AndroidViewClient/culebra version 4.0.0: now 100% pure python describes a solution for this problem