Commit a617988c by Will Daly

Merge pull request #1232 from edx/will/navigation-grading-test-fixes

Fix navigation and grading acceptance tests
parents 4bb9dcf1 3257a7ba
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# pylint: disable=W0621 # pylint: disable=W0621
from lettuce import world, step 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 auth.authz import get_user_by_email, get_course_groupname_for_role
from django.conf import settings from django.conf import settings
...@@ -64,32 +64,16 @@ def select_new_course(_step, whom): ...@@ -64,32 +64,16 @@ def select_new_course(_step, whom):
@step(u'I press the "([^"]*)" notification button$') @step(u'I press the "([^"]*)" notification button$')
def press_the_notification_button(_step, name): 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 # Because the notification uses a CSS transition,
notification_css = 'div#page-notification div.wrapper-notification' # Selenium will always report it as being visible.
world.wait_for_visible(notification_css) # This makes it very difficult to successfully click
# the "Save" button at the UI level.
# You would think that the above would have worked, but it doesn't. # Instead, we use JavaScript to reliably click
# Brute force wait for now. # the button.
world.wait(.5)
# Now make sure the button is there
btn_css = 'div#page-notification a.action-%s' % name.lower() btn_css = 'div#page-notification a.action-%s' % name.lower()
world.wait_for_visible(btn_css) world.trigger_event(btn_css, event='focus')
world.browser.execute_script("$('{}').click()".format(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.wait_for_ajax_complete() world.wait_for_ajax_complete()
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#pylint: disable=C0111 #pylint: disable=C0111
from lettuce import world 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 from terrain.steps import reload_the_page
...@@ -12,9 +12,13 @@ def create_component_instance(step, component_button_css, category, ...@@ -12,9 +12,13 @@ def create_component_instance(step, component_button_css, category,
has_multiple_templates=True): has_multiple_templates=True):
click_new_component_button(step, component_button_css) click_new_component_button(step, component_button_css)
if category in ('problem', 'html'): if category in ('problem', 'html'):
def animation_done(_driver): 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) world.wait_for(animation_done)
if has_multiple_templates: if has_multiple_templates:
...@@ -23,10 +27,7 @@ def create_component_instance(step, component_button_css, category, ...@@ -23,10 +27,7 @@ def create_component_instance(step, component_button_css, category,
if category in ('video',): if category in ('video',):
world.wait_for_xmodule() world.wait_for_xmodule()
assert_equal( assert_true(world.is_css_present(expected_css))
1,
len(world.css_find(expected_css)),
"Component instance with css {css} was not created successfully".format(css=expected_css))
@world.absorb @world.absorb
...@@ -34,7 +35,8 @@ def click_new_component_button(step, component_button_css): ...@@ -34,7 +35,8 @@ def click_new_component_button(step, component_button_css):
step.given('I have clicked the new unit button') step.given('I have clicked the new unit button')
world.wait_for_requirejs( world.wait_for_requirejs(
["jquery", "js/models/course", "coffee/src/models/module", ["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) world.css_click(component_button_css)
......
...@@ -6,7 +6,7 @@ from common import * ...@@ -6,7 +6,7 @@ from common import *
from terrain.steps import reload_the_page from terrain.steps import reload_the_page
from selenium.common.exceptions import ( from selenium.common.exceptions import (
InvalidElementStateException, WebDriverException) 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') @step(u'I am viewing the grading settings')
...@@ -36,7 +36,7 @@ def delete_grade(step): ...@@ -36,7 +36,7 @@ def delete_grade(step):
def view_grade_slider(step, how_many): def view_grade_slider(step, how_many):
grade_slider_css = '.grade-specific-bar' grade_slider_css = '.grade-specific-bar'
all_grades = world.css_find(grade_slider_css) 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') @step(u'I move a grading section')
...@@ -51,7 +51,7 @@ def confirm_change(step): ...@@ -51,7 +51,7 @@ def confirm_change(step):
range_css = '.range' range_css = '.range'
all_ranges = world.css_find(range_css) all_ranges = world.css_find(range_css)
for i in range(len(all_ranges)): 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 "([^"]*)"$') @step(u'I change assignment type "([^"]*)" to "([^"]*)"$')
...@@ -59,7 +59,7 @@ def change_assignment_name(step, old_name, new_name): ...@@ -59,7 +59,7 @@ def change_assignment_name(step, old_name, new_name):
name_id = '#course-grading-assignment-name' name_id = '#course-grading-assignment-name'
index = get_type_index(old_name) index = get_type_index(old_name)
f = world.css_find(name_id)[index] f = world.css_find(name_id)[index]
assert index != -1 assert_not_equal(index, -1)
for count in range(len(old_name)): for count in range(len(old_name)):
f._element.send_keys(Keys.END, Keys.BACK_SPACE) f._element.send_keys(Keys.END, Keys.BACK_SPACE)
f._element.send_keys(new_name) f._element.send_keys(new_name)
...@@ -78,7 +78,10 @@ def main_course_page(step): ...@@ -78,7 +78,10 @@ def main_course_page(step):
def see_assignment_name(step, do_not, name): def see_assignment_name(step, do_not, name):
assignment_menu_css = 'ul.menu > li > a' assignment_menu_css = 'ul.menu > li > a'
# First assert that it is there, make take a bit to redraw # 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) assignment_menu = world.css_find(assignment_menu_css)
allnames = [item.html for item in assignment_menu] allnames = [item.html for item in assignment_menu]
...@@ -113,7 +116,7 @@ def populate_course(step): ...@@ -113,7 +116,7 @@ def populate_course(step):
def changes_not_persisted(step): def changes_not_persisted(step):
reload_the_page(step) reload_the_page(step)
name_id = '#course-grading-assignment-name' 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 "(.*)"$') @step(u'I see the assignment type "(.*)"$')
...@@ -121,7 +124,7 @@ def i_see_the_assignment_type(_step, name): ...@@ -121,7 +124,7 @@ def i_see_the_assignment_type(_step, name):
assignment_css = '#course-grading-assignment-name' assignment_css = '#course-grading-assignment-name'
assignments = world.css_find(assignment_css) assignments = world.css_find(assignment_css)
types = [ele['value'] for ele in assignments] types = [ele['value'] for ele in assignments]
assert name in types assert_in(name, types)
@step(u'I change the highest grade range to "(.*)"$') @step(u'I change the highest grade range to "(.*)"$')
...@@ -135,15 +138,15 @@ def change_grade_range(_step, range_name): ...@@ -135,15 +138,15 @@ def change_grade_range(_step, range_name):
def i_see_highest_grade_range(_step, range_name): def i_see_highest_grade_range(_step, range_name):
range_css = 'span.letter-grade' range_css = 'span.letter-grade'
grade = world.css_find(range_css).first 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$') @step(u'I cannot edit the "Fail" grade range$')
def cannot_edit_fail(_step): def cannot_edit_fail(_step):
range_css = 'span.letter-grade' range_css = 'span.letter-grade'
ranges = world.css_find(range_css) ranges = world.css_find(range_css)
assert len(ranges) == 2 assert_equal(len(ranges), 2)
assert ranges.last.value != 'Failure' assert_not_equal(ranges.last.value, 'Failure')
# try to change the grade range -- this should throw an exception # try to change the grade range -- this should throw an exception
try: try:
...@@ -153,14 +156,23 @@ def cannot_edit_fail(_step): ...@@ -153,14 +156,23 @@ def cannot_edit_fail(_step):
# check to be sure that nothing has changed # check to be sure that nothing has changed
ranges = world.css_find(range_css) ranges = world.css_find(range_css)
assert len(ranges) == 2 assert_equal(len(ranges), 2)
assert ranges.last.value != 'Failure' assert_not_equal(ranges.last.value, 'Failure')
@step(u'I change the grace period to "(.*)"$') @step(u'I change the grace period to "(.*)"$')
def i_change_grace_period(_step, grace_period): def i_change_grace_period(_step, grace_period):
grace_period_css = '#course-grading-graceperiod' grace_period_css = '#course-grading-graceperiod'
ele = world.css_find(grace_period_css).first 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 ele.value = grace_period
...@@ -168,7 +180,7 @@ def i_change_grace_period(_step, grace_period): ...@@ -168,7 +180,7 @@ def i_change_grace_period(_step, grace_period):
def the_grace_period_is(_step, grace_period): def the_grace_period_is(_step, grace_period):
grace_period_css = '#course-grading-graceperiod' grace_period_css = '#course-grading-graceperiod'
ele = world.css_find(grace_period_css).first ele = world.css_find(grace_period_css).first
assert ele.value == grace_period assert_equal(ele.value, grace_period)
def get_type_index(name): def get_type_index(name):
......
...@@ -32,18 +32,21 @@ def shows_captions(_step, show_captions): ...@@ -32,18 +32,21 @@ def shows_captions(_step, show_captions):
@step('I see the correct video settings and default values$') @step('I see the correct video settings and default values$')
def correct_video_settings(_step): def correct_video_settings(_step):
world.verify_all_setting_entries([['Display Name', 'Video', False], expected_entries = [
['Download Track', '', False], ['Display Name', 'Video', False],
['Download Video', '', False], ['Download Track', '', False],
['End Time', '0', False], ['Download Video', '', False],
['HTML5 Timed Transcript', '', False], ['End Time', '0', False],
['Show Captions', 'True', False], ['HTML5 Timed Transcript', '', False],
['Start Time', '0', False], ['Show Captions', 'True', False],
['Video Sources', '', False], ['Start Time', '0', False],
['Youtube ID', 'OEoXaMPEzfM', False], ['Video Sources', '', False],
['Youtube ID for .75x speed', '', False], ['Youtube ID', 'OEoXaMPEzfM', False],
['Youtube ID for 1.25x speed', '', False], ['Youtube ID for .75x speed', '', False],
['Youtube ID for 1.5x 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$') @step('my video display name change is persisted on save$')
...@@ -52,4 +55,8 @@ def video_name_persisted(step): ...@@ -52,4 +55,8 @@ def video_name_persisted(step):
reload_the_page(step) reload_the_page(step)
world.wait_for_xmodule() world.wait_for_xmodule()
world.edit_component() 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 ...@@ -41,13 +41,13 @@ def log_in(username='robot', password='test', email='robot@edx.org', name='Robot
@world.absorb @world.absorb
def register_by_course_id(course_id, is_staff=False): def register_by_course_id(course_id, username='robot', password='test', is_staff=False):
create_user('robot', 'password') create_user(username, password)
u = User.objects.get(username='robot') user = User.objects.get(username=username)
if is_staff: if is_staff:
u.is_staff = True user.is_staff = True
u.save() user.save()
CourseEnrollment.enroll(u, course_id) CourseEnrollment.enroll(user, course_id)
@world.absorb @world.absorb
......
...@@ -34,7 +34,7 @@ def wait(seconds): ...@@ -34,7 +34,7 @@ def wait(seconds):
def wait_for_js_variable_truthy(variable): def wait_for_js_variable_truthy(variable):
""" """
Using Selenium's `execute_async_script` function, poll the Javascript 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. guards against page reloads, and seamlessly retries on the next page.
""" """
js = """ js = """
...@@ -194,8 +194,51 @@ def is_css_not_present(css_selector, wait_time=5): ...@@ -194,8 +194,51 @@ def is_css_not_present(css_selector, wait_time=5):
@world.absorb @world.absorb
def css_has_text(css_selector, text, index=0): def css_has_text(css_selector, text, index=0,
return world.css_text(css_selector, index=index) == text 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 @world.absorb
......
@shard_1 @shard_1
Feature: LMS.Navigate Course Feature: LMS.Navigate Course
As a student in an edX 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 I want to be able to navigate through the content
Scenario: I can navigate to a section Scenario: I can navigate to a section
Given I am viewing a course with multiple sections Given I am viewing a course with multiple sections
When I click on section "2" When I navigate to a section
Then I should see the content of section "2" Then I see the content of the section
Scenario: I can navigate to subsections Scenario: I can navigate to subsections
Given I am viewing a section with multiple subsections Given I am viewing a section with multiple subsections
When I click on subsection "2" When I navigate to a subsection
Then I should see the content of subsection "2" Then I see the content of the subsection
Scenario: I can navigate to sequences Scenario: I can navigate to sequences
Given I am viewing a section with multiple sequences Given I am viewing a section with multiple sequences
When I click on sequence "2" When I navigate to an item in a sequence
Then I should see the content of sequence "2" 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 Given I am viewing a course with multiple sections
When I click on section "2" When I navigate to a section
And I return later And I see the content of the section
Then I should see that I was most recently in section "2" And I return to the courseware
Then I see that I was most recently in the subsection
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