UiBot.java revision a86a3012ef689af659261b209da86f2e24ac21ec
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
19a86a3012ef689af659261b209da86f2e24ac21ecFelipe Lemeimport android.app.Instrumentation;
20a86a3012ef689af659261b209da86f2e24ac21ecFelipe Lemeimport android.app.StatusBarManager;
21e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.By;
22e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiDevice;
23e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiObject;
24e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiObjectNotFoundException;
25a413143afc73792e31d7dca90a7690e7f4352469Felipe Lemeimport android.support.test.uiautomator.UiScrollable;
26e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.UiSelector;
27e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.support.test.uiautomator.Until;
28e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport android.util.Log;
29a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme
30a86a3012ef689af659261b209da86f2e24ac21ecFelipe Lemeimport static junit.framework.Assert.assertFalse;
31e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemeimport static junit.framework.Assert.assertTrue;
32e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
33e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme/**
34e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme * A helper class for UI-related testing tasks.
35e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme */
36e53e85f6051d20cbd477bc25d446a41996411fabFelipe Lemefinal class UiBot {
37e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
38e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    private static final String TAG = "UiBot";
393fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
40e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
41a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme    private final Instrumentation mInstrumentation;
42e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    private final UiDevice mDevice;
43e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    private final int mTimeout;
44e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
45a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme    public UiBot(Instrumentation instrumentation, int timeout) {
46a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        mInstrumentation = instrumentation;
47a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        mDevice = UiDevice.getInstance(instrumentation);
48e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        mTimeout = timeout;
49e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
50e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
51e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
5269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Opens the system notification and gets a given notification.
53e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
54e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param text Notificaton's text as displayed by the UI.
5569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * @return notification object.
56e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
5769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public UiObject getNotification(String text) {
58e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        boolean opened = mDevice.openNotification();
59e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        Log.v(TAG, "openNotification(): " + opened);
603fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGE)), mTimeout);
613fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        assertTrue("could not get system ui (" + SYSTEMUI_PACKAGE + ")", gotIt);
62e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
6369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        return getObject(text);
6469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
65e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
66a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme    public void closeNotifications() throws Exception {
67a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        // TODO: mDevice should provide such method..
68a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        StatusBarManager sbm =
69a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme                (StatusBarManager) mInstrumentation.getContext().getSystemService("statusbar");
70a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        sbm.collapsePanels();
71a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme    }
72a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme
7369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
7469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Opens the system notification and clicks a given notification.
7569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     *
7669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * @param text Notificaton's text as displayed by the UI.
7769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
7869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void clickOnNotification(String text) {
7969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        UiObject notification = getNotification(text);
80e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        click(notification, "bug report notification");
81e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
82e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
83e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
8469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Gets an object that might not yet be available in current UI.
8569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     *
8669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * @param text Object's text as displayed by the UI.
8769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
8869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public UiObject getObject(String text) {
8969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
9069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        assertTrue("object with text '(" + text + "') not visible yet", gotIt);
9169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        return getVisibleObject(text);
9269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
9369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
9469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
95bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Gets an object that might not yet be available in current UI.
96bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     *
97bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * @param id Object's fully-qualified resource id (like {@code android:id/button1})
98bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
99bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    public UiObject getObjectById(String id) {
100bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), mTimeout);
101bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        assertTrue("object with id '(" + id + "') not visible yet", gotIt);
102bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return getVisibleObjectById(id);
103bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
104bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
105bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
10669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Gets an object which is guaranteed to be present in the current UI.
107e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
108e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param text Object's text as displayed by the UI.
109e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
110e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    public UiObject getVisibleObject(String text) {
111e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
11269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        assertTrue("could not find object with text '" + text + "'", uiObject.exists());
113e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        return uiObject;
114e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
115e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
116e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
117bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Gets an object which is guaranteed to be present in the current UI.
118bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     *
119bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * @param text Object's text as displayed by the UI.
120bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
121bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    public UiObject getVisibleObjectById(String id) {
122bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        UiObject uiObject = mDevice.findObject(new UiSelector().resourceId(id));
123bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        assertTrue("could not find object with id '" + id+ "'", uiObject.exists());
124bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return uiObject;
125bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
126bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
127a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme    /**
128a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme     * Asserts an object is not visible.
129a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme     */
130a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme    public void assertNotVisibleById(String id) {
131a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        // TODO: not working when the bugreport dialog is shown, it hangs until the dialog is
132a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        // dismissed and hence always work.
133a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        boolean hasIt = mDevice.hasObject(By.res(id));
134a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme        assertFalse("should not have found object with id '" + id+ "'", hasIt);
135a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme    }
136a86a3012ef689af659261b209da86f2e24ac21ecFelipe Leme
137bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
138bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
139e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * Clicks on a UI element.
140e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
141e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param uiObject UI element to be clicked.
142e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param description Elements's description used on logging statements.
143e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
144e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    public void click(UiObject uiObject, String description) {
145e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        try {
146e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            boolean clicked = uiObject.click();
147e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            // TODO: assertion below fails sometimes, even though the click succeeded,
148e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            // (specially when clicking the "Just Once" button), so it's currently just logged.
149e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            // assertTrue("could not click on object '" + description + "'", clicked);
150e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
151e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            Log.v(TAG, "onClick for " + description + ": " + clicked);
152e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        } catch (UiObjectNotFoundException e) {
153e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme            throw new IllegalStateException("exception when clicking on object '" + description
154e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme                    + "'", e);
155e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        }
156e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
157e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme
158e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    /**
159e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * Chooses a given activity to handle an Intent, using the "Just Once" button.
160e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *
161e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     * @param name name of the activity as displayed in the UI (typically the value set by
162e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     *            {@code android:label} in the manifest).
163e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme     */
164e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    // TODO: UI Automator should provide such logic.
165e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    public void chooseActivity(String name) {
166a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme        // First check if the activity is the default option.
167ba477939f0ae38926b4b0a6501a2371acc612433Felipe Leme        String shareText = "Share with " + name;
168ba477939f0ae38926b4b0a6501a2371acc612433Felipe Leme        Log.v(TAG, "Waiting for ActivityChooser text: '" + shareText + "'");
169a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.text(shareText)), mTimeout);
1703fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        boolean justOnceHack = false;
171a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme
172e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        if (gotIt) {
173a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            Log.v(TAG, "Found activity " + name + ", it's the default action");
1743fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            clickJustOnce();
175e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        } else {
176a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            // Since it's not, need to find it in the scrollable list...
177a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            Log.v(TAG, "Activity " + name + " is not default action");
178a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            UiScrollable activitiesList = new UiScrollable(new UiSelector().scrollable(true));
179a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            try {
180a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme                activitiesList.scrollForward();
181a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            } catch (UiObjectNotFoundException e) {
1823fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                // TODO: for some paranormal issue, the first time a test is run the scrollable
1833fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                // activity list is displayed but calling scrollForwad() (or even isScrollable())
1843fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                // throws a "UiObjectNotFoundException: UiSelector[SCROLLABLE=true]" exception
1853fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                justOnceHack = true;
1863fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                Log.d(TAG, "could not scroll forward", e);
187a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            }
1883fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            UiObject activity = getVisibleObject(name);
189a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            // ... then select it.
190a413143afc73792e31d7dca90a7690e7f4352469Felipe Leme            click(activity, name);
1913fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            if (justOnceHack) {
1923fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                clickJustOnce();
1933fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme            }
194e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme        }
195e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme    }
196bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1973fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme    private void clickJustOnce() {
1983fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        boolean gotIt = mDevice.wait(Until.hasObject(By.res("android", "button_once")), mTimeout);
1993fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        assertTrue("'Just Once' button not visible yet", gotIt);
2003fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme
2013fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        UiObject justOnce = mDevice
2023fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme                .findObject(new UiSelector().resourceId("android:id/button_once"));
2033fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        assertTrue("'Just Once' button not found", justOnce.exists());
2043fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme
2053fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme        click(justOnce, "Just Once");
2063fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme    }
2073fb3d88d2b81122038a47c831d10f6b5ecf2b83aFelipe Leme
208bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    public void pressBack() {
209bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        mDevice.pressBack();
210bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
211e53e85f6051d20cbd477bc25d446a41996411fabFelipe Leme}
212