mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #22164 from jmchilton/playwright_backlog_1
Expanded E2E Testing
This commit is contained in:
@@ -406,7 +406,7 @@ const removeTagsAction = computed(() => {
|
||||
<DraggableWrapper
|
||||
:id="id"
|
||||
ref="terminalComponent"
|
||||
v-b-tooltip.hover="!props.blank ? outputDetails : ''"
|
||||
v-b-tooltip.hover.noninteractive="!props.blank ? outputDetails : ''"
|
||||
class="output-terminal prevent-zoom"
|
||||
:class="{ 'mapped-over': isMultiple, 'blank-output': props.blank }"
|
||||
:output-name="output.name"
|
||||
|
||||
@@ -173,6 +173,10 @@ class HasDriver(TimeoutMessageMixin, WaitMethodsMixin, Generic[WaitTypeT]):
|
||||
"""
|
||||
self.driver.get(url)
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Reload the current page."""
|
||||
self.driver.refresh()
|
||||
|
||||
def re_get_with_query_params(self, params_str: str):
|
||||
driver = self.driver
|
||||
new_url = driver.current_url
|
||||
|
||||
@@ -117,6 +117,11 @@ class HasDriverProtocol(Protocol, Generic[WaitTypeT]):
|
||||
"""Navigate to the specified URL."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def refresh(self) -> None:
|
||||
"""Reload the current page."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def re_get_with_query_params(self, params_str: str):
|
||||
"""Navigate to current URL with additional query parameters."""
|
||||
|
||||
@@ -108,6 +108,10 @@ class HasDriverProxy(ABC, Generic[WaitTypeT]):
|
||||
"""Navigate to the specified URL."""
|
||||
self._driver_impl.navigate_to(url)
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Reload the current page."""
|
||||
self._driver_impl.refresh()
|
||||
|
||||
def re_get_with_query_params(self, params_str: str):
|
||||
"""Navigate to current URL with additional query parameters."""
|
||||
return self._driver_impl.re_get_with_query_params(params_str)
|
||||
|
||||
@@ -315,6 +315,10 @@ class HasPlaywrightDriver(TimeoutMessageMixin, WaitMethodsMixin, Generic[WaitTyp
|
||||
"""
|
||||
self.page.goto(url)
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Reload the current page."""
|
||||
self.page.reload()
|
||||
|
||||
def re_get_with_query_params(self, params_str: str):
|
||||
"""Add query parameters to current URL and reload."""
|
||||
current_url = self.page.url
|
||||
@@ -773,7 +777,10 @@ class HasPlaywrightDriver(TimeoutMessageMixin, WaitMethodsMixin, Generic[WaitTyp
|
||||
|
||||
def _hover(self, element: ElementHandle) -> None:
|
||||
"""Internal implementation of hover."""
|
||||
element.hover()
|
||||
# force=True bypasses actionability checks that fail when overlapping
|
||||
# UI elements (e.g. delete-terminal-button) intercept pointer events.
|
||||
# Hover is non-destructive so this is safe.
|
||||
element.hover(force=True)
|
||||
|
||||
def move_to_and_click(self, element: WebElementProtocol) -> None:
|
||||
"""
|
||||
@@ -788,8 +795,8 @@ class HasPlaywrightDriver(TimeoutMessageMixin, WaitMethodsMixin, Generic[WaitTyp
|
||||
|
||||
def _move_to_and_click(self, element: ElementHandle) -> None:
|
||||
"""Internal implementation of move_to_and_click."""
|
||||
element.hover()
|
||||
element.click()
|
||||
element.hover(force=True)
|
||||
element.click(force=True)
|
||||
|
||||
def drag_and_drop(self, source: WebElementProtocol, target: WebElementProtocol) -> None:
|
||||
"""
|
||||
@@ -807,23 +814,18 @@ class HasPlaywrightDriver(TimeoutMessageMixin, WaitMethodsMixin, Generic[WaitTyp
|
||||
"""
|
||||
Internal implementation of drag and drop.
|
||||
|
||||
Uses JavaScript to simulate drag and drop events.
|
||||
Creates a real DataTransfer via evaluate_handle so setData/getData
|
||||
work across the full drag event sequence (unlike synthetic DragEvents
|
||||
where Chrome restricts getData to return empty).
|
||||
"""
|
||||
self.page.evaluate(
|
||||
"""
|
||||
(elements) => {
|
||||
const [source, target] = elements;
|
||||
const dataTransfer = new DataTransfer();
|
||||
const dragstart = new DragEvent('dragstart', { dataTransfer, bubbles: true });
|
||||
const dragover = new DragEvent('dragover', { dataTransfer, bubbles: true });
|
||||
const drop = new DragEvent('drop', { dataTransfer, bubbles: true });
|
||||
source.dispatchEvent(dragstart);
|
||||
target.dispatchEvent(dragover);
|
||||
target.dispatchEvent(drop);
|
||||
}
|
||||
""",
|
||||
[source, target],
|
||||
)
|
||||
dt = self.page.evaluate_handle("() => new DataTransfer()")
|
||||
source.dispatch_event("pointerdown")
|
||||
source.dispatch_event("dragstart", {"dataTransfer": dt})
|
||||
target.dispatch_event("dragenter", {"dataTransfer": dt})
|
||||
target.dispatch_event("dragover", {"dataTransfer": dt})
|
||||
target.dispatch_event("drop", {"dataTransfer": dt})
|
||||
source.dispatch_event("dragend", {"dataTransfer": dt})
|
||||
source.dispatch_event("pointerup")
|
||||
|
||||
def action_chains(self):
|
||||
"""
|
||||
|
||||
@@ -31,9 +31,16 @@ import yaml
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from .playwright_element import (
|
||||
_SELENIUM_KEY_TO_PLAYWRIGHT,
|
||||
_SELENIUM_MODIFIERS,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
|
||||
from .has_playwright_driver import HasPlaywrightDriver
|
||||
|
||||
from galaxy.navigation.components import (
|
||||
Component,
|
||||
HasText,
|
||||
@@ -115,6 +122,15 @@ class NullTourCallback:
|
||||
pass
|
||||
|
||||
|
||||
def _exception_indicates_playwright_timeout(e):
|
||||
try:
|
||||
from playwright._impl._errors import TimeoutError as PlaywrightTimeoutError
|
||||
|
||||
return isinstance(e, PlaywrightTimeoutError)
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
def exception_seems_to_indicate_transition(e):
|
||||
"""True if exception seems to indicate the page state is transitioning.
|
||||
|
||||
@@ -125,14 +141,16 @@ def exception_seems_to_indicate_transition(e):
|
||||
cause of the exception. The methods that follow use it to allow retrying actions during
|
||||
transitions.
|
||||
|
||||
Currently the two kinds of exceptions that we say may indicate a transition are
|
||||
StaleElement exceptions (a DOM element grabbed at one step is no longer available)
|
||||
and "not clickable" exceptions (so perhaps a popup modal is blocking a click).
|
||||
Currently the kinds of exceptions that we say may indicate a transition are
|
||||
StaleElement exceptions (a DOM element grabbed at one step is no longer available),
|
||||
"not clickable" exceptions (so perhaps a popup modal is blocking a click), and
|
||||
Playwright TimeoutErrors (element not yet present/visible during a transition).
|
||||
"""
|
||||
return (
|
||||
exception_indicates_stale_element(e)
|
||||
or exception_indicates_not_clickable(e)
|
||||
or exception_indicates_click_intercepted(e)
|
||||
or _exception_indicates_playwright_timeout(e)
|
||||
)
|
||||
|
||||
|
||||
@@ -281,7 +299,7 @@ class NavigatesGalaxy(HasDriverProxy[WaitType]):
|
||||
NotImplementedError: If using Selenium backend
|
||||
"""
|
||||
if self._driver_impl.backend_type == "playwright":
|
||||
return self._driver_impl.page # type: ignore[attr-defined]
|
||||
return cast("HasPlaywrightDriver", self._driver_impl).page
|
||||
else:
|
||||
raise NotImplementedError("Functionality cannot be run with Selenium yet.")
|
||||
|
||||
@@ -1415,13 +1433,33 @@ class NavigatesGalaxy(HasDriverProxy[WaitType]):
|
||||
source_id, sink_id = self.workflow_editor_source_sink_terminal_ids(source, sink)
|
||||
source_element = self.find_element_by_selector(f"#{source_id}")
|
||||
sink_element = self.find_element_by_selector(f"#{sink_id}")
|
||||
ac = self.action_chains()
|
||||
ac = ac.move_to_element(source_element).click_and_hold()
|
||||
|
||||
if screenshot_partial:
|
||||
ac = ac.move_by_offset(10, 10)
|
||||
ac.perform()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.screenshot(screenshot_partial)
|
||||
if self._driver_impl.backend_type == "playwright":
|
||||
pw_driver = cast("HasPlaywrightDriver", self._driver_impl)
|
||||
page = pw_driver.page
|
||||
source_handle = pw_driver._unwrap_element(source_element)
|
||||
source_box = source_handle.bounding_box()
|
||||
assert source_box is not None
|
||||
page.mouse.move(
|
||||
source_box["x"] + source_box["width"] / 2,
|
||||
source_box["y"] + source_box["height"] / 2,
|
||||
)
|
||||
page.mouse.down()
|
||||
page.mouse.move(
|
||||
source_box["x"] + source_box["width"] / 2 + 10,
|
||||
source_box["y"] + source_box["height"] / 2 + 10,
|
||||
)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.screenshot(screenshot_partial)
|
||||
page.mouse.up()
|
||||
else:
|
||||
ac = self.action_chains()
|
||||
ac = ac.move_to_element(source_element).click_and_hold()
|
||||
ac = ac.move_by_offset(10, 10)
|
||||
ac.perform()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.screenshot(screenshot_partial)
|
||||
self.drag_and_drop(source_element, sink_element)
|
||||
|
||||
def workflow_editor_source_sink_terminal_ids(self, source, sink):
|
||||
@@ -3020,6 +3058,47 @@ class NavigatesGalaxy(HasDriverProxy[WaitType]):
|
||||
self.wait_for_and_click_selector(search_selector)
|
||||
self.wait_for_selector_visible("#gtn-screen")
|
||||
|
||||
def shift_click(self, element: WebElementProtocol) -> None:
|
||||
"""Shift-click an element. Works with both Selenium and Playwright."""
|
||||
if self._driver_impl.backend_type == "playwright":
|
||||
pw_driver = cast("HasPlaywrightDriver", self._driver_impl)
|
||||
pw_driver._unwrap_element(element).click(modifiers=["Shift"])
|
||||
else:
|
||||
self.action_chains().move_to_element(element).key_down(Keys.SHIFT).click().key_up(Keys.SHIFT).perform()
|
||||
|
||||
def send_keys_to_page(self, *value: str) -> None:
|
||||
"""Send keys to the currently focused element / page.
|
||||
|
||||
Replaces action_chains().send_keys(...).perform() with a backend-agnostic impl.
|
||||
Accepts Selenium Keys constants and plain text, matching PlaywrightElement.send_keys semantics.
|
||||
"""
|
||||
if self._driver_impl.backend_type == "playwright":
|
||||
pw_driver = cast("HasPlaywrightDriver", self._driver_impl)
|
||||
page = pw_driver.page
|
||||
all_chars = "".join(str(v) for v in value)
|
||||
has_special = any(c in _SELENIUM_KEY_TO_PLAYWRIGHT for c in all_chars)
|
||||
if not has_special:
|
||||
page.keyboard.type(all_chars)
|
||||
else:
|
||||
modifiers: list[str] = []
|
||||
for char in all_chars:
|
||||
pw_key = _SELENIUM_KEY_TO_PLAYWRIGHT.get(char)
|
||||
if pw_key and char in _SELENIUM_MODIFIERS:
|
||||
modifiers.append(pw_key)
|
||||
elif pw_key:
|
||||
combo = "+".join(modifiers + [pw_key])
|
||||
page.keyboard.press(combo)
|
||||
modifiers.clear()
|
||||
else:
|
||||
if modifiers:
|
||||
combo = "+".join(modifiers + [char])
|
||||
page.keyboard.press(combo)
|
||||
modifiers.clear()
|
||||
else:
|
||||
page.keyboard.type(char)
|
||||
else:
|
||||
self.action_chains().send_keys(*value).perform()
|
||||
|
||||
def mouse_drag(
|
||||
self,
|
||||
from_element: WebElementProtocol,
|
||||
@@ -3028,18 +3107,51 @@ class NavigatesGalaxy(HasDriverProxy[WaitType]):
|
||||
to_offset=(0, 0),
|
||||
via_offsets: Optional[list[tuple[int, int]]] = None,
|
||||
):
|
||||
chain = self.action_chains().move_to_element(from_element).move_by_offset(*from_offset)
|
||||
chain = chain.click_and_hold().pause(self.wait_length(self.wait_types.UX_RENDER))
|
||||
if self._driver_impl.backend_type == "playwright":
|
||||
pw_driver = cast("HasPlaywrightDriver", self._driver_impl)
|
||||
page = pw_driver.page
|
||||
pause_ms = int(self.wait_length(self.wait_types.UX_RENDER) * 1000)
|
||||
|
||||
if via_offsets is not None:
|
||||
for offset in via_offsets:
|
||||
chain = chain.move_by_offset(*offset).pause(self.wait_length(self.wait_types.UX_RENDER))
|
||||
from_box = pw_driver._unwrap_element(from_element).bounding_box()
|
||||
assert from_box is not None
|
||||
cx = from_box["x"] + from_box["width"] / 2 + from_offset[0]
|
||||
cy = from_box["y"] + from_box["height"] / 2 + from_offset[1]
|
||||
|
||||
if to_element is not None:
|
||||
chain = chain.move_to_element(to_element)
|
||||
page.mouse.move(cx, cy)
|
||||
page.mouse.down()
|
||||
page.wait_for_timeout(pause_ms)
|
||||
|
||||
chain = chain.move_by_offset(*to_offset).pause(self.wait_length(self.wait_types.UX_RENDER)).release()
|
||||
chain.perform()
|
||||
if via_offsets is not None:
|
||||
for offset in via_offsets:
|
||||
cx += offset[0]
|
||||
cy += offset[1]
|
||||
page.mouse.move(cx, cy)
|
||||
page.wait_for_timeout(pause_ms)
|
||||
|
||||
if to_element is not None:
|
||||
to_box = pw_driver._unwrap_element(to_element).bounding_box()
|
||||
assert to_box is not None
|
||||
cx = to_box["x"] + to_box["width"] / 2
|
||||
cy = to_box["y"] + to_box["height"] / 2
|
||||
|
||||
cx += to_offset[0]
|
||||
cy += to_offset[1]
|
||||
page.mouse.move(cx, cy)
|
||||
page.wait_for_timeout(pause_ms)
|
||||
page.mouse.up()
|
||||
else:
|
||||
chain = self.action_chains().move_to_element(from_element).move_by_offset(*from_offset)
|
||||
chain = chain.click_and_hold().pause(self.wait_length(self.wait_types.UX_RENDER))
|
||||
|
||||
if via_offsets is not None:
|
||||
for offset in via_offsets:
|
||||
chain = chain.move_by_offset(*offset).pause(self.wait_length(self.wait_types.UX_RENDER))
|
||||
|
||||
if to_element is not None:
|
||||
chain = chain.move_to_element(to_element)
|
||||
|
||||
chain = chain.move_by_offset(*to_offset).pause(self.wait_length(self.wait_types.UX_RENDER)).release()
|
||||
chain.perform()
|
||||
|
||||
|
||||
class NotLoggedInException(SeleniumTimeoutException):
|
||||
|
||||
@@ -14,6 +14,33 @@ from playwright.sync_api import (
|
||||
ElementHandle,
|
||||
JSHandle,
|
||||
)
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
# Map Selenium Key unicode constants to Playwright key names
|
||||
_SELENIUM_KEY_TO_PLAYWRIGHT = {
|
||||
Keys.CONTROL: "Control",
|
||||
Keys.COMMAND: "Meta",
|
||||
Keys.META: "Meta",
|
||||
Keys.SHIFT: "Shift",
|
||||
Keys.ALT: "Alt",
|
||||
Keys.ENTER: "Enter",
|
||||
Keys.RETURN: "Enter",
|
||||
Keys.ESCAPE: "Escape",
|
||||
Keys.BACKSPACE: "Backspace",
|
||||
Keys.DELETE: "Delete",
|
||||
Keys.TAB: "Tab",
|
||||
Keys.SPACE: " ",
|
||||
Keys.ARROW_DOWN: "ArrowDown",
|
||||
Keys.ARROW_UP: "ArrowUp",
|
||||
Keys.ARROW_LEFT: "ArrowLeft",
|
||||
Keys.ARROW_RIGHT: "ArrowRight",
|
||||
Keys.HOME: "Home",
|
||||
Keys.END: "End",
|
||||
Keys.PAGE_UP: "PageUp",
|
||||
Keys.PAGE_DOWN: "PageDown",
|
||||
}
|
||||
|
||||
_SELENIUM_MODIFIERS = {Keys.CONTROL, Keys.COMMAND, Keys.META, Keys.SHIFT, Keys.ALT}
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .has_playwright_driver import HasPlaywrightDriver
|
||||
@@ -69,23 +96,43 @@ class PlaywrightElement:
|
||||
"""
|
||||
Send keys to the element (type text).
|
||||
|
||||
Uses focus() + cursor-to-end to match Selenium's send_keys behavior
|
||||
of appending text. Playwright's click() positions cursor at click
|
||||
point (center of element), which would insert text mid-content.
|
||||
Translates Selenium Keys constants to Playwright keyboard actions.
|
||||
Modifier keys (Control, Command, etc.) combine with the next key
|
||||
as a keyboard shortcut (e.g. Keys.CONTROL, "a" -> "Control+a").
|
||||
"""
|
||||
text = "".join(str(v) for v in value)
|
||||
self._element.focus()
|
||||
# setSelectionRange is not supported on email, number, date, etc. inputs
|
||||
# per the HTML spec. For those types, use the End key to move cursor to end.
|
||||
input_type = self._element.evaluate("el => (el.type || '').toLowerCase()")
|
||||
no_selection_range_types = {"email", "number", "date", "month", "week", "time", "datetime-local"}
|
||||
if input_type in no_selection_range_types:
|
||||
self._element.press("End")
|
||||
# Flatten all args into a single character stream
|
||||
all_chars = "".join(str(v) for v in value)
|
||||
has_special = any(c in _SELENIUM_KEY_TO_PLAYWRIGHT for c in all_chars)
|
||||
if not has_special:
|
||||
# setSelectionRange is not supported on email, number, date, etc. inputs
|
||||
# per the HTML spec. For those types, use the End key to move cursor to end.
|
||||
input_type = self._element.evaluate("el => (el.type || '').toLowerCase()")
|
||||
no_selection_range_types = {"email", "number", "date", "month", "week", "time", "datetime-local"}
|
||||
if input_type in no_selection_range_types:
|
||||
self._element.press("End")
|
||||
else:
|
||||
self._element.evaluate(
|
||||
"el => { if (el.setSelectionRange) el.setSelectionRange(el.value.length, el.value.length) }"
|
||||
)
|
||||
self._element.type(all_chars)
|
||||
else:
|
||||
self._element.evaluate(
|
||||
"el => { if (el.setSelectionRange) el.setSelectionRange(el.value.length, el.value.length) }"
|
||||
)
|
||||
self._element.type(text)
|
||||
modifiers: list[str] = []
|
||||
for char in all_chars:
|
||||
pw_key = _SELENIUM_KEY_TO_PLAYWRIGHT.get(char)
|
||||
if pw_key and char in _SELENIUM_MODIFIERS:
|
||||
modifiers.append(pw_key)
|
||||
elif pw_key:
|
||||
combo = "+".join(modifiers + [pw_key])
|
||||
self._element.press(combo)
|
||||
modifiers.clear()
|
||||
else:
|
||||
if modifiers:
|
||||
combo = "+".join(modifiers + [char])
|
||||
self._element.press(combo)
|
||||
modifiers.clear()
|
||||
else:
|
||||
self._element.type(char)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
from .framework import (
|
||||
managed_history,
|
||||
selenium_test,
|
||||
SeleniumTestCase,
|
||||
)
|
||||
|
||||
|
||||
class TestHistoryExport(SeleniumTestCase):
|
||||
"""Test history export wizard (requires Celery + STS enabled, the default)."""
|
||||
|
||||
ensure_registered = True
|
||||
|
||||
@selenium_test
|
||||
@managed_history
|
||||
def test_history_native_export_to_file(self):
|
||||
self.perform_upload_of_pasted_content("my cool content")
|
||||
self.history_panel_wait_for_hid_ok(1)
|
||||
|
||||
self.home()
|
||||
self.click_history_option_export_to_file()
|
||||
history_export_tasks = self.components.history_export_tasks
|
||||
last_export_record = self.components.last_export_record
|
||||
|
||||
self.screenshot("history_export_formats")
|
||||
# Step 1: Select export format (tar.gz)
|
||||
history_export_tasks.select_format(format="tar.gz").wait_for_and_click()
|
||||
history_export_tasks.next_button.wait_for_and_click()
|
||||
|
||||
# Step 2: Select download destination
|
||||
history_export_tasks.select_destination(destination="download").wait_for_and_click()
|
||||
self.screenshot("history_export_native_destinations")
|
||||
history_export_tasks.next_button.wait_for_and_click()
|
||||
|
||||
# Step 3: Complete the export
|
||||
self.screenshot("history_export_native_download_options")
|
||||
history_export_tasks.export_button.wait_for_and_click()
|
||||
|
||||
# Wait for export to complete
|
||||
last_export_record.preparing_export_badge.wait_for_visible()
|
||||
self.screenshot("history_export_native_preparing_download")
|
||||
last_export_record.preparing_export_badge.wait_for_absent(wait_type=self.wait_types.DATABASE_OPERATION)
|
||||
last_export_record.download_btn.wait_for_visible()
|
||||
self.screenshot("history_export_native_download_ready")
|
||||
|
||||
@selenium_test
|
||||
@managed_history
|
||||
def test_history_rocrate_export_to_file(self):
|
||||
self.perform_upload_of_pasted_content("my cool content")
|
||||
self.history_panel_wait_for_hid_ok(1)
|
||||
|
||||
self.home()
|
||||
self.click_history_option_export_to_file()
|
||||
history_export_tasks = self.components.history_export_tasks
|
||||
last_export_record = self.components.last_export_record
|
||||
|
||||
self.screenshot("history_export_formats")
|
||||
# Step 1: Select export format (rocrate)
|
||||
history_export_tasks.select_format(format="rocrate.zip").wait_for_and_click()
|
||||
history_export_tasks.next_button.wait_for_and_click()
|
||||
|
||||
# Step 2: Select download destination
|
||||
history_export_tasks.select_destination(destination="download").wait_for_and_click()
|
||||
self.screenshot("history_export_rocrate_destinations")
|
||||
history_export_tasks.next_button.wait_for_and_click()
|
||||
|
||||
# Step 3: Complete the export
|
||||
self.screenshot("history_export_rocrate_download_options")
|
||||
history_export_tasks.export_button.wait_for_and_click()
|
||||
|
||||
# Wait for export to complete
|
||||
last_export_record.preparing_export_badge.wait_for_visible()
|
||||
self.screenshot("history_export_rocrate_preparing_download")
|
||||
last_export_record.preparing_export_badge.wait_for_absent(wait_type=self.wait_types.DATABASE_OPERATION)
|
||||
last_export_record.download_btn.wait_for_visible()
|
||||
self.screenshot("history_export_rocrate_download_ready")
|
||||
@@ -2,8 +2,12 @@ import json
|
||||
from typing import (
|
||||
cast,
|
||||
Optional,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from galaxy.selenium.has_playwright_driver import HasPlaywrightDriver
|
||||
|
||||
import yaml
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.by import By
|
||||
@@ -58,7 +62,6 @@ CHIPSEQ_COLUMNS = [
|
||||
class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions):
|
||||
ensure_registered = True
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_basics(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -81,7 +84,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
|
||||
self.screenshot("workflow_editor_center_pane_maximized")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_edit_annotation(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -98,7 +100,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
self.workflow_index_open_with_name(name)
|
||||
self.assert_wf_annotation_is(new_annotation)
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_edit_name(self):
|
||||
name = self.create_and_wait_for_new_workflow_in_editor()
|
||||
@@ -110,7 +111,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
self.workflow_index_open_with_name(new_name)
|
||||
self.assert_wf_name_is(name)
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_edit_license(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -124,7 +124,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
self.workflow_index_open_with_name(name)
|
||||
assert "MIT" in self.workflow_editor_license_text()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_parameter_regex_validation(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -154,7 +153,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
element = workflow_run.run_error.wait_for_present()
|
||||
assert "input must start with moocow" in element.text
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_int_parameter_minimum_validation(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -181,7 +179,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
# in parameter validators
|
||||
assert "Value ('3') must fulfill (4 <= value <= +infinity)" in element.text, element.text
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_float_parameter_maximum_validation(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -206,7 +203,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
# friendly.
|
||||
assert "Value ('3.2') must fulfill (-infinity <= value <= 3.14)" in element.text, element.text
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_optional_select_data_field(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -235,7 +231,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
tool_state = json.loads(workflow["steps"]["0"]["tool_state"])
|
||||
assert tool_state["select_single"] == ""
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_data_input(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -260,7 +255,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
data_input_node.wait_for_absent()
|
||||
self.screenshot("workflow_editor_data_input_deleted")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_collection_input(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -326,7 +320,6 @@ class TestWorkflowEditor(SeleniumTestCase, RunsWorkflows, UsesWorkflowAssertions
|
||||
assert control["type"] == "element_identifier"
|
||||
assert control["optional"] is True
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_data_column_input_editing(self):
|
||||
self.open_in_workflow_editor("""
|
||||
@@ -351,12 +344,11 @@ steps:
|
||||
self.set_text_element(columns, "4\n5\n6")
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.assert_workflow_has_changes_and_save()
|
||||
self.driver.refresh()
|
||||
self.refresh()
|
||||
node.title.wait_for_and_click()
|
||||
textarea_columns = columns.wait_for_visible()
|
||||
assert textarea_columns.get_attribute("value") == "4\n5\n6"
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_integer_input(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -382,7 +374,6 @@ steps:
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.screenshot("workflow_editor_parameter_input_deleted")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_non_data_connections(self):
|
||||
self.open_in_workflow_editor("""
|
||||
@@ -436,7 +427,6 @@ steps:
|
||||
)
|
||||
self.assert_connected("input_int#output", "tool_exec#inttest")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_non_data_map_over_carried_through(self):
|
||||
# Use auto_layout=false, which prevents placing any
|
||||
@@ -466,7 +456,6 @@ steps:
|
||||
self.workflow_editor_connect("text_input_step#out_file1", "collection_input#input1")
|
||||
self.assert_connected("text_input_step#out_file1", "collection_input#input1")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_connecting_display_in_upload_false_connections(self):
|
||||
self.open_in_workflow_editor("""
|
||||
@@ -481,7 +470,6 @@ steps:
|
||||
self.workflow_editor_connect("step1#qname_input_sorted_bam_output", "step2#input5")
|
||||
self.assert_connected("step1#qname_input_sorted_bam_output", "step2#input5")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_existing_connections(self):
|
||||
self.open_in_workflow_editor(WORKFLOW_SIMPLE_CAT_TWICE)
|
||||
@@ -504,7 +492,6 @@ steps:
|
||||
)
|
||||
self.assert_connected("input1#output", "first_cat#input1")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_reconnecting_nodes(self):
|
||||
name = self.open_in_workflow_editor(WORKFLOW_SIMPLE_CAT_TWICE)
|
||||
@@ -518,14 +505,12 @@ steps:
|
||||
self.workflow_index_open_with_name(name)
|
||||
self.assert_connected("input1#output", "first_cat#input1")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_rendering_output_collection_connections(self):
|
||||
self.open_in_workflow_editor(WORKFLOW_WITH_OUTPUT_COLLECTION)
|
||||
self.workflow_editor_maximize_center_pane()
|
||||
self.screenshot("workflow_editor_output_collections")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_simple_mapping_connections(self):
|
||||
self.open_in_workflow_editor(WORKFLOW_SIMPLE_MAPPING)
|
||||
@@ -539,14 +524,12 @@ steps:
|
||||
self.workflow_editor_connect("input1#output", "cat#input1")
|
||||
self.assert_input_mapped("cat#input1")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_rendering_simple_nested_workflow(self):
|
||||
self.open_in_workflow_editor(WORKFLOW_NESTED_SIMPLE)
|
||||
self.workflow_editor_maximize_center_pane()
|
||||
self.screenshot("workflow_editor_simple_nested")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_best_practices_input_label(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -596,7 +579,6 @@ steps:
|
||||
self.assert_connected(rule_output, random_lines_input)
|
||||
self.assert_input_mapped(random_lines_input)
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_rendering_rules_workflow_2(self):
|
||||
self.open_in_workflow_editor(WORKFLOW_WITH_RULES_2)
|
||||
@@ -645,7 +627,6 @@ steps:
|
||||
# to a list:list, so there should be no mapping anymore even after connected.
|
||||
self.assert_input_not_mapped(copy_list_input)
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_save_as(self):
|
||||
name = self.workflow_upload_yaml_with_random_name(WORKFLOW_SIMPLE_CAT_TWICE)
|
||||
@@ -656,7 +637,6 @@ steps:
|
||||
|
||||
self.components.workflow_editor.save_as_activity.wait_for_and_click()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_tool_upgrade(self):
|
||||
workflow_populator = self.workflow_populator
|
||||
@@ -690,7 +670,6 @@ steps:
|
||||
workflow = self.workflow_populator.download_workflow(workflow_id)
|
||||
assert workflow["steps"]["0"]["tool_version"] == "0.1+galaxy6"
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_tool_upgrade_all_tools(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -707,7 +686,6 @@ steps:
|
||||
version = node.get_attribute("data-version")
|
||||
assert version == "0.2"
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_tool_upgrade_message(self):
|
||||
workflow_populator = self.workflow_populator
|
||||
@@ -719,7 +697,6 @@ steps:
|
||||
self.components.workflow_editor.modal_button_continue.wait_for_and_click()
|
||||
self.assert_workflow_has_changes_and_save()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_subworkflow_tool_upgrade_message(self):
|
||||
workflow_populator = self.workflow_populator
|
||||
@@ -755,7 +732,6 @@ steps:
|
||||
element.wait_for_and_send_keys(Keys.BACKSPACE)
|
||||
element.wait_for_and_send_keys(value)
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_change_datatype(self):
|
||||
self.open_in_workflow_editor("""
|
||||
@@ -791,7 +767,6 @@ steps:
|
||||
# Assert connection is valid
|
||||
self.assert_connected("create_2#out_file1", "checksum#input")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_change_datatype_post_job_action_lost_regression(self):
|
||||
self.open_in_workflow_editor("""
|
||||
@@ -814,7 +789,6 @@ steps:
|
||||
node.wait_for_and_click()
|
||||
self.assert_connected("create_2#out_file1", "metadata_bam#input_bam")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_change_datatype_in_subworkflow(self):
|
||||
self.open_in_workflow_editor("""
|
||||
@@ -870,7 +844,6 @@ steps:
|
||||
node = editor.node._(label="create_2")
|
||||
node.wait_for_and_click()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_duplicate_node(self):
|
||||
workflow_id = self.workflow_populator.upload_yaml_workflow(WORKFLOW_SIMPLE_CAT_TWICE)
|
||||
@@ -913,7 +886,6 @@ steps:
|
||||
assert len(source_step["post_job_actions"]) == len(cloned_step["post_job_actions"]) == 4
|
||||
assert source_step["post_job_actions"] == cloned_step["post_job_actions"]
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_embed_workflow(self):
|
||||
self.setup_subworkflow()
|
||||
@@ -954,7 +926,6 @@ steps:
|
||||
assert subworkflow_step["input_connections"]["input1"]["input_subworkflow_step_id"] == 0
|
||||
return child_workflow_name
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_insert_steps(self):
|
||||
steps_to_insert = self.workflow_upload_yaml_with_random_name(WORKFLOW_SIMPLE_CAT_TWICE)
|
||||
@@ -971,11 +942,10 @@ steps:
|
||||
|
||||
def _download_current_workflow(self):
|
||||
self.sleep_for(self.wait_types.DATABASE_OPERATION)
|
||||
workflow_id = self.driver.current_url.split("id=")[1]
|
||||
workflow_id = self.current_url.split("id=")[1]
|
||||
workflow = self.workflow_populator.download_workflow(workflow_id)
|
||||
return workflow
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_create_conditional_step(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -1005,13 +975,13 @@ steps:
|
||||
# Assert no when input before making step conditional
|
||||
conditional_node.input_terminal(name="when").wait_for_absent()
|
||||
conditional_toggle = editor.step_when.wait_for_present()
|
||||
self.action_chains().move_to_element(conditional_toggle).click().perform()
|
||||
self.move_to_and_click(conditional_toggle)
|
||||
# Toggling conditional should cause when input to appear
|
||||
conditional_node.input_terminal(name="when").wait_for_present()
|
||||
self.action_chains().move_to_element(conditional_toggle).click().perform()
|
||||
self.move_to_and_click(conditional_toggle)
|
||||
# Toggling conditional should cause when input to disappear
|
||||
conditional_node.input_terminal(name="when").wait_for_absent()
|
||||
self.action_chains().move_to_element(conditional_toggle).click().perform()
|
||||
self.move_to_and_click(conditional_toggle)
|
||||
conditional_node.input_terminal(name="when").wait_for_present()
|
||||
# Output connection should be invalid, as output from conditional step is potentially null
|
||||
self.assert_connection_invalid("conditional_step#out_file1", "downstream_step#input1")
|
||||
@@ -1026,6 +996,7 @@ steps:
|
||||
param_type_element = editor.param_type_form.wait_for_present()
|
||||
self.switch_param_type(param_type_element, "Text")
|
||||
self.assert_connection_invalid("param_input#output", "conditional_step#when")
|
||||
editor.node_inspector_close.wait_for_and_click()
|
||||
self.workflow_editor_destroy_connection("conditional_step#when")
|
||||
# Make sure the when input is still shown
|
||||
conditional_node.input_terminal(name="when").wait_for_present()
|
||||
@@ -1057,11 +1028,18 @@ steps:
|
||||
self.assert_workflow_has_changes_and_save()
|
||||
|
||||
def switch_param_type(self, element, param_type):
|
||||
self.action_chains().move_to_element(element).click().pause(1).send_keys(param_type).pause(1).send_keys(
|
||||
Keys.ENTER
|
||||
).perform()
|
||||
if self.backend_type == "playwright":
|
||||
pw_driver = cast("HasPlaywrightDriver", self._driver_impl)
|
||||
element.click()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
pw_driver.page.keyboard.type(param_type)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
pw_driver.page.keyboard.press("Enter")
|
||||
else:
|
||||
self.action_chains().move_to_element(element).click().pause(1).send_keys(param_type).pause(1).send_keys(
|
||||
Keys.ENTER
|
||||
).perform()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_invalid_tool_state(self):
|
||||
workflow_populator = self.workflow_populator
|
||||
@@ -1072,7 +1050,6 @@ steps:
|
||||
self.assert_modal_has_text("Using default: '1'")
|
||||
self.screenshot("workflow_editor_invalid_state")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_missing_tools(self):
|
||||
workflow_populator = self.workflow_populator
|
||||
@@ -1146,7 +1123,6 @@ steps:
|
||||
output_connector.send_keys(Keys.SPACE)
|
||||
assert self.driver.switch_to.active_element.text == "No compatible input found in workflow"
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_insert_input_handling(self):
|
||||
self.open_in_workflow_editor("""class: GalaxyWorkflow
|
||||
@@ -1165,7 +1141,6 @@ steps:
|
||||
node.input_terminal(name="datasets_1|input").wait_for_present()
|
||||
self.assert_workflow_has_changes_and_save()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_workflow_output_handling(self):
|
||||
self.open_in_workflow_editor(
|
||||
@@ -1237,7 +1212,6 @@ steps:
|
||||
# should not show error
|
||||
editor.duplicate_label_error(output="out_file1").wait_for_absent()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_map_over_output_indicator(self):
|
||||
self.open_in_workflow_editor("""
|
||||
@@ -1261,7 +1235,6 @@ steps:
|
||||
self.workflow_editor_destroy_connection("filter#how|filter_source")
|
||||
self.assert_node_output_is("filter#output_filtered", "list")
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_place_comments(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -1277,12 +1250,12 @@ steps:
|
||||
editor.tool_bar.toggle_italic.wait_for_and_click()
|
||||
editor.tool_bar.color(color="pink").wait_for_and_click()
|
||||
editor.tool_bar.font_size.wait_for_and_click()
|
||||
self.action_chains().send_keys(Keys.LEFT * 5).send_keys(Keys.RIGHT).perform()
|
||||
self.send_keys_to_page(Keys.LEFT * 5 + Keys.RIGHT)
|
||||
|
||||
# place text comment
|
||||
self.mouse_drag(from_element=canvas, from_offset=(-200, -200), to_offset=(400, 110))
|
||||
|
||||
self.action_chains().send_keys("Hello World").perform()
|
||||
self.send_keys_to_page("Hello World")
|
||||
|
||||
# check if all options were applied
|
||||
comment_content: WebElementProtocol = editor.comment.text_inner.wait_for_visible()
|
||||
@@ -1306,7 +1279,7 @@ steps:
|
||||
editor.tool_bar.tool(tool="markdown_comment").wait_for_and_click()
|
||||
editor.tool_bar.color(color="lime").wait_for_and_click()
|
||||
self.mouse_drag(from_element=canvas, from_offset=(-100, -100), to_offset=(200, 220))
|
||||
self.action_chains().send_keys("# Hello World").perform()
|
||||
self.send_keys_to_page("# Hello World")
|
||||
|
||||
editor.tool_bar.tool(tool="pointer").wait_for_and_click()
|
||||
|
||||
@@ -1327,7 +1300,7 @@ steps:
|
||||
editor.tool_bar.tool(tool="frame_comment").wait_for_and_click()
|
||||
editor.tool_bar.color(color="blue").wait_for_and_click()
|
||||
self.mouse_drag(from_element=canvas, from_offset=(-200, -150), to_offset=(400, 300))
|
||||
self.action_chains().send_keys("My Frame").perform()
|
||||
self.send_keys_to_page("My Frame")
|
||||
|
||||
title: WebElementProtocol = editor.comment.frame_title.wait_for_visible()
|
||||
assert title.text == "My Frame"
|
||||
@@ -1345,10 +1318,10 @@ steps:
|
||||
editor.tool_bar.tool(tool="freehand_pen").wait_for_and_click()
|
||||
editor.tool_bar.color(color="green").wait_for_and_click()
|
||||
editor.tool_bar.line_thickness.wait_for_and_click()
|
||||
self.action_chains().send_keys(Keys.RIGHT * 20).perform()
|
||||
self.send_keys_to_page(Keys.RIGHT * 20)
|
||||
|
||||
editor.tool_bar.smoothing.wait_for_and_click()
|
||||
self.action_chains().send_keys(Keys.RIGHT * 10).perform()
|
||||
self.send_keys_to_page(Keys.RIGHT * 10)
|
||||
|
||||
self.mouse_drag(from_element=canvas, from_offset=(-100, -100), to_offset=(200, 200))
|
||||
|
||||
@@ -1356,7 +1329,7 @@ steps:
|
||||
|
||||
editor.tool_bar.color(color="black").wait_for_and_click()
|
||||
editor.tool_bar.line_thickness.wait_for_and_click()
|
||||
self.action_chains().send_keys(Keys.LEFT * 20).perform()
|
||||
self.send_keys_to_page(Keys.LEFT * 20)
|
||||
self.mouse_drag(from_element=canvas, from_offset=(-100, -100), via_offsets=[(100, 200)], to_offset=(-200, 30))
|
||||
|
||||
# test bulk remove freehand
|
||||
@@ -1365,7 +1338,7 @@ steps:
|
||||
|
||||
# place another freehand comment and test eraser
|
||||
editor.tool_bar.line_thickness.wait_for_and_click()
|
||||
self.action_chains().send_keys(Keys.RIGHT * 20).perform()
|
||||
self.send_keys_to_page(Keys.RIGHT * 20)
|
||||
editor.tool_bar.color(color="orange").wait_for_and_click()
|
||||
|
||||
self.mouse_drag(from_element=canvas, from_offset=(-100, -100), to_offset=(200, 200))
|
||||
@@ -1374,7 +1347,7 @@ steps:
|
||||
|
||||
# delete by clicking
|
||||
editor.tool_bar.tool(tool="freehand_eraser").wait_for_and_click()
|
||||
self.action_chains().move_to_element(freehand_comment_a).click().perform()
|
||||
self.move_to_and_click(freehand_comment_a)
|
||||
|
||||
editor.comment.freehand_comment.wait_for_absent()
|
||||
|
||||
@@ -1393,7 +1366,6 @@ steps:
|
||||
|
||||
editor.comment.freehand_comment.wait_for_absent()
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_snapping(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -1407,11 +1379,11 @@ steps:
|
||||
# activate snapping and set it to max (200)
|
||||
editor.tool_bar.tool(tool="toggle_snap").wait_for_and_click()
|
||||
editor.tool_bar.snapping_distance.wait_for_and_click()
|
||||
self.action_chains().send_keys(Keys.RIGHT * 10).perform()
|
||||
self.send_keys_to_page(Keys.RIGHT * 10)
|
||||
|
||||
# move the node a bit
|
||||
tool_node = editor.node._(label="tool_node").wait_for_present()
|
||||
self.action_chains().move_to_element(tool_node).click_and_hold().move_by_offset(12, 3).release().perform()
|
||||
self.mouse_drag(from_element=tool_node, to_offset=(12, 3))
|
||||
|
||||
# check if editor position is snapped
|
||||
top, left = self.get_node_position("tool_node")
|
||||
@@ -1421,7 +1393,7 @@ steps:
|
||||
|
||||
# move the node a bit more
|
||||
tool_node = editor.node._(label="tool_node").wait_for_present()
|
||||
self.action_chains().move_to_element(tool_node).click_and_hold().move_by_offset(207, -181).release().perform()
|
||||
self.mouse_drag(from_element=tool_node, to_offset=(207, -181))
|
||||
|
||||
# check if editor position is snapped
|
||||
top, left = self.get_node_position("tool_node")
|
||||
@@ -1429,7 +1401,6 @@ steps:
|
||||
assert top % 200 == 0
|
||||
assert left % 200 == 0
|
||||
|
||||
@selenium_only("Not yet migrated to support Playwright backend")
|
||||
@selenium_test
|
||||
def test_editor_selection(self):
|
||||
editor = self.components.workflow_editor
|
||||
@@ -1447,7 +1418,7 @@ steps:
|
||||
|
||||
# select the node
|
||||
editor.node_inspector_close.wait_for_and_click()
|
||||
self.action_chains().move_to_element(tool_node).key_down(Keys.SHIFT).click().key_up(Keys.SHIFT).perform()
|
||||
self.shift_click(tool_node)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
assert editor.tool_bar.selection_count.wait_for_visible().text.find("1 step") != -1
|
||||
@@ -1544,11 +1515,14 @@ steps:
|
||||
|
||||
return (int(width_stripped), int(height_stripped))
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def assert_node_output_is(self, label: str, output_type: str, subcollection_type: Optional[str] = None):
|
||||
editor = self.components.workflow_editor
|
||||
node_label, output_name = label.split("#")
|
||||
node = editor.node._(label=node_label)
|
||||
node.wait_for_present()
|
||||
# Dismiss any stale tooltip before hovering so retry gets fresh text
|
||||
self.click_center()
|
||||
output_element = node.output_terminal(name=output_name).wait_for_visible()
|
||||
self.hover_over(output_element)
|
||||
element = self.components._.tooltip_inner.wait_for_present()
|
||||
@@ -1581,7 +1555,14 @@ steps:
|
||||
|
||||
def assert_connected(self, source, sink):
|
||||
source_id, sink_id = self.workflow_editor_source_sink_terminal_ids(source, sink)
|
||||
self.components.workflow_editor.connector_for(source_id=source_id, sink_id=sink_id).wait_for_visible()
|
||||
# SVG <g> elements are considered "hidden" by Playwright even when
|
||||
# rendered, so use wait_for_present instead of wait_for_visible
|
||||
# with the Playwright backend.
|
||||
connector = self.components.workflow_editor.connector_for(source_id=source_id, sink_id=sink_id)
|
||||
if self._driver_impl.backend_type == "playwright":
|
||||
connector.wait_for_present()
|
||||
else:
|
||||
connector.wait_for_visible()
|
||||
|
||||
def assert_connection_invalid(self, source, sink):
|
||||
source_id, sink_id = self.workflow_editor_source_sink_terminal_ids(source, sink)
|
||||
@@ -1595,11 +1576,25 @@ steps:
|
||||
name = self.workflow_upload_yaml_with_random_name(yaml_content)
|
||||
self.workflow_index_open()
|
||||
self.workflow_index_open_with_name(name)
|
||||
self.workflow_editor_dismiss_state_upgrade_modal()
|
||||
if auto_layout:
|
||||
self.components.workflow_editor.tool_bar.auto_layout.wait_for_and_click()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
return name
|
||||
|
||||
def workflow_editor_dismiss_state_upgrade_modal(self):
|
||||
"""Dismiss the StateUpgradeModal if it appears when opening a workflow.
|
||||
|
||||
This Bootstrap Vue modal intercepts pointer events and blocks clicks
|
||||
on the workflow editor canvas. Wait briefly for it to potentially
|
||||
appear, then dismiss if present.
|
||||
"""
|
||||
editor = self.components.workflow_editor
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
if not editor.state_modal_body.is_absent:
|
||||
editor.modal_button_continue.wait_for_and_click()
|
||||
editor.state_modal_body.wait_for_absent()
|
||||
|
||||
def workflow_editor_destroy_connection(self, sink):
|
||||
editor = self.components.workflow_editor
|
||||
|
||||
@@ -1637,7 +1632,19 @@ steps:
|
||||
|
||||
def move_center_of_canvas(self, xoffset=0, yoffset=0):
|
||||
_canvas = self.find_element_by_id("canvas-container")
|
||||
assert self.backend_type == "selenium"
|
||||
canvas = cast(WebElement, _canvas)
|
||||
chains = ActionChains(self.driver)
|
||||
chains.click_and_hold(canvas).move_by_offset(xoffset=xoffset, yoffset=yoffset).release().perform()
|
||||
if self.backend_type == "playwright":
|
||||
pw_driver = cast("HasPlaywrightDriver", self._driver_impl)
|
||||
page = pw_driver.page
|
||||
handle = pw_driver._unwrap_element(_canvas)
|
||||
box = handle.bounding_box()
|
||||
assert box is not None
|
||||
cx = box["x"] + box["width"] / 2
|
||||
cy = box["y"] + box["height"] / 2
|
||||
page.mouse.move(cx, cy)
|
||||
page.mouse.down()
|
||||
page.mouse.move(cx + xoffset, cy + yoffset)
|
||||
page.mouse.up()
|
||||
else:
|
||||
canvas = cast(WebElement, _canvas)
|
||||
chains = ActionChains(self.driver)
|
||||
chains.click_and_hold(canvas).move_by_offset(xoffset=xoffset, yoffset=yoffset).release().perform()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../../../tools/filters/catWrapper.xml
|
||||
@@ -288,6 +288,7 @@
|
||||
<tool file="for_workflows/count_multi_file.xml" />
|
||||
<tool file="for_workflows/create_input_collection.xml" />
|
||||
<tool file="for_workflows/randomlines.xml" />
|
||||
<tool file="for_workflows/catWrapper.xml" />
|
||||
<tool file="create_directory_index.xml" />
|
||||
<tool file="use_directory_index.xml" />
|
||||
|
||||
|
||||
+10
-1
@@ -4,7 +4,16 @@ from .framework import (
|
||||
)
|
||||
|
||||
|
||||
class TestHistoryExport(SeleniumIntegrationTestCase):
|
||||
class TestLegacyHistoryExport(SeleniumIntegrationTestCase):
|
||||
"""Test legacy history export for when celery is disabled.
|
||||
|
||||
If Celery is enabled, a wizard will be setup and STS will serve downloads,
|
||||
this is tested in test_history_export.py in the main selenium test suite.
|
||||
|
||||
This test needs to disable celery in order to work so it is an integration
|
||||
test and we disable celery in handle_galaxy_config_kwds.
|
||||
"""
|
||||
|
||||
ensure_registered = True
|
||||
|
||||
@classmethod
|
||||
Reference in New Issue
Block a user