Squish-6.1 for Android now has support for the UIAutomator framework for Android 4.3 or newer.
Qt applications running on Android have C++ and Java code bundled together and running side-by-side. Squish can access objects from each language, but from a single test case, we must distinguish between these two parts of an application somehow. We achieve this with the Application Context.
Testing an application running on Android with Squish requires usage of the built-in hook to access Qt objects from Squish. Squish can access such a running app from a test script, using the attachToApplication function.
To record a test script, we can start with a script like this:
function main() {
attachToApplication("nexus4711");
snooze(1);
}
Place a breakpoint on the snooze line, and run the script (b.t.w. the “nexus4711” name I chose for the attachable AUT as the Android device being used is an Nexus9 and the attachable TCP port is 4711). The app is the quickaddressbook example app shipping with Squish. The application startup can be automated using the command line call adb shell am start package/activity.
Fast forward: after a recording and some application startup fixup, the test script code looks like this:
function main() {
OS.system(OS.getenv("SQUISH_PREFIX") + "/bin/adb shell am start " +
"org.qtproject.example.quickaddressbook/org.qtproject.qt5.android.bindings.QtActivity");
for (var i = 0; i < 20; ++i) {
try {
attachToApplication("nexus4711");
break;
} catch (e) {
snooze(1);
}
}
tapObject(waitForObject(":Add_Button"));
tapObject(waitForObject(":editView.firstNameField_TextField"), 77, 43);
type(waitForObject(":editView.firstNameField_TextField"), "Koos");
type(waitForObject(":editView.lastNameField_TextField"), "Vriezen");
tapObject(waitForObject(":editView.phoneNumberField_TextField"), 41, 46);
type(waitForObject(":editView.phoneNumberField_TextField"), "1234");
tapObject(waitForObject(":editView.emailAddressField_TextField"), 26, 46);
type(waitForObject(":editView.emailAddressField_TextField"), "koos@example.com");
tapObject(waitForObject(":Back_Button"));
}
Next, the test is supposed to open the Android Settings app:
There are two ways to create a script for opening the Settings app. Both are based on the UIAutomator framework from Android. One is a direct API mapping and one is a mapping of UIAutomation on the Squish for Android convenience script API.
First, the app must be started by the Squish Android wrapper, using the startApplication function. Two application contexts are in play. One, for the Qt objects, which we got from attachToApplication, and another, for Android objects, which we got from startApplication.
The script will get changed accordingly. Note that the WRAPPERS entry in the suite.conf file should be changed from Qt to Android.
ctx0 = startApplication("org.qtproject.example.quickaddressbook");
for (var i = 0; i < 20; ++i) {
try {
ctx1 = attachToApplication("nexus4711");
...
}
Unlike the am start command above, we don’t have to specify the Activity – the default is taken when not specified.
To interactively create the script code, I put a breakpoint on a snooze line after the Back button tap. Just before the breakpoint, we switch the context in order to access the Android side of things.
...
setApplicationContext(ctx0);
snooze(1);
}
}
Using the UIAutomator bindings in Squish, the script snippet to start the Android Settings app will look like:
var au = androidUiAutomator();
var dev = au.getDevice();
dev.pressHome();
var sel = au.createSelector().description("Apps");
var obj = au.createObject(sel);
obj.click();
dev.swipe(dev.displayWidth/2, dev.displayHeight/2, dev.displayWidth/2, dev.displayHeight/2-50, 50);
sel = au.createSelector().text("Settings");
obj = au.createObject(sel);
obj.click();
}
Here is described how to create such a script with help of the uiautomatorviewer application from the Android SDK.
When using Squish convenience functions instead, the Application Objects view in the IDE can be used to find the object names. Improved in Squish-6.2 is the object snapshot feature which works nicely with the new UIBrowser for getting object names.
Finally, the complete script looks like this:
source(findFile("scripts", "javascript/screen.js"));
function main() {
ctx0 = startApplication("org.qtproject.example.quickaddressbook/org.qtproject.qt5.android.bindings.QtActivity");
for (var i = 0; i < 20; ++i) {
try {
ctx1 = attachToApplication("nexus4711");
break;
} catch (e) {
snooze(1);
}
}
tapObject(waitForObject(":Add_Button"));
tapObject(waitForObject(":editView.firstNameField_TextField"), 77, 43);
type(waitForObject(":editView.firstNameField_TextField"), "Koos");
type(waitForObject(":editView.lastNameField_TextField"), "Vriezen");
tapObject(waitForObject(":editView.phoneNumberField_TextField"), 41, 46);
type(waitForObject(":editView.phoneNumberField_TextField"), "1234");
tapObject(waitForObject(":editView.emailAddressField_TextField"), 26, 46);
type(waitForObject(":editView.emailAddressField_TextField"), "koos@example.com");
tapObject(waitForObject(":Back_Button"));
setApplicationContext(ctx0);
goRecents();
goHome();
tapObject(waitForObject("{description='Apps' simpleName='TextView' type='Clickable' visible='true'}"));
geom = Screen.byIndex(0).geometry;
touchAndDrag(waitForObject("{simpleName='FrameLayout' type='AccessiblePanel' visible='true'}"), geom.width/2, geom.height/2, 0, -400);
tapObject(waitForObject("{simpleName='TextView' text='Settings' type='Clickable' visible='true'}"));
}
}
Note, I first use goRecents here because goHome is implemented differently when the app started by Squish is in the foreground and is somewhat unreliable on the Nexus.
In conclusion, the test script for the UIAutomator framework for Android is a bit cumbersome, but it works, and if you need a setting to be toggled that can only be done in the Settings app, one of these two methods will do the job.