mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #4676 from jmchilton/selenium_fixes_33
Another round of small Selenium fixes.
This commit is contained in:
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'])
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user