From d64c2b299f5f847272483ddd5ffb845d66d3b2bf Mon Sep 17 00:00:00 2001
From: Eric Fischer <efischer@edx.org>
Date: Thu, 20 Aug 2015 11:21:29 -0400
Subject: [PATCH] Fix for flaky TeamDiscussionView js tests

After being able to reproduce the errors exactly by making certain
elements unfindable, I concluded that the issues we've been seeing
on headless jenkins runs are due to execution happening too quickly
and elements not loading in time to be interacted with.

The fix is to make liberal use of Jasmine 1.3 `runs` and `waitsFor`
functionality, to ensure that execution happens in the order that
we want, with rendering being checked before proceeding.

Once again, I can't be 100% sure that this fixes the Jenkins errors,
as those are very hard to reliably reproduce, but this is an
educated guess at fixing the problem with no drawbacks, as I've
been able to successfully run all tests locally.
---
 .../js/spec/views/team_discussion_spec.js     | 300 +++++++++++-------
 1 file changed, 185 insertions(+), 115 deletions(-)

diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/team_discussion_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/team_discussion_spec.js
index 889577224b8..59dc359b84f 100644
--- a/lms/djangoapps/teams/static/teams/js/spec/views/team_discussion_spec.js
+++ b/lms/djangoapps/teams/static/teams/js/spec/views/team_discussion_spec.js
@@ -38,84 +38,109 @@ define([
         };
 
         createPost = function(requests, view, title, body, threadID) {
-            title = title || "Test title";
-            body = body || "Test body";
-            threadID = threadID || "999";
-            view.$('.new-post-button').click();
-            view.$('.js-post-title').val(title);
-            view.$('.js-post-body textarea').val(body);
-            view.$('.submit').click();
-            AjaxHelpers.expectRequest(
-                requests, 'POST',
-                interpolate(
-                    '/courses/%(courseID)s/discussion/%(discussionID)s/threads/create?ajax=1',
-                    {
-                        courseID: TeamSpecHelpers.testCourseID,
-                        discussionID: TeamSpecHelpers.testTeamDiscussionID
-                    },
-                    true
-                ),
-                interpolate(
-                    'thread_type=discussion&title=%(title)s&body=%(body)s&anonymous=false&anonymous_to_peers=false&auto_subscribe=true',
-                    {
-                        title: title.replace(/ /g, '+'),
-                        body: body.replace(/ /g, '+')
-                    },
-                    true
-                )
-            );
-            AjaxHelpers.respondWithJson(requests, {
-                content: TeamSpecHelpers.createMockPostResponse({
-                    id: threadID,
-                    title: title,
-                    body: body
-                }),
-                annotated_content_info: TeamSpecHelpers.createAnnotatedContentInfo()
+            runs(function() {
+                title = title || "Test title";
+                body = body || "Test body";
+                threadID = threadID || "999";
+                view.$('.new-post-button').click();
+                view.$('.js-post-title').val(title);
+                view.$('.js-post-body textarea').val(body);
             });
+            
+            waitsFor(function() {
+                return $('.submit').length;
+            }, "Submit button never appeared", 1000);
+
+            runs(function() {
+                view.$('.submit').click();
+                AjaxHelpers.expectRequest(
+                    requests, 'POST',
+                    interpolate(
+                        '/courses/%(courseID)s/discussion/%(discussionID)s/threads/create?ajax=1',
+                        {
+                            courseID: TeamSpecHelpers.testCourseID,
+                            discussionID: TeamSpecHelpers.testTeamDiscussionID
+                        },
+                        true
+                    ),
+                    interpolate(
+                        'thread_type=discussion&title=%(title)s&body=%(body)s&anonymous=false&anonymous_to_peers=false&auto_subscribe=true',
+                        {
+                            title: title.replace(/ /g, '+'),
+                            body: body.replace(/ /g, '+')
+                        },
+                        true
+                    )
+                );
+                AjaxHelpers.respondWithJson(requests, {
+                    content: TeamSpecHelpers.createMockPostResponse({
+                        id: threadID,
+                        title: title,
+                        body: body
+                    }),
+                    annotated_content_info: TeamSpecHelpers.createAnnotatedContentInfo()
+                });
+            });            
         };
 
         expandReplies = function(requests, view, threadID) {
-            view.$('.forum-thread-expand').first().click();
-            AjaxHelpers.expectRequest(
-                requests, 'GET',
-                interpolate(
-                    '/courses/%(courseID)s/discussion/forum/%(discussionID)s/threads/%(threadID)s?ajax=1&resp_skip=0&resp_limit=25',
-                    {
-                        courseID: TeamSpecHelpers.testCourseID,
-                        discussionID: TeamSpecHelpers.testTeamDiscussionID,
-                        threadID: threadID || "999"
-                    },
-                    true
-                )
-            );
-            AjaxHelpers.respondWithJson(requests, {
-                content: TeamSpecHelpers.createMockThreadResponse(),
-                annotated_content_info: TeamSpecHelpers.createAnnotatedContentInfo()
-            });
+            waitsFor(function() {
+                return $('.forum-thread-expand').length;
+            }, "Forum expando link never appeared", 1000);
+
+            runs(function() {
+                view.$('.forum-thread-expand').first().click();
+                AjaxHelpers.expectRequest(
+                    requests, 'GET',
+                    interpolate(
+                        '/courses/%(courseID)s/discussion/forum/%(discussionID)s/threads/%(threadID)s?ajax=1&resp_skip=0&resp_limit=25',
+                        {
+                            courseID: TeamSpecHelpers.testCourseID,
+                            discussionID: TeamSpecHelpers.testTeamDiscussionID,
+                            threadID: threadID || "999"
+                        },
+                        true
+                    )
+                );
+                AjaxHelpers.respondWithJson(requests, {
+                    content: TeamSpecHelpers.createMockThreadResponse(),
+                    annotated_content_info: TeamSpecHelpers.createAnnotatedContentInfo()
+                });
+            });            
         };
 
         postReply = function(requests, view, reply, threadID) {
-            var replyForm = view.$('.discussion-reply-new').first();
-            replyForm.find('.reply-body textarea').val(reply);
-            replyForm.find('.discussion-submit-post').click();
-            AjaxHelpers.expectRequest(
-                requests, 'POST',
-                interpolate(
-                    '/courses/%(courseID)s/discussion/threads/%(threadID)s/reply?ajax=1',
-                    {
-                        courseID: TeamSpecHelpers.testCourseID,
-                        threadID: threadID || "999"
-                    },
-                    true
-                ),
-                'body=' + reply.replace(/ /g, '+')
-            );
-            AjaxHelpers.respondWithJson(requests, {
-                content: TeamSpecHelpers.createMockThreadResponse({
-                    body: reply,
-                    comments_count: 1
-                }),
-                "annotated_content_info": TeamSpecHelpers.createAnnotatedContentInfo()
+            var replyForm;
+            runs(function() {
+                replyForm = view.$('.discussion-reply-new').first();
+            });
+
+            waitsFor(function() {
+                return replyForm.find('.discussion-submit-post').length;
+            }, "submit reply button never appeared", 1000);
+                
+            runs(function() {
+                replyForm.find('.reply-body textarea').val(reply);
+                replyForm.find('.discussion-submit-post').click();
+                AjaxHelpers.expectRequest(
+                    requests, 'POST',
+                    interpolate(
+                        '/courses/%(courseID)s/discussion/threads/%(threadID)s/reply?ajax=1',
+                        {
+                            courseID: TeamSpecHelpers.testCourseID,
+                            threadID: threadID || "999"
+                        },
+                        true
+                    ),
+                    'body=' + reply.replace(/ /g, '+')
+                );
+                AjaxHelpers.respondWithJson(requests, {
+                    content: TeamSpecHelpers.createMockThreadResponse({
+                        body: reply,
+                        comments_count: 1
+                    }),
+                    "annotated_content_info": TeamSpecHelpers.createAnnotatedContentInfo()
+                });
             });
         };
 
@@ -127,17 +152,26 @@ define([
 
         it('can create a new post', function() {
             var requests = AjaxHelpers.requests(this),
-                view = createDiscussionView(requests),
+                view,
                 testTitle = 'New Post',
                 testBody = 'New post body',
                 newThreadElement;
-            createPost(requests, view, testTitle, testBody);
+            runs(function() {
+                view = createDiscussionView(requests);
+                createPost(requests, view, testTitle, testBody);
+            });
+            
+            waitsFor(function() {
+                return $('.discussion-thread').length;
+            }, "Discussion thread never appeared", 1000);
 
-            // Expect the first thread to be the new post
-            expect(view.$('.discussion-thread').length).toEqual(4);
-            newThreadElement = view.$('.discussion-thread').first();
-            expect(newThreadElement.find('.post-header-content h1').text().trim()).toEqual(testTitle);
-            expect(newThreadElement.find('.post-body').text().trim()).toEqual(testBody);
+            runs(function() {
+                // Expect the first thread to be the new post
+                expect(view.$('.discussion-thread').length).toEqual(4);
+                newThreadElement = view.$('.discussion-thread').first();
+                expect(newThreadElement.find('.post-header-content h1').text().trim()).toEqual(testTitle);
+                expect(newThreadElement.find('.post-body').text().trim()).toEqual(testBody);
+            });
         });
 
         it('can post a reply', function() {
@@ -145,59 +179,95 @@ define([
                 view = createDiscussionView(requests),
                 testReply = "Test reply",
                 testThreadID = "1";
-            expandReplies(requests, view, testThreadID);
-            postReply(requests, view, testReply, testThreadID);
-            expect(view.$('.discussion-response .response-body').text().trim()).toBe(testReply);
+            runs(function() {
+                expandReplies(requests, view, testThreadID);
+                postReply(requests, view, testReply, testThreadID);
+            });
+
+            waitsFor(function() {
+                return view.$('.discussion-response .response-body').length;
+            }, "Discussion response never made visible", 1000);
+            
+            runs(function() {
+                expect(view.$('.discussion-response .response-body').text().trim()).toBe(testReply);
+            });
         });
 
         it('can post a reply to a new post', function() {
             var requests = AjaxHelpers.requests(this),
                 view = createDiscussionView(requests, []),
                 testReply = "Test reply";
-            createPost(requests, view);
-            expandReplies(requests, view);
-            postReply(requests, view, testReply);
-            expect(view.$('.discussion-response .response-body').text().trim()).toBe(testReply);
+            runs(function() {
+                createPost(requests, view);
+                expandReplies(requests, view);
+                postReply(requests, view, testReply);
+            });
+
+            waitsFor(function() {
+                return view.$('.discussion-response .response-body').length;
+            }, "Discussion response never made visible", 1000);
+            
+            runs(function() {
+                expect(view.$('.discussion-response .response-body').text().trim()).toBe(testReply);
+            }); 
         });
 
         it('cannot move an existing thread to a different topic', function() {
             var requests = AjaxHelpers.requests(this),
-                view = createDiscussionView(requests),
+                view,
                 postTopicButton, updatedThreadElement,
                 updatedTitle = 'Updated title',
                 updatedBody = 'Updated body',
                 testThreadID = "1";
-            expandReplies(requests, view, testThreadID);
-            view.$('.action-more .icon').first().click();
-            view.$('.action-edit').first().click();
-            postTopicButton = view.$('.post-topic');
-            expect(postTopicButton.length).toBe(0);
-            view.$('.js-post-post-title').val(updatedTitle);
-            view.$('.js-post-body textarea').val(updatedBody);
-            view.$('.submit').click();
-            AjaxHelpers.expectRequest(
-                requests, 'POST',
-                interpolate(
-                    '/courses/%(courseID)s/discussion/%(discussionID)s/threads/create?ajax=1',
-                    {
-                        courseID: TeamSpecHelpers.testCourseID,
-                        discussionID: TeamSpecHelpers.testTeamDiscussionID
-                    },
-                    true
-                ),
-                'thread_type=discussion&title=&body=Updated+body&anonymous=false&anonymous_to_peers=false&auto_subscribe=true'
-            );
-            AjaxHelpers.respondWithJson(requests, {
-                content: TeamSpecHelpers.createMockPostResponse({
-                    id: "999", title: updatedTitle, body: updatedBody
-                }),
-                annotated_content_info: TeamSpecHelpers.createAnnotatedContentInfo()
+            runs(function() {
+                view  = createDiscussionView(requests);
+                expandReplies(requests, view, testThreadID);
+            });
+
+            waitsFor(function() {
+                return view.$('.action-more .icon').length;
+            }, "Expanding replies never finished", 1000);
+
+            runs(function() {
+                view.$('.action-more .icon').first().click();
+                view.$('.action-edit').first().click();
+                postTopicButton = view.$('.post-topic');
+                expect(postTopicButton.length).toBe(0);
+                view.$('.js-post-post-title').val(updatedTitle);
+                view.$('.js-post-body textarea').val(updatedBody);
             });
 
-            // Expect the thread to have been updated
-            updatedThreadElement = view.$('.discussion-thread').first();
-            expect(updatedThreadElement.find('.post-header-content h1').text().trim()).toEqual(updatedTitle);
-            expect(updatedThreadElement.find('.post-body').text().trim()).toEqual(updatedBody);
+            waitsFor(function() {
+                return $('.submit').length;
+            }, "submit button never appeared", 1000);
+            
+            runs(function() {
+                view.$('.submit').click();
+                AjaxHelpers.expectRequest(
+                    requests, 'POST',
+                    interpolate(
+                        '/courses/%(courseID)s/discussion/%(discussionID)s/threads/create?ajax=1',
+                        {
+                            courseID: TeamSpecHelpers.testCourseID,
+                            discussionID: TeamSpecHelpers.testTeamDiscussionID
+                        },
+                        true
+                    ),
+                    'thread_type=discussion&title=&body=Updated+body&anonymous=false&anonymous_to_peers=false&auto_subscribe=true'
+                );
+                AjaxHelpers.respondWithJson(requests, {
+                    content: TeamSpecHelpers.createMockPostResponse({
+                        id: "999", title: updatedTitle, body: updatedBody
+                    }),
+                    annotated_content_info: TeamSpecHelpers.createAnnotatedContentInfo()
+                });
+
+
+                // Expect the thread to have been updated
+                updatedThreadElement = view.$('.discussion-thread').first();
+                expect(updatedThreadElement.find('.post-header-content h1').text().trim()).toEqual(updatedTitle);
+                expect(updatedThreadElement.find('.post-body').text().trim()).toEqual(updatedBody);
+            });
         });
 
         it('cannot move a new thread to a different topic', function() {
-- 
GitLab