package org.restcomm.android.olympus; import android.os.Build; import android.support.test.InstrumentationRegistry; import android.support.test.espresso.NoMatchingViewException; import android.support.test.espresso.ViewInteraction; import android.support.test.espresso.matcher.BoundedMatcher; import android.support.test.internal.util.Checks; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiSelector; import android.support.v4.content.ContextCompat; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.EditText; import android.widget.TextView; // REST client stuff import com.loopj.android.http.*; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.hamcrest.TypeSafeMatcher; import static android.support.test.espresso.Espresso.onData; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.AllOf.allOf; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.net.URI; import cz.msebera.android.httpclient.Header; import cz.msebera.android.httpclient.HttpResponse; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.*; import static android.support.test.espresso.assertion.ViewAssertions.*; import static android.support.test.espresso.matcher.ViewMatchers.*; @LargeTest @RunWith(AndroidJUnit4.class) public class BasicUITests { private static final String TAG = "BasicUITests"; final String smsText = "Hello there"; @Rule public ActivityTestRule<SigninActivity> mActivityTestRule = new ActivityTestRule<>(SigninActivity.class); @Before public void signinIfNeeded() { // Signin if not already signed in ViewInteraction signinEditText = onView(withId(R.id.signin_username)); try { signinEditText.check(matches(isDisplayed())); } catch (NoMatchingViewException e) { signinEditText = null; } // we generally try to avoid conditional logic in test cases to avoid getting overly complex, but this is an exception, at least for now // sign-in screen screen is in the hierarchy only when running App for the first time so we need to check and keep sign-in logic out if not showing if (signinEditText != null) { signinEditText.perform(scrollTo(), replaceText(BuildConfig.TEST_RESTCOMM_LOGIN), closeSoftKeyboard()); ViewInteraction appCompatEditText2 = onView(withId(R.id.signin_password)); appCompatEditText2.perform(scrollTo(), replaceText(BuildConfig.TEST_RESTCOMM_PASSWORD), closeSoftKeyboard()); onView(withId(R.id.signin_domain)).perform(scrollTo(), replaceText("cloud.restcomm.com"), closeSoftKeyboard()); onView( allOf( withId(R.id.signin_button), withText("Sign in"), withParent( allOf( withId(R.id.email_login_form), withParent(withId(R.id.login_form)) ) ) ) ).perform(scrollTo(), click()); } } /* @After public void afterAction() { } */ @Test // Test on calling a Restcomm number, using 1235 public void callRCNumber() { // Wait until we are REGISTERED. Notice that we could use IdlingResource to wait for specific event and avoid sleeping but I'd like to also // use specific timer of 5 seconds and break otherwise as it would signify a huge delay from the server side that we want to catch // Also, IdlingResources don't seem to allow to specify timeout for specific check() instance that would help here (for more info on that // check https://medium.com/azimolabs/wait-for-it-idlingresource-and-conditionwatcher-602055f32356). We could probably use IdlingPolicies // but still seems a bit messy. try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } onView( allOf( childAtPosition(withId(android.R.id.list), 1), isDisplayed() ) ).perform(click()); onView( allOf( withId(R.id.action_audio_call), withContentDescription("Settings"), isDisplayed() ) ).perform(click()); // handle the permissions dialog if needed allowPermissionsIfNeeded(); // If after 10 seconds we 're still not connected then we have an issue. try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } onView(withId(R.id.label_status)).check(matches(withText("Connected"))); onView( allOf( withId(R.id.button_hangup), withParent(allOf(withId(R.id.layout_video_call), withParent(withId(android.R.id.content)))), isDisplayed() ) ).perform(click()); /* Seems that we 're ok without that still. Lets keep just in case we need it in the future // Added a sleep statement to match the app's execution delay. try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } */ pressBack(); } @Test // Create contact +30213001 that belongs to SMS App, send text message and retrieve answer public void textRCNumber() { //final String smsNumber = "+1235"; // Wait until we are REGISTERED. Notice that we could use IdlingResource to wait for specific event and avoid sleeping but I'd like to also // use specific timer of 5 seconds and break otherwise as it would signify a huge delay from the server side that we want to catch // Also, IdlingResources don't seem to allow to specify timeout for specific check() instance that would help here (for more info on that // check https://medium.com/azimolabs/wait-for-it-idlingresource-and-conditionwatcher-602055f32356). We could probably use IdlingPolicies // but still seems a bit messy. try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // Add contact /* ViewInteraction floatingActionButton = onView( Matchers.allOf(withId(R.id.imageButton_add), withParent(Matchers.allOf(withId(R.id.coordinatorLayout), withParent(withId(android.R.id.content)))), isDisplayed())); floatingActionButton.perform(click()); ViewInteraction appCompatEditText3 = onView( Matchers.allOf(withId(R.id.editText_username), isDisplayed())); appCompatEditText3.perform(replaceText("Sms Echo"), closeSoftKeyboard()); ViewInteraction appCompatEditText4 = onView( Matchers.allOf(withId(R.id.editText_sipuri), isDisplayed())); appCompatEditText4.perform(replaceText(smsNumber), closeSoftKeyboard()); ViewInteraction appCompatButton2 = onView( Matchers.allOf(withId(android.R.id.button1), withText("Add"), withParent(Matchers.allOf(withClassName(is("com.android.internal.widget.ButtonBarLayout")), withParent(withClassName(is("android.widget.LinearLayout"))))), isDisplayed())); appCompatButton2.perform(click()); */ ViewInteraction relativeLayout = onView( Matchers.allOf(childAtPosition( withId(android.R.id.list), 1), isDisplayed())); relativeLayout.perform(click()); ViewInteraction appCompatEditText5 = onView( Matchers.allOf(withId(R.id.text_message), isDisplayed())); appCompatEditText5.perform(replaceText("Hello there!"), closeSoftKeyboard()); ViewInteraction appCompatImageButton = onView( Matchers.allOf(withId(R.id.button_send), isDisplayed())); appCompatImageButton.perform(click()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } onView(Matchers.allOf(withId(R.id.message_text), withText("Hello there!"), isDisplayed())).check( matches( withTextColor(mActivityTestRule.getActivity().getResources().getColor(R.color.colorTextSecondary)) ) ); // Verify that the echo message was received properly // onView(withId(R.id.label_status)).check(matches(withText("Connected"))); //id/message_text /* onView(Matchers.allOf(withId(R.id.message_username), withText(smsNumber), isDisplayed())).check(matches(withText(smsNumber))); pressBack(); onView( allOf( childAtPosition(withId(android.R.id.list), 3), isDisplayed() ) ).perform(longClick()); ViewInteraction appCompatTextView = onView( Matchers.allOf(withId(android.R.id.title), withText("Remove Contact"), isDisplayed())); appCompatTextView.perform(click()); */ } @Test public void triggerIncomingCall() { // Wait until we are REGISTERED. Notice that we could use IdlingResource to wait for specific event and avoid sleeping but I'd like to also // use specific timer of 5 seconds and break otherwise as it would signify a huge delay from the server side that we want to catch // Also, IdlingResources don't seem to allow to specify timeout for specific check() instance that would help here (for more info on that // check https://medium.com/azimolabs/wait-for-it-idlingresource-and-conditionwatcher-602055f32356). We could probably use IdlingPolicies // but still seems a bit messy. try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } /* // TODO: TestFairy prompt for update ViewInteraction appCompatButton2 = onView( Matchers.allOf(withId(android.R.id.button2), withText("No"), withParent(Matchers.allOf(withClassName(is("com.android.internal.widget.ButtonBarLayout")), withParent(withClassName(is("android.widget.LinearLayout"))))), isDisplayed())); appCompatButton2.perform(click()); */ doRestRequest("call"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } ViewInteraction appCompatImageButton = onView( Matchers.allOf(withId(R.id.button_answer_audio), withParent(Matchers.allOf(withId(R.id.layout_video_call), withParent(withId(android.R.id.content)))), isDisplayed())); appCompatImageButton.perform(click()); // handle the permissions dialog if needed allowPermissionsIfNeeded(); // Added a sleep statement to match the app's execution delay. // The recommended way to handle such scenarios is to use Espresso idling resources: // https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/index.html try { Thread.sleep(15000); } catch (InterruptedException e) { e.printStackTrace(); } onView(withId(R.id.label_status)).check(matches(withText("Connected"))); ViewInteraction appCompatImageButton2 = onView( Matchers.allOf(withId(R.id.button_hangup), withParent(Matchers.allOf(withId(R.id.layout_video_call), withParent(withId(android.R.id.content)))), isDisplayed())); appCompatImageButton2.perform(click()); } @Test public void triggerIncomingMessage() { // Wait until we are REGISTERED. Notice that we could use IdlingResource to wait for specific event and avoid sleeping but I'd like to also // use specific timer of 5 seconds and break otherwise as it would signify a huge delay from the server side that we want to catch // Also, IdlingResources don't seem to allow to specify timeout for specific check() instance that would help here (for more info on that // check https://medium.com/azimolabs/wait-for-it-idlingresource-and-conditionwatcher-602055f32356). We could probably use IdlingPolicies // but still seems a bit messy. try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } /* // TODO: TestFairy prompt for update ViewInteraction appCompatButton2 = onView( Matchers.allOf(withId(android.R.id.button2), withText("No"), withParent(Matchers.allOf(withClassName(is("com.android.internal.widget.ButtonBarLayout")), withParent(withClassName(is("android.widget.LinearLayout"))))), isDisplayed())); appCompatButton2.perform(click()); */ doRestRequest("message"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // Verify that the incoming message was received properly // IMPORTANT: we assume that after the message arrives there's only one message in the ListView. If more are there the assertion will fail. onView(Matchers.allOf(withId(R.id.message_text), isDisplayed())).check(matches(withText(smsText))); } // // Helper methods // // Custom matcher to test if color of TextView is correct public static Matcher<View> withTextColor(final int color) { Checks.checkNotNull(color); return new BoundedMatcher<View, TextView>(TextView.class) { @Override public boolean matchesSafely(TextView textView) { return color == textView.getCurrentTextColor(); } @Override public void describeTo(Description description) { description.appendText("Expected Color: " + color); } }; } private static Matcher<View> childAtPosition( final Matcher<View> parentMatcher, final int position) { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("Child at position " + position + " in parent "); parentMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { ViewParent parent = view.getParent(); return parent instanceof ViewGroup && parentMatcher.matches(parent) && view.equals(((ViewGroup) parent).getChildAt(position)); } }; } public void doRestRequest(String type) { Log.i(TAG, "------ doRestRequest()"); AsyncHttpClient client = new AsyncHttpClient(); // curl -X POST // https://ACae6e420f425248d6a26948c17a9e2acf:77f8c12cc7b8f8423e5c38b035249166@127.0.0.1:8080 // /restcomm/2012-04-24/Accounts/ACae6e420f425248d6a26948c17a9e2acf // /SMS/Messages -d "To=%2B13216549878" -d "From=%2B19876543212" -d "Body=This is a test from RestComm" RequestParams params = new RequestParams(); params.put("From", "alice"); String url = ""; if (type.equals("call")) { params.put("To", "client:" + BuildConfig.TEST_RESTCOMM_LOGIN); params.put("Url", "https://cloud.restcomm.com/restcomm/demos/hello-world.xml"); url = "https://" + BuildConfig.TEST_RESTCOMM_ACCOUNT_SID + ":" + BuildConfig.TEST_RESTCOMM_AUTH_TOKEN + "@cloud.restcomm.com/restcomm/2012-04-24/Accounts/" + BuildConfig.TEST_RESTCOMM_ACCOUNT_SID + "/Calls.json"; } else if (type.equals("message")) { // due to a bug in Restcomm (https://github.com/RestComm/Restcomm-Connect/issues/2108) To needs to have the 'client:' part removed params.put("To", BuildConfig.TEST_RESTCOMM_LOGIN); params.put("Body", smsText); url = "https://" + BuildConfig.TEST_RESTCOMM_ACCOUNT_SID + ":" + BuildConfig.TEST_RESTCOMM_AUTH_TOKEN + "@cloud.restcomm.com/restcomm/2012-04-24/Accounts/" + BuildConfig.TEST_RESTCOMM_ACCOUNT_SID + "/SMS/Messages"; } client.post(url, params, new ResponseHandlerInterface() { @Override public void sendResponseMessage(HttpResponse httpResponse) throws IOException { } @Override public void sendStartMessage() { } @Override public void sendFinishMessage() { } @Override public void sendProgressMessage(long l, long l1) { } @Override public void sendCancelMessage() { } @Override public void sendSuccessMessage(int i, Header[] headers, byte[] bytes) { Log.i(TAG, "------ request was handled successfully"); } @Override public void sendFailureMessage(int i, Header[] headers, byte[] bytes, Throwable throwable) { } @Override public void sendRetryMessage(int i) { } @Override public URI getRequestURI() { return null; } @Override public void setRequestURI(URI uri) { } @Override public Header[] getRequestHeaders() { return new Header[0]; } @Override public void setRequestHeaders(Header[] headers) { } @Override public boolean getUseSynchronousMode() { return false; } @Override public void setUseSynchronousMode(boolean b) { } @Override public boolean getUsePoolThread() { return false; } @Override public void setUsePoolThread(boolean b) { } @Override public void onPreProcessResponse(ResponseHandlerInterface responseHandlerInterface, HttpResponse httpResponse) { Log.i(TAG, "------ response is about to be processed by the system"); } @Override public void onPostProcessResponse(ResponseHandlerInterface responseHandlerInterface, HttpResponse httpResponse) { Log.i(TAG, "------ request has been fully sent, handled and finished"); } @Override public Object getTag() { return null; } @Override public void setTag(Object o) { } }); } private void allowPermissionsIfNeeded() { if (Build.VERSION.SDK_INT >= 23) { // Initialize UiDevice instance UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); // notice that we typically receive 2-3 permission prompts, hence the loop while (true) { UiObject allowPermissions = uiDevice.findObject(new UiSelector().text("Allow")); if (allowPermissions.exists()) { try { allowPermissions.click(); } catch (UiObjectNotFoundException e) { e.printStackTrace(); Log.e(TAG, "There is no permissions dialog to interact with "); } } else { break; } } } } }