Commit 3257a7ba by Will Daly

Refactored navigation feature

Fixed grading tests
parent 4bb9dcf1
......@@ -2,7 +2,7 @@
# pylint: disable=W0621
from lettuce import world, step
from nose.tools import assert_true, assert_in, assert_false # pylint: disable=E0611
from nose.tools import assert_true, assert_equal, assert_in, assert_false # pylint: disable=E0611
from auth.authz import get_user_by_email, get_course_groupname_for_role
from django.conf import settings
......@@ -64,32 +64,16 @@ def select_new_course(_step, whom):
@step(u'I press the "([^"]*)" notification button$')
def press_the_notification_button(_step, name):
# TODO: fix up this code. Selenium is not dealing well with css transforms,
# as it thinks that the notification and the buttons are always visible
# First wait for the notification to pop up
notification_css = 'div#page-notification div.wrapper-notification'
world.wait_for_visible(notification_css)
# You would think that the above would have worked, but it doesn't.
# Brute force wait for now.
world.wait(.5)
# Now make sure the button is there
# Because the notification uses a CSS transition,
# Selenium will always report it as being visible.
# This makes it very difficult to successfully click
# the "Save" button at the UI level.
# Instead, we use JavaScript to reliably click
# the button.
btn_css = 'div#page-notification a.action-%s' % name.lower()
world.wait_for_visible(btn_css)
# You would think that the above would have worked, but it doesn't.
# Brute force wait for now.
world.wait(.5)
if world.is_firefox():
# This is done to explicitly make the changes save on firefox.
# It will remove focus from the previously focused element
world.trigger_event(btn_css, event='focus')
world.browser.execute_script("$('{}').click()".format(btn_css))
else:
world.css_click(btn_css)
world.trigger_event(btn_css, event='focus')
world.browser.execute_script("$('{}').click()".format(btn_css))
world.wait_for_ajax_complete()
......
......@@ -2,7 +2,7 @@
#pylint: disable=C0111
from lettuce import world
from nose.tools import assert_equal # pylint: disable=E0611
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
from terrain.steps import reload_the_page
......@@ -12,9 +12,13 @@ def create_component_instance(step, component_button_css, category,
has_multiple_templates=True):
click_new_component_button(step, component_button_css)
if category in ('problem', 'html'):
def animation_done(_driver):
return world.browser.evaluate_script("$('div.new-component').css('display')") == 'none'
script = "$('div.new-component').css('display')"
return world.browser.evaluate_script(script) == 'none'
world.wait_for(animation_done)
if has_multiple_templates:
......@@ -23,10 +27,7 @@ def create_component_instance(step, component_button_css, category,
if category in ('video',):
world.wait_for_xmodule()
assert_equal(
1,
len(world.css_find(expected_css)),
"Component instance with css {css} was not created successfully".format(css=expected_css))
assert_true(world.is_css_present(expected_css))
@world.absorb
......@@ -34,7 +35,8 @@ def click_new_component_button(step, component_button_css):
step.given('I have clicked the new unit button')
world.wait_for_requirejs(
["jquery", "js/models/course", "coffee/src/models/module",
"coffee/src/views/unit", "jquery.ui"])
"coffee/src/views/unit", "jquery.ui"]
)
world.css_click(component_button_css)
......
......@@ -6,7 +6,7 @@ from common import *
from terrain.steps import reload_the_page
from selenium.common.exceptions import (
InvalidElementStateException, WebDriverException)
from nose.tools import assert_in, assert_not_in # pylint: disable=E0611
from nose.tools import assert_in, assert_not_in, assert_equal, assert_not_equal # pylint: disable=E0611
@step(u'I am viewing the grading settings')
......@@ -36,7 +36,7 @@ def delete_grade(step):
def view_grade_slider(step, how_many):
grade_slider_css = '.grade-specific-bar'
all_grades = world.css_find(grade_slider_css)
assert len(all_grades) == int(how_many)
assert_equal(len(all_grades), int(how_many))
@step(u'I move a grading section')
......@@ -51,7 +51,7 @@ def confirm_change(step):
range_css = '.range'
all_ranges = world.css_find(range_css)
for i in range(len(all_ranges)):
assert world.css_html(range_css, index=i) != '0-50'
assert_not_equal(world.css_html(range_css, index=i), '0-50')
@step(u'I change assignment type "([^"]*)" to "([^"]*)"$')
......@@ -59,7 +59,7 @@ def change_assignment_name(step, old_name, new_name):
name_id = '#course-grading-assignment-name'
index = get_type_index(old_name)
f = world.css_find(name_id)[index]
assert index != -1
assert_not_equal(index, -1)
for count in range(len(old_name)):
f._element.send_keys(Keys.END, Keys.BACK_SPACE)
f._element.send_keys(new_name)
......@@ -78,7 +78,10 @@ def main_course_page(step):
def see_assignment_name(step, do_not, name):
assignment_menu_css = 'ul.menu > li > a'
# First assert that it is there, make take a bit to redraw
assert world.css_find(assignment_menu_css)
assert_true(
world.css_find(assignment_menu_css),
msg="Could not find assignment menu"
)
assignment_menu = world.css_find(assignment_menu_css)
allnames = [item.html for item in assignment_menu]
......@@ -113,7 +116,7 @@ def populate_course(step):
def changes_not_persisted(step):
reload_the_page(step)
name_id = '#course-grading-assignment-name'
assert(world.css_value(name_id) == 'Homework')
assert_equal(world.css_value(name_id), 'Homework')
@step(u'I see the assignment type "(.*)"$')
......@@ -121,7 +124,7 @@ def i_see_the_assignment_type(_step, name):
assignment_css = '#course-grading-assignment-name'
assignments = world.css_find(assignment_css)
types = [ele['value'] for ele in assignments]
assert name in types
assert_in(name, types)
@step(u'I change the highest grade range to "(.*)"$')
......@@ -135,15 +138,15 @@ def change_grade_range(_step, range_name):
def i_see_highest_grade_range(_step, range_name):
range_css = 'span.letter-grade'
grade = world.css_find(range_css).first
assert grade.value == range_name, "{0} != {1}".format(grade.value, range_name)
assert_equal(grade.value, range_name)
@step(u'I cannot edit the "Fail" grade range$')
def cannot_edit_fail(_step):
range_css = 'span.letter-grade'
ranges = world.css_find(range_css)
assert len(ranges) == 2
assert ranges.last.value != 'Failure'
assert_equal(len(ranges), 2)
assert_not_equal(ranges.last.value, 'Failure')
# try to change the grade range -- this should throw an exception
try:
......@@ -153,14 +156,23 @@ def cannot_edit_fail(_step):
# check to be sure that nothing has changed
ranges = world.css_find(range_css)
assert len(ranges) == 2
assert ranges.last.value != 'Failure'
assert_equal(len(ranges), 2)
assert_not_equal(ranges.last.value, 'Failure')
@step(u'I change the grace period to "(.*)"$')
def i_change_grace_period(_step, grace_period):
grace_period_css = '#course-grading-graceperiod'
ele = world.css_find(grace_period_css).first
# Sometimes it takes a moment for the JavaScript
# to populate the field. If we don't wait for
# this to happen, then we can end up with
# an invalid value (e.g. "00:0048:00")
# which prevents us from saving.
assert_true(world.css_has_value(grace_period_css, "00:00", allow_blank=False))
# Set the new grace period
ele.value = grace_period
......@@ -168,7 +180,7 @@ def i_change_grace_period(_step, grace_period):
def the_grace_period_is(_step, grace_period):
grace_period_css = '#course-grading-graceperiod'
ele = world.css_find(grace_period_css).first
assert ele.value == grace_period
assert_equal(ele.value, grace_period)
def get_type_index(name):
......
......@@ -32,18 +32,21 @@ def shows_captions(_step, show_captions):
@step('I see the correct video settings and default values$')
def correct_video_settings(_step):
world.verify_all_setting_entries([['Display Name', 'Video', False],
['Download Track', '', False],
['Download Video', '', False],
['End Time', '0', False],
['HTML5 Timed Transcript', '', False],
['Show Captions', 'True', False],
['Start Time', '0', False],
['Video Sources', '', False],
['Youtube ID', 'OEoXaMPEzfM', False],
['Youtube ID for .75x speed', '', False],
['Youtube ID for 1.25x speed', '', False],
['Youtube ID for 1.5x speed', '', False]])
expected_entries = [
['Display Name', 'Video', False],
['Download Track', '', False],
['Download Video', '', False],
['End Time', '0', False],
['HTML5 Timed Transcript', '', False],
['Show Captions', 'True', False],
['Start Time', '0', False],
['Video Sources', '', False],
['Youtube ID', 'OEoXaMPEzfM', False],
['Youtube ID for .75x speed', '', False],
['Youtube ID for 1.25x speed', '', False],
['Youtube ID for 1.5x speed', '', False]
]
world.verify_all_setting_entries(expected_entries)
@step('my video display name change is persisted on save$')
......@@ -52,4 +55,8 @@ def video_name_persisted(step):
reload_the_page(step)
world.wait_for_xmodule()
world.edit_component()
world.verify_setting_entry(world.get_setting_entry('Display Name'), 'Display Name', '3.4', True)
world.verify_setting_entry(
world.get_setting_entry('Display Name'),
'Display Name', '3.4', True
)
......@@ -41,13 +41,13 @@ def log_in(username='robot', password='test', email='robot@edx.org', name='Robot
@world.absorb
def register_by_course_id(course_id, is_staff=False):
create_user('robot', 'password')
u = User.objects.get(username='robot')
def register_by_course_id(course_id, username='robot', password='test', is_staff=False):
create_user(username, password)
user = User.objects.get(username=username)
if is_staff:
u.is_staff = True
u.save()
CourseEnrollment.enroll(u, course_id)
user.is_staff = True
user.save()
CourseEnrollment.enroll(user, course_id)
@world.absorb
......
......@@ -34,7 +34,7 @@ def wait(seconds):
def wait_for_js_variable_truthy(variable):
"""
Using Selenium's `execute_async_script` function, poll the Javascript
enviornment until the given variable is defined and truthy. This process
environment until the given variable is defined and truthy. This process
guards against page reloads, and seamlessly retries on the next page.
"""
js = """
......@@ -194,8 +194,51 @@ def is_css_not_present(css_selector, wait_time=5):
@world.absorb
def css_has_text(css_selector, text, index=0):
return world.css_text(css_selector, index=index) == text
def css_has_text(css_selector, text, index=0,
strip=False, allow_blank=True):
"""
Return a boolean indicating whether the element with `css_selector`
has `text`.
If `strip` is True, strip whitespace at beginning/end of both
strings before comparing.
If `allow_blank` is False, wait for the element to have non-empty
text before making the assertion. This is useful for elements
that are populated by JavaScript after the page loads.
If there are multiple elements matching the css selector,
use `index` to indicate which one.
"""
if not allow_blank:
world.wait_for(lambda _: world.css_text(css_selector, index=index))
actual_text = world.css_text(css_selector, index=index)
if strip:
actual_text = actual_text.strip()
text = text.strip()
return actual_text == text
@world.absorb
def css_has_value(css_selector, value, index=0, allow_blank=False):
"""
Return a boolean indicating whether the element with
`css_selector` has the specified `value`.
If `allow_blank` is False, wait for the element to have
a value that is a non-empty string.
If there are multiple elements matching the css selector,
use `index` to indicate which one.
"""
if not allow_blank:
world.wait_for(lambda _: world.css_value(css_selector, index=index))
return world.css_value(css_selector, index=index) == value
@world.absorb
......
@shard_1
Feature: LMS.Navigate Course
As a student in an edX course
In order to view the course properly
In order to access courseware
I want to be able to navigate through the content
Scenario: I can navigate to a section
Given I am viewing a course with multiple sections
When I click on section "2"
Then I should see the content of section "2"
When I navigate to a section
Then I see the content of the section
Scenario: I can navigate to subsections
Given I am viewing a section with multiple subsections
When I click on subsection "2"
Then I should see the content of subsection "2"
When I navigate to a subsection
Then I see the content of the subsection
Scenario: I can navigate to sequences
Given I am viewing a section with multiple sequences
When I click on sequence "2"
Then I should see the content of sequence "2"
When I navigate to an item in a sequence
Then I see the content of the sequence item
Scenario: I can go back to where I was after I log out and back in
Scenario: I can return to the last section I visited
Given I am viewing a course with multiple sections
When I click on section "2"
And I return later
Then I should see that I was most recently in section "2"
When I navigate to a section
And I see the content of the section
And I return to the courseware
Then I see that I was most recently in the subsection
......@@ -2,37 +2,39 @@
#pylint: disable=W0621
from lettuce import world, step
from django.contrib.auth.models import User
from lettuce.django import django_url
from student.models import CourseEnrollment
from common import course_id, course_location
from problems_setup import PROBLEM_DICT
TEST_SECTION_NAME = 'Test Section'
TEST_SUBSECTION_NAME = 'Test Subsection'
from nose.tools import assert_in
@step(u'I am viewing a course with multiple sections')
def view_course_multiple_sections(step):
create_course()
# Add a section to the course to contain problems
section1 = world.ItemFactory.create(parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name=section_name(1))
# Add a section to the course to contain problems
section2 = world.ItemFactory.create(parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name=section_name(2))
section1 = world.ItemFactory.create(
parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name="Test Section 1"
)
section2 = world.ItemFactory.create(
parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name="Test Section 2"
)
place1 = world.ItemFactory.create(parent_location=section1.location,
category='sequential',
display_name=subsection_name(1))
place1 = world.ItemFactory.create(
parent_location=section1.location,
category='sequential',
display_name="Test Subsection 1"
)
place2 = world.ItemFactory.create(parent_location=section2.location,
category='sequential',
display_name=subsection_name(2))
place2 = world.ItemFactory.create(
parent_location=section2.location,
category='sequential',
display_name="Test Subsection 2"
)
add_problem_to_course_section('model_course', 'multiple choice', place1.location)
add_problem_to_course_section('model_course', 'drop down', place2.location)
add_problem_to_course_section(place1.location, "Problem 1")
add_problem_to_course_section(place2.location, "Problem 2")
create_user_and_visit_course()
......@@ -41,19 +43,24 @@ def view_course_multiple_sections(step):
def view_course_multiple_subsections(step):
create_course()
# Add a section to the course to contain problems
section1 = world.ItemFactory.create(parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name=section_name(1))
section1 = world.ItemFactory.create(
parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name="Test Section 1"
)
place1 = world.ItemFactory.create(parent_location=section1.location,
category='sequential',
display_name=subsection_name(1))
place1 = world.ItemFactory.create(
parent_location=section1.location,
category='sequential',
display_name="Test Subsection 1"
)
place2 = world.ItemFactory.create(parent_location=section1.location,
display_name=subsection_name(2))
place2 = world.ItemFactory.create(
parent_location=section1.location,
display_name="Test Subsection 2"
)
add_problem_to_course_section('model_course', 'multiple choice', place1.location)
add_problem_to_course_section('model_course', 'drop down', place2.location)
add_problem_to_course_section(place1.location, "Problem 3")
add_problem_to_course_section(place2.location, "Problem 4")
create_user_and_visit_course()
......@@ -61,121 +68,116 @@ def view_course_multiple_subsections(step):
@step(u'I am viewing a section with multiple sequences')
def view_course_multiple_sequences(step):
create_course()
# Add a section to the course to contain problems
section1 = world.ItemFactory.create(parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name=section_name(1))
place1 = world.ItemFactory.create(parent_location=section1.location,
category='sequential',
display_name=subsection_name(1))
section1 = world.ItemFactory.create(
parent_location=course_location(world.scenario_dict['COURSE'].number),
display_name="Test Section 1"
)
add_problem_to_course_section('model_course', 'multiple choice', place1.location)
add_problem_to_course_section('model_course', 'drop down', place1.location)
place1 = world.ItemFactory.create(
parent_location=section1.location,
category='sequential',
display_name="Test Subsection 1"
)
add_problem_to_course_section(place1.location, "Problem 5")
add_problem_to_course_section(place1.location, "Problem 6")
create_user_and_visit_course()
@step(u'I click on section "([^"]*)"$')
def click_on_section(step, section):
@step(u'I navigate to a section')
def when_i_navigate_to_a_section(step):
section_css = 'h3[tabindex="-1"]'
world.css_click(section_css)
subid = "ui-accordion-accordion-panel-{}".format(str(int(section) - 1))
subid = "ui-accordion-accordion-panel-1"
world.wait_for_visible("#" + subid)
subsection_css = "ul.ui-accordion-content-active[id='{}'] > li > a".format(subid)
world.css_click(subsection_css)
@step(u'I click on subsection "([^"]*)"$')
def click_on_subsection(step, subsection):
@step(u'I navigate to a subsection')
def when_i_navigate_to_a_subsection(step):
subsection_css = 'ul[id="ui-accordion-accordion-panel-0"]> li > a'
world.css_click(subsection_css, index=(int(subsection) - 1))
world.css_click(subsection_css, index=1)
@step(u'I click on sequence "([^"]*)"$')
def click_on_sequence(step, sequence):
sequence_css = 'a[data-element="%s"]' % sequence
@step(u'I navigate to an item in a sequence')
def when_i_navigate_to_an_item_in_a_sequence(step):
sequence_css = 'a[data-element="2"]'
world.css_click(sequence_css)
@step(u'I should see the content of (?:sub)?section "([^"]*)"$')
def see_section_content(step, section):
world.wait(0.5)
if section == "2":
text = 'The correct answer is Option 2'
elif section == "1":
text = 'The correct answer is Choice 3'
step.given('I should see "' + text + '" somewhere on the page')
@step(u'I see the content of the section')
def then_i_see_the_content_of_the_section(step):
wait_for_problem('PROBLEM 2')
@step(u'I should see the content of sequence "([^"]*)"$')
def see_sequence_content(step, sequence):
step.given('I should see the content of section "2"')
@step(u'I return later')
def return_to_course(step):
step.given('I visit the homepage')
world.click_link("View Course")
world.click_link("Courseware")
@step(u'I see the content of the subsection')
def then_i_see_the_content_of_the_subsection(step):
wait_for_problem('PROBLEM 4')
@step(u'I should see that I was most recently in section "([^"]*)"$')
def see_recent_section(step, section):
step.given('I should see "You were most recently in %s" somewhere on the page' % subsection_name(int(section)))
#####################
# HELPERS
#####################
@step(u'I see the content of the sequence item')
def then_i_see_the_content_of_the_sequence_item(step):
wait_for_problem('PROBLEM 6')
def section_name(section):
return TEST_SECTION_NAME + str(section)
@step(u'I return to the courseware')
def and_i_return_to_the_courseware(step):
world.visit('/')
world.click_link("View Course")
world.click_link("Courseware")
def subsection_name(section):
return TEST_SUBSECTION_NAME + str(section)
@step(u'I see that I was most recently in the subsection')
def then_i_see_that_i_was_most_recently_in_the_subsection(step):
message = world.css_text('section.course-content > p')
assert_in("You were most recently in Test Subsection 2", message)
def create_course():
world.clear_courses()
world.scenario_dict['COURSE'] = world.CourseFactory.create(org='edx', number='model_course', display_name='Test Course')
world.scenario_dict['COURSE'] = world.CourseFactory.create(
org='edx', number='999', display_name='Test Course'
)
def create_user_and_visit_course():
world.create_user('robot', 'test')
u = User.objects.get(username='robot')
world.register_by_course_id('edx/999/Test_Course')
world.log_in()
world.visit('/courses/edx/999/Test_Course/courseware/')
CourseEnrollment.enroll(u, course_id(world.scenario_dict['COURSE'].number))
world.log_in(username='robot', password='test')
chapter_name = (TEST_SECTION_NAME + "1").replace(" ", "_")
section_name = (TEST_SUBSECTION_NAME + "1").replace(" ", "_")
url = django_url('/courses/edx/model_course/Test_Course/courseware/%s/%s' %
(chapter_name, section_name))
def add_problem_to_course_section(parent_location, display_name):
"""
Add a problem to the course at `parent_location` (a `Location` instance)
world.browser.visit(url)
def add_problem_to_course_section(course, problem_type, parent_location, extraMeta=None):
'''
Add a problem to the course we have created using factories.
'''
assert(problem_type in PROBLEM_DICT)
`display_name` is the name of the problem to display, which
is useful to identify which problem we're looking at.
"""
# Generate the problem XML using capa.tests.response_xml_factory
factory_dict = PROBLEM_DICT[problem_type]
# Since this is just a placeholder, we always use multiple choice.
factory_dict = PROBLEM_DICT['multiple choice']
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.
world.ItemFactory.create(parent_location=parent_location,
category='problem',
display_name=str(problem_type),
data=problem_xml,
metadata=metadata)
# Add the problem
world.ItemFactory.create(
parent_location=parent_location,
category='problem',
display_name=display_name,
data=problem_xml
)
def wait_for_problem(display_name):
"""
Wait for the problem with `display_name` to appear on the page.
"""
wait_func = lambda _: world.css_has_text(
'h2.problem-header', display_name, strip=True
)
world.wait_for(wait_func)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment