diff --git a/client/src/components/Workflow/Editor/Comments/MarkdownComment.vue b/client/src/components/Workflow/Editor/Comments/MarkdownComment.vue index 87b0787afe0e..62b5c73eee61 100644 --- a/client/src/components/Workflow/Editor/Comments/MarkdownComment.vue +++ b/client/src/components/Workflow/Editor/Comments/MarkdownComment.vue @@ -257,7 +257,7 @@ $min-height: 1.5em; position: absolute; top: $gap-y; left: $gap-x; - overflow-y: scroll; + overflow-y: auto; line-height: 1.2; width: calc(100% - $gap-x - $gap-x); diff --git a/client/src/utils/navigation/navigation.yml b/client/src/utils/navigation/navigation.yml index b10ded3abde6..f11a66e04c69 100644 --- a/client/src/utils/navigation/navigation.yml +++ b/client/src/utils/navigation/navigation.yml @@ -693,13 +693,15 @@ workflow_editor: comment: selectors: _: ".workflow-editor-comment" - text: ".text-workflow-comment" - markdown: ".markdown-workflow-comment" + text_comment: ".text-workflow-comment" + text_inner: ".text-workflow-comment span" + markdown_comment: ".markdown-workflow-comment" markdown_rendered: ".markdown-workflow-comment .rendered-markdown" - frame: ".frame-workflow-comment" + frame_comment: ".frame-workflow-comment" frame_title: ".frame-workflow-comment .frame-comment-header" - freehand: ".freehand-workflow-comment" + freehand_comment: ".freehand-workflow-comment" freehand_path: ".freehand-workflow-comment path" + delete: "button[title='Delete comment']" selectors: canvas_body: '#workflow-canvas' edit_annotation: '#workflow-annotation' diff --git a/lib/galaxy/selenium/navigates_galaxy.py b/lib/galaxy/selenium/navigates_galaxy.py index 71764955fa4d..e34e2946615d 100644 --- a/lib/galaxy/selenium/navigates_galaxy.py +++ b/lib/galaxy/selenium/navigates_galaxy.py @@ -17,8 +17,10 @@ Any, cast, Dict, + List, NamedTuple, Optional, + Tuple, Union, ) @@ -26,6 +28,7 @@ import yaml from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver +from selenium.webdriver.remote.webelement import WebElement from galaxy.navigation.components import ( Component, @@ -2240,6 +2243,27 @@ def tutorial_mode_activate(self): self.wait_for_and_click_selector(search_selector) self.wait_for_selector_visible("#gtn-screen") + def mouse_drag( + self, + from_element: WebElement, + to_element: Optional[WebElement] = None, + from_offset=(0, 0), + 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 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): def __init__(self, timeout_exception, user_info, dom_message): diff --git a/lib/galaxy_test/selenium/test_workflow_editor.py b/lib/galaxy_test/selenium/test_workflow_editor.py index 3ce9d38977a1..750668bdedc8 100644 --- a/lib/galaxy_test/selenium/test_workflow_editor.py +++ b/lib/galaxy_test/selenium/test_workflow_editor.py @@ -4,7 +4,9 @@ import pytest import yaml from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys +from selenium.webdriver.remote.webelement import WebElement from seletools.actions import drag_and_drop from galaxy_test.base.workflow_fixtures import ( @@ -1076,6 +1078,134 @@ def test_map_over_output_indicator(self): self.workflow_editor_destroy_connection("filter#how|filter_source") self.assert_node_output_is("filter#output_filtered", "list") + @selenium_test + def test_editor_place_comments(self): + editor = self.components.workflow_editor + self.workflow_create_new(annotation="simple workflow") + self.sleep_for(self.wait_types.UX_RENDER) + + tool_bar = editor.tool_bar._.wait_for_visible() + + # select text comment tool use all options and set font size to 2 + editor.tool_bar.tool(tool="text_comment").wait_for_and_click() + editor.tool_bar.toggle_bold.wait_for_and_click() + editor.tool_bar.toggle_italic.wait_for_and_click() + editor.tool_bar.colour(colour="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() + + # place text comment + self.mouse_drag(from_element=tool_bar, from_offset=(100, 100), to_offset=(500, 110)) + + self.action_chains().send_keys("Hello World").perform() + + # check if all options were applied + comment_content: WebElement = editor.comment.text_inner.wait_for_visible() + assert comment_content.text == "Hello World" + assert "bold" in comment_content.get_attribute("class") + assert "italic" in comment_content.get_attribute("class") + + # check for correct size + width, height = self.get_element_size(editor.comment._.wait_for_visible()) + + assert width == 500 + assert height == 110 + + editor.comment.text_comment.wait_for_and_click() + editor.comment.delete.wait_for_and_click() + editor.comment.text_comment.wait_for_absent() + + # place and test markdown comment + editor.tool_bar.tool(tool="markdown_comment").wait_for_and_click() + editor.tool_bar.colour(colour="lime").wait_for_and_click() + self.mouse_drag(from_element=tool_bar, from_offset=(100, 100), to_offset=(200, 220)) + self.action_chains().send_keys("# Hello World").perform() + + editor.tool_bar.tool(tool="pointer").wait_for_and_click() + + markdown_comment_content: WebElement = editor.comment.markdown_rendered.wait_for_visible() + assert markdown_comment_content.text == "Hello World" + assert markdown_comment_content.find_element(By.TAG_NAME, "h2") is not None + + width, height = self.get_element_size(editor.comment._.wait_for_visible()) + + assert width == 200 + assert height == 220 + + editor.comment.markdown_rendered.wait_for_and_click() + editor.comment.delete.wait_for_and_click() + editor.comment.markdown_comment.wait_for_absent() + + # place and test frame comment + editor.tool_bar.tool(tool="frame_comment").wait_for_and_click() + editor.tool_bar.colour(colour="blue").wait_for_and_click() + self.mouse_drag(from_element=tool_bar, from_offset=(100, 100), to_offset=(400, 300)) + self.action_chains().send_keys("My Frame").perform() + + title: WebElement = editor.comment.frame_title.wait_for_visible() + assert title.text == "My Frame" + + width, height = self.get_element_size(editor.comment._.wait_for_visible()) + + assert width == 400 + assert height == 300 + + editor.comment.frame_comment.wait_for_and_click() + editor.comment.delete.wait_for_and_click() + editor.comment.frame_comment.wait_for_absent() + + # test freehand and eraser + editor.tool_bar.tool(tool="freehand_pen").wait_for_and_click() + editor.tool_bar.colour(colour="green").wait_for_and_click() + editor.tool_bar.line_thickness.wait_for_and_click() + self.action_chains().send_keys(Keys.RIGHT * 20).perform() + + editor.tool_bar.smoothing.wait_for_and_click() + self.action_chains().send_keys(Keys.RIGHT * 10).perform() + + self.mouse_drag(from_element=tool_bar, from_offset=(100, 100), to_offset=(200, 200)) + + editor.comment.freehand_comment.wait_for_visible() + + editor.tool_bar.colour(colour="black").wait_for_and_click() + editor.tool_bar.line_thickness.wait_for_and_click() + self.action_chains().send_keys(Keys.LEFT * 20).perform() + self.mouse_drag(from_element=tool_bar, from_offset=(300, 300), via_offsets=[(100, 200)], to_offset=(-200, 30)) + + # test bulk remove freehand + editor.tool_bar.remove_freehand.wait_for_and_click() + editor.comment.freehand_comment.wait_for_absent() + + # 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() + editor.tool_bar.colour(colour="orange").wait_for_and_click() + + self.mouse_drag(from_element=tool_bar, from_offset=(100, 100), to_offset=(200, 200)) + + freehand_comment_a: WebElement = editor.comment.freehand_comment.wait_for_visible() + + # 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() + + editor.comment.freehand_comment.wait_for_absent() + + # delete by dragging + editor.tool_bar.tool(tool="freehand_pen").wait_for_and_click() + editor.tool_bar.colour(colour="yellow").wait_for_and_click() + + self.mouse_drag(from_element=tool_bar, from_offset=(100, 100), to_offset=(200, 200)) + + freehand_comment_b: WebElement = editor.comment.freehand_comment.wait_for_visible() + + editor.tool_bar.tool(tool="freehand_eraser").wait_for_and_click() + self.mouse_drag( + from_element=freehand_comment_b, from_offset=(100, -100), via_offsets=[(-100, 100)], to_offset=(-100, 100) + ) + + editor.comment.freehand_comment.wait_for_absent() + @selenium_test def test_editor_snapping(self): editor = self.components.workflow_editor @@ -1116,14 +1246,26 @@ def test_editor_snapping(self): def get_node_position(self, label: str): node = self.components.workflow_editor.node._(label=label).wait_for_present() - left: str = node.value_of_css_property("left") - top: str = node.value_of_css_property("top") + return self.get_element_position(node) + + def get_element_position(self, element: WebElement): + left = element.value_of_css_property("left") + top = element.value_of_css_property("top") left_stripped = "".join(char for char in left if char.isdigit()) top_stripped = "".join(char for char in top if char.isdigit()) return (int(left_stripped), int(top_stripped)) + def get_element_size(self, element: WebElement): + width = element.value_of_css_property("width") + height = element.value_of_css_property("height") + + width_stripped = "".join(char for char in width if char.isdigit()) + height_stripped = "".join(char for char in height if char.isdigit()) + + return (int(width_stripped), int(height_stripped)) + 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("#")