Automate Android Sender UI Tests

Testing your app is a necessary part of the Cast development process. Your app should comply with the Cast UX Guidelines and Design Checklist to ensure users have a consistent Cast experience.

For Android apps, leverage the UI Automator and Espresso testing frameworks to simulate user interactions on your app and run your UI tests in an automated and repeatable way. To learn more about automated UI tests, see Automate user interface tests.

This guide describes how to add automated UI tests to your Android sender app.

Set up the test environment

Android Studio is recommended for building and running your app and tests.

On the physical device used for testing, under Settings > Developer options, turn off the following system animations:

  • Window animation scale
  • Transition animation scale
  • Animator duration scale

Example Gradle build file

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28

    defaultConfig {
        applicationId "com.example.package"
        minSdkVersion 18
        targetSdkVersion 28
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    ...

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test:rules:1.1.1'
}

Add the first Cast UI test

By default, Android Studio provides a source code directory at src/androidTest/java/ to place your instrumented and UI tests. For more information, see Test types and location.

To test if a Cast icon is displayed on the app:

package com.example.package;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.mediarouter.app.MediaRouteButton;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
import androidx.test.rule.ActivityTestRule;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;

@RunWith(AndroidJUnit4ClassRunner.class)
public class MyCastUITest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule =
            new ActivityTestRule<>(MainActivity.class);

    @Test
    public void testCastButtonDisplay() throws InterruptedException {
        // wait for Cast button
        Thread.sleep(2000);

        onView(isAssignableFrom(MediaRouteButton.class)).check(matches(isDisplayed()));
    }
}

Test Cast connection

This example shows how to simulate user actions connecting to a Cast device:

import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;

import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

@RunWith(AndroidJUnit4ClassRunner.class)
public class MyCastUITest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule =
            new ActivityTestRule<>(MainActivity.class);

    /**
     * Connecting to Cast device
     *  - Open Cast menu dialog when tapping the Cast icon
     *  - Select target Cast device and connect
     *  - Assert the Cast state is connected
     */
    @Test
    public void testConnectToCastDevice()
             throws InterruptedException, UiObjectNotFoundException {

        // wait for Cast button ready
        Thread.sleep(2000);

        // click on Cast icon and show a dialog
        onView(isAssignableFrom(MediaRouteButton.class))
                .perform(click());
        onView(withId(R.id.action_bar_root))
                .check(matches(isDisplayed()));

        // select target Cast device to connect
        UiDevice mDevice = UiDevice.getInstance(
                InstrumentationRegistry.getInstrumentation());
        mDevice.findObject(new UiSelector().text(TARGET_DEVICE)).click();

        // assert the Cast state is connected
        assertCastStateIsConnected(MAX_TIMEOUT_MS);
    }
}

The Cast session and connection state can be retrieved by executing a call on the application's main thread:

import android.content.Context;
import android.os.SystemClock;

import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManager;

import static org.junit.Assert.assertTrue;

@RunWith(AndroidJUnit4ClassRunner.class)
public class MyCastUITest {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private boolean isCastConnected;

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule =
            new ActivityTestRule<>(MainActivity.class);

    /**
     * Connecting to Cast device
     */
    @Test
    public void testConnectToCastDevice()
             throws InterruptedException, UiObjectNotFoundException {
        ......

        // assert the Cast state is connected
        assertCastStateIsConnected(MAX_TIMEOUT_MS);
    }

    /**
     * Check connection status from Cast session
     */
    private void assertCastStateIsConnected(long timeout)
              throws InterruptedException {

        long startTime = SystemClock.uptimeMillis();
        isCastConnected = false;

        while (!isCastConnected && SystemClock.uptimeMillis() - startTime < timeout) {

            Thread.sleep(500);

            // get cast instance and cast session from the app's main thread
            InstrumentationRegistry.getInstrumentation().runOnMainSync(
                    new Runnable() {
                        @Override
                        public void run() {
                            Context mTargetContext =
                                InstrumentationRegistry.getInstrumentation().getTargetContext();
                            mCastContext =
                                CastContext.getSharedInstance(mTragetContext);
                            mSessionManager = mCastContext.getSessionManager();
                            mCastSession =
                                mSessionManager.getCurrentCastSession();
                            isCastConnected = mCastSession.isConnected();
                        }
                    }
            );
        }

        assertTrue(isCastConnected);
    }
}