Monday, August 01, 2016

Turns your Android smartphone into a complete laptop

I do not usually mention third party products in this blog unless there is a very good reason. The Superbook certainly is one of those.  It is a smart laptop shell that provides a large screen, keyboard and multi-touch trackpad, 8+ hours of battery, and phone charging capabilities. When plugged into your Android smartphone, it launches our app to deliver the full laptop experience.


The idea of using the same core device for your mobile and desktop experiences has been tried before, but this time, the Superbook gives it a twist and uses a full Android desktop. This is mainly possible due to the huge improvement lastest Android versions received in areas as multi-window support,  Android for work and Keyboard Shortcuts screen that surely move  the platform in the desktop direction.

That was not true some years ago. 6 years to be more precise, when one of the projects I was part of implemented the same concept but in the opposite direction I would say.

A mobile computing device with a mobile operating system and desktop operating system running concurrently and independently on a shared kernel without virtualization. The mobile operating system provides a user experience for the mobile computing device that suits the mobile environment. The desktop operating system provides a full desktop user experience when the mobile computing device is docked to a secondary terminal environment. The mobile computing device was a smartphone running the Android mobile OS and a full desktop Linux distribution (Ubuntu) on a modified Android kernel.



The concept included also a Smart book where you could dock your device.


Unfortunately, this project was later discontinued.

As I mentioned, a lot has changed in these last 6 years on Android and devices are much more powerful now to support this approach so take a look at Superbook as it might change your mobile and desktop experiences.



Friday, May 20, 2016

No Espresso Test Recorder in Android Studio 2.2

After all the frustration you may have experienced after discovering that Espresso Test Recorder is not in Android Studio 2.2 as announced in Google IO/16 presentation (see https://code.google.com/p/android/issues/detail?id=210478) you can have something to play with today. Sign up for CulebraTester private beta see opt-in form)



Saturday, May 14, 2016

CulebraTester Private Beta Opt-in

Android testing can be complicated, time-consuming, and tedious. 
What if it didn’t have to be?

CulebraTester provides a real-time point and click test recording through a web browser. This browser is connected to the Android device under test. Not sure what we mean?
Visit culebra.dtmilano.com for details.

If you are interested in being part of the Private Beta please fill out this form.


 

Tuesday, December 22, 2015

AndroidViewClient/culebra vs. MonkeyRunner

More than 2 years ago I took a crucial decision in AndroidViewClient/culebra development plan and that was to free it from `monkeyrunner`, Jython and Chimpchat.

AndroidViewClient/culebra was liberated and starting with version 4.0.0 it does not require any other runtime environment than python 2.x (read announcement). It can be installed and upgraded using the corresponding platform tools like easy_install or pip and can be easily integrated into IDEs like Eclipse PyDev or Pycharm. It also improves speed, solves chimpchat bugs, and even provides a GUI whre you can automatically create tests or scripts without writing a single line of code.

Nonetheless, from time to time, I receive some questions or reports about problems with scripts created with `culebra` that are attempted to run with `monkeyrunner` or some other combinations. I take the blame for it. I failed at communicating that AndroidViewClient/culebra is a complete replacement and should not be used together.

In order to improve the situation I gave a very detailed, easy to follow, step-by-step answer to
Error of Script with MonkeyRunner and AndroidViewClient (Touch) on Stackoverflow, showing how you can create a test case that automatically starts.and Activity (Duolingo) , checks if some Views are on the screen, touches them and finally take the screenshot. All from the GUI.



I hope you find this explanation useful.

Monday, August 24, 2015

culebra: concertina mode

One of the latest additions to culebra is the concertina mode. Named after the concertina movement occurring in snakes and other legless organisms that consists of gripping or anchoring with portions of the body while pulling/pushing other sections in the direction of movement, this mode allows culebra to move across the UI.

You may have used android's monkey before. monkey is a UI/Application exerciser that generates pseudo-random streams of user events such as clicks, touches, or gestures, as well as a number of system-level events. It is commonly used to stress-test application. While it may serve this purpose, it's overly complicated to be used in other scenarios.


Then, it comes culebra concertina mode that instead of sending pseudo-random events, analyzes the content of the screen and randomly selects a suitable event or action for the also randomly selected target, normally a View.

For example, if the randomly selected view is an EditText, culebra enters some random text. However if the EditText turns to be a password, it sends random passwords, that is instead of just entering a normal sentence it selected sample passwords from a list like the infamous "querty".
Furthermore, if it detects the EditText is expecting an email address, by inspecting the id, the hint or anything else that may suggest that, it sends random email addresses like user@example.com. Of course, other Views, receive other treatment, like Buttons being clicked, Scrollables being scrolled, etc.

Additionally, if the content description suggest the application may be awaiting for you to talk, like 'Tap to Speak' or 'Voice Search', culebra will actually speak random text to the app (this feature is only available on Linux and OSX).

You should check this aspect, it's sometimes hilarious.


Taking advantage of the culebra GUI already described in previous posts, while it's running the randomly selected Views are highlighted. At the same time, the python script is being generated. You can save it, modify it and run the same tests again and again. It's worth to mention that commonly the script eliminates the random selection of components and thus it runs deterministically.

Run culebra in concertina mode

$ culebra --gui --concertina --scale=0.5

and you can obtain something like this, where the nickname of a new contact was randomly selected from the available Views and the random text "chaos reigned" was entered.


BTW, this screenshot was taken via dump as described in android: obtaining beautiful screenshots automatically.


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