Merge pull request #4676 from jmchilton/selenium_fixes_33

Another round of small Selenium fixes.
This commit is contained in:
Martin Cech
2017-09-23 16:09:24 -04:00
committed by GitHub
7 changed files with 107 additions and 44 deletions
+12
View File
@@ -140,5 +140,17 @@ class HasDriver:
)
def execption_indicates_not_clickable(exception):
return "not clickable" in str(exception)
def exception_indicates_stale_element(exception):
return "stale" in str(exception)
__all__ = (
"execption_indicates_not_clickable",
"exception_indicates_stale_element",
"HasDriver",
"TimeoutException",
)
+62 -3
View File
@@ -15,12 +15,20 @@ import requests
import yaml
from .data import NAVIGATION_DATA
from .has_driver import exception_indicates_stale_element, HasDriver, TimeoutException
from .has_driver import (
execption_indicates_not_clickable,
exception_indicates_stale_element,
HasDriver,
TimeoutException,
)
from . import sizzle
# Test case data
DEFAULT_PASSWORD = '123456'
RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT = .1
RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT = 10
class NullTourCallback(object):
@@ -28,7 +36,24 @@ class NullTourCallback(object):
pass
def retry_call_during_transitions(f, attempts=5, sleep=.1, exception_check=exception_indicates_stale_element):
def excepion_seems_to_indicate_transition(e):
"""True if exception seems to indicate the page state is transitioning.
Galaxy features many different transition effects that change the page state over time.
These transitions make it slightly more difficult to test Galaxy because atomic input
actions take an indeterminate amount of time to be reflected on the screen. This method
takes a Selenium assertion and tries to infer if such a transition could be the root
cause of the exception. The methods that follow use it to allow retrying actions during
transitions.
Currently the two kinds of exceptions that we say may indicate a transition are
StaleElement exceptions (a DOM element grabbed at one step is no longer available)
and "not clickable" exceptions (so perhaps a popup modal is blocking a click).
"""
return exception_indicates_stale_element(e) or execption_indicates_not_clickable(e)
def retry_call_during_transitions(f, attempts=RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT, sleep=RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT, exception_check=excepion_seems_to_indicate_transition):
previous_attempts = 0
while True:
try:
@@ -44,7 +69,7 @@ def retry_call_during_transitions(f, attempts=5, sleep=.1, exception_check=excep
previous_attempts += 1
def retry_during_transitions(f, attempts=5, sleep=.1, exception_check=exception_indicates_stale_element):
def retry_during_transitions(f, attempts=RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT, sleep=RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT, exception_check=excepion_seems_to_indicate_transition):
@wraps(f)
def _retry(*args, **kwds):
@@ -201,12 +226,27 @@ class NavigatesGalaxy(HasDriver):
raise self.prepend_timeout_message(e, message)
return history_item_selector_state
def published_grid_search_for(self, search_term=None):
return self._inline_search_for(
'#input-free-text-search-filter',
search_term,
)
def get_logged_in_user(self):
return self.api_get("users/current")
def is_logged_in(self):
return "email" in self.get_logged_in_user()
@retry_during_transitions
def _inline_search_for(self, selector, search_term=None):
search_box = self.wait_for_and_click_selector(selector)
search_box.clear()
if search_term is not None:
search_box.send_keys(search_term)
self.send_enter(search_box)
return search_box
def _get_random_name(self, prefix=None, suffix=None, len=10):
return '%s%s%s' % (
prefix or '',
@@ -480,6 +520,12 @@ class NavigatesGalaxy(HasDriver):
def workflow_index_click_search(self):
return self.wait_for_and_click_selector("input.search-wf")
def workflow_index_search_for(self, search_term=None):
return self._inline_search_for(
"input.search-wf",
search_term,
)
def workflow_index_click_import(self):
self.wait_for_and_click_selector(self.test_data["selectors"]["workflows"]["import_button"])
@@ -516,6 +562,7 @@ class NavigatesGalaxy(HasDriver):
tag_display = workflow_row_element.find_element_by_css_selector(".tags-display")
tag_display.click()
@retry_during_transitions
def workflow_index_tags(self, workflow_index=0):
workflow_row_element = self.workflow_index_table_row(workflow_index)
tag_display = workflow_row_element.find_element_by_css_selector(".tags-display")
@@ -796,6 +843,18 @@ class NavigatesGalaxy(HasDriver):
self.click_center()
return text
@retry_during_transitions
def assert_selector_absent_or_hidden_after_transitions(self, selector):
"""Variant of assert_selector_absent_or_hidden that retries during transitions.
In the parent method - the element is found and then it is checked to see
if it is visible. It may disappear from the page in the middle there
and cause a StaleElement error. For checks where we care about the final
resting state after transitions - this method can be used to retry
during those transitions.
"""
return self.assert_selector_absent_or_hidden(selector)
def assert_tooltip_text(self, element, expected, sleep=0, click_away=True):
text = self.get_tooltip_text(element, sleep=sleep, click_away=click_away)
assert text == expected, "Tooltip text [%s] was not expected text [%s]." % (text, expected)
+1 -1
View File
@@ -27,4 +27,4 @@ class HistoryOptionsTestCase(SeleniumTestCase):
self.click_hda_title(hda_id, wait=True)
self.assert_selector_absent_or_hidden(hda_body_selector)
self.assert_selector_absent_or_hidden_after_transitions(hda_body_selector)
@@ -9,10 +9,6 @@ from .framework import (
class HistoryGridTestCase(SharedStateSeleniumTestCase):
def setUp(self):
super(HistoryGridTestCase, self).setUp()
self.home()
@selenium_test
def test_history_grid_histories(self):
self.navigate_to_published_histories_page()
@@ -22,17 +18,11 @@ class HistoryGridTestCase(SharedStateSeleniumTestCase):
def test_history_grid_search_standard(self):
self.navigate_to_published_histories_page()
input_selector = '#input-free-text-search-filter'
search_input = self.wait_for_selector(input_selector)
search_input.send_keys(self.history1_name)
self.send_enter(search_input)
self.published_grid_search_for(self.history1_name)
self.assert_grid_histories_are([self.history1_name])
self.unset_filter('free-text-search', self.history1_name)
search_input = self.wait_for_selector(input_selector)
search_input.send_keys(self.history4_name)
self.send_enter(search_input)
self.published_grid_search_for(self.history4_name)
self.assert_grid_histories_are(['No Items'])
+16 -5
View File
@@ -9,18 +9,15 @@ from .framework import (
class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
def setUp(self):
super(SavedHistoriesTestCase, self).setUp()
self.home()
self.submit_login(self.user_email, retries=3)
@selenium_test
def test_saved_histories_list(self):
self._login()
self.navigate_to_saved_histories_page()
self.assert_histories_in_grid([self.history2_name, self.history3_name])
@selenium_test
def test_history_switch(self):
self._login()
self.navigate_to_saved_histories_page()
self.click_popup_option(self.history2_name, 'Switch')
time.sleep(1)
@@ -29,6 +26,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_history_view(self):
self._login()
self.navigate_to_saved_histories_page()
self.click_popup_option(self.history2_name, 'View')
history_name = self.wait_for_selector('.name.editable-text')
@@ -36,6 +34,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_history_publish(self):
self._login()
self.navigate_to_saved_histories_page()
# Publish the history
@@ -52,6 +51,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_rename_history(self):
self._login()
self.navigate_to_saved_histories_page()
self.click_popup_option('Unnamed history', 'Rename')
@@ -72,6 +72,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_delete_and_undelete_history(self):
self._login()
self.navigate_to_saved_histories_page()
# Delete the history
@@ -93,6 +94,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_permanently_delete_history(self):
self._login()
self.create_history(self.history4_name)
self.navigate_to_saved_histories_page()
@@ -111,6 +113,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_delete_and_undelete_multiple_histories(self):
self._login()
self.navigate_to_saved_histories_page()
delete_button_selector = 'input[type="button"][value="Delete"]'
@@ -139,6 +142,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_sort_by_name(self):
self._login()
self.navigate_to_saved_histories_page()
self.wait_for_and_click_selector('.sort-link[sort_key="name"]')
@@ -156,6 +160,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_standard_search(self):
self._login()
self.navigate_to_saved_histories_page()
input_selector = '#input-free-text-search-filter'
@@ -174,6 +179,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_advanced_search(self):
self._login()
self.navigate_to_saved_histories_page()
self.show_advanced_search()
@@ -201,6 +207,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
@selenium_test
def test_tags(self):
self._login()
self.navigate_to_saved_histories_page()
# Click the add tag button
@@ -221,6 +228,10 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
self.assert_grid_histories_are([self.history2_name], False)
def _login(self):
self.home()
self.submit_login(self.user_email, retries=3)
@retry_assertion_during_transitions
def assert_grid_histories_are(self, expected_histories, sort_matters=True):
actual_histories = self.get_histories()
+11 -13
View File
@@ -1,6 +1,7 @@
import time
from .framework import (
retry_assertion_during_transitions,
selenium_test,
SeleniumTestCase
)
@@ -52,10 +53,7 @@ class WorkflowEditorTestCase(SeleniumTestCase):
workflow_populator.upload_yaml_workflow(WORKFLOW_WITH_OLD_TOOL_VERSION, exact_tools=True)
self.workflow_index_open()
self.workflow_index_click_option("Edit")
time.sleep(.5)
modal_element = self.wait_for_selector_visible(self.modal_body_selector())
text = modal_element.text
assert "Using version '0.2' instead of version '0.0.1'" in text, text
self.assert_modal_has_text("Using version '0.2' instead of version '0.0.1'")
@selenium_test
def test_editor_invalid_tool_state(self):
@@ -63,11 +61,8 @@ class WorkflowEditorTestCase(SeleniumTestCase):
workflow_populator.upload_yaml_workflow(WORKFLOW_WITH_INVALID_STATE, exact_tools=True)
self.workflow_index_open()
self.workflow_index_click_option("Edit")
time.sleep(.5)
modal_element = self.wait_for_selector_visible(self.modal_body_selector())
text = modal_element.text
assert "Using version '0.2' instead of version '0.0.1'" in text, text
assert "Using default: '1'" in text, text
self.assert_modal_has_text("Using version '0.2' instead of version '0.0.1'")
self.assert_modal_has_text("Using default: '1'")
@selenium_test
def test_missing_tools(self):
@@ -84,10 +79,7 @@ steps:
""")
self.workflow_index_open()
self.workflow_index_click_option("Edit")
time.sleep(.5)
modal_element = self.wait_for_selector_visible(self.modal_body_selector())
text = modal_element.text
assert "Tool is not installed" in text, text
self.assert_modal_has_text("Tool is not installed")
def workflow_create_new(self, name=None, annotation=None):
self.workflow_index_open()
@@ -104,3 +96,9 @@ steps:
'workflow_annotation': annotation,
})
self.click_submit(form_element)
@retry_assertion_during_transitions
def assert_modal_has_text(self, expected_text):
modal_element = self.wait_for_selector_visible(self.modal_body_selector())
text = modal_element.text
assert expected_text in text, "Failed to find expected text [%s] in modal text [%s]" % (expected_text, text)
@@ -73,20 +73,13 @@ class WorkflowManagementTestCase(SeleniumTestCase):
self.workflow_index_rename("searchforthis")
self._assert_showing_n_workflows(1)
search_box = self.workflow_index_click_search()
search_box.send_keys("doesnotmatch")
self.workflow_index_search_for("doesnotmatch")
self._assert_showing_n_workflows(0)
# Prevent stale element textbox by re-fetching, seems to be
# needed but I don't understand why exactly. -John
search_box = self.workflow_index_click_search()
search_box.clear()
self.send_enter(search_box)
self.workflow_index_search_for()
self._assert_showing_n_workflows(1)
search_box = self.workflow_index_click_search()
search_box.send_keys("searchforthis")
self.send_enter(search_box)
self.workflow_index_search_for("searchforthis")
self._assert_showing_n_workflows(1)
@selenium_test