Source code for galaxy.selenium.navigates_galaxy

"""A mixin that extends a HasDriver class with Galaxy-specific utilities.

Implementer must provide a self.build_url method to target Galaxy.
"""

import collections
import contextlib
import random
import string
import time
from abc import abstractmethod
from dataclasses import (
    dataclass,
    field,
)
from functools import (
    partial,
    wraps,
)
from typing import (
    Any,
    cast,
    Dict,
    List,
    Literal,
    NamedTuple,
    Optional,
    Tuple,
    Union,
)

import yaml
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as ec

from galaxy.navigation.components import (
    Component,
    HasText,
)
from galaxy.navigation.data import load_root_component
from galaxy.util import (
    DEFAULT_SOCKET_TIMEOUT,
    requests,
)
from . import sizzle
from .has_driver import (
    exception_indicates_click_intercepted,
    exception_indicates_not_clickable,
    exception_indicates_stale_element,
    HasDriver,
    SeleniumTimeoutException,
)
from .smart_components import SmartComponent

# Test case data
DEFAULT_PASSWORD = "123456"

RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT = 0.1
RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT = 10

GALAXY_MAIN_FRAME_ID = "galaxy_main"
GALAXY_VISUALIZATION_FRAME_ID = "galaxy_visualization"

WaitType = collections.namedtuple("WaitType", ["name", "default_length"])


[docs]class HistoryEntry(NamedTuple): id: str hid: str history_content_type: str
# Default wait times should make sense for a development server under low # load. Wait times for production servers can be scaled up with a multiplier.
[docs]class WAIT_TYPES: # Rendering a form and registering callbacks, etc... UX_RENDER = WaitType("ux_render", 1) # Fade in, fade out, etc... UX_TRANSITION = WaitType("ux_transition", 5) # Toastr popup and dismissal, etc... UX_POPUP = WaitType("ux_popup", 15) # Creating a new history and loading it into the panel. DATABASE_OPERATION = WaitType("database_operation", 10) # Wait time for jobs to complete in default environment. JOB_COMPLETION = WaitType("job_completion", 30) # Wait time for a GIE to spawn. GIE_SPAWN = WaitType("gie_spawn", 30) # Wait time for toolshed search SHED_SEARCH = WaitType("shed_search", 30) # Wait time for repository installation REPO_INSTALL = WaitType("repo_install", 60) # History Polling Duration HISTORY_POLL = WaitType("history_poll", 3)
# Choose a moderate wait type for operations that don't specify a type. DEFAULT_WAIT_TYPE = WAIT_TYPES.DATABASE_OPERATION
[docs]class NullTourCallback:
[docs] def handle_step(self, step, step_index): pass
[docs]def exception_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 exception_indicates_not_clickable(e) or exception_indicates_click_intercepted(e) )
[docs]def retry_call_during_transitions( f, attempts=RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT, sleep=RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT, exception_check=exception_seems_to_indicate_transition, ): previous_attempts = 0 while True: try: return f() except Exception as e: if previous_attempts > attempts: raise if not exception_check(e): raise time.sleep(sleep) previous_attempts += 1
[docs]def retry_during_transitions( f, attempts=RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT, sleep=RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT, exception_check=exception_seems_to_indicate_transition, ): @wraps(f) def _retry(*args, **kwds): return retry_call_during_transitions( partial(f, *args, **kwds), attempts=attempts, sleep=sleep, exception_check=exception_check ) return _retry
[docs]def retry_index_during_transitions(): def exception_check(e): return exception_seems_to_indicate_transition(e) or isinstance(e, IndexError) return partial(retry_during_transitions, exception_check=exception_check)
[docs]def edit_details(f, scope=".history-index"): """Open the editor, run the edits, hit the save button""" @wraps(f) def func_wrapper(self, *args, **kwds): # open editor self.open_history_editor(scope=scope) # run edits result = f(self, *args, **kwds) # save edits self.history_click_editor_save() return result return func_wrapper
[docs]@dataclass class ConfigTemplateParameter: form_element_type: Literal["string", "boolean", "integer"] name: str value: Any
[docs]@dataclass class FileSourceInstance: template_id: str name: str description: Optional[str] parameters: List[ConfigTemplateParameter] = field(default_factory=list)
[docs]@dataclass class ObjectStoreInstance: template_id: str name: str description: Optional[str] parameters: List[ConfigTemplateParameter] = field(default_factory=list)
[docs]class NotLoggedInException(SeleniumTimeoutException):
[docs] def __init__(self, timeout_exception, user_info, dom_message): template = "Waiting for UI to reflect user logged in but it did not occur. API indicates no user is currently logged in. %s API response was [%s]. %s" msg = template % (dom_message, user_info, timeout_exception.msg) super().__init__(msg=msg, screen=timeout_exception.screen, stacktrace=timeout_exception.stacktrace)
[docs]class ClientBuildException(SeleniumTimeoutException):
[docs] def __init__(self, timeout_exception: SeleniumTimeoutException): msg = f"Error waiting for Galaxy masthead to appear, this frequently means there is a problem with the client build and the Galaxy client is broken. {timeout_exception.msg}" super().__init__(msg=msg, screen=timeout_exception.screen, stacktrace=timeout_exception.stacktrace)