textAreas = getRubricTextareas(questionNum, i + 2);
fillTextBox(textAreas.get(0), subQuestions.get(i));
for (int j = 0; j < numChoices; j++) {
+ fillTextBox(textAreas.get(j + 1), descriptions.get(i).get(j));
if (descriptions.get(i).get(j).isEmpty()) {
- // using clear does not work here
- textAreas.get(j + 1).sendKeys(Keys.chord(Keys.CONTROL, "a"));
- textAreas.get(j + 1).sendKeys(Keys.DELETE);
- } else {
- fillTextBox(textAreas.get(j + 1), descriptions.get(i).get(j));
+ // using clear does not send the required event
+ // as a workaround, after clearing without event, enter a random character and delete it
+ textAreas.get(j + 1).sendKeys("a");
+ textAreas.get(j + 1).sendKeys(Keys.BACK_SPACE);
}
}
}
diff --git a/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java b/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java
index 46c4c02e4ac..43a9bff6cc6 100644
--- a/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java
+++ b/src/e2e/java/teammates/e2e/pageobjects/InstructorHomePage.java
@@ -39,7 +39,7 @@ protected boolean containsExpectedPageContents() {
public InstructorSearchPage searchKeyword(String keyword) {
fillTextBox(searchBar, keyword);
click(searchButton);
- waitForPageToLoad(true);
+ waitUntilAnimationFinish();
return changePageType(InstructorSearchPage.class);
}
diff --git a/src/e2e/java/teammates/e2e/pageobjects/InstructorSearchPage.java b/src/e2e/java/teammates/e2e/pageobjects/InstructorSearchPage.java
index f6d71608b76..7953dbbacfd 100644
--- a/src/e2e/java/teammates/e2e/pageobjects/InstructorSearchPage.java
+++ b/src/e2e/java/teammates/e2e/pageobjects/InstructorSearchPage.java
@@ -60,8 +60,7 @@ public void search(boolean searchForStudents, boolean searchForComments, String
searchKeyword.clear();
searchKeyword.sendKeys(searchTerm);
click(searchButton);
- waitForPageToLoad(true);
- waitUntilAnimationFinish();
+ waitForLoadingElement();
} else {
verifyUnclickable(searchButton);
}
diff --git a/src/e2e/java/teammates/e2e/util/EmailAccount.java b/src/e2e/java/teammates/e2e/util/EmailAccount.java
index 58808268787..bde2b9abbb7 100644
--- a/src/e2e/java/teammates/e2e/util/EmailAccount.java
+++ b/src/e2e/java/teammates/e2e/util/EmailAccount.java
@@ -6,11 +6,9 @@
import java.util.Collections;
import java.util.List;
import java.util.Properties;
+import java.util.concurrent.TimeUnit;
-import javax.mail.BodyPart;
import javax.mail.MessagingException;
-import javax.mail.Multipart;
-import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
@@ -21,8 +19,6 @@
import com.google.api.services.gmail.model.ModifyMessageRequest;
import com.google.common.io.BaseEncoding;
-import teammates.common.util.EmailType;
-
/**
* Provides an access to real Gmail inbox used for testing.
*
@@ -47,10 +43,12 @@ public void getUserAuthenticated() throws IOException {
// assume user is authenticated before
service = new GmailServiceMaker(username).makeGmailService();
- while (true) {
+ int retryLimit = 5;
+ while (retryLimit > 0) {
try {
+ retryLimit--;
// touch one API endpoint to check authentication
- getListOfUnreadEmailOfUser();
+ getListOfUnreadEmailFromSender(1L, "");
break;
} catch (HttpResponseException e) {
if (e.getStatusCode() == HttpStatusCodes.STATUS_CODE_FORBIDDEN
@@ -67,53 +65,24 @@ public void getUserAuthenticated() throws IOException {
}
/**
- * Retrieves the registration key among the unread emails
- * with {@code courseId} and {@code courseName} sent to the Gmail inbox.
- *
- * After retrieving, marks the email as read.
- *
- *
If multiple emails of the same course are in the inbox, return the registration key presented in one of them.
- *
- * @return registration key (null if cannot be found).
+ * Returns true if unread mail that arrived in the past minute contains mail with the specified subject.
*/
- public String getRegistrationKeyFromUnreadEmails(String courseName, String courseId)
+ public boolean isRecentEmailWithSubjectPresent(String subject, String senderEmail)
throws IOException, MessagingException {
- List messageStubs = getListOfUnreadEmailOfUser();
+ List messageStubs = getListOfUnreadEmailFromSender(10L, senderEmail);
for (Message messageStub : messageStubs) {
Message message = service.users().messages().get(username, messageStub.getId()).setFormat("raw")
.execute();
MimeMessage email = convertFromMessageToMimeMessage(message);
+ boolean isSubjectEqual = email.getSubject().equals(subject);
+ boolean isSentWithinLastMin =
+ message.getInternalDate() > System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(1);
- if (isStudentCourseJoinRegistrationEmail(email, courseName, courseId)) {
- String body = getTextFromEmail(email);
-
+ if (isSubjectEqual && isSentWithinLastMin) {
markMessageAsRead(messageStub);
-
- return getKey(body);
- }
- }
-
- return null;
- }
-
- /**
- * Returns true if unread mail contains mail with the specified subject.
- */
- public boolean isEmailWithSubjectPresent(String subject)
- throws IOException, MessagingException {
-
- List messageStubs = getListOfUnreadEmailOfUser();
-
- for (Message messageStub : messageStubs) {
- Message message = service.users().messages().get(username, messageStub.getId()).setFormat("raw")
- .execute();
-
- MimeMessage email = convertFromMessageToMimeMessage(message);
-
- if (email.getSubject().equals(subject)) {
return true;
}
}
@@ -121,21 +90,13 @@ public boolean isEmailWithSubjectPresent(String subject)
}
/**
- * Marks all unread emails in the user's inbox as read.
+ * Returns a list of up to maxResults number of unread emails from the sender.
+ * Returns an empty list if there is no unread email from sender.
*/
- public void markAllUnreadEmailAsRead() throws IOException {
- List messageStubs = getListOfUnreadEmailOfUser();
-
- for (Message messageStub : messageStubs) {
- markMessageAsRead(messageStub);
- }
- }
-
- /**
- * Returns an empty list if there is no unread email of the user.
- */
- private List getListOfUnreadEmailOfUser() throws IOException {
- List messageStubs = service.users().messages().list(username).setQ("is:UNREAD").execute().getMessages();
+ private List getListOfUnreadEmailFromSender(long maxResults, String senderEmail) throws IOException {
+ List messageStubs = service.users().messages().list(username)
+ .setQ("is:UNREAD from:" + senderEmail).setMaxResults(maxResults).execute()
+ .getMessages();
return messageStubs == null ? new ArrayList<>() : messageStubs;
}
@@ -157,126 +118,6 @@ private MimeMessage convertFromMessageToMimeMessage(Message message) throws Mess
return new MimeMessage(session, new ByteArrayInputStream(emailBytes));
}
- private boolean isStudentCourseJoinRegistrationEmail(MimeMessage message, String courseName, String courseId)
- throws MessagingException {
- String subject = message.getSubject();
- return subject != null && subject.equals(String.format(EmailType.STUDENT_COURSE_JOIN.getSubject(),
- courseName, courseId));
- }
-
- /**
- * Gets the email message body as text.
- */
- private String getTextFromEmail(MimeMessage email) throws MessagingException, IOException {
- if (email.isMimeType("text/*")) {
- return (String) email.getContent();
- } else {
- return getTextFromPart(email);
- }
- }
-
- private String getTextFromPart(Part part) throws MessagingException, IOException {
- if (part.isMimeType("multipart/alternative")) {
- return getTextFromMultiPartAlternative((Multipart) part.getContent());
- } else if (part.isMimeType("multipart/digest")) {
- return getTextFromMultiPartDigest((Multipart) part.getContent());
- } else if (mimeTypeCanBeHandledAsMultiPartMixed(part)) {
- return getTextHandledAsMultiPartMixed(part);
- }
-
- return null;
- }
-
- /**
- * Returns if the part can be handled as multipart/mixed.
- */
- private boolean mimeTypeCanBeHandledAsMultiPartMixed(Part part) throws MessagingException {
- return part.isMimeType("multipart/mixed") || part.isMimeType("multipart/parallel")
- || part.isMimeType("message/rfc822")
- // as per the RFC2046 specification, other multipart subtypes are recognized as multipart/mixed
- || part.isMimeType("multipart/*");
- }
-
- private String getTextFromMultiPartDigest(Multipart multipart) throws IOException, MessagingException {
- StringBuilder textBuilder = new StringBuilder();
- for (int i = 0; i < multipart.getCount(); i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
- if (bodyPart.isMimeType("message/rfc822")) {
- String text = getTextFromPart(bodyPart);
- if (text != null) {
- textBuilder.append(text);
- }
- }
- }
- String text = textBuilder.toString();
-
- if (text.isEmpty()) {
- return null;
- }
-
- return text;
- }
-
- /**
- * Returns the text from multipart/alternative, the type of text returned follows the preference of the sending agent.
- */
- private String getTextFromMultiPartAlternative(Multipart multipart) throws IOException, MessagingException {
- // search in reverse order as a multipart/alternative should have their most preferred format last
- for (int i = multipart.getCount() - 1; i >= 0; i--) {
- BodyPart bodyPart = multipart.getBodyPart(i);
-
- if (bodyPart.isMimeType("text/html")) {
- return (String) bodyPart.getContent();
- } else if (bodyPart.isMimeType("text/plain")) {
- // Since we are looking in reverse order, if we did not encounter a text/html first we can return the plain
- // text because that is the best preferred format that we understand. If a text/html comes along later it
- // means the agent sending the email did not set the html text as preferable or did not set their preferred
- // order correctly, and in that case we do not handle that.
- return (String) bodyPart.getContent();
- } else if (bodyPart.isMimeType("multipart/*") || bodyPart.isMimeType("message/rfc822")) {
- String text = getTextFromPart(bodyPart);
- if (text != null) {
- return text;
- }
- }
- }
- // we do not know how to handle the text in the multipart or there is no text
- return null;
- }
-
- private String getTextHandledAsMultiPartMixed(Part part) throws IOException, MessagingException {
- return getTextFromMultiPartMixed((Multipart) part.getContent());
- }
-
- private String getTextFromMultiPartMixed(Multipart multipart) throws IOException, MessagingException {
- StringBuilder textBuilder = new StringBuilder();
- for (int i = 0; i < multipart.getCount(); i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
- if (bodyPart.isMimeType("text/*")) {
- textBuilder.append((String) bodyPart.getContent());
- } else if (bodyPart.isMimeType("multipart/*")) {
- String text = getTextFromPart(bodyPart);
- if (text != null) {
- textBuilder.append(text);
- }
- }
- }
- String text = textBuilder.toString();
-
- if (text.isEmpty()) {
- return null;
- }
-
- return text;
- }
-
- private String getKey(String body) {
- String key = body.substring(
- body.indexOf("key=") + "key=".length(),
- body.indexOf("studentemail=") - 1); //*If prompted to log in
- return key.trim();
- }
-
public String getUsername() {
return username;
}
diff --git a/src/e2e/java/teammates/e2e/util/EmailAccountTest.java b/src/e2e/java/teammates/e2e/util/EmailAccountTest.java
new file mode 100644
index 00000000000..154699725c7
--- /dev/null
+++ b/src/e2e/java/teammates/e2e/util/EmailAccountTest.java
@@ -0,0 +1,19 @@
+package teammates.e2e.util;
+
+import org.testng.annotations.Test;
+
+/**
+ * Checks that the email account is ready for testing against staging/production server.
+ */
+public final class EmailAccountTest {
+
+ @Test
+ public void checkEmailAccount() throws Exception {
+ if (TestProperties.isDevServer()) {
+ // Access to actual email account is not necessary for dev server testing
+ return;
+ }
+ new EmailAccount(TestProperties.TEST_EMAIL).getUserAuthenticated();
+ }
+
+}
diff --git a/src/e2e/java/teammates/e2e/util/TestProperties.java b/src/e2e/java/teammates/e2e/util/TestProperties.java
index 44c7defabb8..4d51dfaddc9 100644
--- a/src/e2e/java/teammates/e2e/util/TestProperties.java
+++ b/src/e2e/java/teammates/e2e/util/TestProperties.java
@@ -25,6 +25,9 @@ public final class TestProperties {
/** The email address used for testing that emails are sent by the system. */
public static final String TEST_EMAIL;
+ /** The email address used by the system the send emails. */
+ public static final String TEST_SENDER_EMAIL;
+
/** The value of "test.csrf.key" in test.properties file. */
public static final String CSRF_KEY;
@@ -62,6 +65,9 @@ public final class TestProperties {
/** The value of "test.persistence.timeout" in test.properties file. */
public static final int PERSISTENCE_RETRY_PERIOD_IN_S;
+ /** The flag to indicate whether emails sent should be verified. */
+ public static final boolean INCLUDE_EMAIL_VERIFICATION;
+
/** The directory where credentials used in Gmail API are stored. */
static final String TEST_GMAIL_API_FOLDER = "src/e2e/resources/gmail-api";
@@ -75,6 +81,7 @@ public final class TestProperties {
TEAMMATES_URL = UrlExtension.trimTrailingSlash(prop.getProperty("test.app.url"));
TEST_EMAIL = prop.getProperty("test.email");
+ TEST_SENDER_EMAIL = prop.getProperty("test.senderemail");
CSRF_KEY = prop.getProperty("test.csrf.key");
BACKDOOR_KEY = prop.getProperty("test.backdoor.key");
@@ -90,6 +97,8 @@ public final class TestProperties {
TEST_TIMEOUT = Integer.parseInt(prop.getProperty("test.timeout"));
PERSISTENCE_RETRY_PERIOD_IN_S = Integer.parseInt(prop.getProperty("test.persistence.timeout"));
+ INCLUDE_EMAIL_VERIFICATION = Boolean.parseBoolean(prop.getProperty("test.verify.emails"));
+
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/src/e2e/resources/test.template.properties b/src/e2e/resources/test.template.properties
index c8478c5cdbf..09f18dc6f8c 100644
--- a/src/e2e/resources/test.template.properties
+++ b/src/e2e/resources/test.template.properties
@@ -73,4 +73,16 @@ test.persistence.timeout=16
# The email address below will receive some emails from the production server,
# which will be programmatically checked as part of E2E tests.
-test.email=alice.tmms@example.tmt
+test.email=alice.tmms@gmail.tmt
+
+# This is the sender email for all emails sent by the system.
+# It should have the same value as app.email.senderemail in build.properties
+# It is used to filter emails by sender for more efficient verification
+
+test.senderemail=admin@teammates-john.appspotmail.com
+
+# Indicates whether emails should be verified during E2E test.
+# Please note that GAE has limited free email quota.
+# Email verification should be stopped once the quota is depleted.
+
+test.verify.emails=true
diff --git a/src/e2e/resources/test.travis-chrome.properties b/src/e2e/resources/test.travis-chrome.properties
index 0b48aacc093..b8ae5feeff3 100644
--- a/src/e2e/resources/test.travis-chrome.properties
+++ b/src/e2e/resources/test.travis-chrome.properties
@@ -10,4 +10,5 @@ test.browser.closeonfailure=true
test.chromedriver.path=/home/travis/chromedriver
test.timeout=5
test.persistence.timeout=16
-test.email=alice.tmms@example.tmt
+test.email=alice.tmms@gmail.tmt
+test.verify.emails=false
diff --git a/src/e2e/resources/test.travis.properties b/src/e2e/resources/test.travis.properties
index 97f2607d5fe..73b594bc709 100644
--- a/src/e2e/resources/test.travis.properties
+++ b/src/e2e/resources/test.travis.properties
@@ -11,4 +11,5 @@ test.geckodriver.path=/home/travis/geckodriver
test.firefox.path=
test.timeout=5
test.persistence.timeout=16
-test.email=alice.tmms@example.tmt
+test.email=alice.tmms@gmail.tmt
+test.verify.emails=false
diff --git a/src/main/java/teammates/common/util/Const.java b/src/main/java/teammates/common/util/Const.java
index f4c51fe0b32..538e90bfe03 100644
--- a/src/main/java/teammates/common/util/Const.java
+++ b/src/main/java/teammates/common/util/Const.java
@@ -620,6 +620,7 @@ public static class WebPageURIs {
public static final String STUDENT_HOME_PAGE = STUDENT_PAGE + "/home";
public static final String STUDENT_COURSE_DETAILS_PAGE = STUDENT_PAGE + "/course";
public static final String STUDENT_PROFILE_PAGE = STUDENT_PAGE + "/profile";
+ public static final String STUDENT_SESSION_SUBMISSION_PAGE = STUDENT_PAGE + "/sessions/submission";
public static final String STUDENT_SESSION_RESULTS_PAGE = STUDENT_PAGE + "/sessions/result";
public static final String SESSION_RESULTS_PAGE = URI_PREFIX + "/sessions/result";