Commit cbbfc41c by Jay Zoldak

Merge pull request #995 from edx/zoldak/fix-acceptance

Simplify retry logic for ui helper functions
parents 259d3124 30b13d3c
......@@ -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()
# 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
# 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)
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
......@@ -19,11 +19,6 @@ def wait(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)
return world.browser.find_by_css(css)
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 css_click(css_selector, index=0, max_attempts=5, success_condition=lambda: True):
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
"""
Perform a click on a CSS selector, retrying if it initially fails.
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))
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).
@world.absorb
def wait_for_invisible(css_selector, timeout=30):
"""
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")
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 css_check(css_selector, index=0, max_attempts=5, success_condition=lambda: True):
def wait_for_clickable(css_selector, timeout=30):
"""
Checks a check box based on a CSS selector, retrying if it initially fails.
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))
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).
@world.absorb
def css_find(css, wait_time=30):
"""
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")
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, wait_time=30):
"""
Perform a click on a CSS selector, first waiting for the element
to be present and clickable.
This method will return True if the click worked.
"""
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_click_at(css, x_cord=10, y_cord=10):
def css_check(css_selector, index=0, wait_time=30):
"""
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 method will return True if the check worked.
"""
return css_click(css_selector=css_selector, index=index, wait_time=wait_time)
@world.absorb
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$')
......
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