Refactor wait_for methods in ui_helpers for acceptance tests.
This commit is contained in:
@@ -283,24 +283,33 @@ def button_disabled(step, value):
|
|||||||
assert world.css_has_class(button_css, 'is-disabled')
|
assert world.css_has_class(button_css, 'is-disabled')
|
||||||
|
|
||||||
|
|
||||||
|
def _do_studio_prompt_action(intent, action):
|
||||||
|
"""
|
||||||
|
Wait for a studio prompt to appear and press the specified action button
|
||||||
|
See cms/static/js/views/feedback_prompt.js for implementation
|
||||||
|
"""
|
||||||
|
assert intent in ['warning', 'error', 'confirmation', 'announcement',
|
||||||
|
'step-required', 'help', 'mini']
|
||||||
|
assert action in ['primary', 'secondary']
|
||||||
|
|
||||||
|
world.wait_for_present('div.wrapper-prompt.is-shown#prompt-{}'.format(intent))
|
||||||
|
|
||||||
|
action_css = 'li.nav-item > a.action-{}'.format(action)
|
||||||
|
world.trigger_event(action_css, event='focus')
|
||||||
|
world.browser.execute_script("$('{}').click()".format(action_css))
|
||||||
|
|
||||||
|
world.wait_for_ajax_complete()
|
||||||
|
world.wait_for_present('div.wrapper-prompt.is-hiding#prompt-{}'.format(intent))
|
||||||
|
|
||||||
|
|
||||||
|
@world.absorb
|
||||||
|
def confirm_studio_prompt():
|
||||||
|
_do_studio_prompt_action('warning', 'primary')
|
||||||
|
|
||||||
|
|
||||||
@step('I confirm the prompt')
|
@step('I confirm the prompt')
|
||||||
def confirm_the_prompt(step):
|
def confirm_the_prompt(step):
|
||||||
|
confirm_studio_prompt()
|
||||||
def click_button(btn_css):
|
|
||||||
world.css_click(btn_css)
|
|
||||||
return world.css_find(btn_css).visible == False
|
|
||||||
|
|
||||||
prompt_css = 'div.prompt.has-actions'
|
|
||||||
world.wait_for_visible(prompt_css)
|
|
||||||
|
|
||||||
btn_css = 'a.button.action-primary'
|
|
||||||
world.wait_for_visible(btn_css)
|
|
||||||
|
|
||||||
# Sometimes you can do a click before the prompt is up.
|
|
||||||
# Thus we need some retry logic here.
|
|
||||||
world.wait_for(lambda _driver: click_button(btn_css))
|
|
||||||
|
|
||||||
assert_false(world.css_find(btn_css).visible)
|
|
||||||
|
|
||||||
|
|
||||||
@step(u'I am shown a prompt$')
|
@step(u'I am shown a prompt$')
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ def create_component_instance(step, category, component_type=None, is_advanced=F
|
|||||||
----------
|
----------
|
||||||
category: component type (discussion, html, problem, video)
|
category: component type (discussion, html, problem, video)
|
||||||
component_type: for components with multiple templates, the link text in the menu
|
component_type: for components with multiple templates, the link text in the menu
|
||||||
is_advanced: for html and problem, is the desired component under the
|
is_advanced: for problems, is the desired component under the advanced menu?
|
||||||
advanced menu
|
|
||||||
"""
|
"""
|
||||||
assert_in(category, ['problem', 'html', 'video', 'discussion'])
|
assert_in(category, ['problem', 'html', 'video', 'discussion'])
|
||||||
|
|
||||||
@@ -40,6 +39,8 @@ def create_component_instance(step, category, component_type=None, is_advanced=F
|
|||||||
# because it's ok if there are currently zero of them.
|
# because it's ok if there are currently zero of them.
|
||||||
module_count_before = len(world.browser.find_by_css(module_css))
|
module_count_before = len(world.browser.find_by_css(module_css))
|
||||||
|
|
||||||
|
# Disable the jquery animation for the transition to the menus.
|
||||||
|
world.disable_jquery_animations()
|
||||||
world.css_click(component_button_css)
|
world.css_click(component_button_css)
|
||||||
|
|
||||||
if category in ('problem', 'html'):
|
if category in ('problem', 'html'):
|
||||||
@@ -50,17 +51,13 @@ def create_component_instance(step, category, component_type=None, is_advanced=F
|
|||||||
module_count_before + 1))
|
module_count_before + 1))
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
|
||||||
def click_new_component_button(step, component_button_css):
|
|
||||||
step.given('I have clicked the new unit button')
|
|
||||||
world.css_click(component_button_css)
|
|
||||||
|
|
||||||
|
|
||||||
def _click_advanced():
|
def _click_advanced():
|
||||||
css = 'ul.problem-type-tabs a[href="#tab2"]'
|
css = 'ul.problem-type-tabs a[href="#tab2"]'
|
||||||
world.css_click(css)
|
world.css_click(css)
|
||||||
my_css = 'ul.problem-type-tabs li.ui-state-active a[href="#tab2"]'
|
|
||||||
assert(world.css_find(my_css))
|
# Wait for the advanced tab items to be displayed
|
||||||
|
tab2_css = 'div.ui-tabs-panel#tab2'
|
||||||
|
world.wait_for_visible(tab2_css)
|
||||||
|
|
||||||
|
|
||||||
def _find_matching_link(category, component_type):
|
def _find_matching_link(category, component_type):
|
||||||
|
|||||||
@@ -2,22 +2,24 @@
|
|||||||
#pylint: disable=W0621
|
#pylint: disable=W0621
|
||||||
|
|
||||||
from lettuce import world, step
|
from lettuce import world, step
|
||||||
from auth.authz import get_course_groupname_for_role, get_user_by_email
|
from nose.tools import assert_in # pylint: disable=E0611
|
||||||
from nose.tools import assert_true, assert_in # pylint: disable=E0611
|
|
||||||
|
|
||||||
|
|
||||||
@step(u'(I am viewing|s?he views) the course team settings')
|
@step(u'(I am viewing|s?he views) the course team settings$')
|
||||||
def view_grading_settings(_step, whom):
|
def view_grading_settings(_step, whom):
|
||||||
world.click_course_settings()
|
world.click_course_settings()
|
||||||
link_css = 'li.nav-course-settings-team a'
|
link_css = 'li.nav-course-settings-team a'
|
||||||
world.css_click(link_css)
|
world.css_click(link_css)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I add "([^"]*)" to the course team')
|
@step(u'I add "([^"]*)" to the course team$')
|
||||||
def add_other_user(_step, name):
|
def add_other_user(_step, name):
|
||||||
new_user_css = 'a.create-user-button'
|
new_user_css = 'a.create-user-button'
|
||||||
world.css_click(new_user_css)
|
world.css_click(new_user_css)
|
||||||
world.wait(0.5)
|
|
||||||
|
# Wait for the css animation to apply the is-shown class
|
||||||
|
shown_css = 'div.wrapper-create-user.is-shown'
|
||||||
|
world.wait_for_present(shown_css)
|
||||||
|
|
||||||
email_css = 'input#user-email-input'
|
email_css = 'input#user-email-input'
|
||||||
world.css_fill(email_css, name + '@edx.org')
|
world.css_fill(email_css, name + '@edx.org')
|
||||||
@@ -27,35 +29,30 @@ def add_other_user(_step, name):
|
|||||||
world.css_click(confirm_css)
|
world.css_click(confirm_css)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I delete "([^"]*)" from the course team')
|
@step(u'I delete "([^"]*)" from the course team$')
|
||||||
def delete_other_user(_step, name):
|
def delete_other_user(_step, name):
|
||||||
to_delete_css = '.user-item .item-actions a.remove-user[data-id="{email}"]'.format(
|
to_delete_css = '.user-item .item-actions a.remove-user[data-id="{email}"]'.format(
|
||||||
email="{0}{1}".format(name, '@edx.org'))
|
email="{0}{1}".format(name, '@edx.org'))
|
||||||
world.css_click(to_delete_css)
|
world.css_click(to_delete_css)
|
||||||
# confirm prompt
|
world.confirm_studio_prompt()
|
||||||
# need to wait for the animation to be done, there isn't a good success condition that won't work both on latest chrome and jenkins
|
|
||||||
world.wait(.5)
|
|
||||||
world.css_click(".wrapper-prompt-warning .action-primary")
|
|
||||||
|
|
||||||
|
|
||||||
@step(u's?he deletes me from the course team')
|
@step(u's?he deletes me from the course team$')
|
||||||
def other_delete_self(_step):
|
def other_delete_self(_step):
|
||||||
to_delete_css = '.user-item .item-actions a.remove-user[data-id="{email}"]'.format(
|
to_delete_css = '.user-item .item-actions a.remove-user[data-id="{email}"]'.format(
|
||||||
email="robot+studio@edx.org")
|
email="robot+studio@edx.org")
|
||||||
world.css_click(to_delete_css)
|
world.css_click(to_delete_css)
|
||||||
# confirm prompt
|
world.confirm_studio_prompt()
|
||||||
world.wait(.5)
|
|
||||||
world.css_click(".wrapper-prompt-warning .action-primary")
|
|
||||||
|
|
||||||
|
|
||||||
@step(u'I make "([^"]*)" a course team admin')
|
@step(u'I make "([^"]*)" a course team admin$')
|
||||||
def make_course_team_admin(_step, name):
|
def make_course_team_admin(_step, name):
|
||||||
admin_btn_css = '.user-item[data-email="{name}@edx.org"] .user-actions .add-admin-role'.format(
|
admin_btn_css = '.user-item[data-email="{name}@edx.org"] .user-actions .add-admin-role'.format(
|
||||||
name=name)
|
name=name)
|
||||||
world.css_click(admin_btn_css)
|
world.css_click(admin_btn_css)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I remove admin rights from ("([^"]*)"|myself)')
|
@step(u'I remove admin rights from ("([^"]*)"|myself)$')
|
||||||
def remove_course_team_admin(_step, outer_capture, name):
|
def remove_course_team_admin(_step, outer_capture, name):
|
||||||
if outer_capture == "myself":
|
if outer_capture == "myself":
|
||||||
email = world.scenario_dict["USER"].email
|
email = world.scenario_dict["USER"].email
|
||||||
@@ -66,8 +63,8 @@ def remove_course_team_admin(_step, outer_capture, name):
|
|||||||
world.css_click(admin_btn_css)
|
world.css_click(admin_btn_css)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I( do not)? see the course on my page')
|
@step(u'I( do not)? see the course on my page$')
|
||||||
@step(u's?he does( not)? see the course on (his|her) page')
|
@step(u's?he does( not)? see the course on (his|her) page$')
|
||||||
def see_course(_step, do_not_see, gender='self'):
|
def see_course(_step, do_not_see, gender='self'):
|
||||||
class_css = 'h3.course-title'
|
class_css = 'h3.course-title'
|
||||||
if do_not_see:
|
if do_not_see:
|
||||||
@@ -78,7 +75,7 @@ def see_course(_step, do_not_see, gender='self'):
|
|||||||
assert_in(world.scenario_dict['COURSE'].display_name, all_names)
|
assert_in(world.scenario_dict['COURSE'].display_name, all_names)
|
||||||
|
|
||||||
|
|
||||||
@step(u'"([^"]*)" should( not)? be marked as an admin')
|
@step(u'"([^"]*)" should( not)? be marked as an admin$')
|
||||||
def marked_as_admin(_step, name, not_marked_admin):
|
def marked_as_admin(_step, name, not_marked_admin):
|
||||||
flag_css = '.user-item[data-email="{name}@edx.org"] .flag-role.flag-role-admin'.format(
|
flag_css = '.user-item[data-email="{name}@edx.org"] .flag-role.flag-role-admin'.format(
|
||||||
name=name)
|
name=name)
|
||||||
@@ -88,13 +85,13 @@ def marked_as_admin(_step, name, not_marked_admin):
|
|||||||
assert world.is_css_present(flag_css)
|
assert world.is_css_present(flag_css)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I should( not)? be marked as an admin')
|
@step(u'I should( not)? be marked as an admin$')
|
||||||
def self_marked_as_admin(_step, not_marked_admin):
|
def self_marked_as_admin(_step, not_marked_admin):
|
||||||
return marked_as_admin(_step, "robot+studio", not_marked_admin)
|
return marked_as_admin(_step, "robot+studio", not_marked_admin)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I can(not)? delete users')
|
@step(u'I can(not)? delete users$')
|
||||||
@step(u's?he can(not)? delete users')
|
@step(u's?he can(not)? delete users$')
|
||||||
def can_delete_users(_step, can_not_delete):
|
def can_delete_users(_step, can_not_delete):
|
||||||
to_delete_css = 'a.remove-user'
|
to_delete_css = 'a.remove-user'
|
||||||
if can_not_delete:
|
if can_not_delete:
|
||||||
@@ -103,8 +100,8 @@ def can_delete_users(_step, can_not_delete):
|
|||||||
assert world.is_css_present(to_delete_css)
|
assert world.is_css_present(to_delete_css)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I can(not)? add users')
|
@step(u'I can(not)? add users$')
|
||||||
@step(u's?he can(not)? add users')
|
@step(u's?he can(not)? add users$')
|
||||||
def can_add_users(_step, can_not_add):
|
def can_add_users(_step, can_not_add):
|
||||||
add_css = 'a.create-user-button'
|
add_css = 'a.create-user-button'
|
||||||
if can_not_add:
|
if can_not_add:
|
||||||
@@ -113,8 +110,8 @@ def can_add_users(_step, can_not_add):
|
|||||||
assert world.is_css_present(add_css)
|
assert world.is_css_present(add_css)
|
||||||
|
|
||||||
|
|
||||||
@step(u'I can(not)? make ("([^"]*)"|myself) a course team admin')
|
@step(u'I can(not)? make ("([^"]*)"|myself) a course team admin$')
|
||||||
@step(u's?he can(not)? make ("([^"]*)"|me) a course team admin')
|
@step(u's?he can(not)? make ("([^"]*)"|me) a course team admin$')
|
||||||
def can_make_course_admin(_step, can_not_make_admin, outer_capture, name):
|
def can_make_course_admin(_step, can_not_make_admin, outer_capture, name):
|
||||||
if outer_capture == "myself":
|
if outer_capture == "myself":
|
||||||
email = world.scenario_dict["USER"].email
|
email = world.scenario_dict["USER"].email
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from lettuce import world, step
|
|||||||
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
|
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
|
||||||
from common import type_in_codemirror, open_new_course
|
from common import type_in_codemirror, open_new_course
|
||||||
from course_import import import_file, go_to_import
|
from course_import import import_file, go_to_import
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
DISPLAY_NAME = "Display Name"
|
DISPLAY_NAME = "Display Name"
|
||||||
MAXIMUM_ATTEMPTS = "Maximum Attempts"
|
MAXIMUM_ATTEMPTS = "Maximum Attempts"
|
||||||
@@ -47,9 +47,7 @@ def i_can_modify_the_display_name(_step):
|
|||||||
# Verifying that the display name can be a string containing a floating point value
|
# Verifying that the display name can be a string containing a floating point value
|
||||||
# (to confirm that we don't throw an error because it is of the wrong type).
|
# (to confirm that we don't throw an error because it is of the wrong type).
|
||||||
index = world.get_setting_entry_index(DISPLAY_NAME)
|
index = world.get_setting_entry_index(DISPLAY_NAME)
|
||||||
world.css_fill('.wrapper-comp-setting .setting-input', '3.4', index=index)
|
set_field_value(index, '3.4')
|
||||||
if world.is_firefox():
|
|
||||||
world.trigger_event('.wrapper-comp-setting .setting-input', index=index)
|
|
||||||
verify_modified_display_name()
|
verify_modified_display_name()
|
||||||
|
|
||||||
|
|
||||||
@@ -62,9 +60,7 @@ def my_display_name_change_is_persisted_on_save(step):
|
|||||||
@step('I can specify special characters in the display name')
|
@step('I can specify special characters in the display name')
|
||||||
def i_can_modify_the_display_name_with_special_chars(_step):
|
def i_can_modify_the_display_name_with_special_chars(_step):
|
||||||
index = world.get_setting_entry_index(DISPLAY_NAME)
|
index = world.get_setting_entry_index(DISPLAY_NAME)
|
||||||
world.css_fill('.wrapper-comp-setting .setting-input', "updated ' \" &", index=index)
|
set_field_value(index, "updated ' \" &")
|
||||||
if world.is_firefox():
|
|
||||||
world.trigger_event('.wrapper-comp-setting .setting-input', index=index)
|
|
||||||
verify_modified_display_name_with_special_chars()
|
verify_modified_display_name_with_special_chars()
|
||||||
|
|
||||||
|
|
||||||
@@ -136,11 +132,10 @@ def set_the_weight_to_abc(step, bad_weight):
|
|||||||
|
|
||||||
@step('if I set the max attempts to "(.*)", it will persist as a valid integer$')
|
@step('if I set the max attempts to "(.*)", it will persist as a valid integer$')
|
||||||
def set_the_max_attempts(step, max_attempts_set):
|
def set_the_max_attempts(step, max_attempts_set):
|
||||||
# on firefox with selenium, the behaviour is different. eg 2.34 displays as 2.34 and is persisted as 2
|
# on firefox with selenium, the behaviour is different.
|
||||||
|
# eg 2.34 displays as 2.34 and is persisted as 2
|
||||||
index = world.get_setting_entry_index(MAXIMUM_ATTEMPTS)
|
index = world.get_setting_entry_index(MAXIMUM_ATTEMPTS)
|
||||||
world.css_fill('.wrapper-comp-setting .setting-input', max_attempts_set, index=index)
|
set_field_value(index, max_attempts_set)
|
||||||
if world.is_firefox():
|
|
||||||
world.trigger_event('.wrapper-comp-setting .setting-input', index=index)
|
|
||||||
world.save_component_and_reopen(step)
|
world.save_component_and_reopen(step)
|
||||||
value = world.css_value('input.setting-input', index=index)
|
value = world.css_value('input.setting-input', index=index)
|
||||||
assert value != "", "max attempts is blank"
|
assert value != "", "max attempts is blank"
|
||||||
@@ -276,12 +271,23 @@ def verify_unset_display_name():
|
|||||||
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'Blank Advanced Problem', False)
|
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'Blank Advanced Problem', False)
|
||||||
|
|
||||||
|
|
||||||
|
def set_field_value(index, value):
|
||||||
|
"""
|
||||||
|
Set the field to the specified value.
|
||||||
|
|
||||||
|
Note: we cannot use css_fill here because the value is not set
|
||||||
|
until after you move away from that field.
|
||||||
|
Instead we will find the element, set its value, then hit the Tab key
|
||||||
|
to get to the next field.
|
||||||
|
"""
|
||||||
|
elem = world.css_find('div.wrapper-comp-setting input.setting-input')[index]
|
||||||
|
elem.value = value
|
||||||
|
elem.type(Keys.TAB)
|
||||||
|
|
||||||
|
|
||||||
def set_weight(weight):
|
def set_weight(weight):
|
||||||
index = world.get_setting_entry_index(PROBLEM_WEIGHT)
|
index = world.get_setting_entry_index(PROBLEM_WEIGHT)
|
||||||
world.css_fill('.wrapper-comp-setting .setting-input', weight, index=index)
|
set_field_value(index, weight)
|
||||||
if world.is_firefox():
|
|
||||||
world.trigger_event('.wrapper-comp-setting .setting-input', index=index, event='blur')
|
|
||||||
world.trigger_event('a.save-button', event='focus')
|
|
||||||
|
|
||||||
|
|
||||||
def open_high_level_source():
|
def open_high_level_source():
|
||||||
|
|||||||
@@ -88,11 +88,7 @@ def delete_file(_step, file_name):
|
|||||||
assert index != -1
|
assert index != -1
|
||||||
delete_css = "a.remove-asset-button"
|
delete_css = "a.remove-asset-button"
|
||||||
world.css_click(delete_css, index=index)
|
world.css_click(delete_css, index=index)
|
||||||
|
world.confirm_studio_prompt()
|
||||||
world.wait_for_present(".wrapper-prompt.is-shown")
|
|
||||||
world.wait(0.2) # wait for css animation
|
|
||||||
prompt_confirm_css = 'li.nav-item > a.action-primary'
|
|
||||||
world.css_click(prompt_confirm_css)
|
|
||||||
|
|
||||||
|
|
||||||
@step(u'I should see only one "([^"]*)"$')
|
@step(u'I should see only one "([^"]*)"$')
|
||||||
|
|||||||
@@ -223,7 +223,20 @@ def wait_for_ajax_complete():
|
|||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
"""
|
"""
|
||||||
world.browser.driver.execute_async_script(dedent(javascript))
|
# Sometimes the ajax when it returns will make the browser reload
|
||||||
|
# the DOM, and throw a WebDriverException with the message:
|
||||||
|
# 'javascript error: document unloaded while waiting for result'
|
||||||
|
for _ in range(5): # 5 attempts max
|
||||||
|
try:
|
||||||
|
result = world.browser.driver.execute_async_script(dedent(javascript))
|
||||||
|
except WebDriverException as wde:
|
||||||
|
if "document unloaded while waiting for result" in wde.msg:
|
||||||
|
# Wait a bit, and try again, when the browser has reloaded the page.
|
||||||
|
world.wait(1)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
@@ -268,9 +281,9 @@ def css_has_text(css_selector, text, index=0, strip=False):
|
|||||||
# If we're expecting a non-empty string, give the page
|
# If we're expecting a non-empty string, give the page
|
||||||
# a chance to fill in text fields.
|
# a chance to fill in text fields.
|
||||||
if text:
|
if text:
|
||||||
world.wait_for(lambda _: world.css_text(css_selector, index=index))
|
wait_for(lambda _: css_text(css_selector, index=index))
|
||||||
|
|
||||||
actual_text = world.css_text(css_selector, index=index)
|
actual_text = css_text(css_selector, index=index)
|
||||||
|
|
||||||
if strip:
|
if strip:
|
||||||
actual_text = actual_text.strip()
|
actual_text = actual_text.strip()
|
||||||
@@ -291,88 +304,76 @@ def css_has_value(css_selector, value, index=0):
|
|||||||
# If we're expecting a non-empty string, give the page
|
# If we're expecting a non-empty string, give the page
|
||||||
# a chance to fill in values
|
# a chance to fill in values
|
||||||
if value:
|
if value:
|
||||||
world.wait_for(lambda _: world.css_value(css_selector, index=index))
|
wait_for(lambda _: css_value(css_selector, index=index))
|
||||||
|
|
||||||
return world.css_value(css_selector, index=index) == value
|
return css_value(css_selector, index=index) == value
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
def wait_for(func, timeout=5):
|
def wait_for(func, timeout=5, timeout_msg=None):
|
||||||
WebDriverWait(
|
"""
|
||||||
driver=world.browser.driver,
|
Calls the method provided with the driver as an argument until the
|
||||||
timeout=timeout,
|
return value is not False.
|
||||||
ignored_exceptions=(StaleElementReferenceException)
|
Throws an error if the WebDriverWait timeout clock expires.
|
||||||
).until(func)
|
Otherwise this method will return None.
|
||||||
|
"""
|
||||||
|
msg = timeout_msg or "Timed out after {} seconds.".format(timeout)
|
||||||
|
try:
|
||||||
|
WebDriverWait(
|
||||||
|
driver=world.browser.driver,
|
||||||
|
timeout=timeout,
|
||||||
|
ignored_exceptions=(StaleElementReferenceException)
|
||||||
|
).until(func)
|
||||||
|
except TimeoutException:
|
||||||
|
raise TimeoutException(msg)
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
def wait_for_present(css_selector, timeout=30):
|
def wait_for_present(css_selector, timeout=30):
|
||||||
"""
|
"""
|
||||||
Waiting for the element to be present in the DOM.
|
Wait for the element to be present in the DOM.
|
||||||
Throws an error if the WebDriverWait timeout clock expires.
|
|
||||||
Otherwise this method will return None
|
|
||||||
"""
|
"""
|
||||||
try:
|
wait_for(
|
||||||
WebDriverWait(
|
func=lambda _: EC.presence_of_element_located((By.CSS_SELECTOR, css_selector,)),
|
||||||
driver=world.browser.driver,
|
timeout=timeout,
|
||||||
timeout=timeout,
|
timeout_msg="Timed out waiting for {} to be present.".format(css_selector)
|
||||||
ignored_exceptions=(StaleElementReferenceException)
|
)
|
||||||
).until(EC.presence_of_element_located((By.CSS_SELECTOR, css_selector,)))
|
|
||||||
except TimeoutException:
|
|
||||||
raise TimeoutException("Timed out waiting for {} to be present.".format(css_selector))
|
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
def wait_for_visible(css_selector, timeout=30):
|
def wait_for_visible(css_selector, index=0, timeout=30):
|
||||||
"""
|
"""
|
||||||
Waiting for the element to be visible in the DOM.
|
Wait for the element to be visible in the DOM.
|
||||||
Throws an error if the WebDriverWait timeout clock expires.
|
|
||||||
Otherwise this method will return None
|
|
||||||
"""
|
"""
|
||||||
try:
|
wait_for(
|
||||||
WebDriverWait(
|
func=lambda _: css_visible(css_selector, index),
|
||||||
driver=world.browser.driver,
|
timeout=timeout,
|
||||||
timeout=timeout,
|
timeout_msg="Timed out waiting for {} to be visible.".format(css_selector)
|
||||||
ignored_exceptions=(StaleElementReferenceException)
|
)
|
||||||
).until(EC.visibility_of_element_located((By.CSS_SELECTOR, css_selector,)))
|
|
||||||
except TimeoutException:
|
|
||||||
raise TimeoutException("Timed out waiting for {} to be visible.".format(css_selector))
|
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
def wait_for_invisible(css_selector, timeout=30):
|
def wait_for_invisible(css_selector, timeout=30):
|
||||||
"""
|
"""
|
||||||
Waiting for the element to be either invisible or not present on the DOM.
|
Wait for the element to be either invisible or not present on the DOM.
|
||||||
Throws an error if the WebDriverWait timeout clock expires.
|
|
||||||
Otherwise this method will return None
|
|
||||||
"""
|
"""
|
||||||
try:
|
wait_for(
|
||||||
WebDriverWait(
|
func=lambda _: EC.invisibility_of_element_located((By.CSS_SELECTOR, css_selector,)),
|
||||||
driver=world.browser.driver,
|
timeout=timeout,
|
||||||
timeout=timeout,
|
timeout_msg="Timed out waiting for {} to be invisible.".format(css_selector)
|
||||||
ignored_exceptions=(StaleElementReferenceException)
|
)
|
||||||
).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, css_selector,)))
|
|
||||||
except TimeoutException:
|
|
||||||
raise TimeoutException("Timed out waiting for {} to be invisible.".format(css_selector))
|
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
def wait_for_clickable(css_selector, timeout=30):
|
def wait_for_clickable(css_selector, timeout=30):
|
||||||
"""
|
"""
|
||||||
Waiting for the element to be present and clickable.
|
Wait for the element to be present and clickable.
|
||||||
Throws an error if the WebDriverWait timeout clock expires.
|
|
||||||
Otherwise this method will return None.
|
|
||||||
"""
|
"""
|
||||||
# Sometimes the element is clickable then gets obscured.
|
wait_for(
|
||||||
# In this case, pause so that it is not reported clickable too early
|
func=lambda _: EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector,)),
|
||||||
try:
|
timeout=timeout,
|
||||||
WebDriverWait(
|
timeout_msg="Timed out waiting for {} to be clickable.".format(css_selector)
|
||||||
driver=world.browser.driver,
|
)
|
||||||
timeout=timeout,
|
|
||||||
ignored_exceptions=(StaleElementReferenceException)
|
|
||||||
).until(EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector,)))
|
|
||||||
except TimeoutException:
|
|
||||||
raise TimeoutException("Timed out waiting for {} to be clickable.".format(css_selector))
|
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
@@ -396,12 +397,13 @@ def css_click(css_selector, index=0, wait_time=30):
|
|||||||
This method will return True if the click worked.
|
This method will return True if the click worked.
|
||||||
"""
|
"""
|
||||||
wait_for_clickable(css_selector, timeout=wait_time)
|
wait_for_clickable(css_selector, timeout=wait_time)
|
||||||
|
wait_for_visible(css_selector, index=index, timeout=wait_time)
|
||||||
assert_true(
|
assert_true(
|
||||||
world.css_visible(css_selector, index=index),
|
css_visible(css_selector, index=index),
|
||||||
msg="Element {}[{}] is present but not visible".format(css_selector, index)
|
msg="Element {}[{}] is present but not visible".format(css_selector, index)
|
||||||
)
|
)
|
||||||
|
|
||||||
result = retry_on_exception(lambda: world.css_find(css_selector)[index].click())
|
result = retry_on_exception(lambda: css_find(css_selector)[index].click())
|
||||||
if result:
|
if result:
|
||||||
wait_for_js_to_load()
|
wait_for_js_to_load()
|
||||||
return result
|
return result
|
||||||
@@ -453,6 +455,7 @@ def css_fill(css_selector, text, index=0):
|
|||||||
Note that this will replace the current value completely.
|
Note that this will replace the current value completely.
|
||||||
Then for synchronization purposes, wait for the value on the page.
|
Then for synchronization purposes, wait for the value on the page.
|
||||||
"""
|
"""
|
||||||
|
wait_for_visible(css_selector, index=index)
|
||||||
retry_on_exception(lambda: css_find(css_selector)[index].fill(text))
|
retry_on_exception(lambda: css_find(css_selector)[index].fill(text))
|
||||||
wait_for(lambda _: css_has_value(css_selector, text, index=index))
|
wait_for(lambda _: css_has_value(css_selector, text, index=index))
|
||||||
return True
|
return True
|
||||||
@@ -525,19 +528,19 @@ def save_the_html(path='/tmp'):
|
|||||||
@world.absorb
|
@world.absorb
|
||||||
def click_course_content():
|
def click_course_content():
|
||||||
course_content_css = 'li.nav-course-courseware'
|
course_content_css = 'li.nav-course-courseware'
|
||||||
world.css_click(course_content_css)
|
css_click(course_content_css)
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
def click_course_settings():
|
def click_course_settings():
|
||||||
course_settings_css = 'li.nav-course-settings'
|
course_settings_css = 'li.nav-course-settings'
|
||||||
world.css_click(course_settings_css)
|
css_click(course_settings_css)
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
def click_tools():
|
def click_tools():
|
||||||
tools_css = 'li.nav-course-tools'
|
tools_css = 'li.nav-course-tools'
|
||||||
world.css_click(tools_css)
|
css_click(tools_css)
|
||||||
|
|
||||||
|
|
||||||
@world.absorb
|
@world.absorb
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ def select_the_verified_track(step):
|
|||||||
create_cert_course()
|
create_cert_course()
|
||||||
register()
|
register()
|
||||||
select_contribution(32)
|
select_contribution(32)
|
||||||
|
world.wait_for_ajax_complete()
|
||||||
btn_css = 'input[value="Select Certificate"]'
|
btn_css = 'input[value="Select Certificate"]'
|
||||||
world.css_click(btn_css)
|
world.css_click(btn_css)
|
||||||
assert world.is_css_present('section.progress')
|
assert world.is_css_present('section.progress')
|
||||||
@@ -174,6 +175,9 @@ def at_the_payment_page(step):
|
|||||||
|
|
||||||
@step(u'I submit valid payment information$')
|
@step(u'I submit valid payment information$')
|
||||||
def submit_payment(step):
|
def submit_payment(step):
|
||||||
|
# First make sure that the page is done if it still executing
|
||||||
|
# an ajax query.
|
||||||
|
world.wait_for_ajax_complete()
|
||||||
button_css = 'input[value=Submit]'
|
button_css = 'input[value=Submit]'
|
||||||
world.css_click(button_css)
|
world.css_click(button_css)
|
||||||
|
|
||||||
|
|||||||
@@ -180,7 +180,6 @@ Feature: LMS.Answer problems
|
|||||||
Given I am viewing a "<ProblemType>" problem
|
Given I am viewing a "<ProblemType>" problem
|
||||||
Then my "<ProblemType>" answer is marked "unanswered"
|
Then my "<ProblemType>" answer is marked "unanswered"
|
||||||
When I answer a "<ProblemType>" problem "<InitialCorrectness>ly"
|
When I answer a "<ProblemType>" problem "<InitialCorrectness>ly"
|
||||||
And I wait for "1" seconds
|
|
||||||
And I input an answer on a "<ProblemType>" problem "<OtherCorrectness>ly"
|
And I input an answer on a "<ProblemType>" problem "<OtherCorrectness>ly"
|
||||||
Then my "<ProblemType>" answer is marked "unanswered"
|
Then my "<ProblemType>" answer is marked "unanswered"
|
||||||
And I reset the problem
|
And I reset the problem
|
||||||
@@ -208,7 +207,6 @@ Feature: LMS.Answer problems
|
|||||||
Scenario: I can reset the correctness of a radiogroup problem after changing my answer
|
Scenario: I can reset the correctness of a radiogroup problem after changing my answer
|
||||||
Given I am viewing a "<ProblemType>" problem
|
Given I am viewing a "<ProblemType>" problem
|
||||||
When I answer a "<ProblemType>" problem "<InitialCorrectness>ly"
|
When I answer a "<ProblemType>" problem "<InitialCorrectness>ly"
|
||||||
And I wait for "1" seconds
|
|
||||||
Then my "<ProblemType>" answer is marked "<InitialCorrectness>"
|
Then my "<ProblemType>" answer is marked "<InitialCorrectness>"
|
||||||
And I input an answer on a "<ProblemType>" problem "<OtherCorrectness>ly"
|
And I input an answer on a "<ProblemType>" problem "<OtherCorrectness>ly"
|
||||||
Then my "<ProblemType>" answer is NOT marked "<InitialCorrectness>"
|
Then my "<ProblemType>" answer is NOT marked "<InitialCorrectness>"
|
||||||
|
|||||||
@@ -163,6 +163,10 @@ PROBLEM_DICT = {
|
|||||||
|
|
||||||
|
|
||||||
def answer_problem(problem_type, correctness):
|
def answer_problem(problem_type, correctness):
|
||||||
|
# Make sure that the problem has been completely rendered before
|
||||||
|
# starting to input an answer.
|
||||||
|
world.wait_for_ajax_complete()
|
||||||
|
|
||||||
if problem_type == "drop down":
|
if problem_type == "drop down":
|
||||||
select_name = "input_i4x-edx-model_course-problem-drop_down_2_1"
|
select_name = "input_i4x-edx-model_course-problem-drop_down_2_1"
|
||||||
option_text = 'Option 2' if correctness == 'correct' else 'Option 3'
|
option_text = 'Option 2' if correctness == 'correct' else 'Option 3'
|
||||||
|
|||||||
Reference in New Issue
Block a user