1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2024-10-06 19:01:15 +01:00

Add googleplaybooks workload

This commit is contained in:
John Richardson 2016-07-22 15:00:26 +01:00
parent 25172fb027
commit 0f579e18b3
6 changed files with 785 additions and 0 deletions

View File

@ -0,0 +1,94 @@
# Copyright 2014-2016 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 os
import re
from wlauto import AndroidUiAutoBenchmark, Parameter
from wlauto.exceptions import DeviceError
class Googleplaybooks(AndroidUiAutoBenchmark):
name = 'googleplaybooks'
package = 'com.google.android.apps.books'
version = '3.8.15' # apk version
activity = 'com.google.android.apps.books.app.BooksActivity'
view = [package + '/com.google.android.apps.books.app.HomeActivity',
package + '/com.android.vending/com.google.android.finsky.activities.MainActivity',
package + '/com.google.android.apps.books.app.ReadingActivity',
package + '/com.google.android.apps.books.app.TableOfContentsActivityLight']
description = """
A workload to perform standard productivity tasks with googleplaybooks.
This workload performs various tasks, such as searching for a book title
online, browsing through a book, adding and removing notes, word searching,
and querying information about the book.
Test description:
1. Open Google Play Books application
2. Dismisses sync operation (if applicable)
3. Searches for a book title
4. Adds books to library if not already present
5. Opens 'My Library' contents
6. Opens selected book
7. Gestures are performed to swipe between pages and pinch zoom in and out of a page
8. Selects a specified chapter based on page number from the navigation view
9. Selects a word in the centre of screen and adds a test note to the page
10. Removes the test note from the page (clean up)
11. Searches for the number of occurrences of a common word throughout the book
12. Switches page styles from 'Day' to 'Night' to 'Sepia' and back to 'Day'
13. Uses the 'About this book' facility on the currently selected book
NOTE: This workload requires a network connection (ideally, wifi) to run
and a Google account to be setup on the device.
"""
parameters = [
Parameter('search_book_title', kind=str, mandatory=False, default="Hamlet",
description="""
The book title to search for within Google Play Books archive.
The book must either be already in the account's library, or free to purchase.
"""),
Parameter('select_chapter_page_number', kind=int, mandatory=False, default=22,
description="""
The Page Number to search for within a selected book's Chapter list.
Note: Accepts integers only.
"""),
Parameter('search_word', kind=str, mandatory=False, default='the',
description="""
The word to search for within a selected book.
Note: Accepts single words only.
"""),
Parameter('markers_enabled', kind=bool, default=True,
description="""
If ``True``, UX_PERF action markers will be emitted to logcat during
the test run.
"""),
]
def validate(self):
super(Googleplaybooks, self).validate()
self.uiauto_params['package'] = self.package
self.uiauto_params['markers_enabled'] = self.markers_enabled
self.uiauto_params['book_title'] = self.search_book_title.replace(" ", "_")
self.uiauto_params['chapter_page_number'] = self.select_chapter_page_number
self.uiauto_params['search_word'] = self.search_word
def initialize(self, context):
super(Googleplaybooks, self).initialize(context)
# This workload relies on the internet so check that there is a working internet connection
if not self.device.is_network_connected():
raise DeviceError('Network is not connected for device {}'.format(self.device.name))

View File

@ -0,0 +1,39 @@
#!/bin/bash
# CD into build dir if possible - allows building from any directory
script_path='.'
if `readlink -f $0 &>/dev/null`; then
script_path=`readlink -f $0 2>/dev/null`
fi
script_dir=`dirname $script_path`
cd $script_dir
# Ensure build.xml exists before starting
if [[ ! -f build.xml ]]; then
echo 'Ant build.xml file not found! Check that you are in the right directory.'
exit 9
fi
# Copy base classes from wlauto dist
class_dir=bin/classes/com/arm/wlauto/uiauto
base_classes=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.class')"`
mkdir -p $class_dir
cp $base_classes $class_dir
# Build and return appropriate exit code if failed
ant build
exit_code=$?
if [[ $exit_code -ne 0 ]]; then
echo "ERROR: 'ant build' exited with code $exit_code"
exit $exit_code
fi
# If successful move JAR file to workload folder (overwrite previous)
package=com.arm.wlauto.uiauto.googleplaybooks.jar
rm -f ../$package
if [[ -f bin/$package ]]; then
cp bin/$package ..
else
echo 'ERROR: UiAutomator JAR could not be found!'
exit 9
fi

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="com.arm.wlauto.uiauto.googleplaybooks" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: VERSION_TAG -->
<import file="${sdk.dir}/tools/ant/uibuild.xml" />
</project>

View File

@ -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-18

View File

@ -0,0 +1,546 @@
/* Copyright 2014-2016 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.wlauto.uiauto.googleplaybooks;
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.UiSelector;
import com.android.uiautomator.core.UiWatcher;
import com.android.uiautomator.core.UiScrollable;
import com.arm.wlauto.uiauto.UxPerfUiAutomation;
import java.util.concurrent.TimeUnit;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class UiAutomation extends UxPerfUiAutomation {
public static String TAG = "uxperf_googleplaybooks";
public Bundle parameters;
private int viewTimeoutSecs = 10;
private long viewTimeout = TimeUnit.SECONDS.toMillis(viewTimeoutSecs);
public void runUiAutomation() throws Exception {
this.uiAutoTimeout = TimeUnit.SECONDS.toMillis(8);
parameters = getParams();
String bookTitle = parameters.getString("book_title").replace("_", " ");
String chapterPageNumber = parameters.getString("chapter_page_number");
String searchWord = parameters.getString("search_word");
String noteText = "This is a test note";
setScreenOrientation(ScreenOrientation.NATURAL);
clearFirstRunDialogues();
dismissSync();
searchForBook(bookTitle);
addToLibrary();
openMyLibrary();
openBook(bookTitle);
UiWatcher pageSyncPopUpWatcher = createPopUpWatcher();
registerWatcher("pageSyncPopUp", pageSyncPopUpWatcher);
runWatchers();
gesturesTest();
selectChapter(chapterPageNumber);
addNote(noteText);
removeNote();
searchForWord(searchWord);
switchPageStyles();
aboutBook();
removeWatcher("pageSyncPop");
pressBack();
unsetScreenOrientation();
}
// Creates a watcher for when a pop up warning appears when pages are out
// of sync across multiple devices.
private UiWatcher createPopUpWatcher() throws Exception {
UiWatcher pageSyncPopUpWatcher = new UiWatcher() {
@Override
public boolean checkForCondition() {
UiObject popUpDialogue =
new UiObject(new UiSelector().resourceId("android:id/message")
.textStartsWith("You're on page"));
// Don't sync and stay on the current page
if (popUpDialogue.exists()) {
try {
UiObject stayOnPage = new UiObject(new UiSelector()
.className("android.widget.Button")
.text("Yes"));
stayOnPage.click();
} catch (UiObjectNotFoundException e) {
e.printStackTrace();
}
return popUpDialogue.waitUntilGone(viewTimeout);
}
return false;
}
};
return pageSyncPopUpWatcher;
}
private void dismissSync() throws Exception {
UiObject keepSyncOff =
new UiObject(new UiSelector().textContains("Keep sync off")
.className("android.widget.Button"));
if (keepSyncOff.exists()) {
keepSyncOff.click();
}
}
// If there is no sample book in My library we are prompted to choose a
// book the first time application is run. Try to skip the screen or
// pick a random sample book.
private void clearFirstRunDialogues() throws Exception {
UiObject startButton =
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/start_button"));
// First try and skip the sample book selection
if (startButton.exists()) {
startButton.click();
}
UiObject endButton =
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/end_button"));
// Click next button if it exists
if (endButton.exists()) {
endButton.click();
// Select a random sample book to add to My library
sleep(1);
tapDisplayCentre();
sleep(1);
// Click done button (uses same resource-id)
endButton.click();
}
}
// Searches for a "free" or "purchased" book title in Google play
private void searchForBook(final String bookTitle) throws Exception {
UiObject search =
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/menu_search"));
search.click();
UiObject searchText = new UiObject(new UiSelector().textContains("Search")
.className("android.widget.EditText"));
searchText.setText(bookTitle);
pressEnter();
UiObject resultList =
new UiObject(new UiSelector().resourceId("com.android.vending:id/search_results_list"));
if (!resultList.waitForExists(viewTimeout)) {
throw new UiObjectNotFoundException("Could not find \"search results list view\".");
}
String desc = String.format("Book: " + bookTitle);
// Create a selector so that we can search for siblings of the desired
// book that contains a "free" or "purchased" book identifier
UiSelector bookSelector = new UiSelector().description(desc).className("android.widget.TextView");
UiObject freeLabel =
new UiObject(new UiSelector().fromParent(bookSelector)
.resourceId("com.android.vending:id/li_label")
.description("Free"));
UiObject purchasedLabel =
new UiObject(new UiSelector().fromParent(bookSelector)
.resourceId("com.android.vending:id/li_label")
.description("Purchased"));
UiScrollable searchResultsList =
new UiScrollable(new UiSelector().resourceId("com.android.vending:id/search_results_list"));
int maxSwipes = 10;
while (!freeLabel.exists() && !purchasedLabel.exists()) {
if (maxSwipes <= 0) {
throw new UiObjectNotFoundException("Could not find free or purchased book \"" + bookTitle + "\"");
} else {
searchResultsList.swipeUp(10);
maxSwipes--;
}
}
// Click on either the first "free" or "purchased" book found that
// matches the book title
try {
freeLabel.click();
} catch (UiObjectNotFoundException e) {
purchasedLabel.click();
}
}
private void addToLibrary() throws Exception {
UiObject add = new UiObject(new UiSelector().textContains("ADD TO LIBRARY")
.className("android.widget.Button"));
if (add.exists()) {
add.click(); // add to My Library and opens book by default
} else {
UiObject read = getUiObjectByText("READ", "android.widget.Button");
read.click(); // opens book
}
waitForPage();
UiObject navigationButton = new UiObject(new UiSelector().description("Navigate up"));
// Return to main app window
pressBack();
// On some devices screen ordering is not preserved so check for
// navigation button to determine current screen
if (navigationButton.exists()) {
pressBack();
pressBack();
}
}
private void openMyLibrary() throws Exception {
String testTag = "open_library";
ActionLogger logger = new ActionLogger(testTag, parameters);
logger.start();
UiObject openDrawer = getUiObjectByDescription("Show navigation drawer");
openDrawer.click();
// To correctly find the UiObject we need to specify the index also here
UiObject myLibrary =
new UiObject(new UiSelector().className("android.widget.TextView")
.text("My library").index(3));
myLibrary.clickAndWaitForNewWindow(uiAutoTimeout);
logger.stop();
}
private void openBook(final String bookTitle) throws Exception {
UiScrollable cardsGrid =
new UiScrollable(new UiSelector().resourceId("com.google.android.apps.books:id/cards_grid"));
UiSelector bookSelector = new UiSelector().text(bookTitle).className("android.widget.TextView");
UiObject book = new UiObject(bookSelector);
// Check that books are sorted by time added to library. This way we
// can assume any newly downloaded books will be visible on the first
// screen.
UiObject menuSort =
getUiObjectByResourceId("com.google.android.apps.books:id/menu_sort", "android.widget.TextView");
menuSort.click();
UiObject sortByRecent = getUiObjectByText("Recent", "android.widget.TextView");
sortByRecent.click();
// When the book is first added to library it may not appear in
// cardsGrid until it has been fully downloaded. Wait for fully
// downloaded books
UiObject downloadComplete =
new UiObject(new UiSelector().fromParent(bookSelector).description("100% downloaded"));
int maxDownloadTime = 120; // seconds
while (!downloadComplete.exists()) {
if (maxDownloadTime <= 0) {
throw new UiObjectNotFoundException(
"Exceeded maximum wait time (" + maxDownloadTime + " seconds) to download book \"" + bookTitle + "\"");
} else {
sleep(1);
maxDownloadTime--;
}
}
book.click();
waitForPage();
}
private void gesturesTest() throws Exception {
String testTag = "gestures";
// Perform a range of swipe tests while browsing home photoplaybooks gallery
LinkedHashMap<String, GestureTestParams> testParams = new LinkedHashMap<String, GestureTestParams>();
testParams.put("swipe_left", new GestureTestParams(GestureType.UIDEVICE_SWIPE, Direction.LEFT, 20));
testParams.put("swipe_right", new GestureTestParams(GestureType.UIDEVICE_SWIPE, Direction.RIGHT, 20));
testParams.put("pinch_out", new GestureTestParams(GestureType.PINCH, PinchType.OUT, 100, 50));
testParams.put("pinch_in", new GestureTestParams(GestureType.PINCH, PinchType.IN, 100, 50));
Iterator<Entry<String, GestureTestParams>> it = testParams.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, GestureTestParams> pair = it.next();
GestureType type = pair.getValue().gestureType;
Direction dir = pair.getValue().gestureDirection;
PinchType pinch = pair.getValue().pinchType;
int steps = pair.getValue().steps;
int percent = pair.getValue().percent;
String runName = String.format(testTag + "_" + pair.getKey());
ActionLogger logger = new ActionLogger(runName, parameters);
UiObject pageView = waitForPage();
logger.start();
switch (type) {
case UIDEVICE_SWIPE:
uiDeviceSwipe(dir, steps);
break;
case UIOBJECT_SWIPE:
uiObjectSwipe(pageView, dir, steps);
break;
case PINCH:
uiObjectVertPinch(pageView, pinch, steps, percent);
break;
default:
break;
}
logger.stop();
}
waitForPage();
}
private UiObject searchPage(final UiObject view, final String pagenum, final Direction updown,
final int attempts) throws Exception {
if (attempts <= 0) {
throw new UiObjectNotFoundException("Could not find \"page number\" after several attempts.");
}
String search = String.format("page " + pagenum);
UiObject page = new UiObject(new UiSelector().description(search)
.className("android.widget.TextView"));
if (!page.exists()) {
// Scroll up by swiping down
if (updown == Direction.UP) {
view.swipeDown(200);
// Default case is to scroll down (swipe up)
} else {
view.swipeUp(200);
}
searchPage(view, pagenum, updown, attempts - 1);
}
return page;
}
private void selectChapter(final String chapterPageNumber) throws Exception {
getDropdownMenu();
UiObject contents = getUiObjectByResourceId("com.google.android.apps.books:id/menu_reader_toc",
"android.widget.TextView");
contents.clickAndWaitForNewWindow(uiAutoTimeout);
UiObject toChapterView = getUiObjectByResourceId("com.google.android.apps.books:id/toc_list_view",
"android.widget.ExpandableListView");
// Navigate to top of chapter view
searchPage(toChapterView, "1", Direction.UP, 10);
UiObject page = searchPage(toChapterView, chapterPageNumber, Direction.DOWN, 10);
page.clickAndWaitForNewWindow(viewTimeout);
waitForPage();
}
private void addNote(final String text) throws Exception {
String testTag = "add_note";
ActionLogger logger = new ActionLogger(testTag, parameters);
logger.start();
UiObject clickable = new UiObject(new UiSelector().longClickable(true));
uiObjectPerformLongClick(clickable, 100);
UiObject addNoteButton = new UiObject(
new UiSelector().resourceId("com.google.android.apps.books:id/add_note_button"));
addNoteButton.click();
UiObject noteEditText = getUiObjectByResourceId("com.google.android.apps.books:id/note_edit_text",
"android.widget.EditText");
noteEditText.setText(text);
UiObject noteMenuButton = getUiObjectByResourceId("com.google.android.apps.books:id/note_menu_button",
"android.widget.ImageButton");
noteMenuButton.click();
UiObject saveButton = getUiObjectByText("Save", "android.widget.TextView");
saveButton.click();
logger.stop();
waitForPage();
}
private void removeNote() throws Exception {
String testTag = "remove_note";
ActionLogger logger = new ActionLogger(testTag, parameters);
logger.start();
UiObject clickable = new UiObject(new UiSelector().longClickable(true));
uiObjectPerformLongClick(clickable, 100);
UiObject removeButton = new UiObject(
new UiSelector().resourceId("com.google.android.apps.books:id/remove_highlight_button"));
removeButton.click();
UiObject confirmRemove = getUiObjectByText("Remove", "android.widget.Button");
confirmRemove.click();
logger.stop();
waitForPage();
}
private void searchForWord(final String text) throws Exception {
String testTag = "search_for_word";
ActionLogger logger = new ActionLogger(testTag, parameters);
getDropdownMenu();
logger.start();
UiObject search = new UiObject(
new UiSelector().resourceId("com.google.android.apps.books:id/menu_search"));
search.click();
UiObject searchText = new UiObject(
new UiSelector().resourceId("com.google.android.apps.books:id/search_src_text"));
searchText.setText(text);
pressEnter();
UiObject resultList = new UiObject(
new UiSelector().resourceId("com.google.android.apps.books:id/search_results_list"));
// Allow extra time for search queries involing high freqency words
final long searchTimeout = TimeUnit.SECONDS.toMillis(20);
if (!resultList.waitForExists(searchTimeout)) {
throw new UiObjectNotFoundException("Could not find \"search results list view\".");
}
UiObject searchWeb =
new UiObject(new UiSelector().text("Search web")
.className("android.widget.TextView"));
if (!searchWeb.waitForExists(searchTimeout)) {
throw new UiObjectNotFoundException("Could not find \"Search web view\".");
}
logger.stop();
pressBack();
}
private void switchPageStyles() throws Exception {
String testTag = "switch_page_style";
getDropdownMenu();
UiObject readerSettings = getUiObjectByResourceId("com.google.android.apps.books:id/menu_reader_settings",
"android.widget.TextView");
readerSettings.click();
// Check for lighting option button on newer versions
UiObject lightingOptionsButton =
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/lighting_options_button"));
if (lightingOptionsButton.exists()) {
lightingOptionsButton.click();
}
String[] styles = {"Night", "Sepia", "Day"};
for (String style : styles) {
ActionLogger logger = new ActionLogger(testTag + "_" + style, parameters);
logger.start();
UiObject pageStyle = new UiObject(new UiSelector().description(style));
pageStyle.clickAndWaitForNewWindow(viewTimeout);
logger.stop();
}
sleep(2);
tapDisplayCentre();
waitForPage();
}
private void aboutBook() throws Exception {
String testTag = "about_book";
ActionLogger logger = new ActionLogger(testTag, parameters);
getDropdownMenu();
logger.start();
UiObject moreOptions = getUiObjectByDescription("More options", "android.widget.ImageView");
moreOptions.click();
UiObject bookInfo = getUiObjectByText("About this book", "android.widget.TextView");
bookInfo.clickAndWaitForNewWindow(uiAutoTimeout);
UiObject detailsPanel =
new UiObject(new UiSelector().resourceId("com.android.vending:id/item_details_panel"));
waitObject(detailsPanel, viewTimeoutSecs);
logger.stop();
pressBack();
}
// Helper for accessing the drop down menu
private void getDropdownMenu() throws Exception {
sleep(1); // Allow previous views to settle
int height = getDisplayHeight();
int width = getDisplayCentreWidth();
getUiDevice().swipe(width, 20, width, height / 10, 50);
// selecting the drop down menu can be unreliable so check for its
// existence and if not present try for a second time using a different
// start point and step size
UiObject actionBar =
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/action_bar"));
long actionBarTimeout = TimeUnit.SECONDS.toMillis(3);
if (!actionBar.waitForExists(actionBarTimeout)) {
getUiDevice().swipe(width, 5, width, height / 10, 20);
}
}
// Helper for waiting on a page between actions
private UiObject waitForPage() throws Exception {
UiObject activityReader =
new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/activity_reader")
.childSelector(new UiSelector().focusable(true)));
// On some devices the object in the view hierarchy is found before it
// becomes visible on the screen. Therefore add pause instead.
sleep(3);
if (!activityReader.waitForExists(viewTimeout)) {
throw new UiObjectNotFoundException("Could not find \"activity reader view\".");
}
return activityReader;
}
}