Merge pull request #10 from edx/jonahstanley/add-more-tests
Jonahstanley/add more tests
This commit is contained in:
1
AUTHORS
1
AUTHORS
@@ -72,4 +72,5 @@ Giulio Gratta <giulio@giuliogratta.com>
|
||||
David Baumgold <david@davidbaumgold.com>
|
||||
Jason Bau <jbau@stanford.edu>
|
||||
Frances Botsford <frances@edx.org>
|
||||
Jonah Stanley <Jonah_Stanley@brown.edu>
|
||||
Slater Victoroff <slater.r.victoroff@gmail.com>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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$')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 "<TabName>" tab
|
||||
Then the page title should contain "<PageTitle>"
|
||||
|
||||
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 |
|
||||
|
||||
11
lms/djangoapps/courseware/features/high-level-tabs.py
Normal file
11
lms/djangoapps/courseware/features/high-level-tabs.py
Normal file
@@ -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)
|
||||
@@ -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 "<id>" called "<Link>"
|
||||
|
||||
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 "<Partner>" in the Partners section
|
||||
|
||||
Examples:
|
||||
Then I should see the following Partners in the Partners section
|
||||
| Partner |
|
||||
| MITx |
|
||||
| HarvardX |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,119 +7,42 @@ Steps for problem.feature lettuce tests
|
||||
|
||||
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"}', }},
|
||||
}
|
||||
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
|
||||
|
||||
|
||||
def add_problem_to_course(course, problem_type):
|
||||
'''
|
||||
Add a problem to the course we have created using factories.
|
||||
'''
|
||||
@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')
|
||||
|
||||
assert(problem_type in PROBLEM_FACTORY_DICT)
|
||||
# Ensure that the course has this problem type
|
||||
add_problem_to_course('model_course', problem_type, {'attempts': attempts})
|
||||
|
||||
# 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'])
|
||||
# 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))
|
||||
|
||||
# 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'})
|
||||
world.browser.visit(url)
|
||||
|
||||
|
||||
@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')
|
||||
|
||||
# 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 <textarea> we can just fill text into)
|
||||
# For this reason, we submit the initial code in the response
|
||||
# (configured in the problem XML above)
|
||||
pass
|
||||
assert(problem_type in PROBLEM_DICT)
|
||||
answer_problem(problem_type, correctness)
|
||||
|
||||
# Submit the problem
|
||||
check_problem(step)
|
||||
|
||||
|
||||
@step(u'I check a problem')
|
||||
def check_problem(step):
|
||||
world.css_click("input.check")
|
||||
|
||||
|
||||
@step(u'The "([^"]*)" problem displays a "([^"]*)" answer')
|
||||
def assert_problem_has_answer(step, problem_type, answer_class):
|
||||
'''
|
||||
@@ -239,67 +107,8 @@ def assert_problem_has_answer(step, problem_type, answer_class):
|
||||
by setting answer_class='blank'
|
||||
'''
|
||||
assert answer_class in ['correct', 'incorrect', 'blank']
|
||||
|
||||
if problem_type == "drop down":
|
||||
if answer_class == 'blank':
|
||||
assert world.browser.is_element_not_present_by_css('option[selected="true"]')
|
||||
else:
|
||||
actual = world.browser.find_by_css('option[selected="true"]').value
|
||||
expected = 'Option 2' if answer_class == 'correct' else 'Option 3'
|
||||
assert actual == expected
|
||||
|
||||
elif problem_type == "multiple choice":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('multiple choice', ['choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('multiple choice', ['choice_1'])
|
||||
else:
|
||||
assert_checked('multiple choice', [])
|
||||
|
||||
elif problem_type == "checkbox":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('checkbox', ['choice_0', 'choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('checkbox', ['choice_3'])
|
||||
else:
|
||||
assert_checked('checkbox', [])
|
||||
|
||||
elif problem_type == "radio":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('radio', ['choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('radio', ['choice_1'])
|
||||
else:
|
||||
assert_checked('radio', [])
|
||||
|
||||
elif problem_type == 'string':
|
||||
if answer_class == 'blank':
|
||||
expected = ''
|
||||
else:
|
||||
expected = 'correct string' if answer_class == 'correct' \
|
||||
else 'incorrect'
|
||||
|
||||
assert_textfield('string', expected)
|
||||
|
||||
elif problem_type == 'formula':
|
||||
if answer_class == 'blank':
|
||||
expected = ''
|
||||
else:
|
||||
expected = "x^2+2*x+y" if answer_class == 'correct' else 'x^2'
|
||||
|
||||
assert_textfield('formula', expected)
|
||||
|
||||
else:
|
||||
# The other response types use random data,
|
||||
# which would be difficult to check
|
||||
# We trade input value coverage in the other tests for
|
||||
# input type coverage in this test.
|
||||
pass
|
||||
|
||||
|
||||
@step(u'I check a problem')
|
||||
def check_problem(step):
|
||||
world.css_click("input.check")
|
||||
assert problem_type in PROBLEM_DICT
|
||||
problem_has_answer(problem_type, answer_class)
|
||||
|
||||
|
||||
@step(u'I reset the problem')
|
||||
@@ -307,45 +116,13 @@ def reset_problem(step):
|
||||
world.css_click('input.reset')
|
||||
|
||||
|
||||
# Dictionaries that map problem types to the css selectors
|
||||
# for correct/incorrect/unanswered 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
|
||||
# item or submits without selecting any item)
|
||||
CORRECTNESS_SELECTORS = {
|
||||
'correct': {'drop down': ['span.correct'],
|
||||
'multiple choice': ['label.choicegroup_correct'],
|
||||
'checkbox': ['span.correct'],
|
||||
'radio': ['label.choicegroup_correct'],
|
||||
'string': ['div.correct'],
|
||||
'numerical': ['div.correct'],
|
||||
'formula': ['div.correct'],
|
||||
'script': ['div.correct'],
|
||||
'code': ['span.correct']},
|
||||
|
||||
'incorrect': {'drop down': ['span.incorrect'],
|
||||
'multiple choice': ['label.choicegroup_incorrect',
|
||||
'span.incorrect'],
|
||||
'checkbox': ['span.incorrect'],
|
||||
'radio': ['label.choicegroup_incorrect',
|
||||
'span.incorrect'],
|
||||
'string': ['div.incorrect'],
|
||||
'numerical': ['div.incorrect'],
|
||||
'formula': ['div.incorrect'],
|
||||
'script': ['div.incorrect'],
|
||||
'code': ['span.incorrect']},
|
||||
|
||||
'unanswered': {'drop down': ['span.unanswered'],
|
||||
'multiple choice': ['span.unanswered'],
|
||||
'checkbox': ['span.unanswered'],
|
||||
'radio': ['span.unanswered'],
|
||||
'string': ['div.unanswered'],
|
||||
'numerical': ['div.unanswered'],
|
||||
'formula': ['div.unanswered'],
|
||||
'script': ['div.unanswered'],
|
||||
'code': ['span.unanswered']}}
|
||||
@step(u'The "([^"]*)" button does( not)? appear')
|
||||
def action_button_present(step, buttonname, doesnt_appear):
|
||||
button_css = 'section.action input[value*="%s"]' % buttonname
|
||||
if doesnt_appear:
|
||||
assert world.is_css_not_present(button_css)
|
||||
else:
|
||||
assert world.is_css_present(button_css)
|
||||
|
||||
|
||||
@step(u'My "([^"]*)" answer is marked "([^"]*)"')
|
||||
@@ -359,12 +136,11 @@ def assert_answer_mark(step, problem_type, correctness):
|
||||
"""
|
||||
|
||||
# Determine which selector(s) to look for based on correctness
|
||||
assert(correctness in CORRECTNESS_SELECTORS)
|
||||
selector_dict = CORRECTNESS_SELECTORS[correctness]
|
||||
assert(problem_type in selector_dict)
|
||||
assert(correctness in ['correct', 'incorrect', 'unanswered'])
|
||||
assert(problem_type in PROBLEM_DICT)
|
||||
|
||||
# At least one of the correct selectors should be present
|
||||
for sel in selector_dict[problem_type]:
|
||||
for sel in PROBLEM_DICT[problem_type][correctness]:
|
||||
has_expected = world.is_css_present(sel)
|
||||
|
||||
# As soon as we find the selector, break out of the loop
|
||||
@@ -373,49 +149,3 @@ def assert_answer_mark(step, problem_type, correctness):
|
||||
|
||||
# Expect that we found the expected selector
|
||||
assert(has_expected)
|
||||
|
||||
|
||||
def inputfield(problem_type, choice=None, input_num=1):
|
||||
""" Return the <input> element for *problem_type*.
|
||||
For example, if problem_type is 'string', return
|
||||
the text field for the string problem in the test course.
|
||||
|
||||
*choice* is the name of the checkbox input in a group
|
||||
of checkboxes. """
|
||||
|
||||
sel = ("input#input_i4x-edx-model_course-problem-%s_2_%s" %
|
||||
(problem_type.replace(" ", "_"), str(input_num)))
|
||||
|
||||
if choice is not None:
|
||||
base = "_choice_" if problem_type == "multiple choice" else "_"
|
||||
sel = sel + base + str(choice)
|
||||
|
||||
|
||||
# If the input element doesn't exist, fail immediately
|
||||
assert world.is_css_present(sel)
|
||||
|
||||
# Retrieve the input element
|
||||
return world.browser.find_by_css(sel)
|
||||
|
||||
|
||||
def assert_checked(problem_type, choices):
|
||||
'''
|
||||
Assert that choice names given in *choices* are the only
|
||||
ones checked.
|
||||
|
||||
Works for both radio and checkbox problems
|
||||
'''
|
||||
|
||||
all_choices = ['choice_0', 'choice_1', 'choice_2', 'choice_3']
|
||||
for this_choice in all_choices:
|
||||
element = inputfield(problem_type, choice=this_choice)
|
||||
|
||||
if this_choice in choices:
|
||||
assert element.checked
|
||||
else:
|
||||
assert not element.checked
|
||||
|
||||
|
||||
def assert_textfield(problem_type, expected_text, input_num=1):
|
||||
element = inputfield(problem_type, input_num=input_num)
|
||||
assert element.value == expected_text
|
||||
|
||||
325
lms/djangoapps/courseware/features/problems_setup.py
Normal file
325
lms/djangoapps/courseware/features/problems_setup.py
Normal file
@@ -0,0 +1,325 @@
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
#EVERY PROBLEM TYPE MUST HAVE THE FOLLOWING:
|
||||
# -Section in Dictionary containing:
|
||||
# -factory
|
||||
# -kwargs
|
||||
# -(optional metadata)
|
||||
# -Correct, Incorrect and Unanswered CSS selectors
|
||||
# -A way to answer the problem correctly and incorrectly
|
||||
# -A way to check the problem was answered correctly, incorrectly and blank
|
||||
|
||||
from lettuce import world
|
||||
import random
|
||||
import textwrap
|
||||
from common import 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_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'},
|
||||
'correct': ['span.correct'],
|
||||
'incorrect': ['span.incorrect'],
|
||||
'unanswered': ['span.unanswered']},
|
||||
|
||||
'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']},
|
||||
'correct': ['label.choicegroup_correct', 'span.correct'],
|
||||
'incorrect': ['label.choicegroup_incorrect', 'span.incorrect'],
|
||||
'unanswered': ['span.unanswered']},
|
||||
|
||||
'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']},
|
||||
'correct': ['span.correct'],
|
||||
'incorrect': ['span.incorrect'],
|
||||
'unanswered': ['span.unanswered']},
|
||||
|
||||
'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']},
|
||||
'correct': ['label.choicegroup_correct', 'span.correct'],
|
||||
'incorrect': ['label.choicegroup_incorrect', 'span.incorrect'],
|
||||
'unanswered': ['span.unanswered']},
|
||||
|
||||
'string': {
|
||||
'factory': StringResponseXMLFactory(),
|
||||
'kwargs': {
|
||||
'question_text': 'The answer is "correct string"',
|
||||
'case_sensitive': False,
|
||||
'answer': 'correct string'},
|
||||
'correct': ['div.correct'],
|
||||
'incorrect': ['div.incorrect'],
|
||||
'unanswered': ['div.unanswered']},
|
||||
|
||||
'numerical': {
|
||||
'factory': NumericalResponseXMLFactory(),
|
||||
'kwargs': {
|
||||
'question_text': 'The answer is pi + 1',
|
||||
'answer': '4.14159',
|
||||
'tolerance': '0.00001',
|
||||
'math_display': True},
|
||||
'correct': ['div.correct'],
|
||||
'incorrect': ['div.incorrect'],
|
||||
'unanswered': ['div.unanswered']},
|
||||
|
||||
'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'},
|
||||
'correct': ['div.correct'],
|
||||
'incorrect': ['div.incorrect'],
|
||||
'unanswered': ['div.unanswered']},
|
||||
|
||||
'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)
|
||||
""")},
|
||||
'correct': ['div.correct'],
|
||||
'incorrect': ['div.incorrect'],
|
||||
'unanswered': ['div.unanswered']},
|
||||
|
||||
'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"}', },
|
||||
'correct': ['span.correct'],
|
||||
'incorrect': ['span.incorrect'],
|
||||
'unanswered': ['span.unanswered']}
|
||||
}
|
||||
|
||||
|
||||
def answer_problem(problem_type, correctness):
|
||||
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 <textarea> we can just fill text into)
|
||||
# For this reason, we submit the initial code in the response
|
||||
# (configured in the problem XML above)
|
||||
pass
|
||||
|
||||
|
||||
def problem_has_answer(problem_type, answer_class):
|
||||
if problem_type == "drop down":
|
||||
if answer_class == 'blank':
|
||||
assert world.browser.is_element_not_present_by_css('option[selected="true"]')
|
||||
else:
|
||||
actual = world.browser.find_by_css('option[selected="true"]').value
|
||||
expected = 'Option 2' if answer_class == 'correct' else 'Option 3'
|
||||
assert actual == expected
|
||||
|
||||
elif problem_type == "multiple choice":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('multiple choice', ['choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('multiple choice', ['choice_1'])
|
||||
else:
|
||||
assert_checked('multiple choice', [])
|
||||
|
||||
elif problem_type == "checkbox":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('checkbox', ['choice_0', 'choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('checkbox', ['choice_3'])
|
||||
else:
|
||||
assert_checked('checkbox', [])
|
||||
|
||||
elif problem_type == "radio":
|
||||
if answer_class == 'correct':
|
||||
assert_checked('radio', ['choice_2'])
|
||||
elif answer_class == 'incorrect':
|
||||
assert_checked('radio', ['choice_1'])
|
||||
else:
|
||||
assert_checked('radio', [])
|
||||
|
||||
elif problem_type == 'string':
|
||||
if answer_class == 'blank':
|
||||
expected = ''
|
||||
else:
|
||||
expected = 'correct string' if answer_class == 'correct' else 'incorrect'
|
||||
assert_textfield('string', expected)
|
||||
|
||||
elif problem_type == 'formula':
|
||||
if answer_class == 'blank':
|
||||
expected = ''
|
||||
else:
|
||||
expected = "x^2+2*x+y" if answer_class == 'correct' else 'x^2'
|
||||
assert_textfield('formula', expected)
|
||||
|
||||
else:
|
||||
# The other response types use random data,
|
||||
# which would be difficult to check
|
||||
# We trade input value coverage in the other tests for
|
||||
# input type coverage in this test.
|
||||
pass
|
||||
|
||||
|
||||
##############################
|
||||
# HELPER METHODS
|
||||
##############################
|
||||
|
||||
def add_problem_to_course(course, problem_type, extraMeta=None):
|
||||
'''
|
||||
Add a problem to the course we have created using factories.
|
||||
'''
|
||||
|
||||
assert(problem_type in PROBLEM_DICT)
|
||||
|
||||
# Generate the problem XML using capa.tests.response_xml_factory
|
||||
factory_dict = PROBLEM_DICT[problem_type]
|
||||
problem_xml = factory_dict['factory'].build_xml(**factory_dict['kwargs'])
|
||||
metadata = {'rerandomize': 'always'} if not 'metadata' in factory_dict else factory_dict['metadata']
|
||||
if extraMeta:
|
||||
metadata = dict(metadata, **extraMeta)
|
||||
|
||||
# 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=metadata)
|
||||
|
||||
|
||||
def inputfield(problem_type, choice=None, input_num=1):
|
||||
""" Return the <input> element for *problem_type*.
|
||||
For example, if problem_type is 'string', return
|
||||
the text field for the string problem in the test course.
|
||||
|
||||
*choice* is the name of the checkbox input in a group
|
||||
of checkboxes. """
|
||||
|
||||
sel = ("input#input_i4x-edx-model_course-problem-%s_2_%s" %
|
||||
(problem_type.replace(" ", "_"), str(input_num)))
|
||||
|
||||
if choice is not None:
|
||||
base = "_choice_" if problem_type == "multiple choice" else "_"
|
||||
sel = sel + base + str(choice)
|
||||
|
||||
# If the input element doesn't exist, fail immediately
|
||||
assert world.is_css_present(sel)
|
||||
|
||||
# Retrieve the input element
|
||||
return world.browser.find_by_css(sel)
|
||||
|
||||
|
||||
def assert_checked(problem_type, choices):
|
||||
'''
|
||||
Assert that choice names given in *choices* are the only
|
||||
ones checked.
|
||||
|
||||
Works for both radio and checkbox problems
|
||||
'''
|
||||
|
||||
all_choices = ['choice_0', 'choice_1', 'choice_2', 'choice_3']
|
||||
for this_choice in all_choices:
|
||||
element = inputfield(problem_type, choice=this_choice)
|
||||
|
||||
if this_choice in choices:
|
||||
assert element.checked
|
||||
else:
|
||||
assert not element.checked
|
||||
|
||||
|
||||
def assert_textfield(problem_type, expected_text, input_num=1):
|
||||
element = inputfield(problem_type, input_num=input_num)
|
||||
assert element.value == expected_text
|
||||
@@ -16,5 +16,5 @@ Feature: Register for a course
|
||||
Then I should see the course numbered "6.002x" in my dashboard
|
||||
When I unregister for the course numbered "6.002x"
|
||||
Then I should be on the dashboard page
|
||||
And I should see "Looks like you haven't registered for any courses yet." somewhere in the page
|
||||
And I should see an empty dashboard message
|
||||
And I should NOT see the course numbered "6.002x" in my dashboard
|
||||
|
||||
@@ -19,16 +19,19 @@ def i_register_for_the_course(step, course):
|
||||
assert world.is_css_present('section.container.dashboard')
|
||||
|
||||
|
||||
@step(u'I should see the course numbered "([^"]*)" in my dashboard$')
|
||||
def i_should_see_that_course_in_my_dashboard(step, course):
|
||||
course_link_css = 'section.my-courses a[href*="%s"]' % course
|
||||
assert world.is_css_present(course_link_css)
|
||||
@step(u'I should see an empty dashboard message')
|
||||
def i_should_see_empty_dashboard(step):
|
||||
empty_dash_css = 'section.empty-dashboard-message'
|
||||
assert world.is_css_present(empty_dash_css)
|
||||
|
||||
|
||||
@step(u'I should NOT see the course numbered "([^"]*)" in my dashboard$')
|
||||
def i_should_not_see_that_course_in_my_dashboard(step, course):
|
||||
@step(u'I should( NOT)? see the course numbered "([^"]*)" in my dashboard$')
|
||||
def i_should_see_that_course_in_my_dashboard(step, doesnt_appear, course):
|
||||
course_link_css = 'section.my-courses a[href*="%s"]' % course
|
||||
assert not world.is_css_present(course_link_css)
|
||||
if doesnt_appear:
|
||||
assert world.is_css_not_present(course_link_css)
|
||||
else:
|
||||
assert world.is_css_present(course_link_css)
|
||||
|
||||
|
||||
@step(u'I unregister for the course numbered "([^"]*)"')
|
||||
|
||||
Reference in New Issue
Block a user