Commit 2ff056df by Jay Zoldak

Simplify retry logic for ui helper functions

parent 50f190c5
...@@ -93,7 +93,9 @@ def assert_policy_entries(expected_keys, expected_values): ...@@ -93,7 +93,9 @@ def assert_policy_entries(expected_keys, expected_values):
for key, value in zip(expected_keys, expected_values): for key, value in zip(expected_keys, expected_values):
index = get_index_of(key) index = get_index_of(key)
assert_false(index == -1, "Could not find key: {key}".format(key=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,
"{} is not equal to {}".format(value, found_value))
def get_index_of(expected_key): def get_index_of(expected_key):
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# pylint: disable=W0621 # pylint: disable=W0621
from lettuce import world, step from lettuce import world, step
from nose.tools import assert_true # 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 auth.authz import get_user_by_email, get_course_groupname_for_role
from django.conf import settings from django.conf import settings
...@@ -19,8 +19,6 @@ from terrain.browser import reset_data ...@@ -19,8 +19,6 @@ from terrain.browser import reset_data
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
########### STEP HELPERS ##############
@step('I (?:visit|access|open) the Studio homepage$') @step('I (?:visit|access|open) the Studio homepage$')
def i_visit_the_studio_homepage(_step): def i_visit_the_studio_homepage(_step):
...@@ -66,20 +64,32 @@ def select_new_course(_step, whom): ...@@ -66,20 +64,32 @@ def select_new_course(_step, whom):
@step(u'I press the "([^"]*)" notification button$') @step(u'I press the "([^"]*)" notification button$')
def press_the_notification_button(_step, name): def press_the_notification_button(_step, name):
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
# The button was clicked if either the notification bar is gone,
# or we see an error overlaying it (expected for invalid inputs). # First wait for the notification to pop up
def button_clicked(): notification_css = 'div#page-notification div.wrapper-notification'
confirmation_dismissed = world.is_css_not_present('.is-shown.wrapper-notification-warning') world.wait_for_visible(notification_css)
error_showing = world.is_css_present('.is-shown.wrapper-notification-error')
return confirmation_dismissed or error_showing # 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(): if world.is_firefox():
# This is done to explicitly make the changes save on firefox. It will remove focus from the previously focused element # This is done to explicitly make the changes save on firefox.
world.trigger_event(css, event='focus') # It will remove focus from the previously focused element
world.browser.execute_script("$('{}').click()".format(css)) world.trigger_event(btn_css, event='focus')
world.browser.execute_script("$('{}').click()".format(btn_css))
else: 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 "(.*)"$') @step('I change the "(.*)" field to "(.*)"$')
...@@ -110,7 +120,6 @@ def i_see_a_confirmation(step): ...@@ -110,7 +120,6 @@ def i_see_a_confirmation(step):
assert world.is_css_present(confirmation_css) assert world.is_css_present(confirmation_css)
####### HELPER FUNCTIONS ##############
def open_new_course(): def open_new_course():
world.clear_courses() world.clear_courses()
create_studio_user() create_studio_user()
...@@ -156,8 +165,8 @@ def log_into_studio( ...@@ -156,8 +165,8 @@ def log_into_studio(
world.log_in(username=uname, password=password, email=email, name=name) world.log_in(username=uname, password=password, email=email, name=name)
# Navigate to the studio dashboard # Navigate to the studio dashboard
world.visit('/') world.visit('/')
assert_in(uname, world.css_text('h2.title', timeout=60))
assert uname in world.css_text('h2.title', max_attempts=15)
def create_a_course(): def create_a_course():
course = world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') course = world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
...@@ -247,8 +256,22 @@ def button_disabled(step, value): ...@@ -247,8 +256,22 @@ def button_disabled(step, value):
@step('I confirm the prompt') @step('I confirm the prompt')
def confirm_the_prompt(step): 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 (.*)$') @step(u'I am shown a (.*)$')
...@@ -257,6 +280,7 @@ def i_am_shown_a_notification(step, notification_type): ...@@ -257,6 +280,7 @@ def i_am_shown_a_notification(step, notification_type):
def type_in_codemirror(index, text): 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.css_click("div.CodeMirror-lines", index=index)
world.browser.execute_script("$('div.CodeMirror.CodeMirror-focused > div').css('overflow', '')") world.browser.execute_script("$('div.CodeMirror.CodeMirror-focused > div').css('overflow', '')")
g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea") g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea")
......
...@@ -48,9 +48,7 @@ def click_component_from_menu(category, boilerplate, expected_css): ...@@ -48,9 +48,7 @@ def click_component_from_menu(category, boilerplate, expected_css):
elem_css = "a[data-category='{}']:not([data-boilerplate])".format(category) elem_css = "a[data-category='{}']:not([data-boilerplate])".format(category)
elements = world.css_find(elem_css) elements = world.css_find(elem_css)
assert_equal(len(elements), 1) assert_equal(len(elements), 1)
world.wait_for(lambda _driver: world.css_visible(elem_css)) world.css_click(elem_css)
world.css_click(elem_css, success_condition=lambda: 1 == len(world.css_find(expected_css)))
@world.absorb @world.absorb
def edit_component_and_select_settings(): def edit_component_and_select_settings():
......
...@@ -113,7 +113,7 @@ def test_i_have_entered_a_new_course_start_date(step): ...@@ -113,7 +113,7 @@ def test_i_have_entered_a_new_course_start_date(step):
@step('The warning about course start date goes away$') @step('The warning about course start date goes away$')
def test_the_warning_about_course_start_date_goes_away(step): 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_DATE_CSS).first._element.get_attribute('class'))
assert_false('error' in world.css_find(COURSE_START_TIME_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 ...@@ -5,7 +5,7 @@ from lettuce import world, step
from common import create_studio_user from common import create_studio_user
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from auth.authz import get_course_groupname_for_role, get_user_by_email 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' PASSWORD = 'test'
EMAIL_EXTENSION = '@edx.org' EMAIL_EXTENSION = '@edx.org'
...@@ -112,12 +112,12 @@ def other_user_login(_step, name): ...@@ -112,12 +112,12 @@ def other_user_login(_step, name):
@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, inverted, gender='self'): def see_course(_step, inverted, gender='self'):
class_css = 'h3.course-title' 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: if inverted:
assert not world.scenario_dict['COURSE'].display_name in all_names assert world.is_css_not_present(class_css)
else: 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') @step(u'"([^"]*)" should( not)? be marked as an admin')
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
from lettuce import world, step from lettuce import world, step
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from common import type_in_codemirror from common import type_in_codemirror
from nose.tools import assert_in # pylint: disable=E0611
@step(u'I go to the course updates page') @step(u'I go to the course updates page')
...@@ -21,14 +22,17 @@ def add_update(_step, text): ...@@ -21,14 +22,17 @@ def add_update(_step, text):
change_text(text) change_text(text)
@step(u'I should( not)? see the update "([^"]*)"$') @step(u'I should see the update "([^"]*)"$')
def check_update(_step, doesnt_see_update, text): def check_update(_step, text):
update_css = 'div.update-contents' update_css = 'div.update-contents'
update = world.css_find(update_css, wait_time=1) update_html = world.css_find(update_css).html
if doesnt_see_update: assert_in(text, update_html)
assert len(update) == 0 or not text in update.html
else:
assert text in 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 "([^"]*)"$') @step(u'I modify the text to "([^"]*)"$')
......
...@@ -5,6 +5,7 @@ from lettuce import world, step ...@@ -5,6 +5,7 @@ from lettuce import world, step
from common import * from common import *
from terrain.steps import reload_the_page from terrain.steps import reload_the_page
from selenium.common.exceptions import InvalidElementStateException 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') @step(u'I am viewing the grading settings')
...@@ -65,21 +66,25 @@ def change_assignment_name(step, old_name, new_name): ...@@ -65,21 +66,25 @@ def change_assignment_name(step, old_name, new_name):
@step(u'I go back to the main course page') @step(u'I go back to the main course page')
def main_course_page(step): def main_course_page(step):
main_page_link_css = 'a[href="/%s/%s/course/%s"]' % (world.scenario_dict['COURSE'].org, main_page_link = '/{}/{}/course/{}'.format(world.scenario_dict['COURSE'].org,
world.scenario_dict['COURSE'].number, world.scenario_dict['COURSE'].number,
world.scenario_dict['COURSE'].display_name.replace(' ', '_'),) world.scenario_dict['COURSE'].display_name.replace(' ', '_'),)
world.css_click(main_page_link_css) world.visit(main_page_link)
assert_in('Course Outline', world.css_text('h1.page-header'))
@step(u'I do( not)? see the assignment name "([^"]*)"$') @step(u'I do( not)? see the assignment name "([^"]*)"$')
def see_assignment_name(step, do_not, name): def see_assignment_name(step, do_not, name):
assignment_menu_css = 'ul.menu > li > a' assignment_menu_css = 'ul.menu > li > a'
# First assert that it is there, make take a bit to redraw
assert world.css_find(assignment_menu_css)
assignment_menu = world.css_find(assignment_menu_css) assignment_menu = world.css_find(assignment_menu_css)
allnames = [item.html for item in assignment_menu] allnames = [item.html for item in assignment_menu]
if do_not: if do_not:
assert not name in allnames assert_not_in (name, allnames)
else: else:
assert name in allnames assert_in (name, allnames)
@step(u'I delete the assignment type "([^"]*)"$') @step(u'I delete the assignment type "([^"]*)"$')
......
...@@ -197,9 +197,15 @@ def high_level_source_in_editor(step): ...@@ -197,9 +197,15 @@ def high_level_source_in_editor(step):
def verify_high_level_source_links(step, visible): def verify_high_level_source_links(step, visible):
assert_equal(visible, world.is_css_present('.launch-latex-compiler')) if visible:
assert world.is_css_present('.launch-latex-compiler')
else:
assert world.is_css_not_present('.launch-latex-compiler')
world.cancel_component(step) world.cancel_component(step)
assert_equal(visible, world.is_css_present('.upload-button')) if visible:
assert world.is_css_present('.upload-button')
else:
assert world.is_css_not_present('.upload-button')
def verify_modified_weight(): def verify_modified_weight():
......
...@@ -12,7 +12,7 @@ def i_fill_in_the_registration_form(step): ...@@ -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('password').fill('test')
register_form.find_by_name('username').fill('robot-studio') register_form.find_by_name('username').fill('robot-studio')
register_form.find_by_name('name').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) world.retry_on_exception(fill_in_reg_form)
......
...@@ -2,12 +2,11 @@ ...@@ -2,12 +2,11 @@
#pylint: disable=W0621 #pylint: disable=W0621
from lettuce import world, step from lettuce import world, step
from selenium.webdriver.common.keys import Keys
from nose.tools import assert_true # pylint: disable=E0611 from nose.tools import assert_true # pylint: disable=E0611
@step(u'I go to the static pages page') @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' menu_css = 'li.nav-course-courseware'
static_css = 'li.nav-course-courseware-pages a' static_css = 'li.nav-course-courseware-pages a'
world.css_click(menu_css) world.css_click(menu_css)
...@@ -15,25 +14,37 @@ def go_to_static(_step): ...@@ -15,25 +14,37 @@ def go_to_static(_step):
@step(u'I add a new page') @step(u'I add a new page')
def add_page(_step): def add_page(step):
button_css = 'a.new-button' button_css = 'a.new-button'
world.css_click(button_css) world.css_click(button_css)
@step(u'I should( not)? see a "([^"]*)" static page$') @step(u'I should not see a "([^"]*)" static page$')
def see_page(_step, doesnt, 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 # Need to retry here because the element
# will sometimes exist before the HTML content is loaded # 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) world.wait_for(exists_func)
assert_true(exists_func(None)) assert_true(exists_func(None))
@step(u'I "([^"]*)" the "([^"]*)" page$') @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 button_css = 'a.%s-button' % edit_delete
index = get_index(page) index = get_index(page)
assert index is not None assert index is not None
...@@ -41,7 +52,7 @@ def click_edit_delete(_step, edit_delete, page): ...@@ -41,7 +52,7 @@ def click_edit_delete(_step, edit_delete, page):
@step(u'I change the name to "([^"]*)"$') @step(u'I change the name to "([^"]*)"$')
def change_name(_step, new_name): def change_name(step, new_name):
settings_css = '#settings-mode a' settings_css = '#settings-mode a'
world.css_click(settings_css) world.css_click(settings_css)
input_css = 'input.setting-input' input_css = 'input.setting-input'
...@@ -56,9 +67,10 @@ def get_index(name): ...@@ -56,9 +67,10 @@ def get_index(name):
page_name_css = 'section[data-type="HTMLModule"]' page_name_css = 'section[data-type="HTMLModule"]'
all_pages = world.css_find(page_name_css) all_pages = world.css_find(page_name_css)
for i in range(len(all_pages)): 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 i
return None return None
def page_exists(page): def page_exists(page):
return get_index(page) is not None return get_index(page) is not None
...@@ -7,6 +7,8 @@ import requests ...@@ -7,6 +7,8 @@ import requests
import string import string
import random import random
import os import os
from nose.tools import assert_equal, assert_not_equal # pylint: disable=E0611
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
...@@ -32,7 +34,7 @@ def upload_file(_step, file_name): ...@@ -32,7 +34,7 @@ def upload_file(_step, file_name):
@step(u'I upload the files (".*")$') @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 # Turn files_string to a list of file names
files = files_string.split(",") files = files_string.split(",")
files = map(lambda x: string.strip(x, ' "\''), files) files = map(lambda x: string.strip(x, ' "\''), files)
...@@ -48,19 +50,29 @@ def upload_file(_step, files_string): ...@@ -48,19 +50,29 @@ def upload_file(_step, files_string):
world.css_click(close_css) world.css_click(close_css)
@step(u'I should( not)? see the file "([^"]*)" was uploaded$') @step(u'I should not see the file "([^"]*)" was uploaded$')
def check_upload(_step, do_not_see_file, file_name): 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) index = get_index(file_name)
if do_not_see_file: assert_not_equal(index, -1)
assert index == -1
else:
assert index != -1
@step(u'The url for the file "([^"]*)" is valid$') @step(u'The url for the file "([^"]*)" is valid$')
def check_url(_step, file_name): def check_url(_step, file_name):
r = get_file(file_name) r = get_file(file_name)
assert r.status_code == 200 assert_equal(r.status_code , 200)
@step(u'I delete the file "([^"]*)"$') @step(u'I delete the file "([^"]*)"$')
...@@ -71,7 +83,7 @@ def delete_file(_step, file_name): ...@@ -71,7 +83,7 @@ def delete_file(_step, file_name):
world.css_click(delete_css, index=index) world.css_click(delete_css, index=index)
prompt_confirm_css = 'li.nav-item > a.action-primary' 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 "([^"]*)"$') @step(u'I should see only one "([^"]*)"$')
......
...@@ -75,7 +75,8 @@ def make_desired_capabilities(): ...@@ -75,7 +75,8 @@ def make_desired_capabilities():
desired_capabilities['build'] = settings.SAUCE.get('BUILD') desired_capabilities['build'] = settings.SAUCE.get('BUILD')
desired_capabilities['video-upload-on-pass'] = False desired_capabilities['video-upload-on-pass'] = False
desired_capabilities['sauce-advisor'] = 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['selenium-version'] = "2.34.0"
desired_capabilities['max-duration'] = 3600 desired_capabilities['max-duration'] = 3600
desired_capabilities['public'] = 'public restricted' desired_capabilities['public'] = 'public restricted'
...@@ -87,6 +88,7 @@ def initial_setup(server): ...@@ -87,6 +88,7 @@ def initial_setup(server):
""" """
Launch the browser once before executing the tests. Launch the browser once before executing the tests.
""" """
# from nose.tools import set_trace; set_trace()
world.absorb(settings.SAUCE.get('SAUCE_ENABLED'), 'SAUCE_ENABLED') world.absorb(settings.SAUCE.get('SAUCE_ENABLED'), 'SAUCE_ENABLED')
if not world.SAUCE_ENABLED: if not world.SAUCE_ENABLED:
......
...@@ -5,7 +5,7 @@ from lettuce import world ...@@ -5,7 +5,7 @@ from lettuce import world
import time import time
import platform import platform
from urllib import quote_plus from urllib import quote_plus
from selenium.common.exceptions import WebDriverException, StaleElementReferenceException from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
...@@ -19,11 +19,6 @@ def wait(seconds): ...@@ -19,11 +19,6 @@ def wait(seconds):
@world.absorb @world.absorb
def wait_for(func):
WebDriverWait(world.browser.driver, 5).until(func)
@world.absorb
def visit(url): def visit(url):
world.browser.visit(django_url(url)) world.browser.visit(django_url(url))
...@@ -34,7 +29,7 @@ def url_equals(url): ...@@ -34,7 +29,7 @@ def url_equals(url):
@world.absorb @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) return world.browser.is_element_present_by_css(css_selector, wait_time=wait_time)
...@@ -44,92 +39,116 @@ def is_css_not_present(css_selector, wait_time=5): ...@@ -44,92 +39,116 @@ def is_css_not_present(css_selector, wait_time=5):
@world.absorb @world.absorb
def css_has_text(css_selector, text, index=0, max_attempts=5): def css_has_text(css_selector, text, index=0):
return world.css_text(css_selector, index=index, max_attempts=max_attempts) == text return world.css_text(css_selector, index=index) == text
@world.absorb @world.absorb
def css_find(css, wait_time=5): def wait_for(func, timeout=5):
def is_visible(_driver): WebDriverWait(world.browser.driver, timeout).until(func)
return EC.visibility_of_element_located((By.CSS_SELECTOR, css,))
world.browser.is_element_present_by_css(css, wait_time=wait_time)
wait_for(is_visible) def wait_for_present(css_selector, timeout=30):
return world.browser.find_by_css(css) """
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
"""
WebDriverWait(driver=world.browser.driver,
timeout=60).until(EC.presence_of_element_located((By.CSS_SELECTOR, css_selector,)))
@world.absorb @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. WebDriverWait(driver=world.browser.driver,
timeout=timeout).until(EC.visibility_of_element_located((By.CSS_SELECTOR, 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 @world.absorb
success_condition). def wait_for_invisible(css_selector, timeout=30):
""" """
assert is_css_present(css_selector), "{} is not present".format(css_selector) Waiting for the element to be either invisible or not present on the DOM.
for _ in range(max_attempts): Throws an error if the wait_for time expires.
try: Otherwise this method will return None
world.css_find(css_selector)[index].click() """
if success_condition(): WebDriverWait(driver=world.browser.driver,
return timeout=timeout).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, css_selector,)))
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")
@world.absorb @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
WebDriverWait(world.browser.driver,
timeout=timeout).until(EC.element_to_be_clickable((By.CSS_SELECTOR, 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 @world.absorb
success_condition). def css_find(css, wait_time=30):
""" """
assert is_css_present(css_selector), "{} is not present".format(css_selector) Wait for the element(s) as defined by css locator
for _ in range(max_attempts): to be present.
try:
world.css_find(css_selector)[index].check() This method will return a WebDriverElement.
if success_condition(): """
return wait_for_present(css_selector=css, timeout=wait_time)
except WebDriverException: return world.browser.find_by_css(css)
# Occasionally, MathJax or other JavaScript can cover up
# an element temporarily.
# If this happens, wait a second, then try again @world.absorb
world.wait(1) def css_click(css_selector, index=0, wait_time=30):
except: """
pass Perform a click on a CSS selector, first waiting for the element
else: to be present and clickable.
# try once more, letting exceptions raise
world.css_find(css_selector)[index].check() This method will return True if the click worked.
if not success_condition(): """
raise Exception("unsuccessful check") wait_for_clickable(css_selector, timeout=wait_time)
assert world.css_find(css_selector)[index].visible
# 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 @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 A method to click at x,y coordinates of the element
rather than in the center of the element rather than in the center of the element
''' '''
element = css_find(css).first wait_for_clickable(css_selector, timeout=timeout)
element.action_chains.move_to_element_with_offset(element._element, x_cord, y_cord) element = css_find(css_selector)[index]
assert element.visible
element.action_chains.move_to_element_with_offset(element._element, x_coord, y_coord)
element.action_chains.click() element.action_chains.click()
element.action_chains.perform() element.action_chains.perform()
...@@ -139,58 +158,55 @@ def id_click(elem_id): ...@@ -139,58 +158,55 @@ def id_click(elem_id):
""" """
Perform a click on an element as specified by its id Perform a click on an element as specified by its id
""" """
world.css_click('#%s' % elem_id) css_click('#%s' % elem_id)
@world.absorb @world.absorb
def css_fill(css_selector, text, index=0, max_attempts=5): def css_fill(css_selector, text, index=0):
assert is_css_present(css_selector) css_find(css_selector)[index].fill(text)
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].fill(text), max_attempts=max_attempts)
@world.absorb @world.absorb
def click_link(partial_text, index=0, max_attempts=5): def click_link(partial_text, index=0):
return world.retry_on_exception(lambda: world.browser.find_link_by_partial_text(partial_text)[index].click(), max_attempts=max_attempts) world.browser.find_link_by_partial_text(partial_text)[index].click()
@world.absorb @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 # Wait for the css selector to appear
if world.is_css_present(css_selector): if is_css_present(css_selector):
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].text, max_attempts=max_attempts) return css_find(css_selector, wait_time=timeout)[index].text
else: else:
return "" return ""
@world.absorb @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 # Wait for the css selector to appear
if world.is_css_present(css_selector): if is_css_present(css_selector):
return world.retry_on_exception(lambda: world.browser.find_by_css(css_selector)[index].value, max_attempts=max_attempts) return css_find(css_selector)[index].value
else: else:
return "" return ""
@world.absorb @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) 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 @world.absorb
def css_has_class(css_selector, class_name, index=0, max_attempts=5): def css_has_class(css_selector, class_name, index=0):
return world.retry_on_exception(lambda: world.css_find(css_selector)[index].has_class(class_name), max_attempts=max_attempts) return css_find(css_selector)[index].has_class(class_name)
@world.absorb @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) 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 @world.absorb
...@@ -235,14 +251,17 @@ def click_tools(): ...@@ -235,14 +251,17 @@ def click_tools():
def is_mac(): def is_mac():
return platform.mac_ver()[0] is not '' return platform.mac_ver()[0] is not ''
@world.absorb @world.absorb
def is_firefox(): def is_firefox():
return world.browser.driver_name is 'Firefox' return world.browser.driver_name is 'Firefox'
@world.absorb @world.absorb
def trigger_event(css_selector, event='change', index=0): def trigger_event(css_selector, event='change', index=0):
world.browser.execute_script("$('{}:eq({})').trigger('{}')".format(css_selector, index, event)) world.browser.execute_script("$('{}:eq({})').trigger('{}')".format(css_selector, index, event))
@world.absorb @world.absorb
def retry_on_exception(func, max_attempts=5): def retry_on_exception(func, max_attempts=5):
attempt = 0 attempt = 0
......
...@@ -82,11 +82,7 @@ def click_on_section(step, section): ...@@ -82,11 +82,7 @@ def click_on_section(step, section):
subid = "ui-accordion-accordion-panel-" + str(int(section) - 1) subid = "ui-accordion-accordion-panel-" + str(int(section) - 1)
subsection_css = 'ul.ui-accordion-content-active[id=\'%s\'] > li > a' % subid subsection_css = 'ul.ui-accordion-content-active[id=\'%s\'] > li > a' % subid
prev_url = world.browser.url world.css_click(subsection_css)
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)
@step(u'I click on subsection "([^"]*)"$') @step(u'I click on subsection "([^"]*)"$')
......
...@@ -129,7 +129,7 @@ Feature: Answer problems ...@@ -129,7 +129,7 @@ Feature: Answer problems
When I press the button with the label "Hide Answer(s)" When I press the button with the label "Hide Answer(s)"
Then the button with the label "Show Answer(s)" does appear Then the button with the label "Show Answer(s)" does appear
And I should not see "4.14159" anywhere on the page 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 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 Given I am viewing a "<ProblemType>" problem
When I answer a "<ProblemType>" problem "<Correctness>ly" When I answer a "<ProblemType>" problem "<Correctness>ly"
......
...@@ -101,6 +101,9 @@ def input_problem_answer(_, problem_type, correctness): ...@@ -101,6 +101,9 @@ def input_problem_answer(_, problem_type, correctness):
@step(u'I check a problem') @step(u'I check a problem')
def check_problem(step): 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") world.css_click("input.check")
......
...@@ -23,9 +23,8 @@ def i_press_the_button_on_the_registration_form(step): ...@@ -23,9 +23,8 @@ def i_press_the_button_on_the_registration_form(step):
@step('I check the checkbox named "([^"]*)"$') @step('I check the checkbox named "([^"]*)"$')
def i_check_checkbox(step, checkbox): def i_check_checkbox(step, checkbox):
def check_box(): css_selector = 'input[name={}]'.format(checkbox)
world.browser.find_by_name(checkbox).check() world.css_check(css_selector)
world.retry_on_exception(check_box)
@step('I should see "([^"]*)" in the dashboard banner$') @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