UiBot.java revision 3fb3d88d2b81122038a47c831d10f6b5ecf2b83a
1e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme/*
2e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * Copyright (C) 2015 The Android Open Source Project
3e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme *
4e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * Licensed under the Apache License, Version 2.0 (the "License");
5e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * you may not use this file except in compliance with the License.
6e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * You may obtain a copy of the License at
7e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme *
8e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme *      http://www.apache.org/licenses/LICENSE-2.0
9e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme *
10e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * Unless required by applicable law or agreed to in writing, software
11e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * distributed under the License is distributed on an "AS IS" BASIS,
12e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * See the License for the specific language governing permissions and
14e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * limitations under the License.
15e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme */
16e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
17e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemepackage com.android.shell;
18e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
19e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.By;
20e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiDevice;
21e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiObject;
22e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiObjectNotFoundException;
23a413143afc73792e31d7dca90a7690e7f4352469Felipe Lemeimport android.support.test.uiautomator.UiScrollable;
24e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiSelector;
25e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.Until;
26e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.util.Log;
27e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport static junit.framework.Assert.assertTrue;
28e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
29e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme/**
30e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * A helper class for UI-related testing tasks.
31e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme */
32e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemefinal class UiBot {
33e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
34e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    private static final String TAG = "UiBot";
353fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
36e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
37e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    private final UiDevice mDevice;
38e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    private final int mTimeout;
39e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
40e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    public UiBot(UiDevice device, int timeout) {
41e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        mDevice = device;
42e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        mTimeout = timeout;
43e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
44e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
45e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
4669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Opens the system notification and gets a given notification.
47e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
48e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param text Notificaton's text as displayed by the UI.
4969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * @return notification object.
50e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
5169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public UiObject getNotification(String text) {
52e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        boolean opened = mDevice.openNotification();
53e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        Log.v(TAG, "openNotification(): " + opened);
543fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGE)), mTimeout);
553fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        assertTrue("could not get system ui (" + SYSTEMUI_PACKAGE + ")", gotIt);
56e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
5769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        return getObject(text);
5869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
59e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
6069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
6169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Opens the system notification and clicks a given notification.
6269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     *
6369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * @param text Notificaton's text as displayed by the UI.
6469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
6569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void clickOnNotification(String text) {
6669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        UiObject notification = getNotification(text);
67e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        click(notification, "bug report notification");
68e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
69e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
70e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
7169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Gets an object that might not yet be available in current UI.
7269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     *
7369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * @param text Object's text as displayed by the UI.
7469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
7569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public UiObject getObject(String text) {
7669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
7769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        assertTrue("object with text '(" + text + "') not visible yet", gotIt);
7869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        return getVisibleObject(text);
7969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
8069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
8169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
82bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Gets an object that might not yet be available in current UI.
83bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     *
84bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * @param id Object's fully-qualified resource id (like {@code android:id/button1})
85bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
86bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    public UiObject getObjectById(String id) {
87bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), mTimeout);
88bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        assertTrue("object with id '(" + id + "') not visible yet", gotIt);
89bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return getVisibleObjectById(id);
90bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
91bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
92bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
9369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Gets an object which is guaranteed to be present in the current UI.
94e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
95e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param text Object's text as displayed by the UI.
96e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
97e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    public UiObject getVisibleObject(String text) {
98e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
9969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        assertTrue("could not find object with text '" + text + "'", uiObject.exists());
100e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        return uiObject;
101e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
102e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
103e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
104bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Gets an object which is guaranteed to be present in the current UI.
105bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     *
106bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * @param text Object's text as displayed by the UI.
107bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
108bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    public UiObject getVisibleObjectById(String id) {
109bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        UiObject uiObject = mDevice.findObject(new UiSelector().resourceId(id));
110bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        assertTrue("could not find object with id '" + id+ "'", uiObject.exists());
111bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return uiObject;
112bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
113bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
114bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
115bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
116e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * Clicks on a UI element.
117e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
118e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param uiObject UI element to be clicked.
119e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param description Elements's description used on logging statements.
120e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
121e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    public void click(UiObject uiObject, String description) {
122e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        try {
123e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            boolean clicked = uiObject.click();
124e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            // TODO: assertion below fails sometimes, even though the click succeeded,
125e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            // (specially when clicking the "Just Once" button), so it's currently just logged.
126e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            // assertTrue("could not click on object '" + description + "'", clicked);
127e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
128e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            Log.v(TAG, "onClick for " + description + ": " + clicked);
129e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        } catch (UiObjectNotFoundException e) {
130e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            throw new IllegalStateException("exception when clicking on object '" + description
131e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme                    + "'", e);
132e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        }
133e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
134e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
135e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
136e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * Chooses a given activity to handle an Intent, using the "Just Once" button.
137e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
138e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param name name of the activity as displayed in the UI (typically the value set by
139e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *            {@code android:label} in the manifest).
140e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
141e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    // TODO: UI Automator should provide such logic.
142e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    public void chooseActivity(String name) {
143a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme        // First check if the activity is the default option.
144ba477939f0ae38926b4b0a6501a2371acc612433Felipe Leme        String shareText = "Share with " + name;
145ba477939f0ae38926b4b0a6501a2371acc612433Felipe Leme        Log.v(TAG, "Waiting for ActivityChooser text: '" + shareText + "'");
146a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.text(shareText)), mTimeout);
1473fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        boolean justOnceHack = false;
148a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme
149e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        if (gotIt) {
150a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            Log.v(TAG, "Found activity " + name + ", it's the default action");
1513fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            clickJustOnce();
152e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        } else {
153a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            // Since it's not, need to find it in the scrollable list...
154a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            Log.v(TAG, "Activity " + name + " is not default action");
155a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            UiScrollable activitiesList = new UiScrollable(new UiSelector().scrollable(true));
156a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            try {
157a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme                activitiesList.scrollForward();
158a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            } catch (UiObjectNotFoundException e) {
1593fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                // TODO: for some paranormal issue, the first time a test is run the scrollable
1603fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                // activity list is displayed but calling scrollForwad() (or even isScrollable())
1613fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                // throws a "UiObjectNotFoundException: UiSelector[SCROLLABLE=true]" exception
1623fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                justOnceHack = true;
1633fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                Log.d(TAG, "could not scroll forward", e);
164a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            }
1653fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            UiObject activity = getVisibleObject(name);
166a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            // ... then select it.
167a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            click(activity, name);
1683fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            if (justOnceHack) {
1693fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                clickJustOnce();
1703fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            }
171e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        }
172e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
173bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1743fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme    private void clickJustOnce() {
1753fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.res("android", "button_once")), mTimeout);
1763fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        assertTrue("'Just Once' button not visible yet", gotIt);
1773fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme
1783fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        UiObject justOnce = mDevice
1793fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                .findObject(new UiSelector().resourceId("android:id/button_once"));
1803fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        assertTrue("'Just Once' button not found", justOnce.exists());
1813fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme
1823fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        click(justOnce, "Just Once");
1833fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme    }
1843fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme
185bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    public void pressBack() {
186bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        mDevice.pressBack();
187bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
188e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme}
189