-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
InstructorCourseDetailsPageUiTest: testing on live server: increase success rate for checking email delivery #7788 #8862
InstructorCourseDetailsPageUiTest: testing on live server: increase success rate for checking email delivery #7788 #8862
Conversation
String keyReceivedInEmail = | ||
EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId); | ||
return keyToSend.equals(keyReceivedInEmail); | ||
RetryManager RM = new RetryManager(3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsure if I should still keep this as 5 seconds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep it at 5 seconds. Part of the reason RetryManager
wasn't used at the time was that it only supports exponential backoff. However, emails can take few seconds to a minute to deliver. Using exponential backoff can cause the wait time to explode.
Ideally, we should eventually improve RetryManager
to be more flexible, i.e. allow for linear to exponential backoff.
In any case, it's probably still better to make a single test retry (albeit slowly) than to restart the entire test.
So I suppose there's a separate problem of our UI tests requiring that many tests require on the previous state of the other.
So I guess I would just talk about it here:
Making individual UI tests depend on the state of the previous is not a good idea but we did it anyway to reduce the time for testing.
However, the problems it caused is worse:
- Developers either have to read the entire UI test class to modify tests properly
- Or just read a small part (risky!) and make a change
And because of the second point which is common, it has caused a number of problems in tests and sometimes the unexpected previous state is problematic.
If a developer modify a test before another, it changes the previous state for many other tests that depend on it, which usually don't cause problems but sometimes it breaks other tests but what's worse is it sometimes hides(!!) a problem in another test.
It would be a lot of work to decouple the states and it would probably increase test time by a lot but I think it is probably what we should aim for. Eventually, as we start to be able to create more unit and integration tests, we can get rid of many tests that should not be testing at the UI level. I think we should also use an automated testing platform like Sauce Labs (Not sure if we are applicable for their open source license though) and it can really help in debugging UI tests.
RIght now if we cannot reproduce the UI tests locally, on AppVeyor we have to create a remote session to view the UI test and on Travis we don't even have a GUI so we probably have to resort to different heuristics like generating HTML files and making educated guesses.
Also we may want to use Sauce Labs to run cross browser tests, which is better because we sometimes write tests that are flaky only because they are not written properly but if we enforce that tests must work across browsers it can encourage writing less flaky tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to use RetryableTaskReturns<String>
instead?
What we want to retrieve is keyReceivedInEmail
and not a boolean?
Also, can we change listMessagesResponse = service.users().messages().list(username).setMaxResults(5L) .setQ("is:UNREAD").execute();
to not just retrieve the last 5 emails only?
@Override | ||
public Boolean run() throws RuntimeException { | ||
|
||
keyToSend = BackDoor.getEncryptedKeyForStudent(courseId, studentEmail); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this constant? Why is there a need to put this in run()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LiHaoTan If not just last 5 then how many?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's fetch everything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't handle exceptions like that.
EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId); | ||
RetryManager retryManager = new RetryManager(5); | ||
|
||
String keyReceivedInEmail = retryManager.runUntilSuccessful(new RetryableTaskReturns<String>("Received email") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retrieve registration key
is more accurate?
ThreadHelper.waitFor(5000); //TODO: remove this and use RetryManager with a shorter wait interval | ||
String keyReceivedInEmail = | ||
EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId); | ||
RetryManager retryManager = new RetryManager(5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a // TODO: Use linear backoff first before exponential backoff.
keyReceivedInEmail = | ||
EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId); | ||
isDoneExecuting = true; | ||
} catch (Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't catch all the exceptions like that. Also, these exceptions are unexpected, which means there's some kind of problem but we can't do anything useful so we shouldn't be catching them.
Returning EmailAccount.getRegistrationKeyFromGmail(studentEmail, courseName, courseId);
is enough.
boolean isDoneExecuting; | ||
|
||
@Override | ||
public String run() throws RuntimeException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's throw the specific exceptions here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't possible cause run()
is an inherited method and changing definition function will cause many irrelevant changes in other places.
I have implemented in a different way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use RetryableTaskReturnsThrows
?
|
||
@Override | ||
public boolean isSuccessful(String result) { | ||
return isDoneExecuting; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition should just be result != null
.
@@ -269,12 +270,39 @@ private InstructorCourseDetailsPage getCourseDetailsPage() { | |||
|
|||
private boolean hasStudentReceivedReminder(String courseName, String courseId, String studentEmail) | |||
throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's throw the specific exceptions here.
// unexpectedly if there are 5 additional emails received excluding the one from TEAMMATES. | ||
listMessagesResponse = service.users().messages().list(username).setMaxResults(5L) | ||
.setQ("is:UNREAD").execute(); | ||
// Fetch all emails received by user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is not very useful because it is just repeating what the following code is doing.
I suppose the problem is that each method called should be on a separate line and getRegistrationKeyFromGmail
will be better as non-static.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But EmailAccount.java
is a utility class, so making it non-static doesn't make sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except that EmailAccount
has state such as username
which while immutable is used in many parts of the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one issue
@@ -272,24 +271,18 @@ private InstructorCourseDetailsPage getCourseDetailsPage() { | |||
} | |||
|
|||
private boolean hasStudentReceivedReminder(String courseName, String courseId, String studentEmail) | |||
throws MaximumRetriesExceededException { | |||
throws Throwable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Exception
? TEAMMATES is kinda stuck with it because of some not so good design of mixing exceptions with errors. Some errors in Java are pretty much unrecoverable and we can't do anything useful with it such as OutOfMemmoryError
.
@LiHaoTan, Made necessary changes |
Fixes #7788
Outline of Solution
ThreadHelper.wait(5000)
I haven't tested on live server.