From 50dc93dcbfd3759f465ce0a7910b4ef6cd49249a Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Fri, 27 Sep 2013 17:08:41 -0400 Subject: [PATCH] Re-enable the component acceptance tests --- .../contentstore/features/common.py | 20 +- .../contentstore/features/component.feature | 154 ++++++------ .../contentstore/features/component.py | 222 +++++++++--------- 3 files changed, 208 insertions(+), 188 deletions(-) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index f3875d5dbc..5dd72b3767 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -168,6 +168,18 @@ def log_into_studio( assert_in(uname, world.css_text('h2.title', timeout=10)) +def add_course_author(user, course): + """ + Add the user to the instructor group of the course + so they will have the permissions to see it in studio + """ + for role in ("staff", "instructor"): + groupname = get_course_groupname_for_role(course.location, role) + group, __ = Group.objects.get_or_create(name=groupname) + user.groups.add(group) + user.save() + + def create_a_course(): course = world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') world.scenario_dict['COURSE'] = course @@ -176,13 +188,7 @@ def create_a_course(): if not user: user = get_user_by_email('robot+studio@edx.org') - # Add the user to the instructor group of the course - # so they will have the permissions to see it in studio - for role in ("staff", "instructor"): - groupname = get_course_groupname_for_role(course.location, role) - group, __ = Group.objects.get_or_create(name=groupname) - user.groups.add(group) - user.save() + add_course_author(user, course) # Navigate to the studio dashboard world.visit('/') diff --git a/cms/djangoapps/contentstore/features/component.feature b/cms/djangoapps/contentstore/features/component.feature index 0754049df0..3877cccc55 100644 --- a/cms/djangoapps/contentstore/features/component.feature +++ b/cms/djangoapps/contentstore/features/component.feature @@ -2,85 +2,87 @@ Feature: CMS.Component Adding As a course author, I want to be able to add a wide variety of components - #Scenario: I can add components - # Given I have opened a new course in studio - # And I am editing a new unit - # When I add the following components: - # | Component | - # | Discussion | - # | Blank HTML | - # | LaTex | - # | Blank Problem| - # | Dropdown | - # | Multi Choice | - # | Numerical | - # | Text Input | - # | Advanced | - # | Circuit | - # | Custom Python| - # | Image Mapped | - # | Math Input | - # | Problem LaTex| - # | Adaptive Hint| - # | Video | - # Then I see the following components: - # | Component | - # | Discussion | - # | Blank HTML | - # | LaTex | - # | Blank Problem| - # | Dropdown | - # | Multi Choice | - # | Numerical | - # | Text Input | - # | Advanced | - # | Circuit | - # | Custom Python| - # | Image Mapped | - # | Math Input | - # | Problem LaTex| - # | Adaptive Hint| - # | Video | + Scenario: I can add single step components + Given I am in Studio editing a new unit + When I add this type of single step component: + | Component | + | Discussion | + | Video | + Then I see this type of single step component: + | Component | + | Discussion | + | Video | - #Scenario: I can delete Components - # Given I have opened a new course in studio - # And I am editing a new unit - # And I add the following components: - # | Component | - # | Discussion | - # | Blank HTML | - # | LaTex | - # | Blank Problem| - # | Dropdown | - # | Multi Choice | - # | Numerical | - # | Text Input | - # | Advanced | - # | Circuit | - # | Custom Python| - # | Image Mapped | - # | Math Input | - # | Problem LaTex| - # | Adaptive Hint| - # | Video | - # When I will confirm all alerts - # And I delete all components - # Then I see no components + Scenario: I can add HTML components + Given I am in Studio editing a new unit + When I add this type of HTML component: + | Component | + | Text | + | Announcement | + | E-text Written in LaTeX | + Then I see HTML components in this order: + | Component | + | Text | + | Announcement | + | E-text Written in LaTeX | + + Scenario: I can add Common Problem components + Given I am in Studio editing a new unit + When I add this type of Problem component: + | Component | + | Blank Common Problem | + | Dropdown | + | Multiple Choice | + | Numerical Input | + | Text Input | + Then I see Problem components in this order: + | Component | + | Blank Common Problem | + | Dropdown | + | Multiple Choice | + | Numerical Input | + | Text Input | + + Scenario: I can add Advanced Problem components + Given I am in Studio editing a new unit + When I add this type of Advanced Problem component: + | Component | + | Blank Advanced Problem | + | Circuit Schematic Builder | + | Custom Python-Evaluated Input | + | Drag and Drop | + | Image Mapped Input | + | Math Expression Input | + | Problem Written in LaTeX | + | Problem with Adaptive Hint | + Then I see Problem components in this order: + | Component | + | Blank Advanced Problem | + | Circuit Schematic Builder | + | Custom Python-Evaluated Input | + | Drag and Drop | + | Image Mapped Input | + | Math Expression Input | + | Problem Written in LaTeX | + | Problem with Adaptive Hint | Scenario: I see a prompt on delete - Given I have opened a new course in studio - And I am editing a new unit - And I add the following components: - | Component | - | Discussion | - And I delete a component - Then I am shown a prompt + Given I am in Studio editing a new unit + And I add a "Discussion" "single step" component + And I delete a component + Then I am shown a prompt - Scenario: I see a notification on save - Given I have opened a new course in studio - And I am editing a new unit - And I add the following components: - | Component | - | Discussion | + Scenario: I can delete Components + Given I am in Studio editing a new unit + And I add a "Discussion" "single step" component + And I add a "Text" "HTML" component + And I add a "Blank Common Problem" "Problem" component + And I add a "Blank Advanced Problem" "Advanced Problem" component + And I delete all components + Then I see no components + + Scenario: I see a notification on save + Given I am in Studio editing a new unit + And I add a "Discussion" "single step" component And I edit and save a component Then I am shown a notification diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index d0c1fd59e7..4fd7b01d9d 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -2,38 +2,135 @@ #pylint: disable=W0621 from lettuce import world, step -from nose.tools import assert_true # pylint: disable=E0611 - -DATA_LOCATION = 'i4x://edx/templates' +from nose.tools import assert_true, assert_in, assert_equal # pylint: disable=E0611 +from common import create_studio_user, add_course_author, log_into_studio -@step(u'I am editing a new unit') +@step(u'I am in Studio editing a new unit$') def add_unit(step): - css_selectors = ['a.new-courseware-section-button', 'input.new-section-name-save', 'a.new-subsection-item', - 'input.new-subsection-name-save', 'div.section-item a.expand-collapse-icon', 'a.new-unit-item'] + world.clear_courses() + course = world.CourseFactory.create() + section = world.ItemFactory.create(parent_location=course.location) + world.ItemFactory.create( + parent_location=section.location, + category='sequential', + display_name='Subsection One',) + user = create_studio_user(is_staff=False) + add_course_author(user, course) + log_into_studio() + css_selectors = ['a.course-link', 'div.section-item a.expand-collapse-icon', 'a.new-unit-item'] for selector in css_selectors: world.css_click(selector) -@step(u'I add the following components:') -def add_components(step): - for component in [step_hash['Component'] for step_hash in step.hashes]: - assert component in COMPONENT_DICTIONARY - for css in COMPONENT_DICTIONARY[component]['steps']: - world.css_click(css) +@step(u'I add this type of single step component:$') +def add_a_single_step_component(step): + for step_hash in step.hashes: + component = step_hash['Component'] + assert_in(component, ['Discussion', 'Video']) + css_selector = 'a[data-type="{}"]'.format(component.lower()) + world.css_click(css_selector) -@step(u'I see the following components') -def check_components(step): - for component in [step_hash['Component'] for step_hash in step.hashes]: - assert component in COMPONENT_DICTIONARY - assert_true(COMPONENT_DICTIONARY[component]['found_func'](), "{} couldn't be found".format(component)) +@step(u'I see this type of single step component:$') +def see_a_single_step_component(step): + for step_hash in step.hashes: + component = step_hash['Component'] + assert_in(component, ['Discussion', 'Video']) + component_css = 'section.xmodule_{}Module'.format(component) + assert_true(world.is_css_present(component_css), + "{} couldn't be found".format(component)) -@step(u'I delete all components') +@step(u'I add this type of( Advanced)? (HTML|Problem) component:$') +def add_a_multi_step_component(step, is_advanced, category): + def click_advanced(): + css = 'ul.problem-type-tabs a[href="#tab2"]' + world.css_click(css) + my_css = 'ul.problem-type-tabs li.ui-state-active a[href="#tab2"]' + assert(world.css_find(my_css)) + + def find_matching_link(): + """ + Find the link with the specified text. There should be one and only one. + """ + # The tab shows links for the given category + links = world.css_find('div.new-component-{} a'.format(category)) + + # Find the link whose text matches what you're looking for + matched_links = [link for link in links if link.text == step_hash['Component']] + + # There should be one and only one + assert_equal(len(matched_links), 1) + return matched_links[0] + + def click_link(): + link.click() + + category = category.lower() + for step_hash in step.hashes: + css_selector = 'a[data-type="{}"]'.format(category) + world.css_click(css_selector) + world.wait_for_invisible(css_selector) + + if is_advanced: + # Sometimes this click does not work if you go too fast. + world.retry_on_exception(click_advanced, max_attempts=5, ignored_exceptions=AssertionError) + + # Retry this in case the list is empty because you tried too fast. + link = world.retry_on_exception(func=find_matching_link, ignored_exceptions=AssertionError) + + # Wait for the link to be clickable. If you go too fast it is not. + world.retry_on_exception(click_link) + + +@step(u'I see (HTML|Problem) components in this order:') +def see_a_multi_step_component(step, category): + components = world.css_find('li.component section.xmodule_display') + for idx, step_hash in enumerate(step.hashes): + if category == 'HTML': + html_matcher = { + 'Text': + '\n \n', + 'Announcement': + '

Words of encouragement! This is a short note that most students will read.

', + 'E-text Written in LaTeX': + '

Example: E-text page

', + } + assert_in(html_matcher[step_hash['Component']], components[idx].html) + else: + assert_in(step_hash['Component'].upper(), components[idx].text) + + +@step(u'I add a "([^"]*)" "([^"]*)" component$') +def add_component_catetory(step, component, category): + assert category in ('single step', 'HTML', 'Problem', 'Advanced Problem') + given_string = 'I add this type of {} component:'.format(category) + step.given('{}\n{}\n{}'.format(given_string, '|Component|', '|{}|'.format(component))) + + +@step(u'I delete all components$') def delete_all_components(step): - for _ in range(len(COMPONENT_DICTIONARY)): - world.css_click('a.delete-button') + delete_btn_css = 'a.delete-button' + prompt_css = 'div#prompt-warning' + btn_css = '{} a.button.action-primary'.format(prompt_css) + saving_mini_css = 'div#page-notification .wrapper-notification-mini' + count = len(world.css_find('ol.components li.component')) + for _ in range(int(count)): + world.css_click(delete_btn_css) + assert_true(world.is_css_present('{}.is-shown'.format(prompt_css)), + msg='Waiting for the confirmation prompt to be shown') + + # Pressing the button via css was not working reliably for the last component + # when run in Chrome. + if world.browser.driver_name is 'Chrome': + world.browser.execute_script("$('{}').click()".format(btn_css)) + else: + world.css_click(btn_css) + + # Wait for the saving notification to pop up then disappear + if world.is_css_present('{}.is-shown'.format(saving_mini_css)): + world.css_find('{}.is-hiding'.format(saving_mini_css)) @step(u'I see no components') @@ -50,88 +147,3 @@ def delete_one_component(step): def edit_and_save_component(step): world.css_click('.edit-button') world.css_click('.save-button') - - -def step_selector_list(data_type, path, index=1): - selector_list = ['a[data-type="{}"]'.format(data_type)] - if index != 1: - selector_list.append('a[id="ui-id-{}"]'.format(index)) - if path is not None: - selector_list.append('a[data-location="{}/{}/{}"]'.format(DATA_LOCATION, data_type, path)) - return selector_list - - -def found_text_func(text): - return lambda: world.browser.is_text_present(text) - - -def found_css_func(css): - return lambda: world.is_css_present(css, wait_time=2) - -COMPONENT_DICTIONARY = { - 'Discussion': { - 'steps': step_selector_list('discussion', None), - 'found_func': found_css_func('section.xmodule_DiscussionModule') - }, - 'Blank HTML': { - 'steps': step_selector_list('html', 'Blank_HTML_Page'), - #this one is a blank html so a more refined search is being done - 'found_func': lambda: '\n \n' in [x.html for x in world.css_find('section.xmodule_HtmlModule')] - }, - 'LaTex': { - 'steps': step_selector_list('html', 'E-text_Written_in_LaTeX'), - 'found_func': found_text_func('EXAMPLE: E-TEXT PAGE') - }, - 'Blank Problem': { - 'steps': step_selector_list('problem', 'Blank_Common_Problem'), - 'found_func': found_text_func('BLANK COMMON PROBLEM') - }, - 'Dropdown': { - 'steps': step_selector_list('problem', 'Dropdown'), - 'found_func': found_text_func('DROPDOWN') - }, - 'Multi Choice': { - 'steps': step_selector_list('problem', 'Multiple_Choice'), - 'found_func': found_text_func('MULTIPLE CHOICE') - }, - 'Numerical': { - 'steps': step_selector_list('problem', 'Numerical_Input'), - 'found_func': found_text_func('NUMERICAL INPUT') - }, - 'Text Input': { - 'steps': step_selector_list('problem', 'Text_Input'), - 'found_func': found_text_func('TEXT INPUT') - }, - 'Advanced': { - 'steps': step_selector_list('problem', 'Blank_Advanced_Problem', index=2), - 'found_func': found_text_func('BLANK ADVANCED PROBLEM') - }, - 'Circuit': { - 'steps': step_selector_list('problem', 'Circuit_Schematic_Builder', index=2), - 'found_func': found_text_func('CIRCUIT SCHEMATIC BUILDER') - }, - 'Custom Python': { - 'steps': step_selector_list('problem', 'Custom_Python-Evaluated_Input', index=2), - 'found_func': found_text_func('CUSTOM PYTHON-EVALUATED INPUT') - }, - 'Image Mapped': { - 'steps': step_selector_list('problem', 'Image_Mapped_Input', index=2), - 'found_func': found_text_func('IMAGE MAPPED INPUT') - }, - 'Math Input': { - 'steps': step_selector_list('problem', 'Math_Expression_Input', index=2), - 'found_func': found_text_func('MATH EXPRESSION INPUT') - }, - 'Problem LaTex': { - 'steps': step_selector_list('problem', 'Problem_Written_in_LaTeX', index=2), - 'found_func': found_text_func('PROBLEM WRITTEN IN LATEX') - }, - 'Adaptive Hint': { - 'steps': step_selector_list('problem', 'Problem_with_Adaptive_Hint', index=2), - 'found_func': found_text_func('PROBLEM WITH ADAPTIVE HINT') - }, - 'Video': { - 'steps': step_selector_list('video', None), - 'found_func': found_css_func('section.xmodule_VideoModule') - } -}