Saturday, January 28, 2012

monkeyrunner: testing views properties

There are several questions floating around, like this one in stackoverflow, about how some view properties could be obtained from a monkeyrunner script or putting the question in more general terms:
how a test that verifies some properties can be created using monkeyrunner ?


This is a required feature if we are going to create tests in monkeyrunner otherwise our alternatives to verify some state in the views is limited to visual comparison. These cases were treated in previous articles like:




but now we will be using a logical approach rather than visual comparison. To be able to do it we need a mechanism of obtaining view properties values.
Lastest versions of monkeyrunner provides an extension to MonkeyDevice called EasyMonkeyDevice and this class has methods to get some properties like MonkeyDevice.getText().
This API is not documented so expect changes in the future.
TemperatureConverter, a sample application that has been used in other articles before, will be our application under test. The source code can be obtained from github.


The idea behind this test is, using monkeyrunner to connect to  the device, enter 123 in the Celsius field and expect to find 253.40 in the Fahrenheit field.

#! /usr/bin/env monkeyrunner

import sys
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
from com.android.monkeyrunner.easy import EasyMonkeyDevice
from com.android.monkeyrunner.easy import By

# connect to the device
device = MonkeyRunner.waitForConnection()

# start TemperatureConverter
device.startActivity(component="com.example.i2at.tc/.TemperatureConverterActivity")

# use the EasyMonkey API
easyDevice = EasyMonkeyDevice(device)

celsiusId = By.id('id/celsius')
if not celsiusId:
   raise Exception("View with id/celsius not found")

fahrenheitId = By.id('id/fahrenheit')
if not fahrenheitId:
   raise Exception("View with id/fahrenheit not found")

MonkeyRunner.sleep(3)
easyDevice.type(celsiusId, '123')
MonkeyRunner.sleep(3)

celsius = easyDevice.getText(celsiusId)
fahrenheit = easyDevice.getText(fahrenheitId)
expected = '253.40'

if fahrenheit == expected:
   print 'PASS'
else:
   print 'FAIL: expected %s, actual %s' % (expected, fahrenheit)

Unfortunately, it won't work in most of the cases. You are likely to receive this exception:


        java.lang.RuntimeException: java.lang.RuntimeException: No text property on node

I felt frustrated at first, but what the advantage of an Open Source project is other than going to the source code and find out why it's not working.
I dug into the Chimpchat, HierarchyView and ViewServer code and found out that for some reason EasyMonkeyDevice is looking for the text:mText property when in most of the cases it should be only mText.
So here is the patch, that I will be uploading to android soon:

diff --git a/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java b/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java
index 6ad98ad..6c34d71 100644
--- a/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java
+++ b/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java
@@ -170,7 +170,11 @@ public class HierarchyViewer {
         }
         ViewNode.Property textProperty = node.namedProperties.get("text:mText");
         if (textProperty == null) {
-            throw new RuntimeException("No text property on node");
+            // dtmilano: give it another chance, ICS ViewServer returns mText
+            textProperty = node.namedProperties.get("mText");
+            if ( textProperty == null ) {
+                throw new RuntimeException("No text property on node");
+            }
         }
         return textProperty.value;
     }
Once this patch is applied and you rebuild monkeyrunner or the entire SDK if you prefer, you will be presented with the expected result: PASS


UPDATE
This patch has been submitted to Android Open Source Project as https://android-review.googlesource.com/31850


UPDATE: July 2012
Android SDK Tools Rev 20 includes the aforementioned patch and now the previous monkeyrunner example works! 

16 comments:

Android app development said...

This is one of the user-friendly post.Thanks for share with us.I like your blog tips.
Android app developers

Anand P said...

@diego

Can i get the EasyMonkeyDevice class related jar file for accessing the functionalities of it

Diego Torres Milano said...

EasyMonkeyDevice is part of monkeyrunner. You can access it by from com.android.monkeyrunner.easy import EasyMonkeyDevice in your monkeyrunner script.

Max said...

Hi Diego, at first I want to thank you for your great blog. I'm new to Android Development and it was very helpful! In your post you said "I dug into the source code...". My question is: where can I find the source code of the Monkeyrunner or Chimpchap? I fear the answer is really obvious, but I've searched now for hours and couldn't find anything. Thanks in Advance!

Diego Torres Milano said...

@Max,
Thanks for your comments. Both monkeyrunner and chimpchat are SDK components and thus they are available in the sdk project: https://android.googlesource.com/platform/sdk/+/master/

Max said...

Thanks for your fast answer. Today, I tried to write a simple Chimpchap application to "touch" an icon on my Smartphone Screen. If I touch an app icon on my home screen, everything works fine. But for example in the appdrawer and the picture galery, it seems that a longpress is performed instead of an simple tap. I used the following method:
device.touch(x,y,TouchPressType.DOWN_AND_UP);

and I also tried
device.touch(x,y,TouchPressType.DOWN);
device.touch(x,y;TouchPressType.UP);

When I use the device.drag method, it seems to be the same problem: at first a long press and then a drag is performed. Is there any trick to avoid the long press in these situations? In the forum "Stackoverflow" somebody wrote that disabling the long press is a solution, but I don't have any idea how to do that. I hope you can help me again, many thanks in advance!

Anonymous said...

Hi Diego,

Is there a chance of an EasyMonkeyDevice port/integration with Chimpchat, so we can use the simple verification type methods EMD has directly within Java code? Otherwise, I guess the IChimpView class can be used, but since EMD already provides the interface ...

Thanks for the informative and useful blogs btw.

Diego Torres Milano said...

You should ask the Google team to find out the answer, they maintain monkeyrunner and chimpchat.

Can you please give an example of what you would like to do with EasyMonkeyDevice from Java so perhaps I could give you an alternative approach?

ProficientGroup said...

Hi Diego,

I am currently trying to use AndriodViewClient to configure Wi-Fi and Bluetooth settings. Seems the id/switchWidget is not unique for both. So i want to use Unique ID instead to find the switchWidget. Do we have something like findViewByUniqueId.

Thanks for very useful blog.

Diego Torres Milano said...

@ProficientGroup,
Thanks for your comments.
To find a View by its unique Id you can simply do (here using 23 as an example):

vc.findViewWithAttribute('uniqueId', 'id/no_id/23')

Gibbs said...

HI~
in
fahrenheitId = By.id('id/fahrenheit')
if not fahrenheitId:
raise Exception("View with id/fahrenheit not found"

"if not farhrenheitId"
not working,

I think it's better to use
if not easyDevice.exists(fahrenheitId)

Sammy Odenhoven said...

Hi Diego,

Thanks for your great tutorial/blog. It's very helpful.
I want to use the EasyMonkeyDevice functionality, but I can't seem to get it right. When I try to attach EasyMonkeyDevice to the device (easyDevice = EasyMonkeyDevice(device)), I get a 'java.lang.RuntimeException: Could not connect to the view server'.

Do you have any idea what I'm doing wrong?

Thanks in advance!

Sammy Odenhoven said...
This comment has been removed by the author.
Sammy Odenhoven said...

Seems like I've found an answer. Apparently it's a HierarchyViewer issue. It can only be run on an emulator, or on a real device which has developer version of the Android system. See http://marakana.com/forums/android/learning_android_book/472.html

So nevermind my question!

Diego Torres Milano said...

@Sammy Odenhoven,
You are completely right, HierarchyViewer requires the ViewServer which can only be started on devices not considered in secure mode.

This AndroidViewClient wiki page will guide you to use the possible alternatives.

Sammy Odenhoven said...

I want to use EasyMonkeyDevice for my bachelor's thesis and I have a few questions. Since there is no (?) documentation of EasyMonkeyDevice and you seem to be kind of an expert, I added you on Google+. I have a few questions. If you don't mind, could you accept me, so I can send you an email? Thanks in advance!