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.