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

Tuesday, July 09, 2013

AndroidViewClient/culebra version 2.3.25 released

Github recently introduced some new features to its release system. There are several improvements. Though it was possible to tag versions before, and AndroidViewClient/culebra has been doing it for a while, now the release system is a first class citizen and can include binary files (solving one problem that was solved using Google Drive in previous releases).

Latest AndroidViewClient/culebra release (v2.3.25) can now be found at https://github.com/dtmilano/AndroidViewClient/releases/v2.3.25

These are the release notes:
Added methods to calculate dump distances
  • Version 2.3.25
  • Added Hamming distance
  • Added Levenshtein distance
  • Added View.__microStr__()
  • Traverse refactoring
  • Improved docs

The binary distribution and documentation can also be downloaded from the release.
More on these later.

Saturday, May 25, 2013

monkeyrunner: ImportError: No module named re

If you have recently upgraded to Android SDK Tools Rev. 22 you may have sadly discovered that monkeyrunner is broken because the wrong jython library was included in the build.
This is not due to the upgrade to jython 2.5.3 but to an incorrect library used, that is jython instead of jython-standalone.

It seems that the move to a gradle based build system is causing some trouble. Fortunately, it has been fixed already by https://android-review.googlesource.com/#/c/59173/1 which is now merged, so by the time Android SDK Tools Rev. 22.0.1 is out the problem would be in the rear-view mirror.

In the meantime, you can apply this simple workaround:

  1. Download jython-standalone-2.5.3.jar
  2. Save /tools/lib/jython-2.5.3.jar as jython-2.5.3.jar.ORI (just in case you need it)
  3. Symlink (or copy, if your OS does not support symlinks ;-) jython-standalone-2.5.3.jar to jython-2.5.3.jar
After applying these simple steps monkeyrunner, androidviewclient, dump and culebra will work as before.

Friday, May 17, 2013

AndroidViewClient documentation

AndroidViewClient documentation has been updated and now covers most of the public classes and method.

The documentation is generated by epydoc, which also generates some UML class diagrams.

The objective of this documentation is to allow script development without having to have AndroidViewClient sources open.

Sunday, April 28, 2013

AndroidViewClient @ stackoverflow

AndroidViewClient is constantly increasing its popularity and the number of questions I've been receiving through the open channels is very high and sometimes they may be left unanswered or lost.

Fortunately, the tag androidviewclient was added to stackoverflow and this will greatly improve the follow up of any question or problem.

Visit http://stackoverflow.com/questions/tagged/androidviewclient for more details.

Sunday, March 31, 2013

AndroidViewClient: dump.py explained


dump.py is an extremely valuable tool if you are developing tests or scripts based on AndroidViewClient.
It was created as a simple example to demonstrate AndroidViewClient features but it became something more than that. Nevertheless, the intention is to keep it simple enough to be used as an example, so some extra features will be provided by different tools.
As its name indicates, the main function is to dump the content of the screen present on a device or emulator at any given time, but instead of providing it as a bitmap the result of running dump.py is the logical representation of the screen as a tree that can be also printed in textual form.
A detailed description of this important tool has been made available in AndroidViewClient's wiki at: https://github.com/dtmilano/AndroidViewClient/wiki/dump.py
Dont hesitate to send your comments and questions to AndroidViewClient's Google+ page where they can be easily follow up.

Wednesday, January 16, 2013

android select device

A new version of the script android-select-device has been made available, now as a gist.
Its usage has been described in a previous post: selecting adb device.
Basically, when you are dealing with several devices or emulators, instead of specifying the serial number on every command line you can do something like

$ adb $(android-select-device) shell
1) 02783201431feeee device 3) emulator-5554
2) 3832380FA5F30000 device 4) emulator-5556
Select the device to use, to quit: 1
uid=0 gid=0@android:/ #

I hope you find it useful.

Tuesday, November 27, 2012

AndroidViewClient: UiAutomator support

AndroidViewClient v2.3.1 has been released recently providing UiAutomator compatibility, when supported by the device or emulator. UiAutomator is supported since Android API 16.

This is a great improvement over previous version in two different aspects:

  • it can now be run on non-rooted devices not demanding application modification (as previous version required to use LocalViewServer)
  • the change in the backend now frees the client from port redirection, as ViewServer requests, and at the same time the performance of dumping the View tree is greatly improved
 As an introduction of this new release we will be using a simple example that demonstrates some of the new features. This example us based on the demo application AndroidSampleUi.apk.

This example demonstrates:

  • automatic device connection, handling command line parameters if present
  • automatic View tree dump
  • finding Views using regular expressions or text
  • touching found Views
As a precondition to run this example, install and run AndroidSampleUI.


Zoom buttons let you increase or decrease the margins and consequently move the toggle buttons to demonstrate that they will be found whatever their coordinates are.

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

@author: diego
'''


import re
import sys
import os

# This must be imported before MonkeyRunner and MonkeyDevice,
# otherwise the import fails.
# PyDev sets PYTHONPATH, use it
try:
    for p in os.environ['PYTHONPATH'].split(':'):
       if not p in sys.path:
          sys.path.append(p)
except:
    pass

try:
    sys.path.append(os.path.join(os.environ['ANDROID_VIEW_CLIENT_HOME'], 'src'))
except:
    pass
from com.dtmilano.android.viewclient import ViewClient, ViewNotFoundException

vc = ViewClient(*ViewClient.connectToDeviceOrExit())

# Find the 3 toggle buttons, because the first 2 change their text if they are selected
# we use a regex to find them.
# Once found, we touch them changing their state
for t in [re.compile('Button 1 .*'), re.compile('Button 2 .*'), 'Button with ID']:
    try:
        vc.findViewWithTextOrRaise(t).touch()
    except ViewNotFoundException:
        print >>sys.stderr, "Couldn't find button with text=", t

Once this script is run, ViewClient will find a device, which can be specified using its serial number in the command line invoking the script, connects to, automatically dump the tree and then use regular expressions to find two of the ToggleButtons because we couldn't use a fixed text because it changes when the button is clicked. If, for some reason the Button is not found, perhaps because it was move outside the screen using Zoom buttons, a message is printed.

This screenshot show the state of the Buttons after the script has run.



More articles and examples will be coming soon, but I didn't want to miss the opportunity to introduce this new version. One of the most remarkably advantages over plain UiAutomator is the  simplification of the script or test creation and the expressiveness gain of using Python instead of Java.

dump.py

dump.py is also present in AndroidViewClient examples. It now supports several command line options now

usage: dump.py [-u|--uniqueId] [-x|--position] [-d|--content-description] [serialno]

so we can use it to verify the content of the screen. If everything went well running

dump.py --content-description

will show the View tree including also the content descriptions, as given by the following dump


android.widget.FrameLayout id/no_id/1  
   android.widget.LinearLayout id/no_id/2  
      android.widget.FrameLayout id/no_id/3  
         android.view.View id/no_id/4  
            android.widget.FrameLayout id/no_id/5  
               android.widget.ImageView id/no_id/6  
            android.widget.LinearLayout id/no_id/7  
               android.widget.LinearLayout id/no_id/8  
                  android.widget.TextView id/no_id/9 Sample UI v2.0 
      android.widget.FrameLayout id/no_id/10  
         android.widget.RelativeLayout id/no_id/11  
            android.widget.Button id/no_id/12 Show Dialog show_dialog
            android.widget.LinearLayout id/no_id/13  
               android.widget.TextView id/no_id/14  
               android.widget.ToggleButton id/no_id/15 Button 1 OFF button_1
               android.widget.TextView id/no_id/16 v=(75.0,82.0) lw=(115,272) ls=(115,272) wxh=(290,72) margin=(40,80) button_1_info
               android.widget.ToggleButton id/no_id/17 Button 2 OFF button_2
               android.widget.TextView id/no_id/18 v=(75.0,273.0) lw=(115,463) ls=(115,463) wxh=(290,72) margin=(40,80) button_2_info
               android.widget.ToggleButton id/no_id/19 Button with ID button_with_id
            android.widget.ZoomControls id/no_id/20  zoom
               android.widget.ZoomButton id/no_id/21  
               android.widget.ZoomButton id/no_id/22  


UPDATE:
Changed linked version to AndroidViewClient 2.3.1 as some latest commits were not in version 2.3 as found by Durairaj.

Friday, November 16, 2012

AndroidViewClient: Getting Browser's HTML page source

Standard Android Browser does not provide an option in its menu to view the HTML page source. Some workarounds like installing apps and then using Share page from Browser's menu has been described and also the use of Javascript and JQuery to add to a page has been detailed, but we are hungry for more. Needless to say, all these methods involve some manual step so I felt the need to find a completely automatic way of doing it.

Of course, to do it I would resort to our old pal AndroidViewClient. This is a very interesting example of its use because it lies far from testing and application or UI.

And so, without further ado, let me introduce you to the code...


#! /usr/bin/env monkeyrunner
'''
Copyright (C) 2012  Diego Torres Milano
Created on Oct 12, 2012

@author: diego
'''


import re
import sys
import os

# This must be imported before MonkeyRunner and MonkeyDevice,
# otherwise the import fails.
# PyDev sets PYTHONPATH, use it
try:
    for p in os.environ['PYTHONPATH'].split(':'):
        if not p in sys.path:
            sys.path.append(p)
except:
    pass

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

from com.dtmilano.android.viewclient import ViewClient

from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

VPS = "javascript:alert(document.getElementsByTagName('html')[0].innerHTML);"
PACKAGE = 'com.android.browser'
ACTIVITY = '.BrowserActivity'
COMPONENT = PACKAGE + "/" + ACTIVITY
URI = 'http://dtmilano.blogspot.com'


device, serialno = ViewClient.connectToDeviceOrExit()

device.startActivity(component=COMPONENT, uri=URI)
MonkeyRunner.sleep(3)

vc = ViewClient(device=device, serialno=serialno)

device.drag((240, 180), (240, 420), 10, 10)

url = vc.findViewByIdOrRaise('id/url')
url.touch()
MonkeyRunner.sleep(1)

device.press('KEYCODE_DEL', MonkeyDevice.DOWN_AND_UP)
for c in VPS:
    device.type(c)
device.press('KEYCODE_ENTER', MonkeyDevice.DOWN_AND_UP)
MonkeyRunner.sleep(3)

vc.dump()
print vc.findViewByIdOrRaise('id/message').getText().replace('\\n', "\n")

device.press('KEYCODE_BACK', MonkeyDevice.DOWN_AND_UP)



And now a brief explanation of the most important pieces of this script.

  1. Shebang, you know, to invoke monkeyrunner as the interpreter. I don't have to tell you more (if you are a poor Windows user you may have to invoke monkeyrunner from command line, I feel sad for you)
  2. Some comments and imports
  3. Read PYTHONPATH just in case you are using Eclipse and Pydev (this has been explained in this post)
  4. Then use ANDROID_VIEW_CLIENT_HOME environment variable to find AndroidViewClient in your system
  5. Some constants defined. VPS is the actual javascript to obtain the page source
  6. The standard way of connecting to the device or emulator in AndroidViewClient. This handles errors and timeout automatically solving many problems you find with bare monkeyrunner
  7. We start Browser
  8. Drag a bit to make the URL visible in case the page has scrolled
  9. Next, we find the View with ID id/url, which you know, contains the URL
  10. We touch to focus
  11. And type the javascript in VPS followed by ENTER
  12. By that time the alert dialog should be on screen so we take a new dump
  13. Now we find the View with ID id/message which contains the HTML and print it
  14. Finally, we press BACK to dismiss it
I hope you have enjoyed it as much as I did and this help you find new ways of using AndroidViewClient.

P.S. This script will be part of AndroidViewClient source code distribution examples

Monday, October 15, 2012

AndroidViewClient @ Google+ pages


Until recently, the number of comments related to AndroidViewClient in this blog were not very high and everything was smooth. But, as the number of comments increased and given the non-hierarchical nature of blogspot comments it turned to be a huge mess and it became almost impossible to match a comment, usually containing a question, with its reply.

So, here I am, creating AndroidViewClient's Google+ page where we can easily follow up every comment, or at least this is my hope. The format for these posts is very restrictive, but I think it might be enough.
Time will tell.

Bookmark AndroidViewClient's Google+ page: https://plus.google.com/111731764904697052166


Wednesday, September 19, 2012

IXONOS sponsored AndroidViewClient improvements

Thanks to IXONOS and its commitment with the Open Source community AndroidViewClient has reached a new level of maturity. IXONOS has sponsored the improvement of some core AndroidViewClient features broaden its usage and allowing it to be used as a test workhorse of many other projects.

Ari Manninen, from IXONOS said:

"AndroidViewClient is a very valuable tool for creating device independent MonkeyRunner scripts. It has greatly enhanced MonkeyRunner testing in our customer projects."

Tuesday, September 11, 2012

monkeyrunner: importing from PYTHONPATH

In previous post we analyzed what is needed to develop, run and debug monkeyrunner scripts using Eclipse and PyDev.


#! /usr/bin/env monkeyrunner
'''
Created on Sep 10, 2012

@author: diego
'''

import re
import sys
import os
import java

# This must be imported before MonkeyRunner and MonkeyDevice,
# otherwise the import fails.
# PyDev sets PYTHONPATH, use it
try:
    for p in os.environ['PYTHONPATH'].split(':'):
       if not p in sys.path:
          sys.path.append(p)
except:
    pass

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

from com.dtmilano.android.viewclient import ViewClient, View
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

# usage: script [serialno]
serialno = sys.argv[1] if len(sys.argv) > 1 else 'emulator-5554'
device = MonkeyRunner.waitForConnection(30, serialno)
try:
    device.wake()
except java.lang.NullPointerException, e:
    print "ERROR: Couldn't connect to %s: %s" % (serialno, e)

These are the lines you should add to every monkeyrunner script. Here you are a brief explanation of the snippet.

  1. The shebang line to invoke monkeyrunner  interpreter if you are using Linux or Mac OS X. Unfortunately this is not available on Windows. Eclipse does not use this line but is needed if you want to simplify the way you are running the scripts from the command line.
  2. Some standard imports
  3. PyDev uses PYTHONPATH while monkeyrunner ignores it. This snippet adds the components present in PYTHONPATH to sys.path and makes them visible to monkeyrunner.
  4. Following, we need to locate AndroidViewClient which you should have added to the environment. This can be also added in Eclipse in Run Configurations -> Environment.
    ANDROID_VIEW_CLIENT_HOME should point to your AndroidViewClient installation to the parent folder of src. That is, if you have downloaded AndroidViewClient in /opt/AndroidViewClient and kept the same structure as the distribution, you should set ANDROID_VIEW_CLIENT_HOME=/opt/AndroidViewClient/AndroidViewClient
  5. The imports, which will now succeed because sys.path contains the right components
  6. Gets the device's serial number from the command line or default to emulator-5554.
  7. Connect to the device
  8. Check if the connection was successful. Because MonkeyRunner.waitForConnection() returns a MonkeyDevice even when the connection fails we need to go to this extra step to verify it.