diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py index 6a60802d07..6394959532 100644 --- a/common/djangoapps/terrain/browser.py +++ b/common/djangoapps/terrain/browser.py @@ -6,7 +6,7 @@ import time # Let the LMS and CMS do their one-time setup # For example, setting up mongo caches from lms import one_time_startup -from cms import one_time_startup +from cms import one_time_startup logger = getLogger(__name__) logger.info("Loading the lettuce acceptance testing terrain file...") @@ -21,6 +21,7 @@ def initial_setup(server): # world.browser = Browser('phantomjs') # world.browser = Browser('firefox') + @before.each_scenario def reset_data(scenario): # Clean out the django test database defined in the diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index 5ea34d1190..c36bf935f1 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -154,7 +154,7 @@ class XModuleItemFactory(Factory): # If a display name is set, use that dest_name = display_name.replace(" ", "_") if display_name is not None else uuid4().hex - dest_location = parent_location._replace(category=template.category, + dest_location = parent_location._replace(category=template.category, name=dest_name) new_item = store.clone_item(template, dest_location) diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index 50fe0faf39..52eeb23c4a 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -101,6 +101,7 @@ def i_am_an_edx_user(step): #### helper functions + @world.absorb def scroll_to_bottom(): # Maximize the browser diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py index 08ed1ca668..aa401b70cd 100644 --- a/common/lib/capa/capa/tests/response_xml_factory.py +++ b/common/lib/capa/capa/tests/response_xml_factory.py @@ -1,6 +1,7 @@ from lxml import etree from abc import ABCMeta, abstractmethod + class ResponseXMLFactory(object): """ Abstract base class for capa response XML factories. Subclasses override create_response_element and @@ -13,7 +14,7 @@ class ResponseXMLFactory(object): """ Subclasses override to return an etree element representing the capa response XML (e.g. ). - + The tree should NOT contain any input elements (such as ) as these will be added later.""" return None @@ -25,7 +26,7 @@ class ResponseXMLFactory(object): return None def build_xml(self, **kwargs): - """ Construct an XML string for a capa response + """ Construct an XML string for a capa response based on **kwargs. **kwargs is a dictionary that will be passed @@ -37,7 +38,7 @@ class ResponseXMLFactory(object): *question_text*: The text of the question to display, wrapped in

tags. - + *explanation_text*: The detailed explanation that will be shown if the user answers incorrectly. @@ -75,7 +76,7 @@ class ResponseXMLFactory(object): for i in range(0, int(num_responses)): response_element = self.create_response_element(**kwargs) root.append(response_element) - + # Add input elements for j in range(0, int(num_inputs)): input_element = self.create_input_element(**kwargs) @@ -135,7 +136,7 @@ class ResponseXMLFactory(object): # Names of group elements group_element_names = {'checkbox': 'checkboxgroup', 'radio': 'radiogroup', - 'multiple': 'choicegroup' } + 'multiple': 'choicegroup'} # Retrieve **kwargs choices = kwargs.get('choices', [True]) @@ -215,7 +216,7 @@ class CustomResponseXMLFactory(ResponseXMLFactory): *answer*: Inline script that calculates the answer """ - + # Retrieve **kwargs cfn = kwargs.get('cfn', None) expect = kwargs.get('expect', None) @@ -245,7 +246,7 @@ class SchematicResponseXMLFactory(ResponseXMLFactory): def create_response_element(self, **kwargs): """ Create the XML element. - + Uses *kwargs*: *answer*: The Python script used to evaluate the answer. @@ -272,6 +273,7 @@ class SchematicResponseXMLFactory(ResponseXMLFactory): For testing, we create a bare-bones version of .""" return etree.Element("schematic") + class CodeResponseXMLFactory(ResponseXMLFactory): """ Factory for creating XML trees """ @@ -284,9 +286,9 @@ class CodeResponseXMLFactory(ResponseXMLFactory): def create_response_element(self, **kwargs): """ Create a XML element: - + Uses **kwargs: - + *initial_display*: The code that initially appears in the textbox [DEFAULT: "Enter code here"] *answer_display*: The answer to display to the student @@ -326,6 +328,7 @@ class CodeResponseXMLFactory(ResponseXMLFactory): # return None here return None + class ChoiceResponseXMLFactory(ResponseXMLFactory): """ Factory for creating XML trees """ @@ -354,13 +357,13 @@ class FormulaResponseXMLFactory(ResponseXMLFactory): *num_samples*: The number of times to sample the student's answer to numerically compare it to the correct answer. - + *tolerance*: The tolerance within which answers will be accepted - [DEFAULT: 0.01] + [DEFAULT: 0.01] *answer*: The answer to the problem. Can be a formula string - or a Python variable defined in a script - (e.g. "$calculated_answer" for a Python variable + or a Python variable defined in a script + (e.g. "$calculated_answer" for a Python variable called calculated_answer) [REQUIRED] @@ -385,7 +388,7 @@ class FormulaResponseXMLFactory(ResponseXMLFactory): # Set the sample information sample_str = self._sample_str(sample_dict, num_samples, tolerance) response_element.set("samples", sample_str) - + # Set the tolerance responseparam_element = etree.SubElement(response_element, "responseparam") @@ -406,7 +409,7 @@ class FormulaResponseXMLFactory(ResponseXMLFactory): # We could sample a different range, but for simplicity, # we use the same sample string for the hints - # that we used previously. + # that we used previously. formulahint_element.set("samples", sample_str) formulahint_element.set("answer", str(hint_prompt)) @@ -434,10 +437,11 @@ class FormulaResponseXMLFactory(ResponseXMLFactory): high_range_vals = [str(f[1]) for f in sample_dict.values()] sample_str = (",".join(sample_dict.keys()) + "@" + ",".join(low_range_vals) + ":" + - ",".join(high_range_vals) + + ",".join(high_range_vals) + "#" + str(num_samples)) return sample_str + class ImageResponseXMLFactory(ResponseXMLFactory): """ Factory for producing XML """ @@ -448,9 +452,9 @@ class ImageResponseXMLFactory(ResponseXMLFactory): def create_input_element(self, **kwargs): """ Create the element. - + Uses **kwargs: - + *src*: URL for the image file [DEFAULT: "/static/image.jpg"] *width*: Width of the image [DEFAULT: 100] @@ -488,7 +492,7 @@ class ImageResponseXMLFactory(ResponseXMLFactory): input_element.set("src", str(src)) input_element.set("width", str(width)) input_element.set("height", str(height)) - + if rectangle: input_element.set("rectangle", rectangle) @@ -497,6 +501,7 @@ class ImageResponseXMLFactory(ResponseXMLFactory): return input_element + class JavascriptResponseXMLFactory(ResponseXMLFactory): """ Factory for producing XML """ @@ -520,7 +525,7 @@ class JavascriptResponseXMLFactory(ResponseXMLFactory): # Both display_src and display_class given, # or neither given - assert((display_src and display_class) or + assert((display_src and display_class) or (not display_src and not display_class)) # Create the element @@ -550,6 +555,7 @@ class JavascriptResponseXMLFactory(ResponseXMLFactory): """ Create the element """ return etree.Element("javascriptinput") + class MultipleChoiceResponseXMLFactory(ResponseXMLFactory): """ Factory for producing XML """ @@ -562,6 +568,7 @@ class MultipleChoiceResponseXMLFactory(ResponseXMLFactory): kwargs['choice_type'] = 'multiple' return ResponseXMLFactory.choicegroup_input_xml(**kwargs) + class TrueFalseResponseXMLFactory(ResponseXMLFactory): """ Factory for producing XML """ @@ -574,6 +581,7 @@ class TrueFalseResponseXMLFactory(ResponseXMLFactory): kwargs['choice_type'] = 'multiple' return ResponseXMLFactory.choicegroup_input_xml(**kwargs) + class OptionResponseXMLFactory(ResponseXMLFactory): """ Factory for producing XML""" @@ -618,7 +626,7 @@ class StringResponseXMLFactory(ResponseXMLFactory): def create_response_element(self, **kwargs): """ Create a XML element. - + Uses **kwargs: *answer*: The correct answer (a string) [REQUIRED] @@ -640,7 +648,7 @@ class StringResponseXMLFactory(ResponseXMLFactory): # Create the element response_element = etree.Element("stringresponse") - # Set the answer attribute + # Set the answer attribute response_element.set("answer", str(answer)) # Set the case sensitivity @@ -665,6 +673,7 @@ class StringResponseXMLFactory(ResponseXMLFactory): def create_input_element(self, **kwargs): return ResponseXMLFactory.textline_input_xml(**kwargs) + class AnnotationResponseXMLFactory(ResponseXMLFactory): """ Factory for creating XML trees """ def create_response_element(self, **kwargs): @@ -677,17 +686,17 @@ class AnnotationResponseXMLFactory(ResponseXMLFactory): input_element = etree.Element("annotationinput") text_children = [ - {'tag': 'title', 'text': kwargs.get('title', 'super cool annotation') }, - {'tag': 'text', 'text': kwargs.get('text', 'texty text') }, - {'tag': 'comment', 'text':kwargs.get('comment', 'blah blah erudite comment blah blah') }, - {'tag': 'comment_prompt', 'text': kwargs.get('comment_prompt', 'type a commentary below') }, - {'tag': 'tag_prompt', 'text': kwargs.get('tag_prompt', 'select one tag') } + {'tag': 'title', 'text': kwargs.get('title', 'super cool annotation')}, + {'tag': 'text', 'text': kwargs.get('text', 'texty text')}, + {'tag': 'comment', 'text':kwargs.get('comment', 'blah blah erudite comment blah blah')}, + {'tag': 'comment_prompt', 'text': kwargs.get('comment_prompt', 'type a commentary below')}, + {'tag': 'tag_prompt', 'text': kwargs.get('tag_prompt', 'select one tag')} ] for child in text_children: etree.SubElement(input_element, child['tag']).text = child['text'] - default_options = [('green', 'correct'),('eggs', 'incorrect'),('ham', 'partially-correct')] + default_options = [('green', 'correct'),('eggs', 'incorrect'), ('ham', 'partially-correct')] options = kwargs.get('options', default_options) options_element = etree.SubElement(input_element, 'options') @@ -696,4 +705,3 @@ class AnnotationResponseXMLFactory(ResponseXMLFactory): option_element.text = description return input_element - diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index 4f307511df..8fb2843656 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -89,6 +89,7 @@ TEST_COURSE_ORG = 'edx' TEST_COURSE_NAME = 'Test Course' TEST_SECTION_NAME = "Problem" + @step(u'The course "([^"]*)" exists$') def create_course(step, course): @@ -100,7 +101,7 @@ def create_course(step, course): # Create the course # We always use the same org and display name, # but vary the course identifier (e.g. 600x or 191x) - course = CourseFactory.create(org=TEST_COURSE_ORG, + course = CourseFactory.create(org=TEST_COURSE_ORG, number=course, display_name=TEST_COURSE_NAME) @@ -112,6 +113,7 @@ def create_course(step, course): template='i4x://edx/templates/sequential/Empty', display_name=TEST_SECTION_NAME) + @step(u'I am registered for the course "([^"]*)"$') def i_am_registered_for_the_course(step, course): # Create the course @@ -126,6 +128,7 @@ def i_am_registered_for_the_course(step, course): world.log_in('robot@edx.org', 'test') + @step(u'The course "([^"]*)" has extra tab "([^"]*)"$') def add_tab_to_course(step, course, extra_tab_name): section_item = ItemFactory.create(parent_location=course_location(course), @@ -155,10 +158,12 @@ def flush_xmodule_store(): modulestore().collection.drop() update_templates() + def course_id(course_num): - return "%s/%s/%s" % (TEST_COURSE_ORG, course_num, + return "%s/%s/%s" % (TEST_COURSE_ORG, course_num, TEST_COURSE_NAME.replace(" ", "_")) + def course_location(course_num): return Location(loc_or_tag="i4x", org=TEST_COURSE_ORG, @@ -166,6 +171,7 @@ def course_location(course_num): category='course', name=TEST_COURSE_NAME.replace(" ", "_")) + def section_location(course_num): return Location(loc_or_tag="i4x", org=TEST_COURSE_ORG, diff --git a/lms/djangoapps/courseware/features/courses.py b/lms/djangoapps/courseware/features/courses.py index eb5143b782..4fbbfd24f2 100644 --- a/lms/djangoapps/courseware/features/courses.py +++ b/lms/djangoapps/courseware/features/courses.py @@ -9,6 +9,7 @@ logger = getLogger(__name__) ## support functions + def get_courses(): ''' Returns dict of lists of courses available, keyed by course.org (ie university). diff --git a/lms/djangoapps/courseware/features/high-level-tabs.feature b/lms/djangoapps/courseware/features/high-level-tabs.feature index 102f752e1f..931281a455 100644 --- a/lms/djangoapps/courseware/features/high-level-tabs.feature +++ b/lms/djangoapps/courseware/features/high-level-tabs.feature @@ -3,7 +3,7 @@ Feature: All the high level tabs should work As a student I want to navigate through the high level tabs -Scenario: I can navigate to all high-level tabs in a course +Scenario: I can navigate to all high -level tabs in a course Given: I am registered for the course "6.002x" And The course "6.002x" has extra tab "Custom Tab" And I log in diff --git a/lms/djangoapps/courseware/features/homepage.feature b/lms/djangoapps/courseware/features/homepage.feature index 06a45c4bfa..c0c1c32f02 100644 --- a/lms/djangoapps/courseware/features/homepage.feature +++ b/lms/djangoapps/courseware/features/homepage.feature @@ -39,9 +39,9 @@ Feature: Homepage for web users | MITx | | HarvardX | | BerkeleyX | - | UTx | + | UTx | | WellesleyX | - | GeorgetownX | + | GeorgetownX | # # TODO: Add scenario that tests the courses available # # using a policy or a configuration file diff --git a/lms/djangoapps/courseware/features/login.py b/lms/djangoapps/courseware/features/login.py index ca7d710c61..094db078ca 100644 --- a/lms/djangoapps/courseware/features/login.py +++ b/lms/djangoapps/courseware/features/login.py @@ -34,6 +34,7 @@ def click_the_dropdown(step): #### helper functions + def user_is_an_unactivated_user(uname): u = User.objects.get(username=uname) u.is_active = False diff --git a/lms/djangoapps/courseware/features/openended.feature b/lms/djangoapps/courseware/features/openended.feature index cc9f6e1c5f..1ab496144f 100644 --- a/lms/djangoapps/courseware/features/openended.feature +++ b/lms/djangoapps/courseware/features/openended.feature @@ -3,10 +3,10 @@ Feature: Open ended grading In order to complete the courseware questions I want the machine learning grading to be functional - # Commenting these all out right now until we can + # Commenting these all out right now until we can # make a reference implementation for a course with # an open ended grading problem that is always available - # + # # Scenario: An answer that is too short is rejected # Given I navigate to an openended question # And I enter the answer "z" diff --git a/lms/djangoapps/courseware/features/problems.feature b/lms/djangoapps/courseware/features/problems.feature index 8828ebc699..a7fbac49c7 100644 --- a/lms/djangoapps/courseware/features/problems.feature +++ b/lms/djangoapps/courseware/features/problems.feature @@ -35,7 +35,7 @@ Feature: Answer choice problems Scenario: I can submit a blank answer Given I am viewing a "" problem - When I check a problem + When I check a problem Then My "" answer is marked "incorrect" Examples: diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py index 0b5ecbe20a..a6575c3d22 100644 --- a/lms/djangoapps/courseware/features/problems.py +++ b/lms/djangoapps/courseware/features/problems.py @@ -13,7 +13,7 @@ from capa.tests.response_xml_factory import OptionResponseXMLFactory, \ # 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 = { +PROBLEM_FACTORY_DICT = { 'drop down': { 'factory': OptionResponseXMLFactory(), 'kwargs': { @@ -32,8 +32,8 @@ problem_factory_dict = { 'factory': ChoiceResponseXMLFactory(), 'kwargs': { 'question_text': 'The correct answer is Choices 1 and 3', - 'choice_type':'checkbox', - 'choices':[True, False, True, False, False], + 'choice_type': 'checkbox', + 'choices': [True, False, True, False, False], 'choice_names': ['Choice 1', 'Choice 2', 'Choice 3', 'Choice 4']}}, 'string': { @@ -41,7 +41,7 @@ problem_factory_dict = { 'kwargs': { 'question_text': 'The answer is "correct string"', 'case_sensitive': False, - 'answer': 'correct string' }}, + 'answer': 'correct string'}}, 'numerical': { 'factory': NumericalResponseXMLFactory(), @@ -49,13 +49,13 @@ problem_factory_dict = { 'question_text': 'The answer is pi + 1', 'answer': '4.14159', 'tolerance': '0.00001', - 'math_display': True }}, + '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) }, + 'sample_dict': {'x': (-100, 100), 'y': (-100, 100)}, 'num_samples': 10, 'tolerance': 0.00001, 'math_display': True, @@ -77,15 +77,16 @@ problem_factory_dict = { a1=0 a2=0 return (a1+a2)==int(expect) - """) }}, + """)}}, } + def add_problem_to_course(course, problem_type): - assert(problem_type in problem_factory_dict) + assert(problem_type in PROBLEM_FACTORY_DICT) # Generate the problem XML using capa.tests.response_xml_factory - factory_dict = problem_factory_dict[problem_type] + factory_dict = PROBLEM_FACTORY_DICT[problem_type] problem_xml = factory_dict['factory'].build_xml(**factory_dict['kwargs']) # Create a problem item using our generated XML @@ -95,7 +96,8 @@ def add_problem_to_course(course, problem_type): template="i4x://edx/templates/problem/Blank_Common_Problem", display_name=str(problem_type), data=problem_xml, - metadata={'rerandomize':'always'}) + metadata={'rerandomize': 'always'}) + @step(u'I am viewing a "([^"]*)" problem') def view_problem(step, problem_type): @@ -108,9 +110,9 @@ def view_problem(step, problem_type): # 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' % + url = django_url('/courses/edx/model_course/Test_Course/courseware/%s/%s' % (chapter_name, section_name)) - + world.browser.visit(url) @@ -147,7 +149,7 @@ def answer_problem(step, problem_type, correctness): inputfield('string').fill(textvalue) elif problem_type == 'numerical': - textvalue = "pi + 1" if correctness == 'correct' else str(random.randint(-2,2)) + textvalue = "pi + 1" if correctness == 'correct' else str(random.randint(-2, 2)) inputfield('numerical').fill(textvalue) elif problem_type == 'formula': @@ -170,14 +172,17 @@ def answer_problem(step, problem_type, correctness): # Submit the problem check_problem(step) + @step(u'I check a problem') def check_problem(step): world.browser.find_by_css("input.check").click() + @step(u'I reset the problem') def reset_problem(step): world.browser.find_by_css('input.reset').click() + @step(u'My "([^"]*)" answer is marked "([^"]*)"') def assert_answer_mark(step, problem_type, correctness): """ Assert that the expected answer mark is visible for a given problem type. @@ -190,28 +195,28 @@ def assert_answer_mark(step, problem_type, correctness): This can occur, for example, if the user has reset the problem. """ # Dictionaries that map problem types to the css selectors - # for correct/incorrect marks. + # for correct/incorrect marks. # The elements are lists of selectors because a particular problem type - # might be marked in multiple ways. - # For example, multiple choice is marked incorrect differently - # depending on whether the user selects an incorrect + # might be marked in multiple ways. + # For example, multiple choice is marked incorrect differently + # depending on whether the user selects an incorrect # item or submits without selecting any item) - correct_selectors = { 'drop down': ['span.correct'], + correct_selectors = {'drop down': ['span.correct'], 'multiple choice': ['label.choicegroup_correct'], 'checkbox': ['span.correct'], 'string': ['div.correct'], 'numerical': ['div.correct'], - 'formula': ['div.correct'], + 'formula': ['div.correct'], 'script': ['div.correct'], } - incorrect_selectors = { 'drop down': ['span.incorrect'], - 'multiple choice': ['label.choicegroup_incorrect', + incorrect_selectors = {'drop down': ['span.incorrect'], + 'multiple choice': ['label.choicegroup_incorrect', 'span.incorrect'], 'checkbox': ['span.incorrect'], 'string': ['div.incorrect'], 'numerical': ['div.incorrect'], - 'formula': ['div.incorrect'], - 'script': ['div.incorrect'] } + 'formula': ['div.incorrect'], + 'script': ['div.incorrect']} assert(correctness in ['correct', 'incorrect', 'unanswered']) assert(problem_type in correct_selectors and problem_type in incorrect_selectors) @@ -252,7 +257,7 @@ def inputfield(problem_type, choice=None, input_num=1): *choice* is the name of the checkbox input in a group of checkboxes. """ - sel = ("input#input_i4x-edx-model_course-problem-%s_2_%s" % + sel = ("input#input_i4x-edx-model_course-problem-%s_2_%s" % (problem_type.replace(" ", "_"), str(input_num))) if choice is not None: diff --git a/lms/djangoapps/courseware/features/registration.py b/lms/djangoapps/courseware/features/registration.py index 9587842dd6..94b9b50f6c 100644 --- a/lms/djangoapps/courseware/features/registration.py +++ b/lms/djangoapps/courseware/features/registration.py @@ -2,6 +2,7 @@ from lettuce import world, step from lettuce.django import django_url from common import TEST_COURSE_ORG, TEST_COURSE_NAME + @step('I register for the course "([^"]*)"$') def i_register_for_the_course(step, course): cleaned_name = TEST_COURSE_NAME.replace(' ', '_') diff --git a/lms/djangoapps/courseware/features/smart-accordion.feature b/lms/djangoapps/courseware/features/smart-accordion.feature index ccf1d45601..fc51eca25d 100644 --- a/lms/djangoapps/courseware/features/smart-accordion.feature +++ b/lms/djangoapps/courseware/features/smart-accordion.feature @@ -60,4 +60,4 @@ Feature: There are courses on the homepage # Scenario: Navigate through course BerkeleyX/CS184.1x/2012_Fall # Given I am registered for course "BerkeleyX/CS184.1x/2012_Fall" # And I log in - # Then I verify all the content of each course \ No newline at end of file + # Then I verify all the content of each course