Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ config/plugins/**/.cache
static/welcome.html.*
static/welcome.html
static/client_build_hash.txt
lib/galaxy_test/selenium/jupyter/galaxy_selenium_context.yml

# Tool data.
tool-data/annotation_profiler_options.xml
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ client-lint: client-eslint client-format-check ## ES lint and check format of cl
client-test-watch: client ## Watch and run all client unit tests on changes
cd client && yarn run jest-watch

serve-selenium-notebooks: ## Serve testing notebooks for Jupyter
cd lib && export PYTHONPATH=`pwd`; jupyter notebook --notebook-dir=galaxy_test/selenium/jupyter

# Release Targets
release-create-rc: release-ensure-upstream ## Create a release-candidate branch
git checkout dev
Expand Down
35 changes: 25 additions & 10 deletions doc/source/dev/writing_tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,24 +222,33 @@ This test file shows a fairly typical API test. It demonstrates the basic
structure of a test, how to ``GET`` and ``POST`` against the API, and how to use
both typical user and admin user-only functionality.

### Populating Test Data with ``lib/galaxy_test/base/populators.py``
### Common Code for Populating Fixture Data

The ``test_roles.py`` example above also creates a ``DatasetPopulator``
The [``galaxy_test.base.populators``](../lib/galaxy_test.base.html#module-galaxy_test.base.populators) module contains detailed docstrings
describing the concept and implementation of "Populators" that can be used
for this purposes.

The ``test_roles.py`` example creates a ``DatasetPopulator``
object that it uses to get some common information from the configured
Galaxy server under test. Populators are used extensively throughout
API tests as well as integration and Selenium tests to both populate
data to test (histories, workflows, collections, libraries, etc..)
as well as access information from the Galaxy server (e.g. fetch
information from datasets, users, Galaxy's configuration, etc.).
Galaxy server under test.

Populators are used extensively throughout API tests as well as integration
and Selenium tests to both populate data to test (histories, workflows,
collections, libraries, etc..) as well as access information from the
Galaxy server (e.g. fetch information from datasets, users, Galaxy's
configuration, etc.).

Populators and API tests in general make heavy use of the [requests
library](https://requests.readthedocs.io/en/master/) for Python.

### API Test Assertions

There is a module with common assertions ``galaxy_test.base.api_asserts``
used to check API request status codes, dictionary content, and Galaxy
specific error messages.
The ``galaxy_test.base.api_asserts`` module contains common
assertion functions used to check API request status codes, dictionary
content, and Galaxy specific error messages.

See [``galaxy_test.base.api_asserts`` documentation](../lib/galaxy_test.base.html#module-galaxy_test.base.api_asserts)
for details on each function and information on verifying Galaxy API error codes.

### Continuous Integration

Expand Down Expand Up @@ -340,6 +349,12 @@ GitHub actions workflow definition for these tests is located in
These are full stack tests meant to test the Galaxy UI with real
browsers and are located in ``lib/galaxy_test/selenium``.

### Jupyter + Selenium

Jupyter can leveraged to develop Selenium test cases interactively,
checkout out the [``galaxy_test.selenium.jupyter``](../lib/galaxy_test.selenium.jupyter)
for more a full discussion of this.

### Continuous Integration

The Selenium tests are run against each pull request to Galaxy using
Expand Down
8 changes: 8 additions & 0 deletions doc/source/lib/galaxy.selenium.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ galaxy.selenium.has\_driver module
:undoc-members:
:show-inheritance:

galaxy.selenium.jupyter\_context module
---------------------------------------

.. automodule:: galaxy.selenium.jupyter_context
:members:
:undoc-members:
:show-inheritance:

galaxy.selenium.navigates\_galaxy module
----------------------------------------

Expand Down
7 changes: 7 additions & 0 deletions doc/source/lib/galaxy_test.selenium.jupyter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
galaxy\_test.selenium.jupyter package
=====================================

.. automodule:: galaxy_test.selenium.jupyter
:members:
:undoc-members:
:show-inheritance:
24 changes: 24 additions & 0 deletions doc/source/lib/galaxy_test.selenium.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ galaxy\_test.selenium package
:undoc-members:
:show-inheritance:

Subpackages
-----------

.. toctree::
:maxdepth: 4

galaxy_test.selenium.jupyter

Submodules
----------

Expand All @@ -17,6 +25,14 @@ galaxy\_test.selenium.framework module
:undoc-members:
:show-inheritance:

galaxy\_test.selenium.jupyter\_context module
---------------------------------------------

.. automodule:: galaxy_test.selenium.jupyter_context
:members:
:undoc-members:
:show-inheritance:

galaxy\_test.selenium.test\_admin\_app module
---------------------------------------------

Expand Down Expand Up @@ -273,6 +289,14 @@ galaxy\_test.selenium.test\_workflow\_editor module
:undoc-members:
:show-inheritance:

galaxy\_test.selenium.test\_workflow\_invocation\_details module
----------------------------------------------------------------

.. automodule:: galaxy_test.selenium.test_workflow_invocation_details
:members:
:undoc-members:
:show-inheritance:

galaxy\_test.selenium.test\_workflow\_management module
-------------------------------------------------------

Expand Down
16 changes: 15 additions & 1 deletion lib/galaxy/selenium/context.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
from abc import abstractmethod
from typing import Optional

import yaml
from six.moves.urllib.parse import urljoin

from .driver_factory import ConfiguredDriver
Expand Down Expand Up @@ -46,7 +48,7 @@ class GalaxySeleniumContextImpl(GalaxySeleniumContext):
"""Minimal, simplified GalaxySeleniumContext useful outside the context of test cases.

A variant of this concept that can also populate content via the API
to then interact with via the Selenium is :class:galaxy_test.selenium.framework:`GalaxySeleniumContextImpl`.
to then interact with via the Selenium is :class:`galaxy_test.selenium.framework.GalaxySeleniumContextImpl`.
"""

def __init__(self, from_dict: Optional[dict] = None) -> None:
Expand All @@ -58,3 +60,15 @@ def __init__(self, from_dict: Optional[dict] = None) -> None:

def _screenshot_path(self, label, extension=".png"):
return label + extension


def init(config=None, clazz=GalaxySeleniumContextImpl) -> GalaxySeleniumContext:
if os.path.exists("galaxy_selenium_context.yml"):
with open("galaxy_selenium_context.yml", "r") as f:
as_dict = yaml.safe_load(f)
context = clazz(as_dict)
else:
config = config or {}
context = clazz(config)

return context
7 changes: 4 additions & 3 deletions lib/galaxy/selenium/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from .components import Component

new_data_yaml = resource_string(__name__, 'navigation.yml').decode("UTF-8")
NAVIGATION_RAW = yaml.safe_load(new_data_yaml)

NAVIGATION = Component.from_dict("root", NAVIGATION_RAW)
def load_root_component():
new_data_yaml = resource_string(__name__, 'navigation.yml').decode("UTF-8")
navigation_raw = yaml.safe_load(new_data_yaml)
return Component.from_dict("root", navigation_raw)
1 change: 1 addition & 0 deletions lib/galaxy/selenium/driver_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def _which(file):


__all__ = (
'ConfiguredDriver',
'get_local_driver',
'get_remote_driver',
'is_virtual_display_available',
Expand Down
13 changes: 13 additions & 0 deletions lib/galaxy/selenium/jupyter_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .context import GalaxySeleniumContextImpl, init as c_init


def init(config=None):
return c_init(config=config, clazz=JupyterContextImpl)


class JupyterContextImpl(GalaxySeleniumContextImpl):

def screenshot(self, label):
path = super().screenshot(label)
from IPython.display import Image
return Image(filename=path)
14 changes: 10 additions & 4 deletions lib/galaxy/selenium/navigates_galaxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
import yaml

from . import sizzle
from .components import Component
from .data import (
NAVIGATION,
load_root_component,
)
from .has_driver import (
exception_indicates_not_clickable,
Expand Down Expand Up @@ -123,17 +124,22 @@ class NavigatesGalaxy(HasDriver):
class. For instance, the method for clicking an option in the workflow editor is
workflow_editor_click_option instead of click_workflow_editor_option.
"""

default_password = DEFAULT_PASSWORD
wait_types = WAIT_TYPES
# set to True to reload each invocation (good for interactive test building)
_interactive_components: bool = False
_root_component: Component = load_root_component()

def get(self, url=""):
full_url = self.build_url(url)
return self.driver.get(full_url)

@property
def navigation(self):
return NAVIGATION
def navigation(self) -> Component:
if self._interactive_components:
return load_root_component()
else:
return self._root_component

@property
def components(self):
Expand Down
Loading