Merge pull request #995 from edx/zoldak/fix-acceptance
Simplify retry logic for ui helper functions
This commit is contained in:
@@ -11,7 +11,6 @@ DISPLAY_NAME_KEY = "display_name"
|
||||
DISPLAY_NAME_VALUE = '"Robot Super Course"'
|
||||
|
||||
|
||||
############### ACTIONS ####################
|
||||
@step('I select the Advanced Settings$')
|
||||
def i_select_advanced_settings(step):
|
||||
world.click_course_settings()
|
||||
@@ -45,7 +44,6 @@ def create_value_not_in_quotes(step):
|
||||
change_display_name_value(step, 'quote me')
|
||||
|
||||
|
||||
############### RESULTS ####################
|
||||
@step('I see default advanced settings$')
|
||||
def i_see_default_advanced_settings(step):
|
||||
# Test only a few of the existing properties (there are around 34 of them)
|
||||
@@ -88,12 +86,13 @@ def the_policy_key_value_is_changed(step):
|
||||
assert_equal(get_display_name_value(), '"foo"')
|
||||
|
||||
|
||||
############# HELPERS ###############
|
||||
def assert_policy_entries(expected_keys, expected_values):
|
||||
for key, value in zip(expected_keys, expected_values):
|
||||
index = get_index_of(key)
|
||||
assert_false(index == -1, "Could not find key: {key}".format(key=key))
|
||||
assert_equal(value, world.css_find(VALUE_CSS)[index].value, "value is incorrect")
|
||||
found_value = world.css_find(VALUE_CSS)[index].value
|
||||
assert_equal(value, found_value,
|
||||
"Expected {} to have value {} but found {}".format(key, value, found_value))
|
||||
|
||||
|
||||
def get_index_of(expected_key):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from nose.tools import assert_true # pylint: disable=E0611
|
||||
from nose.tools import assert_true, 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
|
||||
@@ -19,8 +19,6 @@ from terrain.browser import reset_data
|
||||
|
||||
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
|
||||
|
||||
########### STEP HELPERS ##############
|
||||
|
||||
|
||||
@step('I (?:visit|access|open) the Studio homepage$')
|
||||
def i_visit_the_studio_homepage(_step):
|
||||
@@ -66,20 +64,32 @@ def select_new_course(_step, whom):
|
||||
|
||||
@step(u'I press the "([^"]*)" notification button$')
|
||||
def press_the_notification_button(_step, name):
|
||||
css = 'a.action-%s' % name.lower()
|
||||
# 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
|
||||
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)
|
||||
|
||||
# The button was clicked if either the notification bar is gone,
|
||||
# or we see an error overlaying it (expected for invalid inputs).
|
||||
def button_clicked():
|
||||
confirmation_dismissed = world.is_css_not_present('.is-shown.wrapper-notification-warning')
|
||||
error_showing = world.is_css_present('.is-shown.wrapper-notification-error')
|
||||
return confirmation_dismissed or error_showing
|
||||
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(css, event='focus')
|
||||
world.browser.execute_script("$('{}').click()".format(css))
|
||||
# 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(css, success_condition=button_clicked), '%s button not clicked after 5 attempts.' % name
|
||||
world.css_click(btn_css)
|
||||
|
||||
|
||||
@step('I change the "(.*)" field to "(.*)"$')
|
||||
@@ -110,7 +120,6 @@ def i_see_a_confirmation(step):
|
||||
assert world.is_css_present(confirmation_css)
|
||||
|
||||
|
||||
####### HELPER FUNCTIONS ##############
|
||||
def open_new_course():
|
||||
world.clear_courses()
|
||||
create_studio_user()
|
||||
@@ -156,8 +165,8 @@ def log_into_studio(
|
||||
world.log_in(username=uname, password=password, email=email, name=name)
|
||||
# Navigate to the studio dashboard
|
||||
world.visit('/')
|
||||
assert_in(uname, world.css_text('h2.title', timeout=10))
|
||||
|
||||
assert uname in world.css_text('h2.title', max_attempts=15)
|
||||
|
||||
def create_a_course():
|
||||
course = world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||
@@ -247,8 +256,22 @@ def button_disabled(step, value):
|
||||
|
||||
@step('I confirm the prompt')
|
||||
def confirm_the_prompt(step):
|
||||
prompt_css = 'a.button.action-primary'
|
||||
world.css_click(prompt_css, success_condition=lambda: not world.css_visible(prompt_css))
|
||||
|
||||
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 (.*)$')
|
||||
@@ -257,6 +280,7 @@ def i_am_shown_a_notification(step, notification_type):
|
||||
|
||||
|
||||
def type_in_codemirror(index, text):
|
||||
world.wait(1) # For now, slow this down so that it works. TODO: fix it.
|
||||
world.css_click("div.CodeMirror-lines", index=index)
|
||||
world.browser.execute_script("$('div.CodeMirror.CodeMirror-focused > div').css('overflow', '')")
|
||||
g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea")
|
||||
|
||||
@@ -48,9 +48,7 @@ def click_component_from_menu(category, boilerplate, expected_css):
|
||||
elem_css = "a[data-category='{}']:not([data-boilerplate])".format(category)
|
||||
elements = world.css_find(elem_css)
|
||||
assert_equal(len(elements), 1)
|
||||
world.wait_for(lambda _driver: world.css_visible(elem_css))
|
||||
world.css_click(elem_css, success_condition=lambda: 1 == len(world.css_find(expected_css)))
|
||||
|
||||
world.css_click(elem_css)
|
||||
|
||||
@world.absorb
|
||||
def edit_component_and_select_settings():
|
||||
|
||||
@@ -113,7 +113,7 @@ def test_i_have_entered_a_new_course_start_date(step):
|
||||
|
||||
@step('The warning about course start date goes away$')
|
||||
def test_the_warning_about_course_start_date_goes_away(step):
|
||||
assert_equal(0, len(world.css_find('.message-error')))
|
||||
assert world.is_css_not_present('.message-error')
|
||||
assert_false('error' in world.css_find(COURSE_START_DATE_CSS).first._element.get_attribute('class'))
|
||||
assert_false('error' in world.css_find(COURSE_START_TIME_CSS).first._element.get_attribute('class'))
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from lettuce import world, step
|
||||
from common import create_studio_user
|
||||
from django.contrib.auth.models import Group
|
||||
from auth.authz import get_course_groupname_for_role, get_user_by_email
|
||||
from nose.tools import assert_true # pylint: disable=E0611
|
||||
from nose.tools import assert_true, assert_in # pylint: disable=E0611
|
||||
|
||||
PASSWORD = 'test'
|
||||
EMAIL_EXTENSION = '@edx.org'
|
||||
@@ -110,36 +110,36 @@ def other_user_login(_step, name):
|
||||
|
||||
@step(u'I( do not)? see the course on my page')
|
||||
@step(u's?he does( not)? see the course on (his|her) page')
|
||||
def see_course(_step, inverted, gender='self'):
|
||||
def see_course(_step, do_not_see, gender='self'):
|
||||
class_css = 'h3.course-title'
|
||||
all_courses = world.css_find(class_css, wait_time=1)
|
||||
all_names = [item.html for item in all_courses]
|
||||
if inverted:
|
||||
assert not world.scenario_dict['COURSE'].display_name in all_names
|
||||
if do_not_see:
|
||||
assert world.is_css_not_present(class_css)
|
||||
else:
|
||||
assert world.scenario_dict['COURSE'].display_name in all_names
|
||||
all_courses = world.css_find(class_css)
|
||||
all_names = [item.html for item in all_courses]
|
||||
assert_in(world.scenario_dict['COURSE'].display_name, all_names)
|
||||
|
||||
|
||||
@step(u'"([^"]*)" should( not)? be marked as an admin')
|
||||
def marked_as_admin(_step, name, inverted):
|
||||
def marked_as_admin(_step, name, not_marked_admin):
|
||||
flag_css = '.user-item[data-email="{email}"] .flag-role.flag-role-admin'.format(
|
||||
email=name+EMAIL_EXTENSION)
|
||||
if inverted:
|
||||
if not_marked_admin:
|
||||
assert world.is_css_not_present(flag_css)
|
||||
else:
|
||||
assert world.is_css_present(flag_css)
|
||||
|
||||
|
||||
@step(u'I should( not)? be marked as an admin')
|
||||
def self_marked_as_admin(_step, inverted):
|
||||
return marked_as_admin(_step, "robot+studio", inverted)
|
||||
def self_marked_as_admin(_step, not_marked_admin):
|
||||
return marked_as_admin(_step, "robot+studio", not_marked_admin)
|
||||
|
||||
|
||||
@step(u'I can(not)? delete users')
|
||||
@step(u's?he can(not)? delete users')
|
||||
def can_delete_users(_step, inverted):
|
||||
def can_delete_users(_step, can_not_delete):
|
||||
to_delete_css = 'a.remove-user'
|
||||
if inverted:
|
||||
if can_not_delete:
|
||||
assert world.is_css_not_present(to_delete_css)
|
||||
else:
|
||||
assert world.is_css_present(to_delete_css)
|
||||
@@ -147,9 +147,9 @@ def can_delete_users(_step, inverted):
|
||||
|
||||
@step(u'I can(not)? add users')
|
||||
@step(u's?he can(not)? add users')
|
||||
def can_add_users(_step, inverted):
|
||||
def can_add_users(_step, can_not_add):
|
||||
add_css = 'a.create-user-button'
|
||||
if inverted:
|
||||
if can_not_add:
|
||||
assert world.is_css_not_present(add_css)
|
||||
else:
|
||||
assert world.is_css_present(add_css)
|
||||
@@ -157,13 +157,13 @@ def can_add_users(_step, inverted):
|
||||
|
||||
@step(u'I can(not)? make ("([^"]*)"|myself) a course team admin')
|
||||
@step(u's?he can(not)? make ("([^"]*)"|me) a course team admin')
|
||||
def can_make_course_admin(_step, inverted, outer_capture, name):
|
||||
def can_make_course_admin(_step, can_not_make_admin, outer_capture, name):
|
||||
if outer_capture == "myself":
|
||||
email = world.scenario_dict["USER"].email
|
||||
else:
|
||||
email = name + EMAIL_EXTENSION
|
||||
add_button_css = '.user-item[data-email="{email}"] .add-admin-role'.format(email=email)
|
||||
if inverted:
|
||||
if can_not_make_admin:
|
||||
assert world.is_css_not_present(add_button_css)
|
||||
else:
|
||||
assert world.is_css_present(add_button_css)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from lettuce import world, step
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from common import type_in_codemirror
|
||||
from nose.tools import assert_in # pylint: disable=E0611
|
||||
|
||||
|
||||
@step(u'I go to the course updates page')
|
||||
@@ -21,14 +22,17 @@ def add_update(_step, text):
|
||||
change_text(text)
|
||||
|
||||
|
||||
@step(u'I should( not)? see the update "([^"]*)"$')
|
||||
def check_update(_step, doesnt_see_update, text):
|
||||
@step(u'I should see the update "([^"]*)"$')
|
||||
def check_update(_step, text):
|
||||
update_css = 'div.update-contents'
|
||||
update = world.css_find(update_css, wait_time=1)
|
||||
if doesnt_see_update:
|
||||
assert len(update) == 0 or not text in update.html
|
||||
else:
|
||||
assert text in update.html
|
||||
update_html = world.css_find(update_css).html
|
||||
assert_in(text, update_html)
|
||||
|
||||
|
||||
@step(u'I should not see the update "([^"]*)"$')
|
||||
def check_no_update(_step, text):
|
||||
update_css = 'div.update-contents'
|
||||
assert world.is_css_not_present(update_css)
|
||||
|
||||
|
||||
@step(u'I modify the text to "([^"]*)"$')
|
||||
|
||||
@@ -5,6 +5,7 @@ from lettuce import world, step
|
||||
from common import *
|
||||
from terrain.steps import reload_the_page
|
||||
from selenium.common.exceptions import InvalidElementStateException
|
||||
from nose.tools import assert_in, assert_not_in # pylint: disable=E0611
|
||||
|
||||
|
||||
@step(u'I am viewing the grading settings')
|
||||
@@ -65,21 +66,25 @@ def change_assignment_name(step, old_name, new_name):
|
||||
|
||||
@step(u'I go back to the main course page')
|
||||
def main_course_page(step):
|
||||
main_page_link_css = 'a[href="/%s/%s/course/%s"]' % (world.scenario_dict['COURSE'].org,
|
||||
world.scenario_dict['COURSE'].number,
|
||||
world.scenario_dict['COURSE'].display_name.replace(' ', '_'),)
|
||||
world.css_click(main_page_link_css)
|
||||
main_page_link = '/{}/{}/course/{}'.format(world.scenario_dict['COURSE'].org,
|
||||
world.scenario_dict['COURSE'].number,
|
||||
world.scenario_dict['COURSE'].display_name.replace(' ', '_'),)
|
||||
world.visit(main_page_link)
|
||||
assert_in('Course Outline', world.css_text('h1.page-header'))
|
||||
|
||||
|
||||
@step(u'I do( not)? see the assignment name "([^"]*)"$')
|
||||
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)
|
||||
|
||||
assignment_menu = world.css_find(assignment_menu_css)
|
||||
allnames = [item.html for item in assignment_menu]
|
||||
if do_not:
|
||||
assert not name in allnames
|
||||
assert_not_in(name, allnames)
|
||||
else:
|
||||
assert name in allnames
|
||||
assert_in(name, allnames)
|
||||
|
||||
|
||||
@step(u'I delete the assignment type "([^"]*)"$')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#pylint: disable=C0111
|
||||
|
||||
from lettuce import world, step
|
||||
from nose.tools import assert_equal # pylint: disable=E0611
|
||||
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
|
||||
from common import type_in_codemirror
|
||||
|
||||
DISPLAY_NAME = "Display Name"
|
||||
@@ -197,9 +197,20 @@ def high_level_source_in_editor(step):
|
||||
|
||||
|
||||
def verify_high_level_source_links(step, visible):
|
||||
assert_equal(visible, world.is_css_present('.launch-latex-compiler'))
|
||||
if visible:
|
||||
assert_true(world.is_css_present('.launch-latex-compiler'),
|
||||
msg="Expected to find the latex button but it is not present.")
|
||||
else:
|
||||
assert_true(world.is_css_not_present('.launch-latex-compiler'),
|
||||
msg="Expected not to find the latex button but it is present.")
|
||||
|
||||
world.cancel_component(step)
|
||||
assert_equal(visible, world.is_css_present('.upload-button'))
|
||||
if visible:
|
||||
assert_true(world.is_css_present('.upload-button'),
|
||||
msg="Expected to find the upload button but it is not present.")
|
||||
else:
|
||||
assert_true(world.is_css_not_present('.upload-button'),
|
||||
msg="Expected not to find the upload button but it is present.")
|
||||
|
||||
|
||||
def verify_modified_weight():
|
||||
|
||||
@@ -12,7 +12,7 @@ def i_fill_in_the_registration_form(step):
|
||||
register_form.find_by_name('password').fill('test')
|
||||
register_form.find_by_name('username').fill('robot-studio')
|
||||
register_form.find_by_name('name').fill('Robot Studio')
|
||||
register_form.find_by_name('terms_of_service').check()
|
||||
register_form.find_by_name('terms_of_service').click()
|
||||
world.retry_on_exception(fill_in_reg_form)
|
||||
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from nose.tools import assert_true # pylint: disable=E0611
|
||||
|
||||
|
||||
@step(u'I go to the static pages page')
|
||||
def go_to_static(_step):
|
||||
def go_to_static(step):
|
||||
menu_css = 'li.nav-course-courseware'
|
||||
static_css = 'li.nav-course-courseware-pages a'
|
||||
world.css_click(menu_css)
|
||||
@@ -15,25 +14,37 @@ def go_to_static(_step):
|
||||
|
||||
|
||||
@step(u'I add a new page')
|
||||
def add_page(_step):
|
||||
def add_page(step):
|
||||
button_css = 'a.new-button'
|
||||
world.css_click(button_css)
|
||||
|
||||
|
||||
@step(u'I should( not)? see a "([^"]*)" static page$')
|
||||
def see_page(_step, doesnt, page):
|
||||
@step(u'I should not see a "([^"]*)" static page$')
|
||||
def not_see_page(step, page):
|
||||
# Either there are no pages, or there are pages but
|
||||
# not the one I expect not to exist.
|
||||
|
||||
should_exist = not doesnt
|
||||
# Since our only test for deletion right now deletes
|
||||
# the only static page that existed, our success criteria
|
||||
# will be that there are no static pages.
|
||||
# In the future we can refactor if necessary.
|
||||
tabs_css = 'li.component'
|
||||
assert (world.is_css_not_present(tabs_css, wait_time=30))
|
||||
|
||||
|
||||
@step(u'I should see a "([^"]*)" static page$')
|
||||
def see_page(step, page):
|
||||
|
||||
# Need to retry here because the element
|
||||
# will sometimes exist before the HTML content is loaded
|
||||
exists_func = lambda(driver): page_exists(page) == should_exist
|
||||
exists_func = lambda(driver): page_exists(page)
|
||||
|
||||
world.wait_for(exists_func)
|
||||
assert_true(exists_func(None))
|
||||
|
||||
|
||||
@step(u'I "([^"]*)" the "([^"]*)" page$')
|
||||
def click_edit_delete(_step, edit_delete, page):
|
||||
def click_edit_delete(step, edit_delete, page):
|
||||
button_css = 'a.%s-button' % edit_delete
|
||||
index = get_index(page)
|
||||
assert index is not None
|
||||
@@ -41,7 +52,7 @@ def click_edit_delete(_step, edit_delete, page):
|
||||
|
||||
|
||||
@step(u'I change the name to "([^"]*)"$')
|
||||
def change_name(_step, new_name):
|
||||
def change_name(step, new_name):
|
||||
settings_css = '#settings-mode a'
|
||||
world.css_click(settings_css)
|
||||
input_css = 'input.setting-input'
|
||||
@@ -56,9 +67,10 @@ def get_index(name):
|
||||
page_name_css = 'section[data-type="HTMLModule"]'
|
||||
all_pages = world.css_find(page_name_css)
|
||||
for i in range(len(all_pages)):
|
||||
if world.css_html(page_name_css, index=i) == '\n {name}\n'.format(name=name):
|
||||
if all_pages[i].html == '\n {name}\n'.format(name=name):
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def page_exists(page):
|
||||
return get_index(page) is not None
|
||||
|
||||
@@ -7,6 +7,8 @@ import requests
|
||||
import string
|
||||
import random
|
||||
import os
|
||||
from nose.tools import assert_equal, assert_not_equal # pylint: disable=E0611
|
||||
|
||||
|
||||
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
|
||||
|
||||
@@ -32,7 +34,7 @@ def upload_file(_step, file_name):
|
||||
|
||||
|
||||
@step(u'I upload the files (".*")$')
|
||||
def upload_file(_step, files_string):
|
||||
def upload_files(_step, files_string):
|
||||
# Turn files_string to a list of file names
|
||||
files = files_string.split(",")
|
||||
files = map(lambda x: string.strip(x, ' "\''), files)
|
||||
@@ -48,19 +50,29 @@ def upload_file(_step, files_string):
|
||||
world.css_click(close_css)
|
||||
|
||||
|
||||
@step(u'I should( not)? see the file "([^"]*)" was uploaded$')
|
||||
def check_upload(_step, do_not_see_file, file_name):
|
||||
@step(u'I should not see the file "([^"]*)" was uploaded$')
|
||||
def check_not_there(_step, file_name):
|
||||
# Either there are no files, or there are files but
|
||||
# not the one I expect not to exist.
|
||||
|
||||
# Since our only test for deletion right now deletes
|
||||
# the only file that was uploaded, our success criteria
|
||||
# will be that there are no files.
|
||||
# In the future we can refactor if necessary.
|
||||
names_css = 'td.name-col > a.filename'
|
||||
assert(world.is_css_not_present(names_css))
|
||||
|
||||
|
||||
@step(u'I should see the file "([^"]*)" was uploaded$')
|
||||
def check_upload(_step, file_name):
|
||||
index = get_index(file_name)
|
||||
if do_not_see_file:
|
||||
assert index == -1
|
||||
else:
|
||||
assert index != -1
|
||||
assert_not_equal(index, -1)
|
||||
|
||||
|
||||
@step(u'The url for the file "([^"]*)" is valid$')
|
||||
def check_url(_step, file_name):
|
||||
r = get_file(file_name)
|
||||
assert r.status_code == 200
|
||||
assert_equal(r.status_code , 200)
|
||||
|
||||
|
||||
@step(u'I delete the file "([^"]*)"$')
|
||||
@@ -71,7 +83,7 @@ def delete_file(_step, file_name):
|
||||
world.css_click(delete_css, index=index)
|
||||
|
||||
prompt_confirm_css = 'li.nav-item > a.action-primary'
|
||||
world.css_click(prompt_confirm_css, success_condition=lambda: not world.css_visible(prompt_confirm_css))
|
||||
world.css_click(prompt_confirm_css)
|
||||
|
||||
|
||||
@step(u'I should see only one "([^"]*)"$')
|
||||
|
||||
@@ -75,7 +75,8 @@ def make_desired_capabilities():
|
||||
desired_capabilities['build'] = settings.SAUCE.get('BUILD')
|
||||
desired_capabilities['video-upload-on-pass'] = False
|
||||
desired_capabilities['sauce-advisor'] = False
|
||||
desired_capabilities['record-screenshots'] = False
|
||||
desired_capabilities['capture-html'] = True
|
||||
desired_capabilities['record-screenshots'] = True
|
||||
desired_capabilities['selenium-version'] = "2.34.0"
|
||||
desired_capabilities['max-duration'] = 3600
|
||||
desired_capabilities['public'] = 'public restricted'
|
||||
@@ -164,15 +165,18 @@ def reset_databases(scenario):
|
||||
xmodule.modulestore.django.clear_existing_modulestores()
|
||||
|
||||
|
||||
# Uncomment below to trigger a screenshot on error
|
||||
# @after.each_scenario
|
||||
@after.each_scenario
|
||||
def screenshot_on_error(scenario):
|
||||
"""
|
||||
Save a screenshot to help with debugging.
|
||||
"""
|
||||
if scenario.failed:
|
||||
world.browser.driver.save_screenshot('/tmp/last_failed_scenario.png')
|
||||
|
||||
try:
|
||||
output_dir = '{}/log'.format(settings.TEST_ROOT)
|
||||
image_name = '{}/{}.png'.format(output_dir, scenario.name.replace(' ', '_'))
|
||||
world.browser.driver.save_screenshot(image_name)
|
||||
except WebDriverException:
|
||||
LOGGER.error('Could not capture a screenshot')
|
||||
|
||||
@after.all
|
||||
def teardown_browser(total):
|
||||
|
||||
@@ -5,7 +5,7 @@ from lettuce import world
|
||||
import time
|
||||
import platform
|
||||
from urllib import quote_plus
|
||||
from selenium.common.exceptions import WebDriverException, StaleElementReferenceException
|
||||
from selenium.common.exceptions import WebDriverException, TimeoutException
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
@@ -18,11 +18,6 @@ def wait(seconds):
|
||||
time.sleep(float(seconds))
|
||||
|
||||
|
||||
@world.absorb
|
||||
def wait_for(func):
|
||||
WebDriverWait(world.browser.driver, 5).until(func)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def visit(url):
|
||||
world.browser.visit(django_url(url))
|
||||
@@ -34,7 +29,7 @@ def url_equals(url):
|
||||
|
||||
|
||||
@world.absorb
|
||||
def is_css_present(css_selector, wait_time=5):
|
||||
def is_css_present(css_selector, wait_time=10):
|
||||
return world.browser.is_element_present_by_css(css_selector, wait_time=wait_time)
|
||||
|
||||
|
||||
@@ -44,92 +39,130 @@ def is_css_not_present(css_selector, wait_time=5):
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_has_text(css_selector, text, index=0, max_attempts=5):
|
||||
return world.css_text(css_selector, index=index, max_attempts=max_attempts) == text
|
||||
def css_has_text(css_selector, text, index=0):
|
||||
return world.css_text(css_selector, index=index) == text
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_find(css, wait_time=5):
|
||||
def is_visible(_driver):
|
||||
return EC.visibility_of_element_located((By.CSS_SELECTOR, css,))
|
||||
def wait_for(func, timeout=5):
|
||||
WebDriverWait(world.browser.driver, timeout).until(func)
|
||||
|
||||
world.browser.is_element_present_by_css(css, wait_time=wait_time)
|
||||
wait_for(is_visible)
|
||||
|
||||
def wait_for_present(css_selector, timeout=30):
|
||||
"""
|
||||
Waiting for the element to be present in the DOM.
|
||||
Throws an error if the wait_for time expires.
|
||||
Otherwise this method will return None
|
||||
"""
|
||||
try:
|
||||
WebDriverWait(driver=world.browser.driver,
|
||||
timeout=60).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
|
||||
def wait_for_visible(css_selector, timeout=30):
|
||||
"""
|
||||
Waiting for the element to be visible in the DOM.
|
||||
Throws an error if the wait_for time expires.
|
||||
Otherwise this method will return None
|
||||
"""
|
||||
try:
|
||||
WebDriverWait(driver=world.browser.driver,
|
||||
timeout=timeout).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
|
||||
def wait_for_invisible(css_selector, timeout=30):
|
||||
"""
|
||||
Waiting for the element to be either invisible or not present on the DOM.
|
||||
Throws an error if the wait_for time expires.
|
||||
Otherwise this method will return None
|
||||
"""
|
||||
try:
|
||||
WebDriverWait(driver=world.browser.driver,
|
||||
timeout=timeout).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
|
||||
def wait_for_clickable(css_selector, timeout=30):
|
||||
"""
|
||||
Waiting for the element to be present and clickable.
|
||||
Throws an error if the wait_for time expires.
|
||||
Otherwise this method will return None.
|
||||
"""
|
||||
# Sometimes the element is clickable then gets obscured.
|
||||
# In this case, pause so that it is not reported clickable too early
|
||||
try:
|
||||
WebDriverWait(world.browser.driver,
|
||||
timeout=timeout).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
|
||||
def css_find(css, wait_time=30):
|
||||
"""
|
||||
Wait for the element(s) as defined by css locator
|
||||
to be present.
|
||||
|
||||
This method will return a WebDriverElement.
|
||||
"""
|
||||
wait_for_present(css_selector=css, timeout=wait_time)
|
||||
return world.browser.find_by_css(css)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_click(css_selector, index=0, max_attempts=5, success_condition=lambda: True):
|
||||
def css_click(css_selector, index=0, wait_time=30):
|
||||
"""
|
||||
Perform a click on a CSS selector, retrying if it initially fails.
|
||||
Perform a click on a CSS selector, first waiting for the element
|
||||
to be present and clickable.
|
||||
|
||||
This function handles errors that may be thrown if the component cannot be clicked on.
|
||||
However, there are cases where an error may not be thrown, and yet the operation did not
|
||||
actually succeed. For those cases, a success_condition lambda can be supplied to verify that the click worked.
|
||||
|
||||
This function will return True if the click worked (taking into account both errors and the optional
|
||||
success_condition).
|
||||
This method will return True if the click worked.
|
||||
"""
|
||||
assert is_css_present(css_selector), "{} is not present".format(css_selector)
|
||||
for _ in range(max_attempts):
|
||||
try:
|
||||
world.css_find(css_selector)[index].click()
|
||||
if success_condition():
|
||||
return
|
||||
except WebDriverException:
|
||||
# Occasionally, MathJax or other JavaScript can cover up
|
||||
# an element temporarily.
|
||||
# If this happens, wait a second, then try again
|
||||
world.wait(1)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# try once more, letting exceptions raise
|
||||
world.css_find(css_selector)[index].click()
|
||||
if not success_condition():
|
||||
raise Exception("unsuccessful click")
|
||||
wait_for_clickable(css_selector, timeout=wait_time)
|
||||
assert_true(world.css_find(css_selector)[index].visible,
|
||||
msg="Element {}[{}] is present but not visible".format(css_selector, index))
|
||||
|
||||
# Sometimes you can't click in the center of the element, as
|
||||
# another element might be on top of it. In this case, try
|
||||
# clicking in the upper left corner.
|
||||
try:
|
||||
return world.css_find(css_selector)[index].click()
|
||||
|
||||
except WebDriverException:
|
||||
return css_click_at(css_selector, index=index)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_check(css_selector, index=0, max_attempts=5, success_condition=lambda: True):
|
||||
def css_check(css_selector, index=0, wait_time=30):
|
||||
"""
|
||||
Checks a check box based on a CSS selector, retrying if it initially fails.
|
||||
Checks a check box based on a CSS selector, first waiting for the element
|
||||
to be present and clickable. This is just a wrapper for calling "click"
|
||||
because that's how selenium interacts with check boxes and radio buttons.
|
||||
|
||||
This function handles errors that may be thrown if the component cannot be clicked on.
|
||||
However, there are cases where an error may not be thrown, and yet the operation did not
|
||||
actually succeed. For those cases, a success_condition lambda can be supplied to verify that the check worked.
|
||||
|
||||
This function will return True if the check worked (taking into account both errors and the optional
|
||||
success_condition).
|
||||
This method will return True if the check worked.
|
||||
"""
|
||||
assert is_css_present(css_selector), "{} is not present".format(css_selector)
|
||||
for _ in range(max_attempts):
|
||||
try:
|
||||
world.css_find(css_selector)[index].check()
|
||||
if success_condition():
|
||||
return
|
||||
except WebDriverException:
|
||||
# Occasionally, MathJax or other JavaScript can cover up
|
||||
# an element temporarily.
|
||||
# If this happens, wait a second, then try again
|
||||
world.wait(1)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# try once more, letting exceptions raise
|
||||
world.css_find(css_selector)[index].check()
|
||||
if not success_condition():
|
||||
raise Exception("unsuccessful check")
|
||||
return css_click(css_selector=css_selector, index=index, wait_time=wait_time)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_click_at(css, x_cord=10, y_cord=10):
|
||||
def css_click_at(css_selector, index=0, x_coord=10, y_coord=10, timeout=5):
|
||||
'''
|
||||
A method to click at x,y coordinates of the element
|
||||
rather than in the center of the element
|
||||
'''
|
||||
element = css_find(css).first
|
||||
element.action_chains.move_to_element_with_offset(element._element, x_cord, y_cord)
|
||||
wait_for_clickable(css_selector, timeout=timeout)
|
||||
element = css_find(css_selector)[index]
|
||||
assert_true(element.visible,
|
||||
msg="Element {}[{}] is present but not visible".format(css_selector, index))
|
||||
|
||||
element.action_chains.move_to_element_with_offset(element._element, x_coord, y_coord)
|
||||
element.action_chains.click()
|
||||
element.action_chains.perform()
|
||||
|
||||
@@ -139,58 +172,55 @@ def id_click(elem_id):
|
||||
"""
|
||||
Perform a click on an element as specified by its id
|
||||
"""
|
||||
world.css_click('#%s' % elem_id)
|
||||
css_click('#{}'.format(elem_id))
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_fill(css_selector, text, index=0, max_attempts=5):
|
||||
assert is_css_present(css_selector)
|
||||
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].fill(text), max_attempts=max_attempts)
|
||||
def css_fill(css_selector, text, index=0):
|
||||
css_find(css_selector)[index].fill(text)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def click_link(partial_text, index=0, max_attempts=5):
|
||||
return world.retry_on_exception(lambda: world.browser.find_link_by_partial_text(partial_text)[index].click(), max_attempts=max_attempts)
|
||||
def click_link(partial_text, index=0):
|
||||
world.browser.find_link_by_partial_text(partial_text)[index].click()
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_text(css_selector, index=0, max_attempts=5):
|
||||
|
||||
def css_text(css_selector, index=0, timeout=30):
|
||||
# Wait for the css selector to appear
|
||||
if world.is_css_present(css_selector):
|
||||
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].text, max_attempts=max_attempts)
|
||||
if is_css_present(css_selector):
|
||||
return css_find(css_selector, wait_time=timeout)[index].text
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_value(css_selector, index=0, max_attempts=5):
|
||||
|
||||
def css_value(css_selector, index=0):
|
||||
# Wait for the css selector to appear
|
||||
if world.is_css_present(css_selector):
|
||||
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].value, max_attempts=max_attempts)
|
||||
if is_css_present(css_selector):
|
||||
return css_find(css_selector)[index].value
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_html(css_selector, index=0, max_attempts=5):
|
||||
def css_html(css_selector, index=0):
|
||||
"""
|
||||
Returns the HTML of a css_selector and will retry if there is a StaleElementReferenceException
|
||||
Returns the HTML of a css_selector
|
||||
"""
|
||||
assert is_css_present(css_selector)
|
||||
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].html, max_attempts=max_attempts)
|
||||
return css_find(css_selector)[index].html
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_has_class(css_selector, class_name, index=0, max_attempts=5):
|
||||
return world.retry_on_exception(lambda: world.css_find(css_selector)[index].has_class(class_name), max_attempts=max_attempts)
|
||||
def css_has_class(css_selector, class_name, index=0):
|
||||
return css_find(css_selector)[index].has_class(class_name)
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_visible(css_selector, index=0, max_attempts=5):
|
||||
def css_visible(css_selector, index=0):
|
||||
assert is_css_present(css_selector)
|
||||
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].visible, max_attempts=max_attempts)
|
||||
return css_find(css_selector)[index].visible
|
||||
|
||||
|
||||
@world.absorb
|
||||
@@ -235,14 +265,17 @@ def click_tools():
|
||||
def is_mac():
|
||||
return platform.mac_ver()[0] is not ''
|
||||
|
||||
|
||||
@world.absorb
|
||||
def is_firefox():
|
||||
return world.browser.driver_name is 'Firefox'
|
||||
|
||||
|
||||
@world.absorb
|
||||
def trigger_event(css_selector, event='change', index=0):
|
||||
world.browser.execute_script("$('{}:eq({})').trigger('{}')".format(css_selector, index, event))
|
||||
|
||||
|
||||
@world.absorb
|
||||
def retry_on_exception(func, max_attempts=5):
|
||||
attempt = 0
|
||||
|
||||
@@ -80,13 +80,9 @@ def click_on_section(step, section):
|
||||
section_css = 'h3[tabindex="-1"]'
|
||||
world.css_click(section_css)
|
||||
|
||||
subid = "ui-accordion-accordion-panel-" + str(int(section) - 1)
|
||||
subsection_css = 'ul.ui-accordion-content-active[id=\'%s\'] > li > a' % subid
|
||||
prev_url = world.browser.url
|
||||
changed_section = lambda: prev_url != world.browser.url
|
||||
|
||||
#for some reason needed to do it in two steps
|
||||
world.css_click(subsection_css, success_condition=changed_section)
|
||||
subid = "ui-accordion-accordion-panel-{}".format(str(int(section) - 1))
|
||||
subsection_css = "ul.ui-accordion-content-active[id='{}'] > li > a".format(subid)
|
||||
world.css_click(subsection_css)
|
||||
|
||||
|
||||
@step(u'I click on subsection "([^"]*)"$')
|
||||
|
||||
@@ -129,7 +129,7 @@ Feature: Answer problems
|
||||
When I press the button with the label "Hide Answer(s)"
|
||||
Then the button with the label "Show Answer(s)" does appear
|
||||
And I should not see "4.14159" anywhere on the page
|
||||
|
||||
|
||||
Scenario: I can see my score on a problem when I answer it and after I reset it
|
||||
Given I am viewing a "<ProblemType>" problem
|
||||
When I answer a "<ProblemType>" problem "<Correctness>ly"
|
||||
|
||||
@@ -101,6 +101,9 @@ def input_problem_answer(_, problem_type, correctness):
|
||||
|
||||
@step(u'I check a problem')
|
||||
def check_problem(step):
|
||||
# first scroll down so the loading mathjax button does not
|
||||
# cover up the Check button
|
||||
world.browser.execute_script("window.scrollTo(0,1024)")
|
||||
world.css_click("input.check")
|
||||
|
||||
|
||||
|
||||
@@ -166,6 +166,8 @@ 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'
|
||||
# First wait for the element to be there on the page
|
||||
world.wait_for_visible("select#{}".format(select_name))
|
||||
world.browser.select(select_name, option_text)
|
||||
|
||||
elif problem_type == "multiple choice":
|
||||
|
||||
@@ -23,9 +23,8 @@ def i_press_the_button_on_the_registration_form(step):
|
||||
|
||||
@step('I check the checkbox named "([^"]*)"$')
|
||||
def i_check_checkbox(step, checkbox):
|
||||
def check_box():
|
||||
world.browser.find_by_name(checkbox).check()
|
||||
world.retry_on_exception(check_box)
|
||||
css_selector = 'input[name={}]'.format(checkbox)
|
||||
world.css_check(css_selector)
|
||||
|
||||
|
||||
@step('I should see "([^"]*)" in the dashboard banner$')
|
||||
|
||||
Reference in New Issue
Block a user