From 30b13d3cf189b67c1ab924fdc13870d43400c985 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Mon, 16 Sep 2013 11:09:54 -0400 Subject: [PATCH] Improve code clarity and error messages for css selection --- .../features/advanced-settings.py | 5 +-- .../contentstore/features/common.py | 2 +- .../contentstore/features/course-team.py | 24 ++++++------ .../contentstore/features/grading.py | 4 +- .../contentstore/features/problem-editor.py | 15 +++++--- common/djangoapps/terrain/browser.py | 12 +++--- common/djangoapps/terrain/ui_helpers.py | 38 +++++++++++++------ .../courseware/features/navigation.py | 4 +- .../courseware/features/problems_setup.py | 2 + 9 files changed, 63 insertions(+), 43 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index e202031a01..664d8d00ae 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -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,14 +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)) found_value = world.css_find(VALUE_CSS)[index].value assert_equal(value, found_value, - "{} is not equal to {}".format(value, found_value)) + "Expected {} to have value {} but found {}".format(key, value, found_value)) def get_index_of(expected_key): diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 74bab10a24..eb666d1837 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -165,7 +165,7 @@ 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=60)) + assert_in(uname, world.css_text('h2.title', timeout=10)) def create_a_course(): diff --git a/cms/djangoapps/contentstore/features/course-team.py b/cms/djangoapps/contentstore/features/course-team.py index 763c116a56..91049b029e 100644 --- a/cms/djangoapps/contentstore/features/course-team.py +++ b/cms/djangoapps/contentstore/features/course-team.py @@ -110,9 +110,9 @@ 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' - if inverted: + if do_not_see: assert world.is_css_not_present(class_css) else: all_courses = world.css_find(class_css) @@ -121,25 +121,25 @@ def see_course(_step, inverted, gender='self'): @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) diff --git a/cms/djangoapps/contentstore/features/grading.py b/cms/djangoapps/contentstore/features/grading.py index 6f702b8a5d..62b6617823 100644 --- a/cms/djangoapps/contentstore/features/grading.py +++ b/cms/djangoapps/contentstore/features/grading.py @@ -82,9 +82,9 @@ def see_assignment_name(step, do_not, name): assignment_menu = world.css_find(assignment_menu_css) allnames = [item.html for item in assignment_menu] if do_not: - assert_not_in (name, allnames) + assert_not_in(name, allnames) else: - assert_in (name, allnames) + assert_in(name, allnames) @step(u'I delete the assignment type "([^"]*)"$') diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index 414b2dea80..e6528f06f7 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -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" @@ -198,14 +198,19 @@ def high_level_source_in_editor(step): def verify_high_level_source_links(step, visible): if visible: - assert world.is_css_present('.launch-latex-compiler') + assert_true(world.is_css_present('.launch-latex-compiler'), + msg="Expected to find the latex button but it is not present.") else: - assert world.is_css_not_present('.launch-latex-compiler') + 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) if visible: - assert world.is_css_present('.upload-button') + assert_true(world.is_css_present('.upload-button'), + msg="Expected to find the upload button but it is not present.") else: - assert world.is_css_not_present('.upload-button') + 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(): diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py index 0dc7516a15..6454cbf644 100644 --- a/common/djangoapps/terrain/browser.py +++ b/common/djangoapps/terrain/browser.py @@ -88,7 +88,6 @@ def initial_setup(server): """ 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') if not world.SAUCE_ENABLED: @@ -166,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): diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 6bdb11fa2e..0ba2dfba18 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -5,7 +5,7 @@ from lettuce import world import time import platform from urllib import quote_plus -from selenium.common.exceptions import WebDriverException +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 @@ -54,8 +54,11 @@ def wait_for_present(css_selector, timeout=30): 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,))) + 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 @@ -65,8 +68,11 @@ def wait_for_visible(css_selector, timeout=30): Throws an error if the wait_for time expires. Otherwise this method will return None """ - WebDriverWait(driver=world.browser.driver, - timeout=timeout).until(EC.visibility_of_element_located((By.CSS_SELECTOR, css_selector,))) + 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 @@ -76,8 +82,11 @@ def wait_for_invisible(css_selector, timeout=30): Throws an error if the wait_for time expires. Otherwise this method will return None """ - WebDriverWait(driver=world.browser.driver, - timeout=timeout).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, css_selector,))) + 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 @@ -89,8 +98,11 @@ def wait_for_clickable(css_selector, timeout=30): """ # 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,))) + 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 @@ -114,7 +126,8 @@ def css_click(css_selector, index=0, wait_time=30): This method will return True if the click worked. """ wait_for_clickable(css_selector, timeout=wait_time) - assert world.css_find(css_selector)[index].visible + 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 @@ -146,7 +159,8 @@ def css_click_at(css_selector, index=0, x_coord=10, y_coord=10, timeout=5): ''' wait_for_clickable(css_selector, timeout=timeout) element = css_find(css_selector)[index] - assert element.visible + 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() @@ -158,7 +172,7 @@ def id_click(elem_id): """ Perform a click on an element as specified by its id """ - css_click('#%s' % elem_id) + css_click('#{}'.format(elem_id)) @world.absorb diff --git a/lms/djangoapps/courseware/features/navigation.py b/lms/djangoapps/courseware/features/navigation.py index 5ccfdd54ff..e4cd4961c2 100644 --- a/lms/djangoapps/courseware/features/navigation.py +++ b/lms/djangoapps/courseware/features/navigation.py @@ -80,8 +80,8 @@ 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 + 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) diff --git a/lms/djangoapps/courseware/features/problems_setup.py b/lms/djangoapps/courseware/features/problems_setup.py index 45b77eddfa..0253571b47 100644 --- a/lms/djangoapps/courseware/features/problems_setup.py +++ b/lms/djangoapps/courseware/features/problems_setup.py @@ -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":