/* * Copyright 2015 Google Inc. All rights reserved. * * 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.google.samples.apps.iosched.myschedule; import android.content.Intent; import android.net.Uri; import android.os.Looper; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.Espresso; import android.support.test.espresso.IdlingResource; import android.support.test.filters.FlakyTest; import android.support.test.filters.LargeTest; import android.support.test.filters.Suppress; import android.support.test.runner.AndroidJUnit4; import android.util.Log; import com.google.samples.apps.iosched.Config; import com.google.samples.apps.iosched.R; import com.google.samples.apps.iosched.feedback.SessionFeedbackActivity; import com.google.samples.apps.iosched.injection.ModelProvider; import com.google.samples.apps.iosched.mockdata.MyScheduleMockItems; import com.google.samples.apps.iosched.mockdata.StubActivityContext; import com.google.samples.apps.iosched.navigation.NavigationModel; import com.google.samples.apps.iosched.provider.ScheduleContract; import com.google.samples.apps.iosched.settings.SettingsUtils; import com.google.samples.apps.iosched.testutils.BaseActivityTestRule; import com.google.samples.apps.iosched.testutils.NavigationUtils; import com.google.samples.apps.iosched.testutils.OrientationHelper; import com.google.samples.apps.iosched.testutils.ThrottleContentObserverIdlingResource; import com.google.samples.apps.iosched.util.TimeUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Date; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.intent.Intents.intended; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; import static android.support.test.espresso.intent.matcher.IntentMatchers.hasData; import static android.support.test.espresso.matcher.ViewMatchers.hasSibling; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.core.IsNot.not; /** * UI tests for {@link MyScheduleActivity} for when the user is attending the conference and the * second day of the conference starts in 3 hours. * <p/> * This should be run on devices with a narrow layout only (phones all orientation, tablets in * portrait mode) */ @RunWith(AndroidJUnit4.class) @LargeTest public class MyScheduleActivityTest { /** * The {@link StubMyScheduleModel} needs a {@link android.content.Context} but at the stage it * is created, {@link #mActivityRule} hasn't got an {@link android.app.Activity} yet so we use * the instrumentation target context. However, an Actiivty Context is required by the model for * carrying certain actions, such as opening the session that was clicked on (which uses {@link * android.content.Context#startActivity(Intent)} and will not work with a non Activity context. * We use this {@link StubActivityContext} to later set an activity context at the start of each * test if needed (if the test needs to start another activity). */ private StubActivityContext mActivityStubContext; private StubMyScheduleModel mStubMyScheduleModel; @Rule public BaseActivityTestRule<MyScheduleActivity> mActivityRule = new BaseActivityTestRule<MyScheduleActivity>(MyScheduleActivity.class) { @Override protected void beforeActivityLaunched() { prepareActivityForInPersonAttendee(); // Create a stub model to simulate a user attending conference, during the // second day mActivityStubContext = new StubActivityContext(InstrumentationRegistry.getTargetContext()); try { /** * {@link MyScheduleModel} uses a Handler, so we need to run this on the * main thread. If we don't, we need to call {@link Looper#prepare()} but * the test runner uses the same non UI thread for setting up each test in a * test class, and therefore, upon trying to run the second test, it * complains that we call {@link Looper#prepare()} on a thread that has * already been prepared. By using the UI thread, we avoid this issue as * the UI thread is already prepared so we don't need to manually do it. */ runOnUiThread(new Runnable() { @Override public void run() { mStubMyScheduleModel = new StubMyScheduleModel( mActivityStubContext, MyScheduleMockItems.getItemsForAttendeeAfter(1, false), MyScheduleMockItems.getItemsForAttendeeBefore(2)); ModelProvider.setStubModel(mStubMyScheduleModel); } }); } catch (Throwable throwable) { Log.e("DEBUG", "Error running test " + throwable); } } }; @Before public void setUp() { // Set up time to start of second day of conference TimeUtils.setCurrentTimeRelativeToStartOfSecondDayOfConference( InstrumentationRegistry.getTargetContext(), 0); // Don't show notifications for sessions as they get in the way of the UI SettingsUtils.setShowSessionReminders(InstrumentationRegistry.getTargetContext(), false); // Mark use as attending conference SettingsUtils.setAttendeeAtVenue(InstrumentationRegistry.getTargetContext(), true); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void day2Selected() { // Given a current time 3 hours after the start of the second day // Then the second day is selected onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)).check(matches(isDisplayed())); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void viewDay2_clickOnSession_opensSessionScreenIntentFired() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // When clicking on the session onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)).perform(click()); // Then the intent with the session uri is fired Uri expectedSessionUri = ScheduleContract.Sessions.buildSessionUri(MyScheduleMockItems.SESSION_ID); intended(allOf( hasAction(equalTo(Intent.ACTION_VIEW)), hasData(expectedSessionUri))); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void viewDay1_clickOnSession_opensSessionScreenIntentFired() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // When clicking on the session onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)).perform(click()); // Then the intent with the session uri is fired Uri expectedSessionUri = ScheduleContract.Sessions.buildSessionUri(MyScheduleMockItems.SESSION_ID); intended(allOf( hasAction(equalTo(Intent.ACTION_VIEW)), hasData(expectedSessionUri))); } @Test public void viewDay1_clickOnRateSession_opensFeedbackScreenIntentFired() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // Given day 1 visible showDay(1); // When clicking on rate session onView(allOf(withText(R.string.my_schedule_rate_this_session), isDisplayed())) .perform(click()); // Then the intent for the feedback screen is fired Uri expectedSessionUri = ScheduleContract.Sessions.buildSessionUri(MyScheduleMockItems.SESSION_ID); intended(allOf( hasAction(equalTo(Intent.ACTION_VIEW)), hasData(expectedSessionUri), hasComponent(SessionFeedbackActivity.class.getName()))); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void viewDay2_clickOnBrowseSession_opensSessionsListScreen() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // When clicking on browse sessions onView(allOf(withText(R.string.browse_sessions), isDisplayed())).perform(click()); // Then the intent for the sessions list screen is fired long slotStart = Config.CONFERENCE_START_MILLIS + 1 * TimeUtils.DAY + MyScheduleMockItems.SESSION_AVAILABLE_SLOT_TIME_OFFSET; Uri expectedTimeIntervalUri = ScheduleContract.Sessions.buildUnscheduledSessionsInInterval(slotStart, slotStart + MyScheduleMockItems.SESSION_AVAILABLE_SLOT_TIME_DURATION); intended(allOf( hasAction(equalTo(Intent.ACTION_VIEW)), hasData(expectedTimeIntervalUri))); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void viewDay2_clickOnTimeSlot_opensSessionsListScreen() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // When clicking on the time of a time slot long slotStart = Config.CONFERENCE_START_MILLIS + 1 * TimeUtils.DAY; onView(allOf(isDisplayed(), withId(R.id.start_time), withText(TimeUtils.formatShortTime(mActivityStubContext, new Date(slotStart))))) .perform(click()); // Then the intent for the sessions list screen is fired Uri expectedTimeIntervalUri = ScheduleContract.Sessions.buildUnscheduledSessionsInInterval(slotStart, slotStart + MyScheduleMockItems.SESSION_AVAILABLE_SLOT_TIME_DURATION); intended(allOf( hasAction(equalTo(Intent.ACTION_VIEW)), hasData(expectedTimeIntervalUri))); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void viewDay2_clickOnMoreButton_opensSessionsListScreen() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // When clicking on the time of the more button next to a time slot long slotStart = Config.CONFERENCE_START_MILLIS + 1 * TimeUtils.DAY; onView(allOf(isDisplayed(), withId(R.id.more), hasSibling( withText(TimeUtils.formatShortTime(mActivityStubContext, new Date(slotStart)))))) .perform(click()); // Then the intent for the sessions list screen is fired Uri expectedTimeIntervalUri = ScheduleContract.Sessions.buildUnscheduledSessionsInInterval(slotStart, slotStart + MyScheduleMockItems.SESSION_AVAILABLE_SLOT_TIME_DURATION); intended(allOf( hasAction(equalTo(Intent.ACTION_VIEW)), hasData(expectedTimeIntervalUri))); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void timeSlotWithNoSessionInSchedule_MoreButton_IsNotVisible() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // More button is not visible for a time slow with no sessions in schedule long slotStartWithAvailableSessionsButNoneInSchedule = Config.CONFERENCE_START_MILLIS + 1 * TimeUtils.DAY + MyScheduleMockItems.SESSION_AVAILABLE_SLOT_TIME_OFFSET; onView(allOf(withId(R.id.more), hasSibling( withText(TimeUtils.formatShortTime(mActivityStubContext, new Date(slotStartWithAvailableSessionsButNoneInSchedule)))))) .check(matches(not(isDisplayed()))); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void timeSlotWithOneSessionInSchedule_MoreButton_IsVisible() { mActivityStubContext.setActivityContext(mActivityRule.getActivity()); // More button is visible for a time slow with 1 session in schedule long slotStartWithOneSessionInSchedule = Config.CONFERENCE_START_MILLIS + MyScheduleMockItems.SESSION_TITLE_AFTER_START_OFFSET; onView(allOf(isDisplayed(), withId(R.id.more), hasSibling( withText(TimeUtils.formatShortTime(mActivityStubContext, new Date(slotStartWithOneSessionInSchedule)))))) .check(matches(isDisplayed())); } @Test public void viewDay1_sessionVisible() { // Given day 1 visible showDay(1); // Then the session in the first day is displayed onView(withText(MyScheduleMockItems.SESSION_TITLE_AFTER)).check(matches(isDisplayed())); } @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void viewDay2_sessionVisible() { // Given day 2 visible // Then the session in the second day is displayed onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)).check(matches(isDisplayed())); } @Test public void navigationIcon_DisplaysAsMenu() { NavigationUtils.checkNavigationIconIsMenu(); } @Test public void navigationIcon_OnClick_NavigationDisplayed() { NavigationUtils.checkNavigationIsDisplayedWhenClickingMenuIcon(); } @Test public void navigation_WhenShown_CorrectItemIsSelected() { NavigationUtils .checkNavigationItemIsSelected(NavigationModel.NavigationItemEnum.MY_SCHEDULE); } /** * This test works only on phones, where the layout is the same for both orientations (ie tabs) */ @Test @Suppress // Test isn't deterministic when run as part of the full test suite. public void orientationChange_RetainsDataAndCurrentTab_Flaky() { // Given day 2 visible showDay(2); onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)).check(matches(isDisplayed())); // When changing orientation OrientationHelper.rotateOrientation(mActivityRule); // Then day 2 is visible onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)).check(matches(isDisplayed())); // And day 0 is selectable and visible showDay(0); onView(withText(R.string.my_schedule_badgepickup)).check(matches(isDisplayed())); // And day 1 is selectable and visible showDay(1); onView(withText(MyScheduleMockItems.SESSION_TITLE_AFTER)).check(matches(isDisplayed())); // When changing orientation again OrientationHelper.rotateOrientation(mActivityRule); // Then day 1 is visible onView(withText(MyScheduleMockItems.SESSION_TITLE_AFTER)).check(matches(isDisplayed())); // And day 0 is selectable and visible showDay(0); onView(withText(R.string.my_schedule_badgepickup)).check(matches(isDisplayed())); // And day 2 is selectable and visible showDay(2); onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)).check(matches(isDisplayed())); } @Test public void newDataObtained_DataUpdated() { IdlingResource idlingResource = null; try { // Given initial data displayed showDay(2); onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)) .check(matches(isDisplayed())); onView(withText(MyScheduleMockItems.SESSION_TITLE_2)).check(doesNotExist()); showDay(1); onView(withText(MyScheduleMockItems.SESSION_TITLE_AFTER)).check(matches(isDisplayed())); onView(withText(MyScheduleMockItems.SESSION_TITLE_1)).check(doesNotExist()); // When new data is available mStubMyScheduleModel.setMockScheduleDataDay1(MyScheduleMockItems .getItemsForAttendee(1, false, MyScheduleMockItems.SESSION_TITLE_1)); mStubMyScheduleModel.setMockScheduleDataDay2(MyScheduleMockItems .getItemsForAttendee(2, false, MyScheduleMockItems.SESSION_TITLE_2)); mStubMyScheduleModel.fireContentObserver(); // Wait for the ThrottleContentObserver to process the event idlingResource = new ThrottleContentObserverIdlingResource( InstrumentationRegistry.getTargetContext()); Espresso.registerIdlingResources(idlingResource); // Then the new data is shown onView(withText(MyScheduleMockItems.SESSION_TITLE_1)).check(matches(isDisplayed())); onView(withText(MyScheduleMockItems.SESSION_TITLE_AFTER)) .check(doesNotExist()); showDay(2); onView(withText(MyScheduleMockItems.SESSION_TITLE_2)).check(matches(isDisplayed())); onView(withText(MyScheduleMockItems.SESSION_TITLE_BEFORE)) .check(doesNotExist()); } finally { if (idlingResource != null) { Espresso.unregisterIdlingResources(idlingResource); } } } private void showDay(int day) { onView(withId(MyScheduleActivity.BASE_TAB_VIEW_ID + day)).perform(click()); } }