diff --git a/AUTHORS b/AUTHORS index ca6e14b252f2..091e054a4532 100644 --- a/AUTHORS +++ b/AUTHORS @@ -72,4 +72,5 @@ Giulio Gratta David Baumgold Jason Bau Frances Botsford +Jonah Stanley Slater Victoroff diff --git a/cms/djangoapps/contentstore/features/courses.py b/cms/djangoapps/contentstore/features/courses.py index aa2e9d68f8a7..a3e838a9d1ee 100644 --- a/cms/djangoapps/contentstore/features/courses.py +++ b/cms/djangoapps/contentstore/features/courses.py @@ -47,12 +47,6 @@ def i_see_the_course_in_my_courses(step): assert world.css_has_text(course_css, 'Robot Super Course') -@step('the course is loaded$') -def course_is_loaded(step): - class_css = 'a.class-name' - assert world.css_has_text(course_css, 'Robot Super Cousre') - - @step('I am on the "([^"]*)" tab$') def i_am_on_tab(step, tab_name): header_css = 'div.inner-wrapper h1' diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py index 7f717b731c54..3a39f3cc15b8 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py @@ -112,7 +112,7 @@ def all_sections_are_expanded(step): @step(u'all sections are collapsed$') -def all_sections_are_expanded(step): +def all_sections_are_collapsed(step): subsection_locator = 'div.subsection-list' subsections = world.css_find(subsection_locator) for s in subsections: diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index d2be2eeea82a..1d9e59cd7203 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -129,9 +129,12 @@ def should_have_link_with_id_and_text(step, link_id, text): assert_equals(link.text, text) -@step(r'should see "(.*)" (?:somewhere|anywhere) in (?:the|this) page') -def should_see_in_the_page(step, text): - assert_in(text, world.css_text('body')) +@step(r'should( not)? see "(.*)" (?:somewhere|anywhere) (?:in|on) (?:the|this) page') +def should_see_in_the_page(step, doesnt_appear, text): + if doesnt_appear: + assert world.browser.is_text_not_present(text, wait_time=5) + else: + assert world.browser.is_text_present(text, wait_time=5) @step('I am logged in$') diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 45252dbb7b09..ecd43eb71953 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -32,8 +32,13 @@ def url_equals(url): @world.absorb -def is_css_present(css_selector): - return world.browser.is_element_present_by_css(css_selector, wait_time=4) +def is_css_present(css_selector, wait_time=5): + return world.browser.is_element_present_by_css(css_selector, wait_time=wait_time) + + +@world.absorb +def is_css_not_present(css_selector, wait_time=5): + return world.browser.is_element_not_present_by_css(css_selector, wait_time=wait_time) @world.absorb @@ -42,11 +47,11 @@ def css_has_text(css_selector, text): @world.absorb -def css_find(css): +def css_find(css, wait_time=5): def is_visible(driver): return EC.visibility_of_element_located((By.CSS_SELECTOR, css,)) - world.browser.is_element_present_by_css(css, 5) + world.browser.is_element_present_by_css(css, wait_time=wait_time) wait_for(is_visible) return world.browser.find_by_css(css) @@ -56,6 +61,7 @@ def css_click(css_selector): """ Perform a click on a CSS selector, retrying if it initially fails """ + assert is_css_present(css_selector) try: world.browser.find_by_css(css_selector).click() @@ -89,6 +95,7 @@ def id_click(elem_id): @world.absorb def css_fill(css_selector, text): + assert is_css_present(css_selector) world.browser.find_by_css(css_selector).first.fill(text) @@ -114,6 +121,7 @@ def css_text(css_selector): @world.absorb def css_visible(css_selector): + assert is_css_present(css_selector) return world.browser.find_by_css(css_selector).visible diff --git a/lms/djangoapps/courseware/features/courseware_common.py b/lms/djangoapps/courseware/features/courseware_common.py index 4e9aa3fb7b55..aff49d2f9d1d 100644 --- a/lms/djangoapps/courseware/features/courseware_common.py +++ b/lms/djangoapps/courseware/features/courseware_common.py @@ -12,7 +12,6 @@ def i_click_on_view_courseware(step): @step('I click on the "([^"]*)" tab$') def i_click_on_the_tab(step, tab_text): world.click_link(tab_text) - world.save_the_html() @step('I visit the courseware URL$') @@ -20,11 +19,6 @@ def i_visit_the_course_info_url(step): world.visit('/courses/MITx/6.002x/2012_Fall/courseware') -@step(u'I do not see "([^"]*)" anywhere on the page') -def i_do_not_see_text_anywhere_on_the_page(step, text): - assert world.browser.is_text_not_present(text) - - @step(u'I am on the dashboard page$') def i_am_on_the_dashboard_page(step): assert world.is_css_present('section.courses') diff --git a/lms/djangoapps/courseware/features/high-level-tabs.feature b/lms/djangoapps/courseware/features/high-level-tabs.feature index c60ec7b374ea..adbe5ec8a367 100644 --- a/lms/djangoapps/courseware/features/high-level-tabs.feature +++ b/lms/djangoapps/courseware/features/high-level-tabs.feature @@ -8,10 +8,7 @@ Scenario: I can navigate to all high - level tabs in a course And The course "6.002x" has extra tab "Custom Tab" And I am logged in And I click on View Courseware - When I click on the "" tab - Then the page title should contain "" - - Examples: + When I click on the tabs then the page title should contain the following titles: | TabName | PageTitle | | Courseware | 6.002x Courseware | | Course Info | 6.002x Course Info | diff --git a/lms/djangoapps/courseware/features/high-level-tabs.py b/lms/djangoapps/courseware/features/high-level-tabs.py new file mode 100644 index 000000000000..056c62780395 --- /dev/null +++ b/lms/djangoapps/courseware/features/high-level-tabs.py @@ -0,0 +1,11 @@ +from lettuce import world, step +from nose.tools import assert_equals + + +@step(u'I click on the tabs then the page title should contain the following titles:') +def i_click_on_the_tab_and_check(step): + for tab_title in step.hashes: + tab_text = tab_title['TabName'] + title = tab_title['PageTitle'] + world.click_link(tab_text) + assert(title in world.browser.title) diff --git a/lms/djangoapps/courseware/features/homepage.feature b/lms/djangoapps/courseware/features/homepage.feature index 2c354acd4992..140f1f8b5fda 100644 --- a/lms/djangoapps/courseware/features/homepage.feature +++ b/lms/djangoapps/courseware/features/homepage.feature @@ -13,9 +13,7 @@ Feature: Homepage for web users Scenario Outline: User can see main parts of the page Given I visit the homepage - Then I should see a link with the id "" called "" - - Examples: + Then I should see the following links and ids | id | Link | | about | About | | jobs | Jobs | @@ -27,9 +25,7 @@ Feature: Homepage for web users # TODO: test according to domain or policy Scenario: User can see the partner institutions Given I visit the homepage - Then I should see "" in the Partners section - - Examples: + Then I should see the following Partners in the Partners section | Partner | | MITx | | HarvardX | diff --git a/lms/djangoapps/courseware/features/homepage.py b/lms/djangoapps/courseware/features/homepage.py index 62e9096e7089..585d1582d781 100644 --- a/lms/djangoapps/courseware/features/homepage.py +++ b/lms/djangoapps/courseware/features/homepage.py @@ -2,11 +2,22 @@ #pylint: disable=W0621 from lettuce import world, step -from nose.tools import assert_in +from nose.tools import assert_in, assert_equals -@step('I should see "([^"]*)" in the Partners section$') -def i_should_see_partner(step, partner): +@step(u'I should see the following Partners in the Partners section') +def i_should_see_partner(step): partners = world.browser.find_by_css(".partner .name span") names = set(span.text for span in partners) - assert_in(partner, names) + for partner in step.hashes: + assert_in(partner['Partner'], names) + + +@step(u'I should see the following links and ids') +def should_see_a_link_called(step): + for link_id_pair in step.hashes: + link_id = link_id_pair['id'] + text = link_id_pair['Link'] + link = world.browser.find_by_id(link_id) + assert len(link) > 0 + assert_equals(link.text, text) diff --git a/lms/djangoapps/courseware/features/problems.feature b/lms/djangoapps/courseware/features/problems.feature index 266ffa3680f3..44d47fcec491 100644 --- a/lms/djangoapps/courseware/features/problems.feature +++ b/lms/djangoapps/courseware/features/problems.feature @@ -84,3 +84,38 @@ Feature: Answer problems | formula | incorrect | | script | correct | | script | incorrect | + + + Scenario: I can answer a problem with one attempt correctly and not reset + Given I am viewing a "multiple choice" problem with "1" attempt + When I answer a "multiple choice" problem "correctly" + Then The "Reset" button does not appear + + Scenario: I can answer a problem with multiple attempts correctly and still reset the problem + Given I am viewing a "multiple choice" problem with "3" attempts + Then I should see "You have used 0 of 3 submissions" somewhere in the page + When I answer a "multiple choice" problem "correctly" + Then The "Reset" button does appear + + Scenario: I can view how many attempts I have left on a problem + Given I am viewing a "multiple choice" problem with "3" attempts + Then I should see "You have used 0 of 3 submissions" somewhere in the page + When I answer a "multiple choice" problem "incorrectly" + And I reset the problem + Then I should see "You have used 1 of 3 submissions" somewhere in the page + When I answer a "multiple choice" problem "incorrectly" + And I reset the problem + Then I should see "You have used 2 of 3 submissions" somewhere in the page + And The "Final Check" button does appear + When I answer a "multiple choice" problem "correctly" + Then The "Reset" button does not appear + + Scenario: I can view and hide the answer if the problem has it: + Given I am viewing a "numerical" that shows the answer "always" + When I press the "Show Answer" button + Then The "Hide Answer" button does appear + And The "Show Answer" button does not appear + And I should see "4.14159" somewhere in the page + When I press the "Hide Answer" button + Then The "Show Answer" button does appear + And I should not see "4.14159" anywhere on the page diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py index 3d538d7ae10d..763914763a97 100644 --- a/lms/djangoapps/courseware/features/problems.py +++ b/lms/djangoapps/courseware/features/problems.py @@ -7,119 +7,42 @@ from lettuce import world, step from lettuce.django import django_url -import random -import textwrap -from common import i_am_registered_for_the_course, \ - TEST_SECTION_NAME, section_location -from capa.tests.response_xml_factory import OptionResponseXMLFactory, \ - ChoiceResponseXMLFactory, MultipleChoiceResponseXMLFactory, \ - StringResponseXMLFactory, NumericalResponseXMLFactory, \ - FormulaResponseXMLFactory, CustomResponseXMLFactory, \ - CodeResponseXMLFactory - -# Factories from capa.tests.response_xml_factory that we will use -# to generate the problem XML, with the keyword args used to configure -# the output. -PROBLEM_FACTORY_DICT = { - 'drop down': { - 'factory': OptionResponseXMLFactory(), - 'kwargs': { - 'question_text': 'The correct answer is Option 2', - 'options': ['Option 1', 'Option 2', 'Option 3', 'Option 4'], - 'correct_option': 'Option 2'}}, - - 'multiple choice': { - 'factory': MultipleChoiceResponseXMLFactory(), - 'kwargs': { - 'question_text': 'The correct answer is Choice 3', - 'choices': [False, False, True, False], - 'choice_names': ['choice_0', 'choice_1', 'choice_2', 'choice_3']}}, - - 'checkbox': { - 'factory': ChoiceResponseXMLFactory(), - 'kwargs': { - 'question_text': 'The correct answer is Choices 1 and 3', - 'choice_type': 'checkbox', - 'choices': [True, False, True, False, False], - 'choice_names': ['Choice 1', 'Choice 2', 'Choice 3', 'Choice 4']}}, - 'radio': { - 'factory': ChoiceResponseXMLFactory(), - 'kwargs': { - 'question_text': 'The correct answer is Choice 3', - 'choice_type': 'radio', - 'choices': [False, False, True, False], - 'choice_names': ['Choice 1', 'Choice 2', 'Choice 3', 'Choice 4']}}, - 'string': { - 'factory': StringResponseXMLFactory(), - 'kwargs': { - 'question_text': 'The answer is "correct string"', - 'case_sensitive': False, - 'answer': 'correct string'}}, - - 'numerical': { - 'factory': NumericalResponseXMLFactory(), - 'kwargs': { - 'question_text': 'The answer is pi + 1', - 'answer': '4.14159', - 'tolerance': '0.00001', - 'math_display': True}}, - - 'formula': { - 'factory': FormulaResponseXMLFactory(), - 'kwargs': { - 'question_text': 'The solution is [mathjax]x^2+2x+y[/mathjax]', - 'sample_dict': {'x': (-100, 100), 'y': (-100, 100)}, - 'num_samples': 10, - 'tolerance': 0.00001, - 'math_display': True, - 'answer': 'x^2+2*x+y'}}, - - 'script': { - 'factory': CustomResponseXMLFactory(), - 'kwargs': { - 'question_text': 'Enter two integers that sum to 10.', - 'cfn': 'test_add_to_ten', - 'expect': '10', - 'num_inputs': 2, - 'script': textwrap.dedent(""" - def test_add_to_ten(expect,ans): - try: - a1=int(ans[0]) - a2=int(ans[1]) - except ValueError: - a1=0 - a2=0 - return (a1+a2)==int(expect) - """)}}, - 'code': { - 'factory': CodeResponseXMLFactory(), - 'kwargs': { - 'question_text': 'Submit code to an external grader', - 'initial_display': 'print "Hello world!"', - 'grader_payload': '{"grader": "ps1/Spring2013/test_grader.py"}', }}, - } - - -def add_problem_to_course(course, problem_type): - ''' - Add a problem to the course we have created using factories. - ''' +from common import i_am_registered_for_the_course, TEST_SECTION_NAME +from problems_setup import PROBLEM_DICT, answer_problem, problem_has_answer, add_problem_to_course + + +@step(u'I am viewing a "([^"]*)" problem with "([^"]*)" attempt') +def view_problem_with_attempts(step, problem_type, attempts): + i_am_registered_for_the_course(step, 'model_course') + + # Ensure that the course has this problem type + add_problem_to_course('model_course', problem_type, {'attempts': attempts}) + + # Go to the one section in the factory-created course + # which should be loaded with the correct problem + chapter_name = TEST_SECTION_NAME.replace(" ", "_") + section_name = chapter_name + url = django_url('/courses/edx/model_course/Test_Course/courseware/%s/%s' % + (chapter_name, section_name)) + + world.browser.visit(url) - assert(problem_type in PROBLEM_FACTORY_DICT) - # Generate the problem XML using capa.tests.response_xml_factory - factory_dict = PROBLEM_FACTORY_DICT[problem_type] - problem_xml = factory_dict['factory'].build_xml(**factory_dict['kwargs']) +@step(u'I am viewing a "([^"]*)" that shows the answer "([^"]*)"') +def view_problem_with_show_answer(step, problem_type, answer): + i_am_registered_for_the_course(step, 'model_course') - # Create a problem item using our generated XML - # We set rerandomize=always in the metadata so that the "Reset" button - # will appear. - template_name = "i4x://edx/templates/problem/Blank_Common_Problem" - world.ItemFactory.create(parent_location=section_location(course), - template=template_name, - display_name=str(problem_type), - data=problem_xml, - metadata={'rerandomize': 'always'}) + # Ensure that the course has this problem type + add_problem_to_course('model_course', problem_type, {'showanswer': answer}) + + # Go to the one section in the factory-created course + # which should be loaded with the correct problem + chapter_name = TEST_SECTION_NAME.replace(" ", "_") + section_name = chapter_name + url = django_url('/courses/edx/model_course/Test_Course/courseware/%s/%s' % + (chapter_name, section_name)) + + world.browser.visit(url) @step(u'I am viewing a "([^"]*)" problem') @@ -153,7 +76,7 @@ def set_external_grader_response(step, correctness): @step(u'I answer a "([^"]*)" problem "([^"]*)ly"') -def answer_problem(step, problem_type, correctness): +def answer_problem_step(step, problem_type, correctness): """ Mark a given problem type correct or incorrect, then submit it. *problem_type* is a string representing the type of problem (e.g. 'drop down') @@ -161,73 +84,18 @@ def answer_problem(step, problem_type, correctness): """ assert(correctness in ['correct', 'incorrect']) - - if problem_type == "drop down": - select_name = "input_i4x-edx-model_course-problem-drop_down_2_1" - option_text = 'Option 2' if correctness == 'correct' else 'Option 3' - world.browser.select(select_name, option_text) - - elif problem_type == "multiple choice": - if correctness == 'correct': - inputfield('multiple choice', choice='choice_2').check() - else: - inputfield('multiple choice', choice='choice_1').check() - - elif problem_type == "checkbox": - if correctness == 'correct': - inputfield('checkbox', choice='choice_0').check() - inputfield('checkbox', choice='choice_2').check() - else: - inputfield('checkbox', choice='choice_3').check() - - elif problem_type == 'radio': - if correctness == 'correct': - inputfield('radio', choice='choice_2').check() - else: - inputfield('radio', choice='choice_1').check() - - elif problem_type == 'string': - textvalue = 'correct string' if correctness == 'correct' \ - else 'incorrect' - inputfield('string').fill(textvalue) - - elif problem_type == 'numerical': - textvalue = "pi + 1" if correctness == 'correct' \ - else str(random.randint(-2, 2)) - inputfield('numerical').fill(textvalue) - - elif problem_type == 'formula': - textvalue = "x^2+2*x+y" if correctness == 'correct' else 'x^2' - inputfield('formula').fill(textvalue) - - elif problem_type == 'script': - # Correct answer is any two integers that sum to 10 - first_addend = random.randint(-100, 100) - second_addend = 10 - first_addend - - # If we want an incorrect answer, then change - # the second addend so they no longer sum to 10 - if correctness == 'incorrect': - second_addend += random.randint(1, 10) - - inputfield('script', input_num=1).fill(str(first_addend)) - inputfield('script', input_num=2).fill(str(second_addend)) - - elif problem_type == 'code': - # The fake xqueue server is configured to respond - # correct / incorrect no matter what we submit. - # Furthermore, since the inline code response uses - # JavaScript to make the code display nicely, it's difficult - # to programatically input text - # (there's not