mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'dev' into ftp_uploader
This commit is contained in:
@@ -75,6 +75,9 @@ var HistoryView = _super.extend(
|
||||
// control contents/behavior based on where (and in what context) the panel is being used
|
||||
/** where should pages from links be displayed? (default to new tab/window) */
|
||||
this.linkTarget = attributes.linkTarget || '_blank';
|
||||
|
||||
/** timeout id for detailed fetch of collection counts, etc... */
|
||||
this.detailedFetchTimeoutId = null;
|
||||
},
|
||||
|
||||
/** create and return a collection for when none is initially passed */
|
||||
@@ -89,9 +92,18 @@ var HistoryView = _super.extend(
|
||||
if( this.model ){
|
||||
this.model.clearUpdateTimeout();
|
||||
}
|
||||
this._clearDetailedFetchTimeout();
|
||||
return this;
|
||||
},
|
||||
|
||||
/** clear the timeout and the cached timeout id */
|
||||
_clearDetailedFetchTimeout : function(){
|
||||
if( this.detailedFetchTimeoutId ){
|
||||
clearTimeout( this.detailedFetchTimeoutId );
|
||||
this.detailedFetchTimeoutId = null;
|
||||
}
|
||||
},
|
||||
|
||||
/** create any event listeners for the panel
|
||||
* @fires: rendered:initial on the first render
|
||||
* @fires: empty-history when switching to a history with no contents or creating a new history
|
||||
@@ -105,7 +117,8 @@ var HistoryView = _super.extend(
|
||||
'loading-done' : function(){
|
||||
var self = this;
|
||||
// after the initial load, decorate with more time consuming fields (like HDCA element_counts)
|
||||
_.delay( function(){
|
||||
self.detailedFetchTimeoutId = _.delay( function(){
|
||||
self.detailedFetchTimeoutId = null;
|
||||
self.model.contents.fetchCollectionCounts();
|
||||
}, self.FETCH_COLLECTION_COUNTS_DELAY );
|
||||
},
|
||||
|
||||
@@ -234,6 +234,7 @@
|
||||
<datatype extension="sf3" type="galaxy.datatypes.proteomics:Sf3" display_in_upload="true" />
|
||||
<datatype extension="cps" type="galaxy.datatypes.binary:Binary" subclass="true" display_in_upload="true" />
|
||||
<datatype extension="ct" type="galaxy.datatypes.tabular:ConnectivityTable" display_in_upload="true"/>
|
||||
<datatype extension="postgresql" type="galaxy.datatypes.binary:PostgresqlArchive" subclass="True" display_in_upload="True"/>
|
||||
<datatype extension="searchgui_archive" type="galaxy.datatypes.binary:SearchGuiArchive" display_in_upload="true"/>
|
||||
<datatype extension="fast5.tar" type="galaxy.datatypes.binary:Fast5Archive" display_in_upload="true"/>
|
||||
<datatype extension="fast5.tar.gz" type="galaxy.datatypes.binary:Fast5ArchiveGz" display_in_upload="true"/>
|
||||
@@ -679,6 +680,7 @@
|
||||
<sniffer type="galaxy.datatypes.binary:Fast5ArchiveGz" />
|
||||
<sniffer type="galaxy.datatypes.binary:Fast5ArchiveBz2" />
|
||||
<sniffer type="galaxy.datatypes.binary:Fast5Archive" />
|
||||
<sniffer type="galaxy.datatypes.binary:PostgresqlArchive"/>
|
||||
<sniffer type="galaxy.datatypes.triples:Rdf"/>
|
||||
<sniffer type="galaxy.datatypes.blast:BlastXml"/>
|
||||
<sniffer type="galaxy.datatypes.xml:Phyloxml"/>
|
||||
|
||||
@@ -1566,6 +1566,59 @@ Binary.register_sniffable_binary_format("oxli.graphlabels", "oxligl",
|
||||
OxliGraphLabels)
|
||||
|
||||
|
||||
class PostgresqlArchive(CompressedArchive):
|
||||
"""
|
||||
Class describing a Postgresql database packed into a tar archive
|
||||
|
||||
>>> from galaxy.datatypes.sniff import get_test_fname
|
||||
>>> fname = get_test_fname( 'postgresql_fake.tar.bz2' )
|
||||
>>> PostgresqlArchive().sniff( fname )
|
||||
True
|
||||
>>> fname = get_test_fname( 'test.fast5.tar' )
|
||||
>>> PostgresqlArchive().sniff( fname )
|
||||
False
|
||||
"""
|
||||
MetadataElement(name="version", default=None, param=MetadataParameter, desc="PostgreSQL database version",
|
||||
readonly=True, visible=True, no_value=None)
|
||||
file_ext = "postgresql"
|
||||
|
||||
def set_meta(self, dataset, overwrite=True, **kwd):
|
||||
super(PostgresqlArchive, self).set_meta(dataset, overwrite=overwrite, **kwd)
|
||||
try:
|
||||
if dataset and tarfile.is_tarfile(dataset.file_name):
|
||||
with tarfile.open(dataset.file_name, 'r') as temptar:
|
||||
pg_version_file = temptar.extractfile('postgresql/db/PG_VERSION')
|
||||
dataset.metadata.version = pg_version_file.read().strip()
|
||||
except Exception as e:
|
||||
log.warning('%s, set_meta Exception: %s', self, e)
|
||||
|
||||
def sniff(self, filename):
|
||||
if filename and tarfile.is_tarfile(filename):
|
||||
try:
|
||||
with tarfile.open(filename, 'r') as temptar:
|
||||
return 'postgresql/db/PG_VERSION' in temptar.getnames()
|
||||
except Exception as e:
|
||||
log.warning('%s, sniff Exception: %s', self, e)
|
||||
return False
|
||||
|
||||
def set_peek(self, dataset, is_multi_byte=False):
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = "PostgreSQL Archive (%s)" % (nice_size(dataset.get_size()))
|
||||
dataset.blurb = "PostgreSQL version %s" % (dataset.metadata.version or 'unknown')
|
||||
else:
|
||||
dataset.peek = 'file does not exist'
|
||||
dataset.blurb = 'file purged from disk'
|
||||
|
||||
def display_peek(self, dataset):
|
||||
try:
|
||||
return dataset.peek
|
||||
except:
|
||||
return "PostgreSQL Archive (%s)" % (nice_size(dataset.get_size()))
|
||||
|
||||
|
||||
Binary.register_sniffable_binary_format("postgresql_archiv", "postgresql", PostgresqlArchive)
|
||||
|
||||
|
||||
class Fast5Archive(CompressedArchive):
|
||||
"""
|
||||
Class describing a FAST5 archive
|
||||
|
||||
Binary file not shown.
@@ -87,8 +87,8 @@ def set_datatypes_registry(d_registry):
|
||||
|
||||
|
||||
class HasTags(object):
|
||||
dict_collection_visible_keys = ('tags')
|
||||
dict_element_visible_keys = ('tags')
|
||||
dict_collection_visible_keys = ('tags',)
|
||||
dict_element_visible_keys = ('tags',)
|
||||
|
||||
def to_dict(self, *args, **kwargs):
|
||||
rval = super(HasTags, self).to_dict(*args, **kwargs)
|
||||
|
||||
@@ -1056,7 +1056,11 @@ class BaseGalaxyToolBox(AbstractToolBox):
|
||||
|
||||
def __init__(self, config_filenames, tool_root_dir, app):
|
||||
super(BaseGalaxyToolBox, self).__init__(config_filenames, tool_root_dir, app)
|
||||
self._init_dependency_manager()
|
||||
old_toolbox = getattr(app, 'toolbox', None)
|
||||
if old_toolbox:
|
||||
self.dependency_manager = old_toolbox.dependency_manager
|
||||
else:
|
||||
self._init_dependency_manager()
|
||||
|
||||
@property
|
||||
def sa_session(self):
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"deletable": true,
|
||||
"editable": true
|
||||
},
|
||||
"source": [
|
||||
"# Welcome to the interactive Galaxy IPython Notebook."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"deletable": true,
|
||||
"editable": true
|
||||
},
|
||||
"source": [
|
||||
"You can access your data via the dataset number. For example, ``handle = open(get(42), 'r')``.\n",
|
||||
"To save data, write your data to a file, and then call ``put('filename.txt')``. The dataset will then be available in your galaxy history.\n",
|
||||
"Notebooks can be saved to Galaxy by clicking the large green button at the top right of the IPython interface.<br>\n",
|
||||
"More help and informations can be found on the project [website](https://github.com/bgruening/galaxy-ipython)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {
|
||||
"collapsed": false,
|
||||
"deletable": true,
|
||||
"editable": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'/import/1'"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"get(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"chr6_hla_hap2 0 150\r\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!grep chr6 /import/1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!echo \"Hello World\" > /tmp/cow"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"put(\"/tmp/cow\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {
|
||||
"collapsed": false
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1 [2] 1.txt.gg ipython_galaxy_notebook.ipynb\r\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!ls /import"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"put(\"/import/ipython_galaxy_notebook.ipynb\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 2",
|
||||
"language": "python",
|
||||
"name": "python2"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -1,5 +1,158 @@
|
||||
import re
|
||||
import string
|
||||
|
||||
import yaml
|
||||
|
||||
from pkg_resources import resource_string
|
||||
|
||||
data_yaml = resource_string(__name__, 'navigation-data.yml').decode("UTF-8")
|
||||
NAVIGATION_DATA = yaml.safe_load(data_yaml)
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from galaxy.util.bunch import Bunch
|
||||
|
||||
|
||||
old_data_yaml = resource_string(__name__, 'navigation-data.yml').decode("UTF-8")
|
||||
NAVIGATION_DATA = yaml.safe_load(old_data_yaml)
|
||||
new_data_yaml = resource_string(__name__, 'navigation.yml').decode("UTF-8")
|
||||
NAVIGATION_RAW = yaml.safe_load(new_data_yaml)
|
||||
|
||||
|
||||
class SelectorTemplate(object):
|
||||
|
||||
def __init__(self, selector, selector_type, kwds=None, with_classes=None):
|
||||
self._selector = selector
|
||||
self.selector_type = selector_type
|
||||
self.__kwds = kwds or {}
|
||||
self.with_classes = with_classes or []
|
||||
|
||||
@staticmethod
|
||||
def from_dict(raw_value):
|
||||
if isinstance(raw_value, dict):
|
||||
return SelectorTemplate(raw_value["selector"], raw_value.get("type", "css"))
|
||||
else:
|
||||
return SelectorTemplate(raw_value, "css")
|
||||
|
||||
def with_class(self, class_):
|
||||
assert self.selector_type == "css"
|
||||
return SelectorTemplate(self._selector, self.selector_type, kwds=self.__kwds, with_classes=self.with_classes + [class_])
|
||||
|
||||
def __call__(self, **kwds):
|
||||
new_kwds = self.__kwds
|
||||
new_kwds.update(**kwds)
|
||||
return SelectorTemplate(self._selector, self.selector_type, kwds=new_kwds, with_classes=self.with_classes)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
if self.selector_type == "css":
|
||||
template = "CSS selector [%s]"
|
||||
elif self.selector_type == "xpath":
|
||||
template = "XPATH selector [%s]"
|
||||
elif self.selector_type == "id":
|
||||
template = "DOM element with id [%s]"
|
||||
return template % self.selector
|
||||
|
||||
@property
|
||||
def selector(self):
|
||||
selector = self._selector
|
||||
if self.__kwds is not None:
|
||||
selector = string.Template(selector).substitute(self.__kwds)
|
||||
selector = selector + "".join([".%s" % c for c in self.with_classes])
|
||||
return selector
|
||||
|
||||
@property
|
||||
def element_locator(self):
|
||||
if self.selector_type == "css":
|
||||
by = By.CSS_SELECTOR
|
||||
elif self.selector_type == "xpath":
|
||||
by = By.XPATH
|
||||
elif self.selector_type == "id":
|
||||
by = By.ID
|
||||
else:
|
||||
raise Exception("Unknown selector type")
|
||||
return (by, self.selector)
|
||||
|
||||
@property
|
||||
def as_css_class(self):
|
||||
assert self.selector_type == "css"
|
||||
assert re.compile(r"\.\w+").match(self._selector)
|
||||
return self._selector[1:]
|
||||
|
||||
|
||||
class Label(object):
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return "Link text [%s]" % self.text
|
||||
|
||||
@property
|
||||
def element_locator(self):
|
||||
return (By.LINK_TEXT, self.text)
|
||||
|
||||
|
||||
class Text(object):
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return "Text containing [%s]" % self.text
|
||||
|
||||
@property
|
||||
def element_locator(self):
|
||||
return (By.PARTIAL_LINK_TEXT, self.text)
|
||||
|
||||
|
||||
class Component(object):
|
||||
|
||||
def __init__(self, name, sub_components, selectors, labels, text):
|
||||
self.name = name
|
||||
self._sub_components = sub_components
|
||||
self._selectors = selectors
|
||||
self._labels = labels
|
||||
self._text = text
|
||||
|
||||
self.selectors = Bunch(**self._selectors)
|
||||
self.labels = Bunch(**self._labels)
|
||||
self.text = Bunch(**self._text)
|
||||
|
||||
@property
|
||||
def selector(self):
|
||||
if "_" in self._selectors:
|
||||
return self._selectors["_"]
|
||||
else:
|
||||
raise Exception("No _ selector for [%s]" % self)
|
||||
|
||||
@staticmethod
|
||||
def from_dict(name, raw_value):
|
||||
selectors = {}
|
||||
labels = {}
|
||||
text = {}
|
||||
sub_components = {}
|
||||
|
||||
for key, value in raw_value.items():
|
||||
if key == "selectors":
|
||||
for selector_key, selector_value in value.items():
|
||||
selectors[selector_key] = SelectorTemplate.from_dict(selector_value)
|
||||
elif key == "labels":
|
||||
for label_key, label_value in value.items():
|
||||
labels[label_key] = Label(label_value)
|
||||
elif key == "text":
|
||||
for text_key, text_value in value.items():
|
||||
text[text_key] = Text(text_value)
|
||||
else:
|
||||
component = Component.from_dict(key, value)
|
||||
sub_components[key] = component
|
||||
|
||||
return Component(name, sub_components, selectors, labels, text)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return self._sub_components[attr]
|
||||
|
||||
def __str__(self):
|
||||
return "Component[%s]" % self.name
|
||||
|
||||
|
||||
NAVIGATION = Component.from_dict("root", NAVIGATION_RAW)
|
||||
|
||||
@@ -28,73 +28,128 @@ class HasDriver:
|
||||
for element in elements:
|
||||
assert not element.is_displayed()
|
||||
|
||||
def assert_absent_or_hidden(self, selector_template):
|
||||
elements = self.find_elements(selector_template)
|
||||
for element in elements:
|
||||
assert not element.is_displayed()
|
||||
|
||||
def selector_is_displayed(self, selector):
|
||||
element = self.driver.find_element_by_css_selector(selector)
|
||||
return element.is_displayed()
|
||||
|
||||
def is_displayed(self, selector_template):
|
||||
element = self.driver.find_element(*selector_template.element_locator)
|
||||
return element.is_displayed()
|
||||
|
||||
def assert_selector_absent(self, selector):
|
||||
assert len(self.driver.find_elements_by_css_selector(selector)) == 0
|
||||
|
||||
def wait_for_xpath(self, xpath):
|
||||
def find_elements(self, selector_template):
|
||||
return self.driver.find_elements(*selector_template.element_locator)
|
||||
|
||||
def assert_absent(self, selector_template):
|
||||
assert len(self.find_elements(selector_template)) == 0
|
||||
|
||||
def wait_for_xpath(self, xpath, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.presence_of_element_located((By.XPATH, xpath)),
|
||||
"XPATH selector [%s] to become present" % xpath,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_xpath_visible(self, xpath):
|
||||
def wait_for_xpath_visible(self, xpath, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.visibility_of_element_located((By.XPATH, xpath)),
|
||||
"XPATH selector [%s] to become visible" % xpath,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_selector(self, selector):
|
||||
def wait_for_selector(self, selector, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.presence_of_element_located((By.CSS_SELECTOR, selector)),
|
||||
"CSS selector [%s] to become present" % selector,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_selector_visible(self, selector):
|
||||
def wait_for_present(self, selector_template, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.presence_of_element_located(selector_template.element_locator),
|
||||
"%s to become present" % selector_template.description,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_visible(self, selector_template, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.visibility_of_element_located(selector_template.element_locator),
|
||||
"%s to become visible" % selector_template.description,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_selector_visible(self, selector, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.visibility_of_element_located((By.CSS_SELECTOR, selector)),
|
||||
"CSS selector [%s] to become visible" % selector,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_selector_clickable(self, selector):
|
||||
def wait_for_selector_clickable(self, selector, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.element_to_be_clickable((By.CSS_SELECTOR, selector)),
|
||||
"CSS selector [%s] to become clickable" % selector,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_selector_absent_or_hidden(self, selector):
|
||||
def wait_for_clickable(self, selector_template, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.element_to_be_clickable(selector_template.element_locator),
|
||||
"%s to become clickable" % selector_template.description,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_selector_absent_or_hidden(self, selector, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.invisibility_of_element_located((By.CSS_SELECTOR, selector)),
|
||||
"CSS selector [%s] to become absent or hidden" % selector,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_selector_absent(self, selector):
|
||||
def wait_for_selector_absent(self, selector, **kwds):
|
||||
element = self._wait_on(
|
||||
lambda driver: len(driver.find_elements_by_css_selector(selector)) == 0,
|
||||
"CSS selector [%s] to become absent" % selector,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_id(self, id):
|
||||
def wait_for_absent_or_hidden(self, selector_template, **kwds):
|
||||
element = self._wait_on(
|
||||
ec.invisibility_of_element_located(selector_template.element_locator),
|
||||
"%s to become absent or hidden" % selector_template.description,
|
||||
**kwds
|
||||
)
|
||||
return element
|
||||
|
||||
def wait_for_id(self, id, **kwds):
|
||||
return self._wait_on(
|
||||
ec.presence_of_element_located((By.ID, id)),
|
||||
"presence of DOM ID [%s]" % id,
|
||||
**kwds
|
||||
)
|
||||
|
||||
def _wait_on(self, condition, on_str=None):
|
||||
def _wait_on(self, condition, on_str=None, **kwds):
|
||||
if on_str is None:
|
||||
on_str = str(condition)
|
||||
message = "Timeout waiting on %s." % on_str
|
||||
wait = self.wait()
|
||||
wait = self.wait(**kwds)
|
||||
return wait.until(condition, message)
|
||||
|
||||
def action_chains(self):
|
||||
@@ -106,9 +161,9 @@ class HasDriver:
|
||||
def send_escape(self, element):
|
||||
element.send_keys(Keys.ESCAPE)
|
||||
|
||||
def wait(self, timeout=UNSPECIFIED_TIMEOUT):
|
||||
def wait(self, timeout=UNSPECIFIED_TIMEOUT, **kwds):
|
||||
if timeout is UNSPECIFIED_TIMEOUT:
|
||||
timeout = self.default_timeout
|
||||
timeout = self.timeout_for(**kwds)
|
||||
return WebDriverWait(self.driver, timeout)
|
||||
|
||||
def click_xpath(self, xpath):
|
||||
|
||||
@@ -4,6 +4,7 @@ Implementer must provide a self.build_url method to target Galaxy.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import random
|
||||
import string
|
||||
@@ -13,8 +14,13 @@ from functools import partial, wraps
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
from galaxy.util.bunch import Bunch
|
||||
|
||||
from . import sizzle
|
||||
from .data import NAVIGATION_DATA
|
||||
from .data import (
|
||||
NAVIGATION, # newer variant of NAVIGATION_DATA
|
||||
NAVIGATION_DATA,
|
||||
)
|
||||
from .has_driver import (
|
||||
exception_indicates_not_clickable,
|
||||
exception_indicates_stale_element,
|
||||
@@ -28,6 +34,28 @@ DEFAULT_PASSWORD = '123456'
|
||||
RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT = .1
|
||||
RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT = 10
|
||||
|
||||
WaitType = collections.namedtuple("WaitType", ["name", "default_length"])
|
||||
|
||||
# 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.
|
||||
WAIT_TYPES = Bunch(
|
||||
# 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", 10),
|
||||
# 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),
|
||||
)
|
||||
|
||||
# Choose a moderate wait type for operations that don't specify a type.
|
||||
DEFAULT_WAIT_TYPE = WAIT_TYPES.DATABASE_OPERATION
|
||||
|
||||
|
||||
class NullTourCallback(object):
|
||||
|
||||
@@ -35,7 +63,7 @@ class NullTourCallback(object):
|
||||
pass
|
||||
|
||||
|
||||
def excepion_seems_to_indicate_transition(e):
|
||||
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.
|
||||
@@ -52,7 +80,7 @@ def excepion_seems_to_indicate_transition(e):
|
||||
return exception_indicates_stale_element(e) or exception_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):
|
||||
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:
|
||||
@@ -68,7 +96,7 @@ def retry_call_during_transitions(f, attempts=RETRY_DURING_TRANSITIONS_ATTEMPTS_
|
||||
previous_attempts += 1
|
||||
|
||||
|
||||
def retry_during_transitions(f, attempts=RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAULT, sleep=RETRY_DURING_TRANSITIONS_SLEEP_DEFAULT, exception_check=excepion_seems_to_indicate_transition):
|
||||
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):
|
||||
@@ -78,8 +106,24 @@ def retry_during_transitions(f, attempts=RETRY_DURING_TRANSITIONS_ATTEMPTS_DEFAU
|
||||
|
||||
|
||||
class NavigatesGalaxy(HasDriver):
|
||||
"""Class with helpers methods for driving components of the Galaxy interface.
|
||||
|
||||
In most cases, methods for interacting with Galaxy components that appear in
|
||||
multiple tests or applications should be refactored into this class for now.
|
||||
Keep in mind that this class is used outside the context of ``TestCase``s as
|
||||
well - so some methods more explicitly related to test data or assertion checking
|
||||
may make more sense in SeleniumTestCase for instance.
|
||||
|
||||
Some day this class will likely be split up into smaller mixins for particular
|
||||
components of Galaxy, but until that day the best practice is to prefix methods
|
||||
for driving or querying the interface with the name of the component or page
|
||||
the method operates on. These serve as psedu-namespaces until we decompose this
|
||||
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
|
||||
|
||||
def get(self, url=""):
|
||||
full_url = self.build_url(url)
|
||||
@@ -89,13 +133,27 @@ class NavigatesGalaxy(HasDriver):
|
||||
def navigation_data(self):
|
||||
return NAVIGATION_DATA
|
||||
|
||||
@property
|
||||
def navigation(self):
|
||||
return NAVIGATION
|
||||
|
||||
def wait_length(self, wait_type):
|
||||
return wait_type.default_length * self.timeout_multiplier
|
||||
|
||||
def sleep_for(self, wait_type):
|
||||
time.sleep(self.wait_length(wait_type))
|
||||
|
||||
def timeout_for(self, **kwds):
|
||||
wait_type = kwds.get("wait_type", DEFAULT_WAIT_TYPE)
|
||||
return self.wait_length(wait_type)
|
||||
|
||||
def home(self):
|
||||
self.get()
|
||||
self.wait_for_selector_visible("#masthead")
|
||||
self.wait_for_selector_visible("#current-history-panel")
|
||||
self.wait_for_visible(self.navigation.masthead.selector)
|
||||
self.wait_for_visible(self.navigation.history_panel.selector)
|
||||
|
||||
def switch_to_main_panel(self):
|
||||
self.driver.switch_to.frame(self.navigation_data["selectors"]["frames"]["main"])
|
||||
self.driver.switch_to.frame("galaxy_main")
|
||||
|
||||
@contextlib.contextmanager
|
||||
def main_panel(self):
|
||||
@@ -113,6 +171,14 @@ class NavigatesGalaxy(HasDriver):
|
||||
else:
|
||||
return response.json()
|
||||
|
||||
def api_delete(self, endpoint, raw=False):
|
||||
full_url = self.build_url("api/" + endpoint, for_selenium=False)
|
||||
response = requests.get(full_url, cookies=self.selenium_to_requests_cookies())
|
||||
if raw:
|
||||
return response
|
||||
else:
|
||||
return response.json()
|
||||
|
||||
def get_galaxy_session(self):
|
||||
for cookie in self.driver.get_cookies():
|
||||
if cookie["name"] == "galaxysession":
|
||||
@@ -123,12 +189,12 @@ class NavigatesGalaxy(HasDriver):
|
||||
'galaxysession': self.get_galaxy_session()
|
||||
}
|
||||
|
||||
def history_panel_name_selector(self):
|
||||
name_selector = self.test_data["historyPanel"]["selectors"]["history"]["name"]
|
||||
return name_selector
|
||||
|
||||
def history_panel_name_element(self):
|
||||
return self.wait_for_selector(self.history_panel_name_selector())
|
||||
return self.wait_for_present(self.navigation.history_panel.selectors.name)
|
||||
|
||||
@retry_during_transitions
|
||||
def history_panel_name(self):
|
||||
return self.history_panel_name_element().text
|
||||
|
||||
def current_history(self):
|
||||
history = self.api_get("histories")[0]
|
||||
@@ -147,7 +213,7 @@ class NavigatesGalaxy(HasDriver):
|
||||
assert len(history_contents) > 0
|
||||
return history_contents[-1]
|
||||
|
||||
def wait_for_history(self, timeout=30, assert_ok=True):
|
||||
def wait_for_history(self, assert_ok=True):
|
||||
def history_becomes_terminal(driver):
|
||||
current_history_id = self.current_history_id()
|
||||
state = self.api_get("histories/%s" % current_history_id)["state"]
|
||||
@@ -156,41 +222,54 @@ class NavigatesGalaxy(HasDriver):
|
||||
else:
|
||||
return None
|
||||
|
||||
final_state = self.wait(timeout).until(history_becomes_terminal)
|
||||
timeout = self.timeout_for(wait_type=WAIT_TYPES.JOB_COMPLETION)
|
||||
final_state = self.wait(timeout=timeout).until(history_becomes_terminal)
|
||||
if assert_ok:
|
||||
assert final_state == "ok", final_state
|
||||
return final_state
|
||||
|
||||
def history_panel_wait_for_hid_ok(self, hid, timeout=60, allowed_force_refreshes=0):
|
||||
self.history_panel_wait_for_hid_state(hid, 'ok', timeout=timeout, allowed_force_refreshes=allowed_force_refreshes)
|
||||
def history_panel_create_new_with_name(self, name):
|
||||
self.history_panel_create_new()
|
||||
self.history_panel_rename(name)
|
||||
|
||||
def history_panel_wait_for_hid_visible(self, hid, timeout=60, allowed_force_refreshes=0):
|
||||
def history_panel_create_new(self):
|
||||
"""Click create new and pause a bit for the history to begin to refresh."""
|
||||
self.click_history_option('Create New')
|
||||
self.sleep_for(WAIT_TYPES.UX_RENDER)
|
||||
|
||||
def history_panel_wait_for_hid_ok(self, hid, allowed_force_refreshes=0):
|
||||
self.history_panel_wait_for_hid_state(hid, 'ok', allowed_force_refreshes=allowed_force_refreshes)
|
||||
|
||||
def history_panel_wait_for_hid_visible(self, hid, allowed_force_refreshes=0):
|
||||
current_history_id = self.current_history_id()
|
||||
|
||||
def history_has_hid(driver):
|
||||
contents = self.api_get("histories/%s/contents" % current_history_id)
|
||||
return any([d for d in contents if d["hid"] == hid])
|
||||
|
||||
timeout = self.timeout_for(wait_type=WAIT_TYPES.JOB_COMPLETION)
|
||||
self.wait(timeout).until(history_has_hid)
|
||||
contents = self.api_get("histories/%s/contents" % current_history_id)
|
||||
history_item = [d for d in contents if d["hid"] == hid][0]
|
||||
history_item_selector = "#%s-%s" % (history_item["history_content_type"], history_item["id"])
|
||||
|
||||
history_item_selector = self.navigation.history_panel.item.selector(
|
||||
history_content_type=history_item["history_content_type"],
|
||||
id=history_item["id"]
|
||||
)
|
||||
try:
|
||||
self.history_item_wait_for_selector(history_item_selector, allowed_force_refreshes)
|
||||
self.history_item_wait_for(history_item_selector, allowed_force_refreshes)
|
||||
except self.TimeoutException as e:
|
||||
dataset_elements = self.driver.find_elements_by_css_selector("#current-history-panel .list-items div")
|
||||
div_ids = [d.get_attribute('id') for d in dataset_elements]
|
||||
contents_elements = self.find_elements(self.navigation.history_panel.selectors.contents)
|
||||
div_ids = [("#" + d.get_attribute('id')) for d in contents_elements]
|
||||
template = "Failed waiting on history item %d to become visible, visible datasets include [%s]."
|
||||
message = template % (hid, ",".join(div_ids))
|
||||
raise self.prepend_timeout_message(e, message)
|
||||
return history_item_selector
|
||||
|
||||
def history_item_wait_for_selector(self, history_item_selector, allowed_force_refreshes):
|
||||
def history_item_wait_for(self, history_item_selector, allowed_force_refreshes):
|
||||
attempt = 0
|
||||
while True:
|
||||
try:
|
||||
rval = self.wait_for_selector_visible(history_item_selector)
|
||||
rval = self.wait_for_visible(history_item_selector, wait_type=WAIT_TYPES.JOB_COMPLETION)
|
||||
break
|
||||
except self.TimeoutException:
|
||||
if attempt >= allowed_force_refreshes:
|
||||
@@ -204,23 +283,26 @@ class NavigatesGalaxy(HasDriver):
|
||||
# Use the search box showing up as a proxy that the history display
|
||||
# has left the "loading" state and is showing a valid set of history contents
|
||||
# (even if empty).
|
||||
self.wait_for_selector_visible("#current-history-panel input.search-query")
|
||||
self.wait_for_visible(self.navigation.history_panel.selectors.search, wait_type=WAIT_TYPES.DATABASE_OPERATION)
|
||||
|
||||
def history_panel_wait_for_hid_hidden(self, hid, timeout=60):
|
||||
def history_panel_wait_for_hid_hidden(self, hid):
|
||||
current_history_id = self.current_history_id()
|
||||
contents = self.api_get("histories/%s/contents" % current_history_id)
|
||||
history_item = [d for d in contents if d["hid"] == hid][0]
|
||||
history_item_selector = "#%s-%s" % (history_item["history_content_type"], history_item["id"])
|
||||
self.wait_for_selector_absent(history_item_selector)
|
||||
history_item_selector = self.navigation.history_panel.item.selector(
|
||||
history_content_type=history_item["history_content_type"],
|
||||
id=history_item["id"]
|
||||
)
|
||||
self.wait_for_absent_or_hidden(history_item_selector, wait_type=WAIT_TYPES.JOB_COMPLETION)
|
||||
return history_item_selector
|
||||
|
||||
def history_panel_wait_for_hid_state(self, hid, state, timeout=60, allowed_force_refreshes=0):
|
||||
history_item_selector = self.history_panel_wait_for_hid_visible(hid, timeout=timeout, allowed_force_refreshes=allowed_force_refreshes)
|
||||
history_item_selector_state = "%s.state-%s" % (history_item_selector, state)
|
||||
def history_panel_wait_for_hid_state(self, hid, state, allowed_force_refreshes=0):
|
||||
history_item_selector = self.history_panel_wait_for_hid_visible(hid, allowed_force_refreshes=allowed_force_refreshes)
|
||||
history_item_selector_state = history_item_selector.with_class("state-%s" % state)
|
||||
try:
|
||||
self.history_item_wait_for_selector(history_item_selector_state, allowed_force_refreshes)
|
||||
self.history_item_wait_for(history_item_selector_state, allowed_force_refreshes)
|
||||
except self.TimeoutException as e:
|
||||
history_item = self.driver.find_element_by_css_selector(history_item_selector)
|
||||
history_item = self.wait_for_visible(history_item_selector)
|
||||
current_state = "UNKNOWN"
|
||||
classes = history_item.get_attribute("class").split(" ")
|
||||
for clazz in classes:
|
||||
@@ -274,10 +356,11 @@ class NavigatesGalaxy(HasDriver):
|
||||
}
|
||||
|
||||
self.click_masthead_user()
|
||||
self.click_label(self.navigation_data["labels"]["masthead"]["userMenu"]["login"])
|
||||
self.wait_for_and_click(self.navigation.masthead.labels.login)
|
||||
|
||||
with self.main_panel():
|
||||
form = self.wait_for_selector(self.navigation_data["selectors"]["loginPage"]["form"])
|
||||
self.sleep_for(WAIT_TYPES.UX_RENDER)
|
||||
form = self.wait_for_visible(self.navigation.login.selectors.form)
|
||||
self.fill(form, login_info)
|
||||
self.snapshot("logging-in")
|
||||
self.click_submit(form)
|
||||
@@ -306,17 +389,16 @@ class NavigatesGalaxy(HasDriver):
|
||||
|
||||
self.home()
|
||||
self.click_masthead_user()
|
||||
self.click_label(self.navigation_data["labels"]["masthead"]["userMenu"]["register"])
|
||||
self.wait_for_and_click(self.navigation.masthead.labels.register)
|
||||
with self.main_panel():
|
||||
register_form_id = self.navigation_data["selectors"]["registrationPage"]["form"]
|
||||
form = self.wait_for_id(register_form_id)
|
||||
form = self.wait_for_visible(self.navigation.registration.selectors.form)
|
||||
self.fill(form, dict(
|
||||
email=email,
|
||||
password=password,
|
||||
username=username,
|
||||
confirm=confirm
|
||||
))
|
||||
self.click_xpath(self.navigation_data["selectors"]["registrationPage"]["submit_xpath"])
|
||||
self.wait_for_and_click(self.navigation.registration.selectors.submit)
|
||||
# Give the browser a bit of time to submit the request.
|
||||
# It would be good to eliminate this sleep, but it can't be because Galaxy
|
||||
# doesn't swap the "User" menu automatically after it registers a user and
|
||||
@@ -335,7 +417,7 @@ class NavigatesGalaxy(HasDriver):
|
||||
# Make sure the user menu was dropped down
|
||||
user_menu = self.wait_for_selector_visible("ul.nav#user .dropdown-menu")
|
||||
try:
|
||||
user_email_element = self.wait_for_xpath_visible(self.navigation_data["selectors"]["masthead"]["userMenu"]["userEmail_xpath"])
|
||||
user_email_element = self.wait_for_visible(self.navigation.masthead.selectors.user_email)
|
||||
except self.TimeoutException as e:
|
||||
menu_items = user_menu.find_elements_by_css_selector("li a")
|
||||
menu_text = [mi.text for mi in menu_items]
|
||||
@@ -351,7 +433,7 @@ class NavigatesGalaxy(HasDriver):
|
||||
|
||||
def wait_for_logged_in(self):
|
||||
try:
|
||||
self.wait_for_selector_visible("a.loggedin-only")
|
||||
self.wait_for_visible(self.navigation.masthead.selectors.logged_in_only)
|
||||
except self.TimeoutException as e:
|
||||
user_info = self.api_get("users/current")
|
||||
if "username" in user_info:
|
||||
@@ -490,7 +572,7 @@ class NavigatesGalaxy(HasDriver):
|
||||
menu_element = self.workflow_editor_options_menu_element()
|
||||
option_elements = menu_element.find_elements_by_css_selector("a")
|
||||
assert len(option_elements) > 0, "Failed to find workflow editor options"
|
||||
time.sleep(1)
|
||||
self.sleep_for(WAIT_TYPES.UX_RENDER)
|
||||
found_option = False
|
||||
for option_element in option_elements:
|
||||
if option_label in option_element.text:
|
||||
@@ -510,6 +592,90 @@ class NavigatesGalaxy(HasDriver):
|
||||
def workflow_editor_options_menu_element(self):
|
||||
return self.wait_for_selector_visible("#workflow-options-button-menu")
|
||||
|
||||
def libraries_open(self):
|
||||
self.home()
|
||||
self.click_masthead_shared_data()
|
||||
self.click_label(self.navigation_data["labels"]["masthead"]["sharedMenu"]["libraries"])
|
||||
self.wait_for_selector('.library_style_container')
|
||||
|
||||
@retry_during_transitions
|
||||
def libraries_index_table_elements(self):
|
||||
container = self.wait_for_selector_visible(".library_container")
|
||||
elements = container.find_elements_by_css_selector("#library_list_body")
|
||||
if not elements:
|
||||
return []
|
||||
else:
|
||||
assert len(elements) == 1
|
||||
element = elements[0]
|
||||
return element.find_elements_by_css_selector("tr") # [style='display: table-row']
|
||||
|
||||
def libraries_index_click_create_new(self):
|
||||
self.wait_for_and_click_selector("#create_new_library_btn")
|
||||
|
||||
def libraries_index_create(self, name):
|
||||
self.libraries_index_click_create_new()
|
||||
name_text_box = self.wait_for_selector_clickable("input[name='Name']")
|
||||
name_text_box.send_keys(name)
|
||||
|
||||
self.wait_for_and_click_selector("#button-0")
|
||||
|
||||
def libraries_index_click_search(self):
|
||||
self.sleep_for(WAIT_TYPES.UX_RENDER)
|
||||
search_element = self.wait_for_selector_clickable("input.library-search-input")
|
||||
search_element.click()
|
||||
return search_element
|
||||
|
||||
def libraries_index_sort_selector(self):
|
||||
return ".sort-libraries-link"
|
||||
|
||||
def libraries_index_sort_click(self):
|
||||
sort_element = self.wait_for_selector_clickable(self.libraries_index_sort_selector())
|
||||
sort_element.click()
|
||||
return sort_element
|
||||
|
||||
def libraries_index_search_for(self, text):
|
||||
self.wait_for_overlays_cleared()
|
||||
search_box = self.libraries_index_click_search()
|
||||
search_box.clear()
|
||||
search_box.send_keys(text)
|
||||
value = search_box.get_attribute("value")
|
||||
assert value == text, value
|
||||
self.driver.execute_script("$(arguments[0]).keyup();", search_box)
|
||||
|
||||
def libraries_folder_create(self, name):
|
||||
create_folder_button = self.wait_for_selector_clickable("#toolbtn_create_folder")
|
||||
create_folder_button.click()
|
||||
|
||||
name_text_box = self.wait_for_selector_clickable("input[name='Name']")
|
||||
name_text_box.send_keys(name)
|
||||
|
||||
create_button = self.wait_for_selector_clickable("#button-0")
|
||||
create_button.click()
|
||||
|
||||
def libraries_click_dataset_import(self):
|
||||
self.wait_for_and_click(self.navigation.libraries.folder.selectors.add_items_button)
|
||||
|
||||
def libraries_dataset_import_from_history(self):
|
||||
self.libraries_click_dataset_import()
|
||||
|
||||
self.wait_for_visible(self.navigation.libraries.folder.selectors.add_items_menu)
|
||||
self.wait_for_and_click(self.navigation.libraries.folder.labels.from_history)
|
||||
|
||||
def libraries_dataset_import_from_path(self):
|
||||
self.libraries_click_dataset_import()
|
||||
|
||||
self.wait_for_visible(self.navigation.libraries.folder.selectors.add_items_menu)
|
||||
self.wait_for_and_click(self.navigation.libraries.folder.labels.from_path)
|
||||
|
||||
def libraries_table_elements(self):
|
||||
tbody_element = self.wait_for_selector_visible("#folder_list_body")
|
||||
return tbody_element.find_elements_by_css_selector("tr")[1:]
|
||||
|
||||
def wait_for_overlays_cleared(self):
|
||||
"""Wait for modals and Toast notifications to disappear."""
|
||||
self.wait_for_selector_absent_or_hidden(".ui-modal")
|
||||
self.wait_for_selector_absent_or_hidden(".toast")
|
||||
|
||||
def workflow_index_open(self):
|
||||
self.home()
|
||||
self.click_masthead_workflow()
|
||||
@@ -546,6 +712,13 @@ class NavigatesGalaxy(HasDriver):
|
||||
alert.send_keys(new_name)
|
||||
alert.accept()
|
||||
|
||||
@retry_during_transitions
|
||||
def workflow_index_name(self, workflow_index=0):
|
||||
"""Get workflow name for workflow_index'th row."""
|
||||
row_element = self.workflow_index_table_row(workflow_index=workflow_index)
|
||||
workflow_button = row_element.find_element_by_css_selector(".menubutton")
|
||||
return workflow_button.text
|
||||
|
||||
def workflow_index_click_option(self, option_title, workflow_index=0):
|
||||
|
||||
@retry_during_transitions
|
||||
@@ -628,13 +801,19 @@ class NavigatesGalaxy(HasDriver):
|
||||
self.wait_for_and_click_selector("button#execute")
|
||||
|
||||
def click_masthead_user(self):
|
||||
self.click_xpath(self.navigation_data["selectors"]["masthead"]["user"])
|
||||
self.wait_for_and_click(self.navigation.masthead.selectors.user)
|
||||
|
||||
def click_masthead_libraries(self):
|
||||
self.wait_for_and_click(self.navigation.masthead.labels.libraries)
|
||||
|
||||
def click_masthead_workflow(self):
|
||||
self.click_xpath(self.navigation_data["selectors"]["masthead"]["workflow"])
|
||||
self.wait_for_and_click(self.navigation.masthead.labels.workflow)
|
||||
|
||||
def click_masthead_shared_data(self):
|
||||
self.click_xpath(self.navigation_data["selectors"]["masthead"]["shared_data"])
|
||||
|
||||
def click_button_new_workflow(self):
|
||||
self.wait_for_and_click_selector(self.navigation_data["selectors"]["workflows"]["new_button"])
|
||||
self.wait_for_and_click(self.navigation.workflows.selectors.new_button)
|
||||
|
||||
def wait_for_sizzle_selector_clickable(self, selector):
|
||||
element = self._wait_on(
|
||||
@@ -660,16 +839,11 @@ class NavigatesGalaxy(HasDriver):
|
||||
self.click_history_options()
|
||||
|
||||
# Click labelled option
|
||||
menu_selector = self.history_options_menu_selector()
|
||||
self.wait_for_selector_visible(menu_selector)
|
||||
self.wait_for_visible(self.navigation.history_panel.selectors.options_menu)
|
||||
menu_item_sizzle_selector = '#history-options-button-menu > li > a:contains("%s")' % option_label
|
||||
menu_selection_element = self.wait_for_sizzle_selector_clickable(menu_item_sizzle_selector)
|
||||
menu_selection_element.click()
|
||||
|
||||
def history_options_menu_selector(self):
|
||||
menu_selector = self.test_data["historyOptions"]["selectors"]["menu"]
|
||||
return menu_selector
|
||||
|
||||
@retry_during_transitions
|
||||
def histories_click_advanced_search(self):
|
||||
search_selector = '#standard-search .advanced-search-toggle'
|
||||
@@ -696,36 +870,55 @@ class NavigatesGalaxy(HasDriver):
|
||||
self.send_enter(editable_text_input_element)
|
||||
|
||||
def history_panel_click_to_rename(self):
|
||||
self.wait_for_and_click_selector(self.history_panel_name_selector())
|
||||
return self.wait_for_selector(self.history_panel_edit_title_input_selector())
|
||||
|
||||
def history_panel_edit_title_input_selector(self):
|
||||
return self.test_data["historyPanel"]["selectors"]["history"]["nameEditableTextInput"]
|
||||
self.wait_for_and_click(self.navigation.history_panel.selectors.name)
|
||||
return self.wait_for_visible(self.navigation.history_panel.selectors.name_edit_input)
|
||||
|
||||
def history_panel_refresh_click(self):
|
||||
self.wait_for_and_click_selector("#history-refresh-button")
|
||||
|
||||
def history_panel_multi_operations_selector(self):
|
||||
return self.test_data["historyPanel"]["selectors"]["history"]["multiOperationsIcon"]
|
||||
self.wait_for_and_click(self.navigation.history_panel.selectors.refresh_button)
|
||||
|
||||
def history_panel_multi_operations_show(self):
|
||||
operations_selector = self.history_panel_multi_operations_selector()
|
||||
self.wait_for_and_click_selector(operations_selector)
|
||||
return self.wait_for_and_click(self.navigation.history_panel.multi_operations.selectors.show_button)
|
||||
|
||||
def history_panel_muli_operation_select_hid(self, hid):
|
||||
item_selector = self.history_panel_item_selector(hid, wait=True)
|
||||
operation_radio_selector = "%s .selector" % item_selector
|
||||
self.wait_for_and_click_selector(operation_radio_selector)
|
||||
|
||||
def history_panel_multi_operation_action_selector(self):
|
||||
return self.test_data["historyPanel"]["selectors"]["history"]["multiOperationsActionBtn"]
|
||||
|
||||
def history_panel_multi_operation_action_click(self, action):
|
||||
time.sleep(5)
|
||||
self.wait_for_and_click_selector(self.history_panel_multi_operation_action_selector())
|
||||
menu_element = self.wait_for_selector_visible(".list-action-menu.open")
|
||||
action_element = menu_element.find_element_by_link_text(action)
|
||||
action_element.click()
|
||||
# Maybe isn't needed?
|
||||
# self.sleep_for(WAIT_TYPES.UX_RENDER)
|
||||
self.wait_for_and_click(self.navigation.history_panel.multi_operations.selectors.action_button)
|
||||
|
||||
@retry_during_transitions
|
||||
def _click_action_in_menu():
|
||||
menu_element = self.wait_for_visible(self.navigation.history_panel.multi_operations.selectors.action_menu)
|
||||
menu_element.find_element_by_link_text(action.text).click()
|
||||
|
||||
_click_action_in_menu()
|
||||
|
||||
def history_panel_item_click_visualization_menu(self, hid):
|
||||
viz_button_selector = "%s %s" % (self.history_panel_item_selector(hid), ".visualizations-dropdown")
|
||||
self.wait_for_and_click_selector(viz_button_selector)
|
||||
self.wait_for_selector_visible("%s %s" % (viz_button_selector, ".dropdown-menu"))
|
||||
|
||||
def history_panel_item_available_visualizations_elements(self, hid):
|
||||
# Precondition: viz menu has been opened with history_panel_item_click_visualization_menu
|
||||
viz_menu_selectors = "%s %s" % (self.history_panel_item_selector(hid), "a.visualization-link")
|
||||
return self.driver.find_elements_by_css_selector(viz_menu_selectors)
|
||||
|
||||
def history_panel_item_available_visualizations(self, hid):
|
||||
# Precondition: viz menu has been opened with history_panel_item_click_visualization_menu
|
||||
return [e.text for e in self.history_panel_item_available_visualizations_elements(hid)]
|
||||
|
||||
def history_panel_item_click_visualization(self, hid, visualization_name):
|
||||
# Precondition: viz menu has been opened with history_panel_item_click_visualization_menu
|
||||
elements = self.history_panel_item_available_visualizations_elements(hid)
|
||||
for element in elements:
|
||||
if element.text == visualization_name:
|
||||
element.click()
|
||||
return element
|
||||
|
||||
assert False, "No visualization [%s] found." % visualization_name
|
||||
|
||||
def history_panel_item_selector(self, hid, wait=False):
|
||||
current_history_id = self.current_history_id()
|
||||
@@ -780,9 +973,14 @@ class NavigatesGalaxy(HasDriver):
|
||||
else:
|
||||
self.wait_for_selector_visible(details_selector)
|
||||
|
||||
def click_hda_title(self, hda_id, wait=False):
|
||||
# TODO: Replace with calls to history_panel_click_item_title.
|
||||
return self.history_panel_click_item_title(hda_id=hda_id, wait=wait)
|
||||
def history_panel_ensure_showing_item_details(self, hid):
|
||||
if not self.history_panel_item_showing_details(hid):
|
||||
self.history_panel_click_item_title(hid=hid, wait=True)
|
||||
|
||||
def history_panel_item_showing_details(self, hid):
|
||||
item_selector = self.history_panel_item_selector(hid)
|
||||
details_selector = "%s .details" % item_selector
|
||||
return self.selector_is_displayed(details_selector)
|
||||
|
||||
def collection_builder_set_name(self, name):
|
||||
name_element = self.wait_for_selector_visible("input.collection-name")
|
||||
@@ -807,7 +1005,7 @@ class NavigatesGalaxy(HasDriver):
|
||||
if self.is_logged_in():
|
||||
self.home()
|
||||
self.click_masthead_user()
|
||||
self.click_label(self.navigation_data["labels"]["masthead"]["userMenu"]["logout"])
|
||||
self.wait_for_and_click(self.navigation.masthead.labels.logout)
|
||||
self.click_label('go to the home page')
|
||||
assert not self.is_logged_in()
|
||||
|
||||
@@ -876,6 +1074,8 @@ class NavigatesGalaxy(HasDriver):
|
||||
return self.assert_selector_absent_or_hidden(selector)
|
||||
|
||||
def assert_tooltip_text(self, element, expected, sleep=0, click_away=True):
|
||||
if hasattr(expected, "text"):
|
||||
expected = expected.text
|
||||
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)
|
||||
|
||||
@@ -928,6 +1128,12 @@ class NavigatesGalaxy(HasDriver):
|
||||
element.click()
|
||||
return element
|
||||
|
||||
@retry_during_transitions
|
||||
def wait_for_and_click(self, selector_template):
|
||||
element = self.wait_for_clickable(selector_template)
|
||||
element.click()
|
||||
return element
|
||||
|
||||
def select2_set_value(self, container_selector, value, with_click=True):
|
||||
# There are two hacky was to select things from the select2 widget -
|
||||
# with_click=True: This simulates the mouse click after the suggestion contains
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Deprecated navigation constants - prefer navigation.yml.
|
||||
selectors:
|
||||
tooltipBalloon: '.tooltip'
|
||||
editableText: '.editable-text'
|
||||
@@ -15,12 +16,12 @@ selectors:
|
||||
main: 'galaxy_main'
|
||||
masthead:
|
||||
id: '#masthead'
|
||||
adminLink: '#masthead a[href="/admin/index"]'
|
||||
userMenu:
|
||||
userEmail_xpath: '//a[contains(text(), "Logged in as")]'
|
||||
|
||||
user: '//ul[@id="user"]'
|
||||
workflow: '//ul[@id="workflow"]'
|
||||
shared_data: '//ul[@id="shared"]'
|
||||
|
||||
toolMenu:
|
||||
container: '.toolMenuContainer'
|
||||
@@ -55,7 +56,7 @@ labels:
|
||||
menus:
|
||||
analyze: 'Analyze Data'
|
||||
workflow: 'Workflow'
|
||||
libraries: 'Shared Data'
|
||||
shared_data: 'Shared Data'
|
||||
visualization: 'Visualization'
|
||||
help: 'Help'
|
||||
user: 'User'
|
||||
@@ -64,6 +65,9 @@ labels:
|
||||
register: 'Register'
|
||||
login: 'Login'
|
||||
logout: 'Logout'
|
||||
|
||||
sharedMenu:
|
||||
libraries: 'Data Libraries'
|
||||
|
||||
tools:
|
||||
upload:
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# top_level: {global: <components>, workflows: <components>, histories: <components>}
|
||||
# components := {<subcomponent_name>: <components>} | <component>
|
||||
# component := {selectors: <selectors_map>, labels: <labels_map>, ids: <id_map>}
|
||||
# selectors_map := {<selector_name>: <selector>}
|
||||
# selector := <str> | {type: <selector_type>, selector: <str>}
|
||||
# selector_type := css|xpath|sizzle|id
|
||||
# labels_map := {<label_name>: <label>}
|
||||
|
||||
_: # global stuff
|
||||
|
||||
selectors:
|
||||
editable_text: '.editable-text'
|
||||
|
||||
masthead:
|
||||
|
||||
selectors:
|
||||
_: '#masthead'
|
||||
|
||||
user:
|
||||
type: xpath
|
||||
selector: '//ul[@id="user"]'
|
||||
|
||||
workflow:
|
||||
type: xpath
|
||||
selector: '//ul[@id="workflow"]'
|
||||
|
||||
user_email:
|
||||
type: xpath
|
||||
selector: '//a[contains(text(), "Logged in as")]'
|
||||
|
||||
logged_in_only: 'a.loggedin-only'
|
||||
|
||||
labels:
|
||||
# top-level menus
|
||||
analyze: 'Analyze Data'
|
||||
workflow: 'Workflow'
|
||||
libraries: 'Shared Data'
|
||||
visualization: 'Visualization'
|
||||
help: 'Help'
|
||||
user: 'User'
|
||||
|
||||
# user menu
|
||||
register: 'Register'
|
||||
login: 'Login'
|
||||
logout: 'Logout'
|
||||
|
||||
history_panel:
|
||||
menu:
|
||||
labels:
|
||||
new: 'Create New'
|
||||
|
||||
item:
|
||||
selectors:
|
||||
_: '#${history_content_type}-${id}'
|
||||
|
||||
title: '${item_selector} .title'
|
||||
hid: '${item_selector} .hid'
|
||||
name: '${item_selector} .name'
|
||||
titleButtonArea: '${item_selector} .primary-actions'
|
||||
summary: '${item_selector} .summary'
|
||||
blurb: '${item_selector} .blurb .value'
|
||||
dbkey: '${item_selector} .dbkey .value'
|
||||
info: '${item_selector} .info .value'
|
||||
body: '${item_selector} .details'
|
||||
|
||||
multi_operations:
|
||||
selectors:
|
||||
show_button: '#current-history-panel .actions .show-selectors-btn'
|
||||
action_button: '#current-history-panel .list-action-menu .list-action-menu-btn'
|
||||
action_menu: '.list-action-menu.open'
|
||||
|
||||
labels:
|
||||
build_pair: "Build Dataset Pair"
|
||||
build_list: "Build Dataset List"
|
||||
build_list_pairs: "Build List of Dataset Pairs"
|
||||
|
||||
selectors:
|
||||
_: '#current-history-panel'
|
||||
search: '#current-history-panel input.search-query'
|
||||
refresh_button: '#history-refresh-button'
|
||||
name: '.title .name'
|
||||
name_edit_input: '.name input'
|
||||
contents: '#current-history-panel .list-items div.history-content'
|
||||
|
||||
empty_message: '.empty-message'
|
||||
|
||||
tag_icon: '.actions .history-tag-btn'
|
||||
tag_area: '.controls .tags-display'
|
||||
annotation_icon: '.actions .history-annotate-btn'
|
||||
annotation_area: '.controls .annotation-display'
|
||||
annotation_editable_text: '.controls .annotation-display .annotation.editable-text'
|
||||
annotation_edit: '.controls .annotation-display .annotation textarea'
|
||||
annotation_done: '.controls .annotation-display .annotation button'
|
||||
|
||||
options_button: '#history-options-button'
|
||||
options_button_icon: '#history-options-button span.fa-cog'
|
||||
options_menu: '#history-options-button-menu'
|
||||
|
||||
text:
|
||||
tooltip_name: 'Click to rename history'
|
||||
|
||||
collection_builders:
|
||||
|
||||
selectors:
|
||||
|
||||
clear_filters: "a.clear-filters-link"
|
||||
forward_datasets: ".forward-column .column-datasets"
|
||||
reverse_datasets: ".reverse-column .column-datasets"
|
||||
|
||||
login:
|
||||
selectors:
|
||||
form: 'form#login'
|
||||
|
||||
registration:
|
||||
selectors:
|
||||
form: '#registration'
|
||||
submit:
|
||||
type: xpath
|
||||
selector: "//input[@value='Submit']"
|
||||
|
||||
workflows:
|
||||
|
||||
selectors:
|
||||
new_button: '#new-workflow'
|
||||
import_button: '#import-workflow'
|
||||
|
||||
libraries:
|
||||
|
||||
folder:
|
||||
selectors:
|
||||
# There needs to a different class or ID on the dataset button, this isn't
|
||||
# a good selector for that button since .add-library-items also applies to
|
||||
# folder (but it is a button.add-library-items instead).
|
||||
add_items_button: 'div.add-library-items'
|
||||
add_items_menu: 'div.add-library-items .dropdown-menu'
|
||||
add_items_options: 'div.add-library-items .dropdown-menu li a'
|
||||
|
||||
# TODO: Most of these are very good selectors but the same DOM elements
|
||||
# are reused without adding specific classes, IDs, or roles to anything.
|
||||
import_modal: '.modal'
|
||||
import_datasets_ok_button: '.modal-footer .buttons #button-0'
|
||||
import_datasets_cancel_button: '.modal-footer .buttons #button-1'
|
||||
import_progress_bar: '.progress-bar-import'
|
||||
import_history_content: '#selected_history_content'
|
||||
import_history_contents_items: '#selected_history_content ul li'
|
||||
import_from_path_textarea: '#import_paths'
|
||||
|
||||
labels:
|
||||
from_history: 'from History'
|
||||
from_path: 'from Path'
|
||||
|
||||
dataset:
|
||||
selectors:
|
||||
table: '.dataset_table'
|
||||
table_rows: '.dataset_table table tbody tr'
|
||||
|
||||
published_grids:
|
||||
selectors:
|
||||
search: '#input-free-text-search-filter'
|
||||
@@ -5,7 +5,6 @@ from __future__ import print_function
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
from functools import partial, wraps
|
||||
@@ -18,6 +17,7 @@ except ImportError:
|
||||
from six.moves.urllib.parse import urljoin
|
||||
|
||||
from base import populators # noqa: I100
|
||||
from base.api import UsesApiTestCaseMixin # noqa: I100
|
||||
from base.driver_util import classproperty, DEFAULT_WEB_HOST, get_ip_address # noqa: I100
|
||||
from base.testcase import FunctionalTestCase # noqa: I100
|
||||
from base.workflows_format_2 import ( # noqa: I100
|
||||
@@ -30,14 +30,17 @@ from galaxy_selenium import ( # noqa: I100
|
||||
from galaxy_selenium.navigates_galaxy import NavigatesGalaxy, retry_during_transitions # noqa: I100
|
||||
from galaxy.util import asbool
|
||||
|
||||
DEFAULT_WAIT_TIMEOUT = 60
|
||||
DEFAULT_TIMEOUT_MULTIPLIER = 1
|
||||
DEFAULT_TEST_ERRORS_DIRECTORY = os.path.abspath("database/test_errors")
|
||||
DEFAULT_SELENIUM_BROWSER = "auto"
|
||||
DEFAULT_SELENIUM_REMOTE = False
|
||||
DEFAULT_SELENIUM_REMOTE_PORT = "4444"
|
||||
DEFAULT_SELENIUM_REMOTE_HOST = "127.0.0.1"
|
||||
DEFAULT_SELENIUM_HEADLESS = "auto"
|
||||
DEFAULT_ADMIN_USER = "test@bx.psu.edu"
|
||||
DEFAULT_ADMIN_PASSWORD = "testpass"
|
||||
|
||||
TIMEOUT_MULTIPLIER = float(os.environ.get("GALAXY_TEST_TIMEOUT_MULTIPLIER", DEFAULT_TIMEOUT_MULTIPLIER))
|
||||
GALAXY_TEST_ERRORS_DIRECTORY = os.environ.get("GALAXY_TEST_ERRORS_DIRECTORY", DEFAULT_TEST_ERRORS_DIRECTORY)
|
||||
# Test browser can be ["CHROME", "FIREFOX", "OPERA", "PHANTOMJS"]
|
||||
GALAXY_TEST_SELENIUM_BROWSER = os.environ.get("GALAXY_TEST_SELENIUM_BROWSER", DEFAULT_SELENIUM_BROWSER)
|
||||
@@ -49,9 +52,10 @@ GALAXY_TEST_EXTERNAL_FROM_SELENIUM = os.environ.get("GALAXY_TEST_EXTERNAL_FROM_S
|
||||
# Auto-retry selenium tests this many times.
|
||||
GALAXY_TEST_SELENIUM_RETRIES = int(os.environ.get("GALAXY_TEST_SELENIUM_RETRIES", "0"))
|
||||
|
||||
# Test case data
|
||||
DEFAULT_PASSWORD = '123456'
|
||||
|
||||
GALAXY_TEST_SELENIUM_USER_EMAIL = os.environ.get("GALAXY_TEST_SELENIUM_USER_EMAIL", None)
|
||||
GALAXY_TEST_SELENIUM_USER_PASSWORD = os.environ.get("GALAXY_TEST_SELENIUM_USER_PASSWORD", None)
|
||||
GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL = os.environ.get("GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL", DEFAULT_ADMIN_USER)
|
||||
GALAXY_TEST_SELENIUM_ADMIN_USER_PASSWORD = os.environ.get("GALAXY_TEST_SELENIUM_ADMIN_USER_PASSWORD", DEFAULT_ADMIN_PASSWORD)
|
||||
|
||||
try:
|
||||
from nose.tools import nottest
|
||||
@@ -60,6 +64,62 @@ except ImportError:
|
||||
return x
|
||||
|
||||
|
||||
def managed_history(f):
|
||||
"""Ensure a Selenium test has a distinct, named history.
|
||||
|
||||
Cleanup the history after the job is complete as well unless
|
||||
GALAXY_TEST_NO_CLEANUP is set in the environment.
|
||||
"""
|
||||
|
||||
@wraps(f)
|
||||
def func_wrapper(self, *args, **kwds):
|
||||
self.home()
|
||||
history_name = f.__name__ + datetime.datetime.now().strftime("%Y%m%d%H%M%s")
|
||||
self.history_panel_create_new_with_name(history_name)
|
||||
try:
|
||||
f(self, *args, **kwds)
|
||||
finally:
|
||||
if "GALAXY_TEST_NO_CLEANUP" not in os.environ:
|
||||
current_history_id = self.current_history_id()
|
||||
self.api_delete("histories/%s" % current_history_id)
|
||||
|
||||
return func_wrapper
|
||||
|
||||
|
||||
def dump_test_information(self, name_prefix):
|
||||
if GALAXY_TEST_ERRORS_DIRECTORY and GALAXY_TEST_ERRORS_DIRECTORY != "0":
|
||||
if not os.path.exists(GALAXY_TEST_ERRORS_DIRECTORY):
|
||||
os.makedirs(GALAXY_TEST_ERRORS_DIRECTORY)
|
||||
result_name = name_prefix + datetime.datetime.now().strftime("%Y%m%d%H%M%s")
|
||||
target_directory = os.path.join(GALAXY_TEST_ERRORS_DIRECTORY, result_name)
|
||||
|
||||
def write_file(name, content, raw=False):
|
||||
with open(os.path.join(target_directory, name), "wb") as buf:
|
||||
buf.write(content.encode("utf-8") if not raw else content)
|
||||
|
||||
os.makedirs(target_directory)
|
||||
self.driver.save_screenshot(os.path.join(target_directory, "last.png"))
|
||||
write_file("page_source.txt", self.driver.page_source)
|
||||
write_file("DOM.txt", self.driver.execute_script("return document.documentElement.outerHTML"))
|
||||
write_file("stacktrace.txt", traceback.format_exc())
|
||||
|
||||
for snapshot in getattr(self, "snapshots", []):
|
||||
snapshot.write_to_error_directory(write_file)
|
||||
|
||||
for log_type in ["browser", "driver"]:
|
||||
try:
|
||||
write_file("%s.log.json" % log_type, json.dumps(self.driver.get_log(log_type)))
|
||||
except Exception:
|
||||
continue
|
||||
iframes = self.driver.find_elements_by_css_selector("iframe")
|
||||
for iframe in iframes:
|
||||
pass
|
||||
# TODO: Dump content out for debugging in the future.
|
||||
# iframe_id = iframe.get_attribute("id")
|
||||
# if iframe_id:
|
||||
# write_file("iframe_%s" % iframe_id, "My content")
|
||||
|
||||
|
||||
@nottest
|
||||
def selenium_test(f):
|
||||
|
||||
@@ -72,38 +132,7 @@ def selenium_test(f):
|
||||
try:
|
||||
return f(self, *args, **kwds)
|
||||
except Exception:
|
||||
if GALAXY_TEST_ERRORS_DIRECTORY and GALAXY_TEST_ERRORS_DIRECTORY != "0":
|
||||
if not os.path.exists(GALAXY_TEST_ERRORS_DIRECTORY):
|
||||
os.makedirs(GALAXY_TEST_ERRORS_DIRECTORY)
|
||||
result_name = f.__name__ + datetime.datetime.now().strftime("%Y%m%d%H%M%s")
|
||||
target_directory = os.path.join(GALAXY_TEST_ERRORS_DIRECTORY, result_name)
|
||||
|
||||
def write_file(name, content, raw=False):
|
||||
with open(os.path.join(target_directory, name), "wb") as buf:
|
||||
buf.write(content.encode("utf-8") if not raw else content)
|
||||
|
||||
os.makedirs(target_directory)
|
||||
self.driver.save_screenshot(os.path.join(target_directory, "last.png"))
|
||||
write_file("page_source.txt", self.driver.page_source)
|
||||
write_file("DOM.txt", self.driver.execute_script("return document.documentElement.outerHTML"))
|
||||
write_file("stacktrace.txt", traceback.format_exc())
|
||||
|
||||
for snapshot in getattr(self, "snapshots", []):
|
||||
snapshot.write_to_error_directory(write_file)
|
||||
|
||||
for log_type in ["browser", "driver"]:
|
||||
try:
|
||||
write_file("%s.log.json" % log_type, json.dumps(self.driver.get_log(log_type)))
|
||||
except Exception:
|
||||
continue
|
||||
iframes = self.driver.find_elements_by_css_selector("iframe")
|
||||
for iframe in iframes:
|
||||
pass
|
||||
# TODO: Dump content out for debugging in the future.
|
||||
# iframe_id = iframe.get_attribute("id")
|
||||
# if iframe_id:
|
||||
# write_file("iframe_%s" % iframe_id, "My content")
|
||||
|
||||
dump_test_information(self, f.__name__)
|
||||
if retry_attempts < GALAXY_TEST_SELENIUM_RETRIES:
|
||||
retry_attempts += 1
|
||||
else:
|
||||
@@ -131,10 +160,17 @@ class TestSnapshot(object):
|
||||
write_file_func("%s-stack.txt" % prefix, str(self.stack))
|
||||
|
||||
|
||||
class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy):
|
||||
|
||||
class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy, UsesApiTestCaseMixin):
|
||||
# If run one-off via nosetests, the next line ensures test
|
||||
# tools and datatypes are used instead of configured tools.
|
||||
framework_tool_and_types = True
|
||||
|
||||
# Override this in subclasses to ensure a user is logged in
|
||||
# before each test. If GALAXY_TEST_SELENIUM_USER_EMAIL and
|
||||
# GALAXY_TEST_SELENIUM_USER_PASSWORD are set these values
|
||||
# will be used to login.
|
||||
ensure_registered = False
|
||||
requires_admin = False
|
||||
|
||||
def setUp(self):
|
||||
super(SeleniumTestCase, self).setUp()
|
||||
@@ -146,6 +182,21 @@ class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy):
|
||||
self.target_url_from_selenium = self.url
|
||||
self.snapshots = []
|
||||
self.setup_driver_and_session()
|
||||
if self.requires_admin and GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL == DEFAULT_ADMIN_USER:
|
||||
self._setup_interactor()
|
||||
self._setup_user(GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL)
|
||||
try:
|
||||
self.setup_with_driver()
|
||||
except Exception:
|
||||
dump_test_information(self, self.__class__.__name__ + "_setup")
|
||||
raise
|
||||
|
||||
def setup_with_driver(self):
|
||||
"""Override point that allows setting up data using self.driver and Selenium connection.
|
||||
|
||||
Using this instead of overriding will ensure debug data such as screenshots and stack traces
|
||||
are dumped if there are problems with the setup.
|
||||
"""
|
||||
|
||||
def tearDown(self):
|
||||
exception = None
|
||||
@@ -177,6 +228,18 @@ class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy):
|
||||
self.driver.set_window_size(1280, 900)
|
||||
|
||||
if self.ensure_registered:
|
||||
self.login()
|
||||
|
||||
def login(self):
|
||||
if GALAXY_TEST_SELENIUM_USER_EMAIL:
|
||||
assert GALAXY_TEST_SELENIUM_USER_PASSWORD, "If GALAXY_TEST_SELENIUM_USER_EMAIL is set, a password must be set also with GALAXY_TEST_SELENIUM_USER_PASSWORD"
|
||||
self.home()
|
||||
self.submit_login(
|
||||
email=GALAXY_TEST_SELENIUM_USER_EMAIL,
|
||||
password=GALAXY_TEST_SELENIUM_USER_PASSWORD,
|
||||
assert_valid=True,
|
||||
)
|
||||
else:
|
||||
self.register()
|
||||
|
||||
def tear_down_driver(self):
|
||||
@@ -199,8 +262,8 @@ class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy):
|
||||
return default_web_host_for_selenium_tests()
|
||||
|
||||
@property
|
||||
def default_timeout(self):
|
||||
return DEFAULT_WAIT_TIMEOUT
|
||||
def timeout_multiplier(self):
|
||||
return TIMEOUT_MULTIPLIER
|
||||
|
||||
def build_url(self, url, for_selenium=True):
|
||||
if for_selenium:
|
||||
@@ -235,10 +298,28 @@ class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy):
|
||||
assert empty_msg_element.is_displayed()
|
||||
assert empty_msg_str in empty_msg_element.text
|
||||
|
||||
def admin_login(self):
|
||||
self.home()
|
||||
self.submit_login(
|
||||
GALAXY_TEST_SELENIUM_ADMIN_USER_EMAIL,
|
||||
GALAXY_TEST_SELENIUM_ADMIN_USER_PASSWORD
|
||||
)
|
||||
with self.main_panel():
|
||||
self.assert_no_error_message()
|
||||
|
||||
@property
|
||||
def workflow_populator(self):
|
||||
return SeleniumSessionWorkflowPopulator(self)
|
||||
|
||||
def ensure_visualization_available(self, hid, visualization_name):
|
||||
"""Skip or fail a test if visualization for file doesn't appear.
|
||||
|
||||
Precondition: viz menu has been opened with history_panel_item_click_visualization_menu.
|
||||
"""
|
||||
visualization_names = self.history_panel_item_available_visualizations(hid)
|
||||
if visualization_name not in visualization_names:
|
||||
raise unittest.SkipTest("Skipping test, visualization [%s] doesn't appear to be configured." % visualization_name)
|
||||
|
||||
|
||||
class SharedStateSeleniumTestCase(SeleniumTestCase):
|
||||
"""This describes a class Selenium tests that setup class state for all tests.
|
||||
@@ -253,8 +334,7 @@ class SharedStateSeleniumTestCase(SeleniumTestCase):
|
||||
shared_state_initialized = False
|
||||
shared_state_in_error = False
|
||||
|
||||
def setUp(self):
|
||||
super(SharedStateSeleniumTestCase, self).setUp()
|
||||
def setup_with_driver(self):
|
||||
if not self.__class__.shared_state_initialized:
|
||||
try:
|
||||
self.setup_shared_state()
|
||||
@@ -315,7 +395,7 @@ class UsesHistoryItemAssertions:
|
||||
def _assert_item_button(self, buttons_area, expected_button, button_def):
|
||||
selector = button_def["selector"]
|
||||
# Let old tooltip expire, etc...
|
||||
time.sleep(1)
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
button_item = self.wait_for_selector_visible("%s %s" % (buttons_area, selector))
|
||||
expected_tooltip = button_def.get("tooltip")
|
||||
self.assert_tooltip_text(button_item, expected_tooltip)
|
||||
|
||||
@@ -11,19 +11,19 @@ class AnonymousHistoriesTestCase(SeleniumTestCase):
|
||||
self.home()
|
||||
self.assert_initial_history_panel_state_correct()
|
||||
|
||||
tag_icon_selector = self.test_data["historyPanel"]["selectors"]["history"]["tagIcon"]
|
||||
anno_icon_selector = self.test_data["historyPanel"]["selectors"]["history"]["annoIcon"]
|
||||
self.assert_selector_absent(tag_icon_selector)
|
||||
self.assert_selector_absent(anno_icon_selector)
|
||||
tag_icon_selector = self.navigation.history_panel.selectors.tag_icon
|
||||
annotation_icon_selector = self.navigation.history_panel.selectors.annotation_icon
|
||||
self.assert_absent_or_hidden(tag_icon_selector)
|
||||
self.assert_absent_or_hidden(annotation_icon_selector)
|
||||
|
||||
name_selector = self.test_data["historyPanel"]["selectors"]["history"]["name"]
|
||||
name_element = self.wait_for_selector(name_selector)
|
||||
|
||||
# name should NOT be editable when clicked by anon-user
|
||||
editable_text_class = self.test_data["selectors"]["editableText"]
|
||||
assert editable_text_class not in name_element.get_attribute("class")
|
||||
# History has a name but...
|
||||
name_element = self.wait_for_present(self.navigation.history_panel.selectors.name)
|
||||
name_element.click()
|
||||
|
||||
# ... name should NOT be editable when clicked by anon-user
|
||||
editable_text_class = self.navigation._.selectors.editable_text
|
||||
assert editable_text_class.as_css_class not in name_element.get_attribute("class")
|
||||
|
||||
@selenium_test
|
||||
def test_anon_history_upload(self):
|
||||
self.home()
|
||||
@@ -35,8 +35,7 @@ class AnonymousHistoriesTestCase(SeleniumTestCase):
|
||||
assert 'state-ok' in element.get_attribute("class")
|
||||
|
||||
# empty should be NO LONGER be displayed
|
||||
empty_msg_selector = self.test_data["historyPanel"]["selectors"]["history"]["emptyMsg"]
|
||||
self.assert_selector_absent_or_hidden(empty_msg_selector)
|
||||
self.assert_absent_or_hidden(self.navigation.history_panel.selectors.empty_message)
|
||||
|
||||
@selenium_test
|
||||
def test_anon_history_after_registration(self):
|
||||
|
||||
@@ -14,7 +14,7 @@ class CollectionBuildersTestCase(SeleniumTestCase):
|
||||
self.history_panel_wait_for_hid_ok(1)
|
||||
self.history_panel_multi_operations_show()
|
||||
self.history_panel_muli_operation_select_hid(1)
|
||||
self.history_panel_multi_operation_action_click("Build Dataset List")
|
||||
self.history_panel_multi_operation_action_click(self.navigation.history_panel.multi_operations.labels.build_list)
|
||||
|
||||
self.collection_builder_set_name("my cool list")
|
||||
|
||||
@@ -27,7 +27,7 @@ class CollectionBuildersTestCase(SeleniumTestCase):
|
||||
self.history_panel_wait_for_hid_ok(1)
|
||||
self.history_panel_multi_operations_show()
|
||||
self.history_panel_muli_operation_select_hid(1)
|
||||
self.history_panel_multi_operation_action_click("Build Dataset List")
|
||||
self.history_panel_multi_operation_action_click(self.navigation.history_panel.multi_operations.labels.build_list)
|
||||
|
||||
self.collection_builder_hide_originals()
|
||||
self.collection_builder_set_name("my cool list")
|
||||
@@ -46,7 +46,7 @@ class CollectionBuildersTestCase(SeleniumTestCase):
|
||||
self.history_panel_multi_operations_show()
|
||||
self.history_panel_muli_operation_select_hid(1)
|
||||
self.history_panel_muli_operation_select_hid(2)
|
||||
self.history_panel_multi_operation_action_click("Build Dataset Pair")
|
||||
self.history_panel_multi_operation_action_click(self.navigation.history_panel.multi_operations.labels.build_pair)
|
||||
self.collection_builder_set_name("my awesome pair")
|
||||
|
||||
self.collection_builder_create()
|
||||
@@ -61,7 +61,7 @@ class CollectionBuildersTestCase(SeleniumTestCase):
|
||||
self.history_panel_multi_operations_show()
|
||||
self.history_panel_muli_operation_select_hid(1)
|
||||
self.history_panel_muli_operation_select_hid(2)
|
||||
self.history_panel_multi_operation_action_click("Build List of Dataset Pairs")
|
||||
self.history_panel_multi_operation_action_click(self.navigation.history_panel.multi_operations.labels.build_list_pairs)
|
||||
|
||||
self.collection_builder_clear_filters()
|
||||
self.collection_builder_click_paired_item("forward", 0)
|
||||
@@ -80,16 +80,15 @@ class CollectionBuildersTestCase(SeleniumTestCase):
|
||||
self.history_panel_multi_operations_show()
|
||||
self.history_panel_muli_operation_select_hid(1)
|
||||
self.history_panel_muli_operation_select_hid(2)
|
||||
self.history_panel_multi_operation_action_click("Build List of Dataset Pairs")
|
||||
self.history_panel_multi_operation_action_click(self.navigation.history_panel.multi_operations.labels.build_list_pairs)
|
||||
|
||||
clear_filter_link = self.wait_for_selector_visible("a.clear-filters-link")
|
||||
clear_filter_link.click()
|
||||
self.wait_for_and_click(self.navigation.collection_builders.selectors.clear_filters)
|
||||
|
||||
forward_column = self.wait_for_selector_visible(".forward-column .column-datasets")
|
||||
forward_column = self.wait_for_visible(self.navigation.collection_builders.selectors.forward_datasets)
|
||||
first_datset_forward = forward_column.find_elements_by_css_selector("li")[0]
|
||||
first_datset_forward.click()
|
||||
|
||||
reverse_column = self.wait_for_selector_visible(".reverse-column .column-datasets")
|
||||
reverse_column = self.wait_for_visible(self.navigation.collection_builders.selectors.reverse_datasets)
|
||||
second_dataset_reverse = reverse_column.find_elements_by_css_selector("li")[1]
|
||||
second_dataset_reverse.click()
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import time
|
||||
|
||||
from .framework import (
|
||||
retry_assertion_during_transitions,
|
||||
selenium_test,
|
||||
@@ -81,7 +79,7 @@ class CustomBuildsTestcase(SharedStateSeleniumTestCase):
|
||||
delete_button.click()
|
||||
|
||||
def get_custom_builds(self):
|
||||
time.sleep(1)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
builds = []
|
||||
grid = self.wait_for_selector('table.grid > tbody')
|
||||
for row in grid.find_elements_by_tag_name('tr'):
|
||||
@@ -92,8 +90,6 @@ class CustomBuildsTestcase(SharedStateSeleniumTestCase):
|
||||
def navigate_to_custom_builds_page(self):
|
||||
self.home()
|
||||
self.click_masthead_user() # Open masthead menu
|
||||
label = self.navigation_data['labels']['masthead']['menus']['user']
|
||||
self.click_label(label)
|
||||
self.wait_for_and_click_selector('a[href="/custom_builds"]')
|
||||
|
||||
def setup_shared_state(self):
|
||||
|
||||
@@ -13,20 +13,21 @@ class HistoryOptionsTestCase(SeleniumTestCase):
|
||||
self.wait_for_history()
|
||||
self.click_history_options()
|
||||
|
||||
menu_selector = self.history_options_menu_selector()
|
||||
self.wait_for_selector_visible(menu_selector)
|
||||
menu_selector = self.navigation.history_panel.selectors.options_menu
|
||||
self.wait_for_visible(menu_selector)
|
||||
|
||||
# Click away closes history options
|
||||
self.click_center()
|
||||
|
||||
self.assert_selector_absent_or_hidden(menu_selector)
|
||||
self.wait_for_absent_or_hidden(menu_selector)
|
||||
|
||||
hda_id = self.latest_history_item()["id"]
|
||||
self.click_hda_title(hda_id, wait=True)
|
||||
hda = self.latest_history_item()
|
||||
hid = hda["hid"]
|
||||
self.history_panel_click_item_title(hid=hid, wait=True)
|
||||
|
||||
hda_body_selector = self.hda_body_selector(hda_id)
|
||||
hda_body_selector = self.hda_body_selector(hda["id"])
|
||||
self.wait_for_selector_visible(hda_body_selector)
|
||||
|
||||
self.click_hda_title(hda_id, wait=True)
|
||||
self.history_panel_click_item_title(hid=hid, wait=True)
|
||||
|
||||
self.assert_selector_absent_or_hidden_after_transitions(hda_body_selector)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import time
|
||||
|
||||
from .framework import (
|
||||
selenium_test,
|
||||
SeleniumTestCase
|
||||
@@ -13,13 +11,14 @@ class HistoryPanelTestCase(SeleniumTestCase):
|
||||
self.register()
|
||||
self.assert_initial_history_panel_state_correct()
|
||||
|
||||
tag_icon_selector = self.test_data["historyPanel"]["selectors"]["history"]["tagIcon"]
|
||||
anno_icon_selector = self.test_data["historyPanel"]["selectors"]["history"]["annoIcon"]
|
||||
self.assert_selector(tag_icon_selector)
|
||||
self.assert_selector(anno_icon_selector)
|
||||
tag_icon_selector = self.navigation.history_panel.selectors.tag_icon
|
||||
annotation_icon_selector = self.navigation.history_panel.selectors.annotation_icon
|
||||
|
||||
self.wait_for_visible(tag_icon_selector)
|
||||
self.wait_for_visible(annotation_icon_selector)
|
||||
|
||||
name_element = self.history_panel_name_element()
|
||||
self.assert_tooltip_text(name_element, self.test_data["historyPanel"]["text"]["history"]["tooltips"]["name"])
|
||||
self.assert_tooltip_text(name_element, self.navigation.history_panel.text.tooltip_name)
|
||||
|
||||
@selenium_test
|
||||
def test_history_panel_rename(self):
|
||||
@@ -28,8 +27,7 @@ class HistoryPanelTestCase(SeleniumTestCase):
|
||||
editable_text_input_element.send_keys("New History Name")
|
||||
self.send_enter(editable_text_input_element)
|
||||
|
||||
name_element = self.history_panel_name_element()
|
||||
assert "New History Name" in name_element.text
|
||||
assert "New History Name" in self.history_panel_name()
|
||||
|
||||
@selenium_test
|
||||
def test_history_rename_cancel_with_click(self):
|
||||
@@ -37,9 +35,8 @@ class HistoryPanelTestCase(SeleniumTestCase):
|
||||
editable_text_input_element = self.history_panel_click_to_rename()
|
||||
editable_text_input_element.send_keys("New History Name")
|
||||
self.click_center()
|
||||
self.assert_selector_absent(self.history_panel_edit_title_input_selector())
|
||||
name_element = self.history_panel_name_element()
|
||||
assert "New History Name" not in name_element.text
|
||||
self.assert_absent(self.navigation.history_panel.selectors.name_edit_input)
|
||||
assert "New History Name" not in self.history_panel_name()
|
||||
|
||||
@selenium_test
|
||||
def test_history_rename_cancel_with_escape(self):
|
||||
@@ -47,43 +44,43 @@ class HistoryPanelTestCase(SeleniumTestCase):
|
||||
editable_text_input_element = self.history_panel_click_to_rename()
|
||||
editable_text_input_element.send_keys("New History Name")
|
||||
self.send_escape(editable_text_input_element)
|
||||
self.assert_selector_absent(self.history_panel_edit_title_input_selector())
|
||||
name_element = self.history_panel_name_element()
|
||||
assert "New History Name" not in name_element.text
|
||||
self.assert_absent(self.navigation.history_panel.selectors.name_edit_input)
|
||||
assert "New History Name" not in self.history_panel_name()
|
||||
|
||||
@selenium_test
|
||||
def test_history_tags_and_annotations_buttons(self):
|
||||
# TODO: Test actually editing these values.
|
||||
self.register()
|
||||
|
||||
tag_icon_selector = self.test_data["historyPanel"]["selectors"]["history"]["tagIcon"]
|
||||
anno_icon_selector = self.test_data["historyPanel"]["selectors"]["history"]["annoIcon"]
|
||||
tag_area_selector = self.test_data["historyPanel"]["selectors"]["history"]["tagArea"]
|
||||
anno_area_selector = self.test_data["historyPanel"]["selectors"]["history"]["annoArea"]
|
||||
tag_icon_selector = self.navigation.history_panel.selectors.tag_icon
|
||||
annotation_icon_selector = self.navigation.history_panel.selectors.annotation_icon
|
||||
|
||||
tag_icon = self.wait_for_selector_clickable(tag_icon_selector)
|
||||
annon_icon = self.wait_for_selector_clickable(anno_icon_selector)
|
||||
tag_area_selector = self.navigation.history_panel.selectors.tag_area
|
||||
annotation_area_selector = self.navigation.history_panel.selectors.annotation_area
|
||||
|
||||
self.assert_selector_absent_or_hidden(tag_area_selector)
|
||||
self.assert_selector_absent_or_hidden(anno_area_selector)
|
||||
tag_icon = self.wait_for_clickable(tag_icon_selector)
|
||||
annon_icon = self.wait_for_clickable(annotation_icon_selector)
|
||||
|
||||
self.assert_absent_or_hidden(tag_area_selector)
|
||||
self.assert_absent_or_hidden(annotation_area_selector)
|
||||
|
||||
tag_icon.click()
|
||||
|
||||
self.wait_for_selector(tag_area_selector)
|
||||
self.assert_selector_absent_or_hidden(anno_area_selector)
|
||||
self.wait_for_visible(tag_area_selector)
|
||||
self.assert_absent_or_hidden(annotation_area_selector)
|
||||
|
||||
tag_icon.click()
|
||||
time.sleep(.5)
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
annon_icon.click()
|
||||
|
||||
self.wait_for_selector(anno_area_selector)
|
||||
self.assert_selector_absent_or_hidden(tag_area_selector)
|
||||
self.wait_for_visible(annotation_area_selector)
|
||||
self.assert_absent_or_hidden(tag_area_selector)
|
||||
|
||||
annon_icon.click()
|
||||
time.sleep(.5)
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
|
||||
self.assert_selector_absent_or_hidden(tag_area_selector)
|
||||
self.assert_selector_absent_or_hidden(anno_area_selector)
|
||||
self.assert_absent_or_hidden(tag_area_selector)
|
||||
self.assert_absent_or_hidden(annotation_area_selector)
|
||||
|
||||
@selenium_test
|
||||
def test_refresh_preserves_state(self):
|
||||
@@ -91,20 +88,20 @@ class HistoryPanelTestCase(SeleniumTestCase):
|
||||
self.perform_upload(self.get_filename("1.txt"))
|
||||
self.wait_for_history()
|
||||
|
||||
hda_id = self.latest_history_item()["id"]
|
||||
hda_body_selector = self.hda_body_selector(hda_id)
|
||||
self.assert_selector_absent_or_hidden(hda_body_selector)
|
||||
|
||||
self.click_hda_title(hda_id, wait=True)
|
||||
self.wait_for_selector_visible(hda_body_selector)
|
||||
|
||||
# Open the details, verify they are open and do a refresh.
|
||||
self.history_panel_ensure_showing_item_details(hid=1)
|
||||
self.history_panel_item_body_selector(1, wait=True)
|
||||
self.history_panel_refresh_click()
|
||||
|
||||
title_selector = self.hda_div_selector(hda_id)
|
||||
self.wait_for_selector_visible(hda_body_selector)
|
||||
# After the refresh, verify the details are still open.
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
self.wait_for_selector_clickable(self.history_panel_item_selector(hid=1))
|
||||
assert self.history_panel_item_showing_details(hid=1)
|
||||
|
||||
self.click_hda_title(hda_id, wait=True)
|
||||
# Close the detailed display, refresh, and ensure they are still closed.
|
||||
self.history_panel_click_item_title(hid=1, wait=True)
|
||||
assert not self.history_panel_item_showing_details(hid=1)
|
||||
self.history_panel_refresh_click()
|
||||
|
||||
self.wait_for_selector(title_selector)
|
||||
self.assert_selector_absent_or_hidden(hda_body_selector)
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
self.wait_for_selector_clickable(self.history_panel_item_selector(hid=1))
|
||||
assert not self.history_panel_item_showing_details(hid=1)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from .framework import (
|
||||
managed_history,
|
||||
selenium_test,
|
||||
SeleniumTestCase,
|
||||
)
|
||||
|
||||
JUPYTER_VISUALIZATION_NAME = "Jupyter"
|
||||
|
||||
|
||||
class JupyterTestCase(SeleniumTestCase):
|
||||
|
||||
ensure_registered = True
|
||||
|
||||
@selenium_test
|
||||
@managed_history
|
||||
def test_jupyter_session(self):
|
||||
self.perform_upload(self.get_filename("1.txt"))
|
||||
self.perform_upload(self.get_filename("selenium-test-notebook.ipynb"))
|
||||
self.wait_for_history()
|
||||
self.history_panel_ensure_showing_item_details(2)
|
||||
self.history_panel_item_click_visualization_menu(2)
|
||||
self.ensure_visualization_available(2, JUPYTER_VISUALIZATION_NAME)
|
||||
self.history_panel_item_click_visualization(2, JUPYTER_VISUALIZATION_NAME)
|
||||
with self.main_panel():
|
||||
viz_iframe = self.wait_for_selector("body iframe", wait_type=self.wait_types.GIE_SPAWN)
|
||||
try:
|
||||
self.driver.switch_to.frame(viz_iframe)
|
||||
self.wait_for_selector("ul.nav")
|
||||
lis = self.driver.find_elements_by_css_selector("ul.nav li.dropdown")
|
||||
cell_li = None
|
||||
for li in lis:
|
||||
if li.text == "Cell":
|
||||
cell_li = li
|
||||
break
|
||||
cell_li.click()
|
||||
self.wait_for_and_click_selector("ul#cell_menu li#run_all_cells")
|
||||
finally:
|
||||
self.driver.switch_to.default_content()
|
||||
|
||||
# TODO: wait on something in the notebook instead of sleeping so long.
|
||||
self.sleep_for(wait_type=self.wait_types.JOB_COMPLETION)
|
||||
self.history_panel_refresh_click()
|
||||
|
||||
# Re-running notebook should publish two new things to the history.
|
||||
self.history_panel_wait_for_hid_ok(3)
|
||||
self.history_panel_wait_for_hid_ok(4)
|
||||
@@ -0,0 +1,103 @@
|
||||
from .framework import (
|
||||
retry_assertion_during_transitions,
|
||||
selenium_test,
|
||||
SeleniumTestCase,
|
||||
)
|
||||
|
||||
|
||||
class LibraryContentsTestCase(SeleniumTestCase):
|
||||
|
||||
requires_admin = True
|
||||
|
||||
@selenium_test
|
||||
def test_create_folder(self):
|
||||
self._navigate_to_new_library()
|
||||
self._assert_num_displayed_items_is(0)
|
||||
self.libraries_folder_create("folder1")
|
||||
self._assert_num_displayed_items_is(1)
|
||||
|
||||
@selenium_test
|
||||
def test_import_dataset_from_history(self):
|
||||
self.admin_login()
|
||||
self.perform_upload(self.get_filename("1.txt"))
|
||||
self.wait_for_history()
|
||||
self._navigate_to_new_library(login=False)
|
||||
self._assert_num_displayed_items_is(0)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.libraries_dataset_import_from_history()
|
||||
# Click the cancel button, make sure modal is hidden.
|
||||
self.wait_for_visible(self.navigation.libraries.folder.selectors.import_modal)
|
||||
self.wait_for_and_click(self.navigation.libraries.folder.selectors.import_datasets_cancel_button)
|
||||
self.wait_for_absent_or_hidden(self.navigation.libraries.folder.selectors.import_modal)
|
||||
|
||||
self.libraries_dataset_import_from_history()
|
||||
self.wait_for_visible(self.navigation.libraries.folder.selectors.import_history_content)
|
||||
history_elements = self.find_elements(self.navigation.libraries.folder.selectors.import_history_contents_items)
|
||||
assert "1.txt" in history_elements[0].text
|
||||
history_elements[0].find_element_by_css_selector("input").click()
|
||||
# Add
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.wait_for_and_click(self.navigation.libraries.folder.selectors.import_datasets_ok_button)
|
||||
# Let the progress bar disappear...
|
||||
self.wait_for_absent_or_hidden(self.navigation.libraries.folder.selectors.import_progress_bar)
|
||||
self._assert_num_displayed_items_is(1)
|
||||
|
||||
@selenium_test
|
||||
def test_import_dataset_from_path(self):
|
||||
self._navigate_to_new_library()
|
||||
self._assert_num_displayed_items_is(0)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
# Click the cancel button, make sure modal is hidden.
|
||||
self.libraries_dataset_import_from_path()
|
||||
self.wait_for_visible(self.navigation.libraries.folder.selectors.import_modal)
|
||||
self.wait_for_and_click(self.navigation.libraries.folder.selectors.import_datasets_cancel_button)
|
||||
self.wait_for_absent_or_hidden(self.navigation.libraries.folder.selectors.import_modal)
|
||||
|
||||
# Try again... this time actually select some paths.
|
||||
self.libraries_dataset_import_from_path()
|
||||
textarea = self.wait_for_and_click(self.navigation.libraries.folder.selectors.import_from_path_textarea)
|
||||
textarea.send_keys("test-data/1.txt")
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.wait_for_and_click(self.navigation.libraries.folder.selectors.import_datasets_ok_button)
|
||||
# Let the progress bar disappear...
|
||||
self.wait_for_absent_or_hidden(self.navigation.libraries.folder.selectors.import_progress_bar)
|
||||
self._assert_num_displayed_items_is(1)
|
||||
|
||||
self.click_label("1.txt")
|
||||
self.wait_for_visible(self.navigation.libraries.dataset.selectors.table)
|
||||
elements = self.find_elements(self.navigation.libraries.dataset.selectors.table_rows)
|
||||
table_as_dict = {}
|
||||
for element in elements:
|
||||
key = element.find_element_by_tag_name("th").text
|
||||
value = element.find_element_by_tag_name("td").text
|
||||
table_as_dict[key] = value
|
||||
|
||||
assert table_as_dict["Name"] == "1.txt", table_as_dict
|
||||
assert table_as_dict["Genome build"] == "?", table_as_dict
|
||||
|
||||
@selenium_test
|
||||
def test_show_details(self):
|
||||
self._navigate_to_new_library()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.wait_for_selector_clickable(".toolbtn-show-locinfo").click()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.wait_for_selector_clickable(".ui-modal #button-0").click()
|
||||
self.wait_for_overlays_cleared()
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def _assert_num_displayed_items_is(self, n):
|
||||
self.assertEqual(n, self._num_displayed_items())
|
||||
|
||||
def _num_displayed_items(self):
|
||||
return len(self.libraries_table_elements())
|
||||
|
||||
def _navigate_to_new_library(self, login=True):
|
||||
if login:
|
||||
self.admin_login()
|
||||
self.libraries_open()
|
||||
self.name = self._get_random_name(prefix="testcontents")
|
||||
self.libraries_index_create(self.name)
|
||||
self.wait_for_overlays_cleared()
|
||||
self.libraries_index_search_for(self.name)
|
||||
self.libraries_index_table_elements()[0].find_element_by_css_selector("td a").click()
|
||||
@@ -0,0 +1,105 @@
|
||||
from .framework import (
|
||||
retry_assertion_during_transitions,
|
||||
selenium_test,
|
||||
SeleniumTestCase,
|
||||
)
|
||||
|
||||
|
||||
class LibraryLandingTestCase(SeleniumTestCase):
|
||||
|
||||
requires_admin = True
|
||||
|
||||
def setup_with_driver(self):
|
||||
self.admin_login()
|
||||
self.libraries_open()
|
||||
|
||||
@selenium_test
|
||||
def test_create_new_close(self):
|
||||
num_displayed_libraries = self._num_displayed_libraries()
|
||||
self.libraries_index_click_create_new()
|
||||
self.wait_for_selector_visible(".ui-modal")
|
||||
close_button = self.wait_for_selector_clickable("#button-1")
|
||||
close_button.click()
|
||||
self.wait_for_overlays_cleared()
|
||||
assert self._num_displayed_libraries() == num_displayed_libraries
|
||||
|
||||
@selenium_test
|
||||
def test_create_new(self):
|
||||
name = self._get_random_name(prefix="testcreatenew")
|
||||
self.libraries_index_create(name)
|
||||
|
||||
# Must be showing at least one library now.
|
||||
self._assert_at_least_one_library_displayed()
|
||||
|
||||
self._search_for_only_with_name(name)
|
||||
|
||||
@selenium_test
|
||||
def test_rename(self):
|
||||
name = self._get_random_name(prefix="testprerename")
|
||||
self.libraries_index_create(name)
|
||||
self._search_for_only_with_name(name)
|
||||
|
||||
edit_button = self.wait_for_selector_clickable(".edit_library_btn")
|
||||
edit_button.click()
|
||||
|
||||
new_name = self._get_random_name(prefix="testpostrename")
|
||||
name_box = self.wait_for_selector_clickable(".input_library_name")
|
||||
name_box.send_keys(new_name)
|
||||
|
||||
save_button = self.wait_for_selector_clickable(".save_library_btn")
|
||||
save_button.click()
|
||||
|
||||
self._search_for_only_with_name(new_name)
|
||||
|
||||
@selenium_test
|
||||
def test_help(self):
|
||||
help_button = self.wait_for_selector_clickable(".help-button")
|
||||
help_link = help_button.find_element_by_css_selector("a")
|
||||
self.assertEqual(help_link.get_attribute("href"), "https://galaxyproject.org/data-libraries/screen/list-of-libraries/")
|
||||
|
||||
@selenium_test
|
||||
def test_sorting(self):
|
||||
# Throw in another library to ensure filtering is working...
|
||||
other = self._get_random_name(prefix="notthesame")
|
||||
self.libraries_index_create(other)
|
||||
self.wait_for_overlays_cleared()
|
||||
|
||||
namebase = self._get_random_name(prefix="testsort")
|
||||
self.libraries_index_create(namebase + " b")
|
||||
self.wait_for_overlays_cleared()
|
||||
self.libraries_index_create(namebase + " a")
|
||||
self.wait_for_overlays_cleared()
|
||||
self.libraries_index_create(namebase + " c")
|
||||
|
||||
self.libraries_index_search_for(namebase)
|
||||
|
||||
self._assert_num_displayed_libraries_is(3)
|
||||
|
||||
self._assert_names_are([namebase + " a", namebase + " b", namebase + " c"])
|
||||
self.libraries_index_sort_click()
|
||||
|
||||
# TODO: Usability bug in libraries - should need to resort - it should
|
||||
# sort the entities on the screen.
|
||||
self.libraries_index_search_for(namebase)
|
||||
|
||||
self._assert_names_are([namebase + " c", namebase + " b", namebase + " a"])
|
||||
|
||||
def _search_for_only_with_name(self, name):
|
||||
self.libraries_index_search_for(name)
|
||||
self._assert_num_displayed_libraries_is(1)
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def _assert_names_are(self, expected_names):
|
||||
names = [e.find_element_by_css_selector("td a").text for e in self.libraries_index_table_elements()]
|
||||
self.assertEqual(names, expected_names)
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def _assert_at_least_one_library_displayed(self):
|
||||
assert self._num_displayed_libraries() > 0
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def _assert_num_displayed_libraries_is(self, n):
|
||||
self.assertEqual(n, self._num_displayed_libraries())
|
||||
|
||||
def _num_displayed_libraries(self):
|
||||
return len(self.libraries_index_table_elements())
|
||||
@@ -0,0 +1,32 @@
|
||||
from galaxy_selenium.navigates_galaxy import (
|
||||
exception_indicates_not_clickable,
|
||||
exception_seems_to_indicate_transition,
|
||||
)
|
||||
|
||||
from .framework import (
|
||||
selenium_test,
|
||||
SeleniumTestCase,
|
||||
)
|
||||
|
||||
|
||||
class NavigatesGalaxySeleniumTestCase(SeleniumTestCase):
|
||||
"""Test the Selenium test framework itself.
|
||||
|
||||
Unlike the others test cases in this module, this test case tests the
|
||||
test framework itself (for when that makes sense, mostly corner cases).
|
||||
"""
|
||||
|
||||
@selenium_test
|
||||
def test_click_error(self):
|
||||
self.home()
|
||||
self.upload_start_click()
|
||||
# Open the details, verify they are open and do a refresh.
|
||||
exception = None
|
||||
try:
|
||||
refresh_button = self.wait_for_selector("#history-refresh-button")
|
||||
refresh_button.click()
|
||||
except Exception as e:
|
||||
exception = e
|
||||
assert exception is not None
|
||||
assert exception_indicates_not_clickable(exception)
|
||||
assert exception_seems_to_indicate_transition(exception)
|
||||
@@ -1,5 +1,3 @@
|
||||
import time
|
||||
|
||||
from .framework import (
|
||||
retry_assertion_during_transitions,
|
||||
selenium_test,
|
||||
@@ -98,7 +96,7 @@ class HistoryGridTestCase(SharedStateSeleniumTestCase):
|
||||
self.assert_grid_histories_are([self.history1_name, self.history3_name], False)
|
||||
|
||||
def get_histories(self, sleep=False):
|
||||
time.sleep(1.5)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
names = []
|
||||
grid = self.wait_for_selector('#grid-table-body')
|
||||
for row in grid.find_elements_by_tag_name('tr'):
|
||||
@@ -146,27 +144,26 @@ class HistoryGridTestCase(SharedStateSeleniumTestCase):
|
||||
close_link_selector = 'a[filter_key="%s"][filter_val="%s"]' % \
|
||||
(filter_key, filter_value)
|
||||
self.wait_for_and_click_selector(close_link_selector)
|
||||
time.sleep(.5)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
def set_annotation(self, annotation):
|
||||
anno_icon_selector = self.test_data['historyPanel']['selectors']['history']['annoIcon']
|
||||
anno_area_selector = self.test_data['historyPanel']['selectors']['history']['annoArea']
|
||||
self.ensure_annotation_area_displayed()
|
||||
|
||||
if not self.selector_is_displayed(anno_area_selector):
|
||||
self.wait_for_and_click_selector(anno_icon_selector)
|
||||
self.wait_for_and_click(self.navigation.history_panel.selectors.annotation_editable_text)
|
||||
|
||||
anno_area_selector += ' .annotation'
|
||||
self.wait_for_and_click_selector(anno_area_selector)
|
||||
annon_area_editable = self.wait_for_and_click(self.navigation.history_panel.selectors.annotation_edit)
|
||||
anno_done_button = self.wait_for_clickable(self.navigation.history_panel.selectors.annotation_done)
|
||||
|
||||
area_editable_selector = anno_area_selector + ' textarea'
|
||||
done_button_selector = anno_area_selector + ' button'
|
||||
annon_area_editable = self.wait_for_selector_clickable(area_editable_selector)
|
||||
anno_done_button = self.wait_for_selector_clickable(done_button_selector)
|
||||
|
||||
annon_area_editable.click()
|
||||
annon_area_editable.send_keys(annotation)
|
||||
anno_done_button.click()
|
||||
|
||||
def ensure_annotation_area_displayed(self):
|
||||
annotation_area_selector = self.navigation.history_panel.selectors.annotation_area
|
||||
annotation_icon_selector = self.navigation.history_panel.selectors.annotation_icon
|
||||
|
||||
if not self.is_displayed(annotation_area_selector):
|
||||
self.wait_for_and_click(annotation_icon_selector)
|
||||
|
||||
def setup_shared_state(self):
|
||||
tag1 = self._get_random_name(len=5)
|
||||
tag2 = self._get_random_name(len=5)
|
||||
@@ -202,8 +199,7 @@ class HistoryGridTestCase(SharedStateSeleniumTestCase):
|
||||
|
||||
def create_history(self, name):
|
||||
self.home()
|
||||
self.click_history_option('Create New')
|
||||
self.history_panel_rename(name)
|
||||
self.history_panel_create_new_with_name(name)
|
||||
|
||||
def publish_current_history(self):
|
||||
self.click_history_option('Share or Publish')
|
||||
@@ -213,8 +209,6 @@ class HistoryGridTestCase(SharedStateSeleniumTestCase):
|
||||
|
||||
def navigate_to_published_histories_page(self):
|
||||
self.home()
|
||||
self.click_masthead_user() # Open masthead menu
|
||||
self.click_label(
|
||||
self.navigation_data['labels']['masthead']['menus']['libraries'])
|
||||
self.click_masthead_libraries()
|
||||
selector = 'a[href="/histories/list_published"]'
|
||||
self.wait_for_and_click_selector(selector)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from .framework import (
|
||||
DEFAULT_PASSWORD,
|
||||
selenium_test,
|
||||
SeleniumTestCase
|
||||
)
|
||||
@@ -33,7 +32,7 @@ class RegistrationTestCase(SeleniumTestCase):
|
||||
def test_reregister_email_fails(self):
|
||||
self.home()
|
||||
email = self._get_random_email()
|
||||
password = DEFAULT_PASSWORD
|
||||
password = self.default_password
|
||||
confirm = password
|
||||
username = email.split("@")[0]
|
||||
self.register(email, password, username, confirm)
|
||||
@@ -47,7 +46,7 @@ class RegistrationTestCase(SeleniumTestCase):
|
||||
self.home()
|
||||
email1 = self._get_random_email()
|
||||
email2 = self._get_random_email()
|
||||
password = DEFAULT_PASSWORD
|
||||
password = self.default_password
|
||||
confirm = password
|
||||
username = email1.split("@")[0]
|
||||
self.register(email1, password, username, confirm)
|
||||
@@ -60,7 +59,7 @@ class RegistrationTestCase(SeleniumTestCase):
|
||||
def test_bad_emails(self):
|
||||
bad_emails = ['bob', 'bob@', 'bob@idontwanttocleanup', 'bob.cantmakeme']
|
||||
good_email = self._get_random_email()
|
||||
password = DEFAULT_PASSWORD
|
||||
password = self.default_password
|
||||
confirm = password
|
||||
username = good_email.split("@")[0]
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import time
|
||||
|
||||
from .framework import (
|
||||
retry_assertion_during_transitions,
|
||||
retry_during_transitions,
|
||||
@@ -21,12 +19,11 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
|
||||
self._login()
|
||||
self.navigate_to_saved_histories_page()
|
||||
self.click_popup_option(self.history2_name, 'Switch')
|
||||
time.sleep(.5)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def assert_history_name_switched():
|
||||
history_name = self.history_panel_name_element()
|
||||
self.assertEqual(history_name.text, self.history2_name)
|
||||
self.assertEqual(self.history_panel_name(), self.history2_name)
|
||||
|
||||
assert_history_name_switched()
|
||||
|
||||
@@ -51,7 +48,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
|
||||
|
||||
self.histories_click_advanced_search()
|
||||
self.select_filter('sharing', 'published')
|
||||
time.sleep(1)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
self.assert_histories_in_grid([self.history2_name])
|
||||
|
||||
@@ -85,7 +82,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
|
||||
|
||||
self.histories_click_advanced_search()
|
||||
self.select_filter('deleted', 'True')
|
||||
time.sleep(1)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
# Restore the history
|
||||
self.click_popup_option(self.history2_name, 'Undelete')
|
||||
@@ -130,7 +127,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
|
||||
|
||||
self.histories_click_advanced_search()
|
||||
self.select_filter('deleted', 'True')
|
||||
time.sleep(1)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
# Restore multiple histories
|
||||
self.check_histories([self.history2_name, self.history3_name])
|
||||
@@ -254,7 +251,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
|
||||
|
||||
@retry_during_transitions
|
||||
def get_histories(self):
|
||||
time.sleep(.5)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
names = []
|
||||
grid = self.wait_for_selector('#grid-table-body')
|
||||
for row in grid.find_elements_by_tag_name('tr'):
|
||||
@@ -273,13 +270,11 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
|
||||
close_button_selector = 'a[filter_key="%s"][filter_val="%s"]' % \
|
||||
(filter_key, filter_value)
|
||||
self.wait_for_and_click_selector(close_button_selector)
|
||||
time.sleep(.5)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
def navigate_to_saved_histories_page(self):
|
||||
self.home()
|
||||
self.click_masthead_user() # Open masthead menu
|
||||
label = self.navigation_data['labels']['masthead']['menus']['user']
|
||||
self.click_label(label)
|
||||
self.wait_for_and_click_selector('a[href="/histories/list"]')
|
||||
|
||||
def setup_shared_state(self):
|
||||
@@ -300,8 +295,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase):
|
||||
|
||||
def create_history(self, name):
|
||||
self.home()
|
||||
self.click_history_option('Create New')
|
||||
self.history_panel_rename(name)
|
||||
self.history_panel_create_new_with_name(name)
|
||||
|
||||
def select_filter(self, filter_key, filter_value):
|
||||
filter_selector = 'a[filter_key="%s"][filter_val="%s"]' % \
|
||||
|
||||
@@ -17,8 +17,8 @@ class UploadsTestCase(SeleniumTestCase, UsesHistoryItemAssertions):
|
||||
assert history_count == 1, "Incorrect number of items in history - expected 1, found %d" % history_count
|
||||
|
||||
hda = history_contents[0]
|
||||
assert hda["name"] == '1.sam'
|
||||
assert hda["extension"] == "sam"
|
||||
assert hda["name"] == '1.sam', hda
|
||||
assert hda["extension"] == "sam", hda
|
||||
|
||||
self.history_panel_click_item_title(hid=1, wait=True)
|
||||
self.assert_item_dbkey_displayed_as(1, "?")
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import time
|
||||
|
||||
from ._workflow_fixtures import (
|
||||
WORKFLOW_SIMPLE_CAT_TWICE,
|
||||
WORKFLOW_WITH_INVALID_STATE,
|
||||
@@ -27,7 +25,7 @@ class WorkflowEditorTestCase(SeleniumTestCase):
|
||||
def test_data_input(self):
|
||||
self.workflow_create_new()
|
||||
menu = self.wait_for_selector(".toolMenu")
|
||||
time.sleep(1)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
inputs_section = menu.find_element_by_css_selector("#title___workflow__inputs__")
|
||||
inputs_link = inputs_section.find_element_by_css_selector("a span")
|
||||
inputs_link.click()
|
||||
@@ -43,7 +41,7 @@ class WorkflowEditorTestCase(SeleniumTestCase):
|
||||
workflow_populator.upload_yaml_workflow(WORKFLOW_SIMPLE_CAT_TWICE)
|
||||
self.workflow_index_open()
|
||||
self.workflow_index_click_option("Edit")
|
||||
time.sleep(1)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.workflow_editor_click_option("Save As")
|
||||
|
||||
@selenium_test
|
||||
|
||||
@@ -38,9 +38,8 @@ class WorkflowManagementTestCase(SeleniumTestCase):
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def check_name():
|
||||
row_element = self.workflow_index_table_row()
|
||||
renamed_workflow_button = row_element.find_element_by_css_selector(".menubutton")
|
||||
assert 'CoolNewName' in renamed_workflow_button.text, renamed_workflow_button.text
|
||||
name = self.workflow_index_name()
|
||||
assert 'CoolNewName' == name, name
|
||||
|
||||
check_name()
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import time
|
||||
|
||||
from ._workflow_fixtures import (
|
||||
WORKFLOW_SIMPLE_CAT_TWICE,
|
||||
WORKFLOW_WITH_OLD_TOOL_VERSION,
|
||||
@@ -38,6 +36,6 @@ class WorkflowRunTestCase(SeleniumTestCase, UsesHistoryItemAssertions):
|
||||
|
||||
self.workflow_index_open()
|
||||
self.workflow_index_click_option("Run")
|
||||
time.sleep(.5)
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
# Check that this tool form contains a warning about different versions.
|
||||
self.assert_warning_message(contains="different versions")
|
||||
|
||||
Reference in New Issue
Block a user