Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.wordpress.android.e2e;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.wordpress.android.R;
import org.wordpress.android.e2e.flows.LoginFlow;
import org.wordpress.android.e2e.pages.ContactSupportScreen;
import org.wordpress.android.support.BaseTest;

import static org.wordpress.android.BuildConfig.E2E_WP_COM_USER_EMAIL;
import static org.wordpress.android.support.WPSupportUtils.pressBackUntilElementIsDisplayed;

public class ContactUsTests extends BaseTest {
@Before
public void setUp() {
logoutIfNecessary();
}

@After
public void tearDown() {
pressBackUntilElementIsDisplayed(R.id.continue_with_wpcom_button);
}

@Test
public void sendButtonEnabledWhenTextIsEntered() {
try {
new LoginFlow()
.chooseContinueWithWpCom()
.tapHelp()
.assertHelpAndSupportScreenLoaded()
.openContactUs()
.assertContactSupportScreenLoaded()
.assertSendButtonDisabled()
.setMessageText("Hello")
.assertSendButtonEnabled()
.setMessageText("")
.assertSendButtonDisabled();
} finally {
new ContactSupportScreen().goBackAndDeleteUnsentMessageIfNeeded();
}
Comment on lines +40 to +42
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A kind of local teardown, in order to not slow down helpCanBeOpenedWhileEnteringEmail and helpCanBeOpenedWhileEnteringPassword which do not get to ContactSupport screen, so there's no possibility of unsent message.

}

@Ignore("As long as CI does not use gradle.properties from MobileSecrets")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As can be seen from the number of commits in this PR starting with CI fail fix:..., I spent quite a while figuring out why the tests that involve Contact Support screen failed on CI but never failed the same way locally. At some point (thanks to video recording in Firebase) I saw that the screen on CI:

Screenshot 2021-12-23 at 19 58 29

Looks different from the same screen on my local Emulator:

Screenshot 2021-12-23 at 19 52 52

After blaming the Emulator/Android differences for some time, I realized that CI uses the default gradle.properties, which makes the screen look like differently (and the message cannot be sent, hence the disabled test).

Since I automated this test, I decided to keep it for the case if Mobile Secrets will be used on CI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case we have this test running in CI are we going to be creating real Zendesk tickets? I'm afraid it can get real noisy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have this suspicion too. This test is disabled for now, so it's not an urgent matter, but I think we can contact the HE team, and ask them if there's a painless way for them to filter out certain messages, so that they wouldn't be bothered with them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it turns out to be the case that we cannot prevent the creation of real tickets, and idea could be to use the Zendesk APIs to find the tickets created by the UI tests and delete them.

@Test
public void messageCanBeSent() {
String userMessageText = "Please ignore, this is an automated test.";
String automatedReplyText = "Mobile support will respond as soon as possible, "
+ "generally within 48-96 hours. "
+ "Please reply with your site address (URL) "
+ "and any additional details we should know.";
Comment on lines +49 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have this test failing since the message have slightly changed due to holidays. We might want to do a partial check as the two last lines remained the same.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. So far, I'm not sure if it makes sense to maintain this test (or even to keep it) - I probably firstly need to ask if Zendesk connection is ever planned on CI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have it connected for WPiOS because it was not possible to even check if the screen had been loaded. From what I can recall it is possible but not desirable. Given it is an identified critical flow I'd ask @jkmassel to weigh in. =)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also use a less sophisticated check and ensure we get any text in R.id.request_system_message_text. 🤷‍♂️


try {
new LoginFlow()
.chooseContinueWithWpCom()
.tapHelp()
.openContactUs()
.setMessageText(userMessageText)
.tapSendButton()
.assertUserMessageDelivered(userMessageText)
.assertSystemMessageReceived(automatedReplyText);
} finally {
new ContactSupportScreen().goBackAndDeleteUnsentMessageIfNeeded();
}
}

@Test
public void helpCanBeOpenedWhileEnteringEmail() {
new LoginFlow()
.chooseContinueWithWpCom()
.tapHelp()
.assertHelpAndSupportScreenLoaded();
}

@Test
public void helpCanBeOpenedWhileEnteringPassword() {
new LoginFlow()
.chooseContinueWithWpCom()
.enterEmailAddress(E2E_WP_COM_USER_EMAIL)
.tapHelp()
.assertHelpAndSupportScreenLoaded();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.hamcrest.Matchers;
import org.wordpress.android.BuildConfig;
import org.wordpress.android.R;
import org.wordpress.android.e2e.pages.HelpAndSupportScreen;
import org.wordpress.android.ui.accounts.LoginMagicLinkInterceptActivity;

import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
Expand Down Expand Up @@ -128,4 +129,9 @@ public LoginFlow enterSiteAddress(String siteAddress) {
clickOn(R.id.bottom_button);
return this;
}

public HelpAndSupportScreen tapHelp() {
clickOn(onView(withId(R.id.help)));
return new HelpAndSupportScreen();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.wordpress.android.e2e.pages;

import androidx.test.espresso.Espresso;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.ViewActions;

import org.wordpress.android.R;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.not;
import static org.wordpress.android.support.WPSupportUtils.populateTextField;
import static org.wordpress.android.support.WPSupportUtils.sleep;
import static org.wordpress.android.support.WPSupportUtils.waitForElementToBeDisplayed;
import static org.wordpress.android.support.WPSupportUtils.waitForElementToBeDisplayedWithoutFailure;

public class ContactSupportScreen {
// "Contact Support" screen looks differently depending on
// "gradle.properties" content (default or from Mobile Secrets).
// But the elements tree always contains all elements, some are
// just hidden. Locators below attempt to support both variants.
static ViewInteraction textInput = onView(allOf(
isCompletelyDisplayed(),
anyOf(
withId(R.id.message_composer_input_text),
withId(R.id.request_message_field)
)
));
Comment on lines +29 to +35
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This translates to: find an element that is both completely displayed and has either R.id.message_composer_input_text or R.id.request_message_field ID.

In fact, both of these elements are present when the screen is opened, but only one is visible.

static ViewInteraction sendButton = onView(allOf(
isCompletelyDisplayed(),
anyOf(
withId(R.id.message_composer_send_btn),
withId(R.id.request_conversations_disabled_menu_ic_send)
)
));

// Actions:
public ContactSupportScreen tapSendButton() {
sendButton.perform(ViewActions.click());
return this;
}

public ContactSupportScreen setMessageText(String text) {
populateTextField(textInput, text);
// This sleep serves only one purpose: allowing human to notice
// that text was really entered. Especially matters when watching
// low-fps test video recordings from CI.
sleep();
return this;
}

public HelpAndSupportScreen goBackAndDeleteUnsentMessageIfNeeded() {
Espresso.pressBack();

ViewInteraction unsentMessageAlert = onView(
withText("Going back will delete your message. "
+ "Are you sure you want to delete it?"
));

if (waitForElementToBeDisplayedWithoutFailure(unsentMessageAlert)) {
onView(withText("Delete"))
.perform(ViewActions.click());
}

return new HelpAndSupportScreen();
}

// Assertions:
public ContactSupportScreen assertContactSupportScreenLoaded() {
textInput.check(matches(isCompletelyDisplayed()));
sendButton.check(matches(isCompletelyDisplayed()));
return this;
}

public ContactSupportScreen assertSendButtonDisabled() {
sendButton.check(matches(not(isEnabled())));
return this;
}

public ContactSupportScreen assertSendButtonEnabled() {
sendButton.check(matches(isEnabled()));
return this;
}

public ContactSupportScreen assertUserMessageDelivered(String messageText) {
ViewInteraction userMessageContainer = onView(allOf(
withId(R.id.request_user_message_container),
hasDescendant(allOf(
withId(R.id.request_user_message_text),
withText(messageText)
)),
hasDescendant(allOf(
withId(R.id.request_user_message_status),
withText("Delivered")
))
));

waitForElementToBeDisplayed(userMessageContainer);
userMessageContainer.check(matches(isCompletelyDisplayed()));
return this;
}

public ContactSupportScreen assertSystemMessageReceived(String messageText) {
ViewInteraction systemResponseBubble = onView(allOf(
withId(R.id.request_system_message_text),
withText(messageText)));

waitForElementToBeDisplayed(systemResponseBubble);
systemResponseBubble.check(matches(isCompletelyDisplayed()));
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.wordpress.android.e2e.pages;

import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.ViewActions;

import org.wordpress.android.R;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.wordpress.android.support.WPSupportUtils.populateTextField;
import static org.hamcrest.Matchers.anyOf;
import static org.wordpress.android.support.WPSupportUtils.waitForElementToBeDisplayedWithoutFailure;


public class HelpAndSupportScreen {
static ViewInteraction contactUsButton = onView(withId(R.id.contact_us_button));
static ViewInteraction faqButton = onView(withId(R.id.faq_button));
static ViewInteraction myTicketsButton = onView(withId(R.id.my_tickets_button));
static ViewInteraction applicationLogButton = onView(withId(R.id.application_log_button));
static ViewInteraction applicationVersionText = onView(withId(R.id.applicationVersion));
static ViewInteraction emailAddressText = onView(withId(R.id.contactEmailAddress));


public HelpAndSupportScreen assertHelpAndSupportScreenLoaded() {
contactUsButton.check(matches(isCompletelyDisplayed()));
faqButton.check(matches(isCompletelyDisplayed()));
myTicketsButton.check(matches(isCompletelyDisplayed()));
applicationLogButton.check(matches(isCompletelyDisplayed()));
applicationVersionText.check(matches(isCompletelyDisplayed()));
emailAddressText.check(matches(isCompletelyDisplayed()));
return this;
}

public ContactSupportScreen openContactUs() {
contactUsButton.perform(ViewActions.click());
setEmailIfNeeded("WPcomTest@test.com", "TestUser");
return new ContactSupportScreen();
}

public void setEmailIfNeeded(String emailAddress, String userName) {
ViewInteraction emailInput = onView(withId(R.id.support_identity_input_dialog_email_edit_text));

if (!waitForElementToBeDisplayedWithoutFailure(emailInput)) {
return;
}

populateTextField(emailInput, emailAddress);
ViewInteraction nameInput = onView(withId(R.id.support_identity_input_dialog_name_edit_text));
populateTextField(nameInput, userName);

onView(anyOf(
withText(android.R.string.ok),
withId(android.R.id.button1)
))
Comment on lines +54 to +57
Copy link
Contributor Author

@pachlava pachlava Dec 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the locator for OK button in this dialog:

Screenshot 2021-12-23 at 21 35 35

I used something like onView(withText("OK")) initially, and this worked locally, but never on CI. Thanks to this discussion, I found out that "OK" button might be presented differently by different Android versions, while android.R.id.button1 always stands for the "positive" option in the dialog.

.perform(ViewActions.click());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,20 @@ public Boolean get() {
return isElementDisplayed(elementID);
}

public static boolean waitForElementToBeDisplayedWithoutFailure(final ViewInteraction element) {
try {
waitForConditionToBeTrueWithoutFailure(new Supplier<Boolean>() {
@Override
public Boolean get() {
return isElementDisplayed(element);
}
});
} catch (Exception e) {
// ignore the failure
}
return isElementDisplayed(element);
}

Comment on lines +478 to +491
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do the same thing in my PR. The only difference was that I made waitForElementToBeDisplayedWithoutFailure(final Integer elementID) call waitForElementToBeDisplayedWithoutFailure(final ViewInteraction element) to avoid a bit of code duplication.
Nothing to do here.

public static void waitForConditionToBeTrue(Supplier<Boolean> supplier) {
if (supplier.get()) {
return;
Expand Down