package com.tomtom.pnd.firstrunwizard.test; import static android.support.test.espresso.Espresso.closeSoftKeyboard; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.clearText; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.scrollTo; import static android.support.test.espresso.action.ViewActions.typeText; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isChecked; import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; import static android.support.test.espresso.matcher.ViewMatchers.isNotChecked; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static com.tomtom.espresso.test.CucumberHelperTestSteps.checkViewWithIdIsCompletelyDisplayed; import static com.tomtom.espresso.test.CucumberHelperTestSteps.checkViewWithTextIsCompletelyDisplayedOnce; import static com.tomtom.espresso.test.CucumberHelperTestSteps.takeScreenshotOnFail; import static com.tomtom.pnd.firstrunwizard.test.NavInputFieldMatchers.withNormalState; import static com.tomtom.pnd.firstrunwizard.test.NavInputFieldMatchers.withWarningState; import static org.hamcrest.Matchers.allOf; import com.tomtom.pnd.firstrunwizard.FirstRunWizardActivity; import com.tomtom.pnd.firstrunwizard.R; import com.tomtom.pnd.settingslib.PndSettings; import cucumber.api.java.After; import cucumber.api.java.Before; import cucumber.api.java.en.Given; import android.app.Activity; import android.app.Application; import android.app.Application.ActivityLifecycleCallbacks; import android.os.Bundle; import android.support.test.espresso.EspressoException; import android.support.test.espresso.NoMatchingViewException; import android.test.ActivityInstrumentationTestCase2; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import junit.framework.AssertionFailedError; /** * Test steps for the host activity. */ public class FirstRunWizardTestSteps extends ActivityInstrumentationTestCase2<FirstRunWizardActivity> { private static final String TAG = "FirstRunWizardTestSteps"; public static class MissingValuesException extends RuntimeException implements EspressoException { private static final long serialVersionUID = -1247022787790935324L; MissingValuesException(final String message) { super(message); } } // Holds the activity under test private FirstRunWizardActivity mActivity; private Application mApplication; private final ActivityLifecycleCallbacks mActivityLifecycleCallback; private CountDownLatch mDoneSignal; private static final String SCREEN_NAME_LANGUAGE_SELECTION = "language selection"; private static final String SCREEN_NAME_COUNTRY_SELECTION = "country selection"; private static final String SCREEN_NAME_VEHICLE_TYPE_SELECTION = "vehicle type selection"; private static final String TOGGLE_EU_EXPLOSIVE = "eu explosive"; private static final String TOGGLE_EU_HARMFUL = "eu harmful to water"; private static final String TOGGLE_EU_GENERAL = "eu general"; private static final int CLOSE_ACTIVITY_WAIT = 1000; private static HashMap<String, Integer> INPUT_FIELD_MAP = new HashMap<String, Integer>(); static { INPUT_FIELD_MAP.put("vehicle length", Integer.valueOf(R.id.navui_vehicleProfileLengthInput)); INPUT_FIELD_MAP.put("vehicle width", Integer.valueOf(R.id.navui_vehicleProfileWidthInput)); INPUT_FIELD_MAP.put("vehicle height", Integer.valueOf(R.id.navui_vehicleProfileHeightInput)); INPUT_FIELD_MAP.put("vehicle gross weight", Integer.valueOf(R.id.navui_vehicleProfileWeightInput)); INPUT_FIELD_MAP.put("vehicle axle weight", Integer.valueOf(R.id.navui_vehicleProfileAxleWeightInput)); INPUT_FIELD_MAP.put("vehicle max speed", Integer.valueOf(R.id.navui_vehicleProfileMaxSpeedInput)); } private Map<String, String> mVehicleProfileValues; public FirstRunWizardTestSteps() { super(FirstRunWizardActivity.class); mActivityLifecycleCallback = new ActivityLifecycleCallbacks() { @Override public void onActivityDestroyed(final Activity activity) { if (Log.D) Log.d(TAG, "onActivityDestroyed()"); if (mDoneSignal != null) { mDoneSignal.countDown(); } if (mApplication != null) { mApplication.unregisterActivityLifecycleCallbacks(this); } } @Override public void onActivityStopped(final Activity activity) { } @Override public void onActivityStarted(final Activity activity) { } @Override public void onActivitySaveInstanceState(final Activity activity, final Bundle outState) { } @Override public void onActivityResumed(final Activity activity) { } @Override public void onActivityPaused(final Activity activity) { } @Override public void onActivityCreated(final Activity activity, final Bundle savedInstanceState) { } }; } /** * Modify the private variable of FirstRunWizardActivity to control the value. This should * really be a mocked interface, but for one variable this works ok. * @param hasVehicleProfileSupport Enable Vehicle profile flow if true * @return True when successful */ private final boolean setVehicleProfileSupport(final boolean hasVehicleProfileSupport) { try { final Field field = FirstRunWizardActivity.class.getDeclaredField("mSupportsVehicleProfiles"); field.setAccessible(true); field.set(mActivity, hasVehicleProfileSupport); return true; } catch (final NoSuchFieldException e) { fail("Could not find field mSupportsVehicleProfiles in FirstRunWizardActivity"); } catch (final IllegalArgumentException e) { fail("Illegal argument for mSupportsVehicleProfiles field in FirstRunWizardActivity"); } catch (final IllegalAccessException e) { fail("Not allowed to change mSupportsVehicleProfiles field in FirstRunWizardActivity"); } return false; } private void scroll(final String scrollDirection) { if ("up".equals(scrollDirection)) { onView(withId(R.id.navui_upButton)).perform(click()); } else { onView(withId(R.id.navui_downButton)).perform(click()); } } @Before public void before() { PndSettings.setUseMockMode(true); // Disable writing to the actual settings } @After public void after() { takeScreenshotOnFail(); if (mActivity != null) { // Close activity after each test scenario if (!mActivity.isFinishing()) { mActivity.finish(); } try { if (mDoneSignal != null) { if (!mDoneSignal.await(10, TimeUnit.SECONDS)) { if (Log.D) Log.d(TAG, "FirstRunWizard activity didn't finish properly in the given time"); } } } catch (final InterruptedException ex) { if (Log.D) Log.d(TAG, "FirstRunWizard activity didn't finish properly in the given time"); } } mDoneSignal = null; } @Given("^I enable storing First Run Wizard changes into settings$") public void i_enable_store_of_settings() { PndSettings.setUseMockMode(false); } @Given("^I start First Run Wizard$") public void i_start_first_run_wizard() { mActivity = getActivity(); setVehicleProfileSupport(false); // By default vehicle profile selection is disable in PND mDoneSignal = new CountDownLatch(1); if (mActivity != null) { if (mApplication == null) { mApplication = mActivity.getApplication(); } if (mApplication != null) { mApplication.registerActivityLifecycleCallbacks(mActivityLifecycleCallback); } } } @Given("^I see the \"(.+)\" screen$") public void i_see_screenName_screen(final String screenName) { if (SCREEN_NAME_LANGUAGE_SELECTION.equals(screenName)) { checkViewWithIdIsCompletelyDisplayed(R.id.language_selector); } else if (SCREEN_NAME_COUNTRY_SELECTION.equals(screenName)) { checkViewWithIdIsCompletelyDisplayed(R.id.country_selector); } else if (SCREEN_NAME_VEHICLE_TYPE_SELECTION.equals(screenName)) { checkViewWithIdIsCompletelyDisplayed(R.id.vehicletype_list); } else { throw new IllegalArgumentException("Screen name not recognized:" + screenName); } } @Given("^I select \"(.+)\" language$") public void i_select_languageName_language(final String languageName) { onView(withId(R.id.btnAllLanguages)).perform(click()); i_scroll_to_item_with_text("down", languageName); onView(withText(languageName)).perform(click()); } @Given("^tested activity is closed$") public void tested_activity_is_closed() { if (!mActivity.isFinishing()) { throw new IllegalArgumentException("Application is still running"); } } @Given("^I enable vehicle profile support$") public void enable_vehicle_profile_support() { setVehicleProfileSupport(true); } @Given("^I scroll (up|down) to item with text \"(.+)\"$") public void i_scroll_to_item_with_text(final String scrollDirection, final String text) { int maxScroll = 10; // Scroll until item is completely visible or maxScroll while (maxScroll-- > 0) { try { checkViewWithTextIsCompletelyDisplayedOnce(text); return; // Item is fully scrolled into view } catch (final AssertionFailedError e) { scroll(scrollDirection); } catch(final NoMatchingViewException e) { scroll(scrollDirection); } catch(final RuntimeException e) { scroll(scrollDirection); } } } @SuppressWarnings("unchecked") @Given("^I press \"(.+)\" toggle$") public void i_press_toggle(final String resourceName) { final int id = extractToggleResourceIdFromText(resourceName); onView(allOf(withId(R.id.navui_toggleButton), isDescendantOfA(withId(id)))).perform(click()); } @SuppressWarnings("unchecked") @Given("^I see \"(.+)\" toggle (enabled|disabled)$") public void i_press_toggle(final String resourceName, final String enabled) { final int id = extractToggleResourceIdFromText(resourceName); if ("enabled".equalsIgnoreCase(enabled)) { onView(allOf(withId(R.id.navui_toggleButton), isDescendantOfA(withId(id)))).check(matches(isChecked())); } else { onView(allOf(withId(R.id.navui_toggleButton), isDescendantOfA(withId(id)))).check(matches(isNotChecked())); } } @Given("^I see \"(.+)\" hazmat icon$") public void i_see_hazmat_icon(final String iconName) { if (TOGGLE_EU_EXPLOSIVE.equalsIgnoreCase(iconName)) { onView(withId(R.id.navui_vehicleProfileHazmatEuExplosiveIcon)).check(matches(isCompletelyDisplayed())); } } private int extractToggleResourceIdFromText(final String resourceName) { String resourceId = ""; if (TOGGLE_EU_EXPLOSIVE.equalsIgnoreCase(resourceName)) { resourceId = "navui_hazmatEuExplosiveToggle"; } else if (TOGGLE_EU_HARMFUL.equalsIgnoreCase(resourceName)) { resourceId = "navui_hazmatEuHarmfulToWaterToggle"; } else if (TOGGLE_EU_GENERAL.equalsIgnoreCase(resourceName)) { resourceId = "navui_hazmatEuGeneralToggle"; } return mActivity.getResources().getIdentifier(resourceId, "id", mActivity.getPackageName()); } @Given("^I enter text \"(.+)\" into input field \"(.+)\"$") public void enter_text_into_input_field(final String text, final String inputField) { final Integer inputFieldResourceId = INPUT_FIELD_MAP.get(inputField); if (inputFieldResourceId != null) { onView(withId(inputFieldResourceId.intValue())).perform(scrollTo(), clearText(), typeText(text)); } else { throw new IllegalArgumentException("Inputfield with name " + inputField + " not found!"); } } @Given("^I hide the keyboard$") public void hide_keyboard() { closeSoftKeyboard(); } @Given("^I check input field \"(.+)\" contains the text \"(.+)\"$") public void check_input_field_text(final String inputField, final String text) { final Integer inputFieldResourceId = INPUT_FIELD_MAP.get(inputField); if (inputFieldResourceId != null) { onView(withId(inputFieldResourceId.intValue())).check(matches(withText(text))); } else { throw new IllegalArgumentException("Inputfield with name " + inputField + " not found!"); } } @Given("^I check that the input field \"(.+)\" is in normal state$") public void check_input_field_state_normal(final String inputField) { final Integer inputFieldResourceId = INPUT_FIELD_MAP.get(inputField); if (inputFieldResourceId != null) { onView(withId(inputFieldResourceId.intValue())).check(matches(withNormalState())); } else { throw new IllegalArgumentException("Input field with name " + inputField + " not found!"); } } @Given("^I check that the input field \"(.+)\" is in warning state$") public void check_input_field_state_warning(final String inputField) { final Integer inputFieldResourceId = INPUT_FIELD_MAP.get(inputField); if (inputFieldResourceId != null) { onView(withId(inputFieldResourceId.intValue())).check(matches(withWarningState())); } else { throw new IllegalArgumentException("Input field with name " + inputField + " not found!"); } } @Given("^these vehicle profile values$") public void vehicle_profile_values_list(final Map<String, String> values) { mVehicleProfileValues = values; } @Given("^I enter the above values into the input fields$") public void enter_stored_values() { if (mVehicleProfileValues != null) { for (final String inputName : mVehicleProfileValues.keySet()) { enter_text_into_input_field(mVehicleProfileValues.get(inputName), inputName); } } else { throw new MissingValuesException("No vehicle profile values were set, supply them first"); } } @Given("^I enter above values into the input fields they should be in warning state$") public void check_values_for_warning() { if (mVehicleProfileValues != null) { for (final String inputName : mVehicleProfileValues.keySet()) { enter_text_into_input_field(mVehicleProfileValues.get(inputName), inputName); check_input_field_state_warning(inputName); } } else { throw new MissingValuesException("No vehicle profile values were set, supply them first"); } } @Given("^I expect all input fields to have the above values$") public void check_input_field_values() { if (mVehicleProfileValues != null) { for (final String inputName : mVehicleProfileValues.keySet()) { check_input_field_text(inputName, mVehicleProfileValues.get(inputName)); } } else { throw new MissingValuesException("No vehicle profile values were set, supply them first"); } } @Given("^there is a vehicle profile stored with the following settings:$") public void check_stored_vehicle_profile(final Map<String, String> values) { final VehicleProfile profile = mActivity.getVehicleProfile(); for (final Map.Entry<String, String> entry : values.entrySet()) { if (entry.getKey().equals("type")) { if (!profile.getVehicleType().name().equals(entry.getValue())) { throw new IllegalArgumentException("Value for "+ entry.getKey() + " does not match. Expected: "+ entry.getValue() + " Actual: "+ profile.getVehicleType().name()); } continue; } if (entry.getKey().equals("length")) { if (!profile.getLength().getValue().equals(Long.valueOf(entry.getValue()))) { throw new IllegalArgumentException("Value for "+ entry.getKey() + " does not match. Expected: "+ entry.getValue() + " Actual: "+ String.valueOf(profile.getLength().getValue())); } continue; } if (entry.getKey().equals("width")) { if (!profile.getWidth().getValue().equals(Long.valueOf(entry.getValue()))) { throw new IllegalArgumentException("Value for "+ entry.getKey() + " does not match. Expected: "+ entry.getValue() + " Actual: "+ String.valueOf(profile.getWidth().getValue())); } continue; } if (entry.getKey().equals("height")) { if (!profile.getHeight().getValue().equals(Long.valueOf(entry.getValue()))) { throw new IllegalArgumentException("Value for "+ entry.getKey() + " does not match. Expected: "+ entry.getValue() + " Actual: "+ String.valueOf(profile.getHeight().getValue())); } continue; } if (entry.getKey().equals("weight")) { if (!profile.getTotalWeight().getValue().equals(Long.valueOf(entry.getValue()))) { throw new IllegalArgumentException("Value for "+ entry.getKey() + " does not match. Expected: "+ entry.getValue() + " Actual: "+ String.valueOf(profile.getTotalWeight().getValue())); } continue; } if (entry.getKey().equals("axleweight")) { if (!profile.getAxleWeight().getValue().equals(Long.valueOf(entry.getValue()))) { throw new IllegalArgumentException("Value for "+ entry.getKey() + " does not match. Expected: "+ entry.getValue() + " Actual: "+ String.valueOf(profile.getAxleWeight().getValue())); } continue; } if (entry.getKey().equals("maxspeed")) { if (!profile.getMaxSpeed().getValue().equals(Long.valueOf(entry.getValue()))) { throw new IllegalArgumentException("Value for "+ entry.getKey() + " does not match. Expected: "+ entry.getValue() + " Actual: "+ String.valueOf(profile.getMaxSpeed().getValue())); } continue; } throw new IllegalArgumentException("Entry "+ entry.getKey() + " is not handled"); } } }