diff --git a/wa/__init__.py b/wa/__init__.py index cf6a4d2e..d005ef27 100644 --- a/wa/__init__.py +++ b/wa/__init__.py @@ -14,4 +14,4 @@ from wa.framework.plugin import Plugin, Parameter from wa.framework.processor import ResultProcessor from wa.framework.resource import (NO_ONE, JarFile, ApkFile, ReventFile, File, Executable) -from wa.framework.workload import Workload +from wa.framework.workload import Workload, ApkUiautoWorkload diff --git a/wa/framework/execution.py b/wa/framework/execution.py index ebdf0145..d9fc5132 100644 --- a/wa/framework/execution.py +++ b/wa/framework/execution.py @@ -30,7 +30,7 @@ import wa.framework.signal as signal from wa.framework import instrumentation, pluginloader from wa.framework.configuration.core import settings, Status from wa.framework.exception import (WAError, ConfigError, TimeoutError, - InstrumentError, TargetError, + InstrumentError, TargetError, HostError, TargetNotRespondingError) from wa.framework.output import init_job_output from wa.framework.plugin import Artifact @@ -178,6 +178,22 @@ class ExecutionContext(object): classifiers) self.output.add_metric(name, value, units, lower_is_better, classifiers) + def get_artifact(self, name): + try: + return self.output.get_artifact(name) + except HostError: + if not self.current_job: + raise + return self.run_output.get_artifact(name) + + def get_artifact_path(self, name): + try: + return self.output.get_artifact_path(name) + except HostError: + if not self.current_job: + raise + return self.run_output.get_artifact_path(name) + def add_artifact(self, name, path, kind, description=None, classifiers=None): self.output.add_artifact(name, path, kind, description, classifiers) diff --git a/wa/framework/output.py b/wa/framework/output.py index 5f72ccb5..53c9fd3d 100644 --- a/wa/framework/output.py +++ b/wa/framework/output.py @@ -84,6 +84,13 @@ class Output(object): def add_event(self, message): self.result.add_event(message) + def get_artifact(self, name): + return self.result.get_artifact(name) + + def get_artifact_path(self, name): + artifact = self.get_artifact(name) + return self.get_path(artifact.path) + class RunOutput(Output): @@ -234,6 +241,12 @@ class Result(object): def add_event(self, message): self.events.append(Event(message)) + def get_artifact(self, name): + for artifact in self.artifacts: + if artifact.name == name: + return artifact + raise HostError('Artifact "{}" not found'.format(name)) + def to_pod(self): return dict( status=str(self.status), diff --git a/wa/framework/resource.py b/wa/framework/resource.py index 2ce9de7f..44745386 100644 --- a/wa/framework/resource.py +++ b/wa/framework/resource.py @@ -75,7 +75,7 @@ class Resource(object): raise NotImplementedError() def __str__(self): - return '<{}\'s {}>'.format(self.owner, self.name) + return '<{}\'s {}>'.format(self.owner, self.kind) class File(Resource): diff --git a/wa/framework/uiauto/BaseUiAutomation.class b/wa/framework/uiauto/BaseUiAutomation.class new file mode 100644 index 00000000..942f7ca3 Binary files /dev/null and b/wa/framework/uiauto/BaseUiAutomation.class differ diff --git a/wa/framework/uiauto/build.sh b/wa/framework/uiauto/build.sh new file mode 100755 index 00000000..1beddd6a --- /dev/null +++ b/wa/framework/uiauto/build.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Copyright 2013-2015 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +set -e + + +ant build + +cp bin/classes/com/arm/wa/uiauto/BaseUiAutomation.class . diff --git a/wa/framework/uiauto/build.xml b/wa/framework/uiauto/build.xml new file mode 100644 index 00000000..86b8aa0f --- /dev/null +++ b/wa/framework/uiauto/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wa/framework/uiauto/project.properties b/wa/framework/uiauto/project.properties new file mode 100644 index 00000000..a3ee5ab6 --- /dev/null +++ b/wa/framework/uiauto/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-17 diff --git a/wa/framework/uiauto/src/com/arm/wa/uiauto/BaseUiAutomation.java b/wa/framework/uiauto/src/com/arm/wa/uiauto/BaseUiAutomation.java new file mode 100644 index 00000000..fc0faded --- /dev/null +++ b/wa/framework/uiauto/src/com/arm/wa/uiauto/BaseUiAutomation.java @@ -0,0 +1,124 @@ +/* Copyright 2013-2015 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +package com.arm.wa.uiauto; + +import java.io.File; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.concurrent.TimeoutException; + +import android.app.Activity; +import android.os.Bundle; + +// Import the uiautomator libraries +import com.android.uiautomator.core.UiObject; +import com.android.uiautomator.core.UiObjectNotFoundException; +import com.android.uiautomator.core.UiScrollable; +import com.android.uiautomator.core.UiSelector; +import com.android.uiautomator.testrunner.UiAutomatorTestCase; + +public class BaseUiAutomation extends UiAutomatorTestCase { + + public void setup() throws Exception { + } + + public void runWorkload() throws Exception { + } + + public void extractResults() throws Exception { + } + + public void teardown() throws Exception { + } + + public void sleep(int second) { + super.sleep(second * 1000); + } + + public boolean takeScreenshot(String name) { + Bundle params = getParams(); + String png_dir = params.getString("workdir"); + + try { + return getUiDevice().takeScreenshot(new File(png_dir, name + ".png")); + } catch(NoSuchMethodError e) { + return true; + } + } + + public void waitText(String text) throws UiObjectNotFoundException { + waitText(text, 600); + } + + public void waitText(String text, int second) throws UiObjectNotFoundException { + UiSelector selector = new UiSelector(); + UiObject text_obj = new UiObject(selector.text(text) + .className("android.widget.TextView")); + waitObject(text_obj, second); + } + + public void waitObject(UiObject obj) throws UiObjectNotFoundException { + waitObject(obj, 600); + } + + public void waitObject(UiObject obj, int second) throws UiObjectNotFoundException { + if (! obj.waitForExists(second * 1000)){ + throw new UiObjectNotFoundException("UiObject is not found: " + + obj.getSelector().toString()); + } + } + + public boolean waitUntilNoObject(UiObject obj, int second) { + return obj.waitUntilGone(second * 1000); + } + + public void clearLogcat() throws Exception { + Runtime.getRuntime().exec("logcat -c"); + } + + public void waitForLogcatText(String searchText, long timeout) throws Exception { + long startTime = System.currentTimeMillis(); + Process process = Runtime.getRuntime().exec("logcat"); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + + long currentTime = System.currentTimeMillis(); + boolean found = false; + while ((currentTime - startTime) < timeout){ + sleep(2); // poll every two seconds + + while((line=reader.readLine())!=null) { + if (line.contains(searchText)) { + found = true; + break; + } + } + + if (found) { + break; + } + currentTime = System.currentTimeMillis(); + } + + process.destroy(); + + if ((currentTime - startTime) >= timeout) { + throw new TimeoutException("Timed out waiting for Logcat text \"%s\"".format(searchText)); + } + } +} + diff --git a/wa/framework/workload.py b/wa/framework/workload.py index 842e9caa..b2f565e3 100644 --- a/wa/framework/workload.py +++ b/wa/framework/workload.py @@ -13,9 +13,11 @@ # limitations under the License. # import logging +import os +import time from wa.framework.plugin import TargetedPlugin -from wa.framework.resource import JarFile, ReventFile, NO_ONE +from wa.framework.resource import ApkFile, JarFile, ReventFile, NO_ONE from wa.framework.exception import WorkloadError from devlib.utils.android import ApkInfo @@ -91,53 +93,117 @@ class Workload(TargetedPlugin): return ''.format(self.name) -class UiAutomatorGUI(object): +class ApkUiautoWorkload(Workload): + + platform = 'android' - def __init__(self, target, package='', klass='UiAutomation', - method='runUiAutoamtion'): - self.target = target - self.uiauto_package = package - self.uiauto_class = klass - self.uiauto_method = method - self.uiauto_file = None - self.target_uiauto_file = None - self.command = None - self.uiauto_params = {} + def __init__(self, target, **kwargs): + super(ApkUiautoWorkload, self).__init__(target, **kwargs) + self.apk = ApkHander(self) + self.gui = UiAutomatorGUI(self) def init_resources(self, context): - self.uiauto_file = context.resolver.get(JarFile(self)) - self.target_uiauto_file = self.target.path.join(self.target.working_directory, - os.path.basename(self.uiauto_file)) - if not self.uiauto_package: - self.uiauto_package = os.path.splitext(os.path.basename(self.uiauto_file))[0] + self.apk.init_resources(context.resolver) + self.gui.init_resources(context.resolver) + self.gui.init_commands() - def validate(self): - if not self.uiauto_file: - raise WorkloadError('No UI automation JAR file found for workload {}.'.format(self.name)) - if not self.uiauto_package: - raise WorkloadError('No UI automation package specified for workload {}.'.format(self.name)) + def initialize(self, context): + self.gui.deploy() def setup(self, context): - method_string = '{}.{}#{}'.format(self.uiauto_package, self.uiauto_class, self.uiauto_method) + self.apk.setup(context) + self.gui.setup() + + def run(self, context): + self.gui.run() + + def extract_results(self, context): + self.gui.extract_results() + + def teardown(self, context): + self.gui.teardown() + self.apk.teardown() + + def finalize(self, context): + self.gui.remove() + + +class UiAutomatorGUI(object): + + stages = ['setup', 'runWorkload', 'extractResults', 'teardown'] + + def __init__(self, owner, package=None, klass='UiAutomation', timeout=600): + self.owner = owner + self.target = self.owner.target + self.uiauto_package = package + self.uiauto_class = klass + self.timeout = timeout + self.logger = logging.getLogger('gui') + self.jar_file = None + self.target_jar_file = None + self.commands = {} + self.uiauto_params = {} + + def init_resources(self, resolver): + self.jar_file = resolver.get(JarFile(self.owner)) + jar_name = os.path.basename(self.jar_file) + self.target_jar_file = self.target.get_workpath(jar_name) + if not self.uiauto_package: + package = os.path.splitext(os.path.basename(self.jar_file))[0] + self.uiauto_package = package + + def init_commands(self): params_dict = self.uiauto_params params_dict['workdir'] = self.target.working_directory params = '' for k, v in self.uiauto_params.iteritems(): params += ' -e {} {}'.format(k, v) - self.command = 'uiautomator runtest {}{} -c {}'.format(self.target_uiauto_file, params, method_string) - self.target.push_file(self.uiauto_file, self.target_uiauto_file) - self.target.killall('uiautomator') - def run(self, context): - result = self.target.execute(self.command, self.run_timeout) + for stage in self.stages: + method_string = '{}.{}#{}'.format(self.uiauto_package, + self.uiauto_class, + stage) + cmd_template = 'uiautomator runtest {}{} -c {}' + self.commands[stage] = cmd_template.format(self.target_jar_file, + params, method_string) + + def deploy(self): + self.target.push(self.jar_file, self.target_jar_file) + + def set(self, name, value): + self.uiauto_params[name] = value + + def setup(self, timeout=None): + if not self.commands: + raise RuntimeError('Commands have not been initialized') + self.target.killall('uiautomator') + self._execute('setup', timeout or self.timeout) + + def run(self, timeout=None): + if not self.commands: + raise RuntimeError('Commands have not been initialized') + self._execute('runWorkload', timeout or self.timeout) + + def extract_results(self, timeout=None): + if not self.commands: + raise RuntimeError('Commands have not been initialized') + self._execute('extractResults', timeout or self.timeout) + + def teardown(self, timeout=None): + if not self.commands: + raise RuntimeError('Commands have not been initialized') + self._execute('teardown', timeout or self.timeout) + + def remove(self): + self.target.remove(self.target_jar_file) + + def _execute(self, stage, timeout): + result = self.target.execute(self.commands[stage], timeout) if 'FAILURE' in result: raise WorkloadError(result) else: self.logger.debug(result) - time.sleep(DELAY) - - def teardown(self, context): - self.target.delete_file(self.target_uiauto_file) + time.sleep(2) class ReventGUI(object): @@ -197,21 +263,27 @@ class ReventGUI(object): class ApkHander(object): - def __init__(self, owner, target, view, install_timeout=300, version=None, + def __init__(self, owner, install_timeout=300, version=None, variant=None, strict=True, force_install=False, uninstall=False): self.logger = logging.getLogger('apk') self.owner = owner - self.target = target + self.target = self.owner.target + self.install_timeout = install_timeout self.version = version + self.variant = variant + self.strict = strict + self.force_install = force_install + self.uninstall = uninstall self.apk_file = None self.apk_info = None self.apk_version = None self.logcat_log = None - def init_resources(self, context): - self.apk_file = context.resolver.get(ApkFile(self.owner), - version=self.version, - strict=strict) + def init_resources(self, resolver): + self.apk_file = resolver.get(ApkFile(self.owner, + variant=self.variant, + version=self.version), + strict=self.strict) self.apk_info = ApkInfo(self.apk_file) def setup(self, context): @@ -226,16 +298,17 @@ class ApkHander(object): self.initialize_with_host_apk(context, installed_version) else: if not installed_version: - message = '''{} not found found on the device and check_apk is set to "False" - so host version was not checked.''' - raise WorkloadError(message.format(self.package)) + message = '{} not found found on the device and check_apk is set '\ + 'to "False" so host version was not checked.' + raise WorkloadError(message.format(self.apk_info.package)) message = 'Version {} installed on device; skipping host APK check.' self.logger.debug(message.format(installed_version)) self.reset(context) self.version = installed_version def initialize_with_host_apk(self, context, installed_version): - if installed_version != self.apk_file.version_name: + host_version = self.apk_info.version_name + if installed_version != host_version: if installed_version: message = '{} host version: {}, device version: {}; re-installing...' self.logger.debug(message.format(os.path.basename(self.apk_file), @@ -251,42 +324,38 @@ class ApkHander(object): host_version)) if self.force_install: if installed_version: - self.device.uninstall(self.package) + self.target.uninstall_package(self.apk_info.package) self.install_apk(context) else: self.reset(context) self.apk_version = host_version def start_activity(self): - output = self.device.execute('am start -W -n {}/{}'.format(self.package, self.activity)) + cmd = 'am start -W -n {}/{}' + output = self.target.execute(cmd.format(self.apk_info.package, + self.apk_info.activity)) if 'Error:' in output: - self.device.execute('am force-stop {}'.format(self.package)) # this will dismiss any erro dialogs + # this will dismiss any error dialogs + self.target.execute('am force-stop {}'.format(self.apk_info.package)) raise WorkloadError(output) self.logger.debug(output) def reset(self, context): # pylint: disable=W0613 - self.device.execute('am force-stop {}'.format(self.package)) - self.device.execute('pm clear {}'.format(self.package)) + self.target.execute('am force-stop {}'.format(self.apk_info.package)) + self.target.execute('pm clear {}'.format(self.apk_info.package)) def install_apk(self, context): - output = self.device.install(self.apk_file, self.install_timeout) + output = self.target.install_apk(self.apk_file, self.install_timeout) if 'Failure' in output: if 'ALREADY_EXISTS' in output: - self.logger.warn('Using already installed APK (did not unistall properly?)') + msg = 'Using already installed APK (did not unistall properly?)' + self.logger.warn(msg) else: raise WorkloadError(output) else: self.logger.debug(output) - def update_result(self, context): - self.logcat_log = os.path.join(context.output_directory, 'logcat.log') - self.device.dump_logcat(self.logcat_log) - context.add_iteration_artifact(name='logcat', - path='logcat.log', - kind='log', - description='Logact dump for the run.') - - def teardown(self, context): - self.device.execute('am force-stop {}'.format(self.package)) - if self.uninstall_apk: - self.device.uninstall(self.package) + def teardown(self): + self.target.execute('am force-stop {}'.format(self.apk_info.package)) + if self.uninstall: + self.target.uninstall_package(self.apk_info.package) diff --git a/wa/workloads/benchmarkpi/__init__.py b/wa/workloads/benchmarkpi/__init__.py new file mode 100644 index 00000000..d731305e --- /dev/null +++ b/wa/workloads/benchmarkpi/__init__.py @@ -0,0 +1,62 @@ +# Copyright 2013-2015 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +import re + +from wa import ApkUiautoWorkload + + +class BenchmarkPi(ApkUiautoWorkload): + + name = 'benchmarkpi' + description = """ + Measures the time the target device takes to run and complete the Pi + calculation algorithm. + + http://androidbenchmark.com/howitworks.php + + from the website: + + The whole idea behind this application is to use the same Pi calculation + algorithm on every Android Device and check how fast that proccess is. + Better calculation times, conclude to faster Android devices. This way you + can also check how lightweight your custom made Android build is. Or not. + + As Pi is an irrational number, Benchmark Pi does not calculate the actual Pi + number, but an approximation near the first digits of Pi over the same + calculation circles the algorithms needs. + + So, the number you are getting in miliseconds is the time your mobile device + takes to run and complete the Pi calculation algorithm resulting in a + approximation of the first Pi digits. + """ + package = 'gr.androiddev.BenchmarkPi' + activity = '.BenchmarkPi' + + regex = re.compile('You calculated Pi in ([0-9]+)') + + def update_output(self, context): + super(BenchmarkPi, self).update_output(context) + logcat_file = context.get_artifact_path('logcat') + with open(logcat_file) as fh: + for line in fh: + match = self.regex.search(line) + if match: + result = int(match.group(1)) + + if result is not None: + context.add_metric('pi calculation', result, + 'milliseconds', lower_is_better=True) diff --git a/wa/workloads/benchmarkpi/com.arm.wa.uiauto.benchmarkpi.jar b/wa/workloads/benchmarkpi/com.arm.wa.uiauto.benchmarkpi.jar new file mode 100644 index 00000000..e597b3b5 Binary files /dev/null and b/wa/workloads/benchmarkpi/com.arm.wa.uiauto.benchmarkpi.jar differ diff --git a/wa/workloads/benchmarkpi/uiauto/build.sh b/wa/workloads/benchmarkpi/uiauto/build.sh new file mode 100755 index 00000000..3c5f5ba8 --- /dev/null +++ b/wa/workloads/benchmarkpi/uiauto/build.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Copyright 2013-2015 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +set -e + +class_dir=bin/classes/com/arm/wa/uiauto +base_class=`python -c "import os, wa; print os.path.join(os.path.dirname(wa.__file__), 'framework', 'uiauto', 'BaseUiAutomation.class')"` +mkdir -p $class_dir +cp $base_class $class_dir + +ant build + +if [[ -f bin/com.arm.wa.uiauto.benchmarkpi.jar ]]; then + cp bin/com.arm.wa.uiauto.benchmarkpi.jar .. +fi diff --git a/wa/workloads/benchmarkpi/uiauto/build.xml b/wa/workloads/benchmarkpi/uiauto/build.xml new file mode 100644 index 00000000..3f8f1495 --- /dev/null +++ b/wa/workloads/benchmarkpi/uiauto/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wa/workloads/benchmarkpi/uiauto/project.properties b/wa/workloads/benchmarkpi/uiauto/project.properties new file mode 100644 index 00000000..a3ee5ab6 --- /dev/null +++ b/wa/workloads/benchmarkpi/uiauto/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-17 diff --git a/wa/workloads/benchmarkpi/uiauto/src/com/arm/wa/uiauto/UiAutomation.java b/wa/workloads/benchmarkpi/uiauto/src/com/arm/wa/uiauto/UiAutomation.java new file mode 100644 index 00000000..097578f2 --- /dev/null +++ b/wa/workloads/benchmarkpi/uiauto/src/com/arm/wa/uiauto/UiAutomation.java @@ -0,0 +1,62 @@ +/* Copyright 2013-2015 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +package com.arm.wa.uiauto.benchmarkpi; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +// Import the uiautomator libraries +import com.android.uiautomator.core.UiObject; +import com.android.uiautomator.core.UiObjectNotFoundException; +import com.android.uiautomator.core.UiScrollable; +import com.android.uiautomator.core.UiSelector; +import com.android.uiautomator.testrunner.UiAutomatorTestCase; + +import com.arm.wa.uiauto.BaseUiAutomation; + +public class UiAutomation extends BaseUiAutomation { + + public static String TAG = "benchmarkpi"; + + public void runWorkload() throws Exception { + startTest(); + waitForResults(); + } + + public void extractResults() throws Exception { + UiSelector selector = new UiSelector(); + UiObject resultsText = new UiObject(selector.textContains("You calculated Pi in") + .className("android.widget.TextView")); + Log.v(TAG, resultsText.getText()); + } + + public void startTest() throws Exception{ + UiSelector selector = new UiSelector(); + UiObject benchButton = new UiObject(selector.text("Benchmark my Android!") + .className("android.widget.Button")); + benchButton.click(); + } + + public void waitForResults() throws Exception{ + UiSelector selector = new UiSelector(); + UiObject submitButton = new UiObject(selector.text("Submit") + .className("android.widget.Button")); + submitButton.waitForExists(10 * 1000); + } + +}