From b63bfbd5ddbaeea8f5049198afe1f3b745e96fd0 Mon Sep 17 00:00:00 2001 From: "Albert St. Aubin" Date: Wed, 3 Aug 2016 07:42:01 -0400 Subject: [PATCH] Replaced lettuce tests with bokchoy tests for the course updates page and improved flaky test issues. TNL-5051 --- .../features/course-updates.feature | 60 ------- .../contentstore/features/course-updates.py | 69 +-------- .../acceptance/pages/studio/course_info.py | 124 ++++++++++++++- .../tests/studio/test_studio_course_info.py | 146 ++++++++++++++++++ 4 files changed, 268 insertions(+), 131 deletions(-) create mode 100644 common/test/acceptance/tests/studio/test_studio_course_info.py diff --git a/cms/djangoapps/contentstore/features/course-updates.feature b/cms/djangoapps/contentstore/features/course-updates.feature index 79d6445194..f71d6c3d78 100644 --- a/cms/djangoapps/contentstore/features/course-updates.feature +++ b/cms/djangoapps/contentstore/features/course-updates.feature @@ -2,46 +2,6 @@ Feature: CMS.Course updates As a course author, I want to be able to provide updates to my students -# Commenting out as flaky TNL-5051 07/20/2016 - # Internet explorer can't select all so the update appears weirdly -# @skip_internetexplorer -# Scenario: Users can add updates -# Given I have opened a new course in Studio -# And I go to the course updates page -# When I add a new update with the text "Hello" -# Then I should see the update "Hello" -# And I see a "saving" notification - -# Commenting out as flaky TNL-5051 07/20/2016 -# # Internet explorer can't select all so the update appears weirdly -# @skip_internetexplorer -# Scenario: Users can edit updates -# Given I have opened a new course in Studio -# And I go to the course updates page -# When I add a new update with the text "Hello" -# And I modify the text to "Goodbye" -# Then I should see the update "Goodbye" -# And I see a "saving" notification - -# Commenting out as flaky TNL-5051 07/20/2016 -# Scenario: Users can delete updates -# Given I have opened a new course in Studio -# And I go to the course updates page -# And I add a new update with the text "Hello" -# And I delete the update -# And I confirm the prompt -# Then I should not see the update "Hello" -# And I see a "deleting" notification - -# Commenting out as flaky TNL-5051 07/20/2016 -# Scenario: Users can edit update dates -# Given I have opened a new course in Studio -# And I go to the course updates page -# And I add a new update with the text "Hello" -# When I edit the date to "06/01/13" -# Then I should see the date "June 1, 2013" -# And I see a "saving" notification - # Internet explorer can't select all so the update appears weirdly @skip_internetexplorer Scenario: Users can change handouts @@ -51,26 +11,6 @@ Feature: CMS.Course updates Then I see the handout "Test" And I see a "saving" notification - Scenario: Text outside of tags is preserved - Given I have opened a new course in Studio - And I go to the course updates page - When I add a new update with the text "before middle after" - Then I should see the update "before middle after" - And when I reload the page - Then I should see the update "before middle after" - -# Commenting out as flaky TNL-5051 07/22/2016 -# Scenario: Static links are rewritten when previewing a course update -# Given I have opened a new course in Studio -# And I go to the course updates page -# When I add a new update with the text "" -# # Can only do partial text matches because of the quotes with in quotes (and regexp step matching). -# Then I should see the asset update to "my_img.jpg" -# And I change the update from "/static/my_img.jpg" to "" -# Then I should see the asset update to "modified.jpg" -# And when I reload the page -# Then I should see the asset update to "modified.jpg" - Scenario: Static links are rewritten when previewing handouts Given I have opened a new course in Studio And I go to the course updates page diff --git a/cms/djangoapps/contentstore/features/course-updates.py b/cms/djangoapps/contentstore/features/course-updates.py index 1b50910a65..3a9d0103c6 100644 --- a/cms/djangoapps/contentstore/features/course-updates.py +++ b/cms/djangoapps/contentstore/features/course-updates.py @@ -1,8 +1,7 @@ # pylint: disable=missing-docstring +from cms.djangoapps.contentstore.features.common import type_in_codemirror, get_codemirror_value from lettuce import world, step -from selenium.webdriver.common.keys import Keys -from common import type_in_codemirror, get_codemirror_value from nose.tools import assert_in @@ -15,77 +14,11 @@ def go_to_updates(_step): world.wait_for_visible('#course-handouts-view') -@step(u'I add a new update with the text "([^"]*)"$') -def add_update(_step, text): - update_css = '.new-update-button' - world.css_click(update_css) - world.wait_for_visible('.CodeMirror') - change_text(text) - - -@step(u'I should see the update "([^"]*)"$') -def check_update(_step, text): - update_css = 'div.update-contents' - update_html = world.css_find(update_css).html - assert_in(text, update_html) - - -@step(u'I should see the asset update to "([^"]*)"$') -def check_asset_update(_step, asset_file): - update_css = 'div.update-contents' - update_html = world.css_find(update_css).html - asset_key = world.scenario_dict['COURSE'].id.make_asset_key(asset_type='asset', path=asset_file) - assert_in(unicode(asset_key), update_html) - - -@step(u'I should not see the update "([^"]*)"$') -def check_no_update(_step, text): - update_css = 'div.update-contents' - assert world.is_css_not_present(update_css) - - -@step(u'I modify the text to "([^"]*)"$') -def modify_update(_step, text): - button_css = 'div.post-preview .edit-button' - world.css_click(button_css) - change_text(text) - - -@step(u'I change the update from "([^"]*)" to "([^"]*)"$') -def change_existing_update(_step, before, after): - verify_text_in_editor_and_update('div.post-preview .edit-button', before, after) - - @step(u'I change the handout from "([^"]*)" to "([^"]*)"$') def change_existing_handout(_step, before, after): verify_text_in_editor_and_update('div.course-handouts .edit-button', before, after) -@step(u'I delete the update$') -def click_button(_step): - button_css = 'div.post-preview .delete-button' - world.css_click(button_css) - - -@step(u'I edit the date to "([^"]*)"$') -def change_date(_step, new_date): - button_css = 'div.post-preview .edit-button' - world.css_click(button_css) - date_css = 'input.date' - date = world.css_find(date_css) - for __ in range(len(date.value)): - date._element.send_keys(Keys.END, Keys.BACK_SPACE) - date._element.send_keys(new_date) - save_css = '.save-button' - world.css_click(save_css) - - -@step(u'I should see the date "([^"]*)"$') -def check_date(_step, date): - date_css = 'span.date-display' - assert_in(date, world.css_html(date_css)) - - @step(u'I modify the handout to "([^"]*)"$') def edit_handouts(_step, text): edit_css = 'div.course-handouts > .edit-button' diff --git a/common/test/acceptance/pages/studio/course_info.py b/common/test/acceptance/pages/studio/course_info.py index 12b8780085..39f09b4851 100644 --- a/common/test/acceptance/pages/studio/course_info.py +++ b/common/test/acceptance/pages/studio/course_info.py @@ -1,16 +1,134 @@ """ Course Updates page. """ - +from common.test.acceptance.pages.common.utils import click_css, confirm_prompt from common.test.acceptance.pages.studio.course_page import CoursePage +from common.test.acceptance.pages.studio.utils import type_in_codemirror, set_input_value class CourseUpdatesPage(CoursePage): """ Course Updates page. """ - url_path = "course_info" def is_browser_on_page(self): - return self.q(css='body.view-updates').present + """ + Returns whether or not the browser on the page and has loaded the required content + """ + # Check for the presence of handouts-content, when it is present the render function has completed + # loading the updates and handout sections + return (self.q(css='.handouts-content').present and + self.q(css='article#course-update-view.course-updates').present) + + def is_course_update_list_empty(self): + """ + Checks whether or not the update contents list is empty + """ + return len(self.q(css='.update-contents')) == 0 + + def is_new_update_button_present(self): + """ + Checks for the presence of the new update post button. + """ + return self.q(css='.new-update-button').present + + def click_new_update_button(self): + """ + Clicks the new-update button. + """ + click_css(self, '.new-update-button', require_notification=False) + + def submit_update(self, message): + """ + Adds update text to the new update CodeMirror form and submits that text. + + Arguments: + message (str): The message to be added and saved. + """ + type_in_codemirror(self, 0, message) + self.click_new_update_save_button() + + def set_date(self, date): + """ + Sets the updates date input to the provided value. + + Arguments: + date (str): Date string in the format DD/MM/YYYY + """ + set_input_value(self, 'input.date', date) + + def is_first_update_date(self, search_date): + """ + Checks to see if the search date is present + + Arguments: + search_date (str): e.g. 06/01/2013 would be found with June 1, 2013 + + Returns: + bool: True if the date is in the first update and False otherwise. + """ + return search_date == self.q(css='.date-display').html[0] + + def is_new_update_save_button_present(self): + """ + Checks to see if the CodeMirror Update save button is present. + """ + return self.q(css='.save-button').present + + def click_new_update_save_button(self): + """ + Clicks the CodeMirror Update save button. + """ + click_css(self, '.save-button') + + def is_edit_button_present(self): + """ + Checks to see if the edit update post buttons if present. + """ + return self.q(css='.post-preview .edit-button').present + + def click_edit_update_button(self): + """ + Clicks the edit update post button. + """ + click_css(self, '.post-preview .edit-button', require_notification=False) + self.wait_for_element_visibility('.CodeMirror', 'Waiting for .CodeMirror') + + def is_delete_update_button_present(self): + """ + Checks to see if the delete update post button is present. + """ + return self.q(css='.post-preview .delete-button').present + + def click_delete_update_button(self): + """ + Clicks the delete update post button and confirms the delete notification. + """ + click_css(self, '.post-preview .delete-button', require_notification=False) + confirm_prompt(self) + + def is_first_update_message(self, message): + """ + Looks for the message in the first course update posted. + + Arguments: + message (str): String containing the message that is to be searched for + + Returns: + bool: True if the first update is the message, false otherwise. + """ + return message == self.q(css='.update-contents').html[0] + + def first_update_contains_html(self, value): + """ + Looks to see if the html provided is contained in the first update + + Arguments: + value (str): String value that will be looked for + + Returns: + bool: True if the value is contained in the first update + """ + update = self.q(css='.update-contents').html + return value in update[0] diff --git a/common/test/acceptance/tests/studio/test_studio_course_info.py b/common/test/acceptance/tests/studio/test_studio_course_info.py new file mode 100644 index 0000000000..78d5f901e8 --- /dev/null +++ b/common/test/acceptance/tests/studio/test_studio_course_info.py @@ -0,0 +1,146 @@ +""" +Acceptance Tests for Course Information +""" +from common.test.acceptance.pages.studio.course_info import CourseUpdatesPage +from common.test.acceptance.tests.studio.base_studio_test import StudioCourseTest + +from ...pages.studio.auto_auth import AutoAuthPage +from ...pages.studio.index import DashboardPage + + +class UsersCanAddUpdatesTest(StudioCourseTest): + """ + Series of Bok Choy Tests to test the Course Updates page + """ + + def _create_and_verify_update(self, message): + """ + Helper method to create and verify and update based on the message. + + Arguments: + message (str): Message to add to the update. + """ + self.course_updates_page.visit() + self.assertTrue(self.course_updates_page.is_new_update_button_present()) + self.course_updates_page.click_new_update_button() + self.course_updates_page.submit_update(message) + self.assertTrue(self.course_updates_page.is_first_update_message(message)) + + def setUp(self, is_staff=False, test_xss=True): + super(UsersCanAddUpdatesTest, self).setUp() + self.auth_page = AutoAuthPage(self.browser, staff=True) + self.dashboard_page = DashboardPage(self.browser) + self.course_updates_page = CourseUpdatesPage( + self.browser, + self.course_info['org'], + self.course_info['number'], + self.course_info['run'] + ) + + def test_course_updates_page_exists(self): + """ + Scenario: User can access Course Updates Page + Given I have opened a new course in Studio + And I go to the course updates page + When I visit the page + Then I should see any course updates + And I should see the new update button + """ + self.course_updates_page.visit() + self.course_updates_page.wait_for_page() + self.assertTrue(self.course_updates_page.is_new_update_button_present) + + def test_new_course_update_is_present(self): + """ + Scenario: Users can add updates + Given I have opened a new course in Studio + And I go to the course updates page + When I add a new update with the text "Hello" + Then I should see the update "Hello" + And I see a "saving" notification + """ + self._create_and_verify_update('Hello') + + def test_new_course_update_can_be_edited(self): + """ + Scenario: Users can edit updates + Given I have opened a new course in Studio + And I go to the course updates page + When I add a new update with the text "Hello" + And I modify the text to "Goodbye" + Then I should see the update "Goodbye" + """ + self._create_and_verify_update('Hello') + self.assertTrue(self.course_updates_page.is_edit_button_present()) + self.course_updates_page.click_edit_update_button() + self.course_updates_page.submit_update('Goodbye') + self.assertFalse(self.course_updates_page.is_first_update_message('Hello')) + self.assertTrue(self.course_updates_page.is_first_update_message('Goodbye')) + + def test_delete_course_update(self): + """ + Scenario: Users can delete updates + Given I have opened a new course in Studio + And I go to the course updates page + And I add a new update with the text "Hello" + And I delete the update + And I confirm the prompt + Then I should not see the update "Hello" + """ + self._create_and_verify_update('Hello') + self.course_updates_page.click_delete_update_button() + self.assertTrue(self.course_updates_page.is_course_update_list_empty()) + + def test_user_edit_date(self): + """ + Scenario: Users can edit update dates + Given I have opened a new course in Studio + And I go to the course updates page + And I add a new update with the text "Hello" + When I edit the date to "06/01/13" + Then I should see the date "June 1, 2013" + """ + self._create_and_verify_update('Hello') + self.course_updates_page.click_edit_update_button() + self.course_updates_page.set_date('06/01/2013') + self.course_updates_page.click_new_update_save_button() + self.assertTrue(self.course_updates_page.is_first_update_date('June 1, 2013')) + + def test_outside_tag_preserved(self): + """ + Scenario: Text outside of tags is preserved + Given I have opened a new course in Studio + And I go to the course updates page + When I add a new update with the text "before middle after" + Then I should see the update "before middle after" + And when I reload the page + Then I should see the update "before middle after" + """ + self._create_and_verify_update('before middle after') + self.course_updates_page.visit() + self.assertTrue(self.course_updates_page.is_first_update_message('before middle after')) + + def test_asset_change_in_updates(self): + """ + Scenario: Static links are rewritten when previewing a course update + Given I have opened a new course in Studio + And I go to the course updates page + When I add a new update with the text "" + # Can only do partial text matches because of the quotes with in quotes (and regexp step matching). + Then I should see the asset update to "my_img.jpg" + And I change the update from "/static/my_img.jpg" to "" + Then I should see the asset update to "modified.jpg" + And when I reload the page + Then I should see the asset update to "modified.jpg" + """ + self.course_updates_page.visit() + self.assertTrue(self.course_updates_page.is_new_update_button_present()) + self.course_updates_page.click_new_update_button() + self.course_updates_page.submit_update("") + self.assertTrue(self.course_updates_page.first_update_contains_html("my_img.jpg")) + self.course_updates_page.click_edit_update_button() + self.course_updates_page.submit_update("") + self.assertFalse(self.course_updates_page.first_update_contains_html("my_img.jpg")) + self.assertTrue(self.course_updates_page.first_update_contains_html("modified.jpg")) + self.course_updates_page.visit() + self.assertTrue(self.course_updates_page.first_update_contains_html("modified.jpg"))