Skip to content

Commit ea9fde1

Browse files
committed
Add selenium test for creating and running custom tool
1 parent 9b0d97e commit ea9fde1

File tree

3 files changed

+151
-1
lines changed

3 files changed

+151
-1
lines changed

client/src/components/Tool/CustomToolEditor.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { faSave } from "@fortawesome/free-regular-svg-icons";
3+
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
24
import { loader, useMonaco, VueMonacoEditor } from "@guolao/vue-monaco-editor";
35
import * as monaco from "monaco-editor";
46
import { nextTick, onUnmounted, ref, watch } from "vue";
@@ -111,7 +113,14 @@ async function saveTool() {
111113
</b-alert>
112114
<div class="d-flex flex-gapx-1">
113115
<Heading h1 separator inline size="lg" class="flex-grow-1 mb-2">Tool Editor</Heading>
114-
<b-button variant="primary" size="m" @click="saveTool">Save</b-button>
116+
<b-button
117+
variant="primary"
118+
size="m"
119+
title="Save Custom Tool"
120+
data-description="save custom tool"
121+
@click="saveTool"
122+
><FontAwesomeIcon :icon="faSave"
123+
/></b-button>
115124
</div>
116125
<VueMonacoEditor
117126
v-model="yamlRepresentation"

client/src/utils/navigation/navigation.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,17 @@ tools:
10451045
clear_search: '#toolbox-panel .search-clear'
10461046
title: '.toolSectionTitle'
10471047

1048+
custom_tools:
1049+
selectors:
1050+
activity: '#activity-user-defined-tools'
1051+
create_button:
1052+
selector: 'create new custom tool'
1053+
type: data-description
1054+
save_button:
1055+
selector: 'save custom tool'
1056+
type: data-description
1057+
tool_link: "#g-card-custom-tool-${tool_uuid}"
1058+
10481059
admin:
10491060
allowlist:
10501061
selectors:
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""Selenium tests for custom tool creation and management."""
2+
3+
import platform
4+
5+
from selenium.webdriver.common.action_chains import ActionChains
6+
from selenium.webdriver.common.keys import Keys
7+
8+
from .framework import (
9+
selenium_test,
10+
SeleniumTestCase,
11+
)
12+
13+
14+
class TestCustomTools(SeleniumTestCase):
15+
ensure_registered = True
16+
17+
def assert_baseline_accessibility(self):
18+
"""Skip accessibility checks for custom tools tests due to Monaco editor issues."""
19+
pass
20+
21+
@selenium_test
22+
def test_create_custom_tool(self):
23+
"""Test creating a new custom tool through the UI."""
24+
with self.dataset_populator.user_tool_execute_permissions():
25+
tool_uuid = self.create_new_custom_tool()
26+
assert tool_uuid, "Tool UUID should be returned after saving."
27+
self.components.custom_tools.tool_link(tool_uuid=tool_uuid).wait_for_clickable()
28+
29+
@selenium_test
30+
def test_run_custom_tool(self):
31+
test_path = self.get_filename("1.fasta")
32+
self.perform_upload(test_path, on_current_page=True)
33+
self.history_panel_wait_for_hid_ok(1)
34+
with self.dataset_populator.user_tool_execute_permissions():
35+
tool_uuid = self.create_new_custom_tool()
36+
assert tool_uuid, "Tool UUID should be returned after saving."
37+
self.components.custom_tools.tool_link(tool_uuid=tool_uuid).wait_for_and_click()
38+
self.sleep_for(self.wait_types.UX_RENDER)
39+
self.components.tool_form.execute.wait_for_and_click()
40+
self.history_panel_wait_for_hid_ok(2)
41+
self.hda_click_primary_action_button(2, "rerun")
42+
self.components.tool_form.execute.wait_for_and_click()
43+
self.history_panel_wait_for_hid_ok(3)
44+
45+
def create_new_custom_tool(self) -> str:
46+
self.home()
47+
self.open_tool_editor()
48+
self.paste_tool()
49+
return self.save_tool()
50+
51+
def open_tool_editor(self):
52+
# Navigate via Custom Tools activity panel
53+
self.components.custom_tools.activity.wait_for_and_click()
54+
# Use the component selector for the create button
55+
self.components.custom_tools.create_button.wait_for_and_click()
56+
# Wait for the Tool Editor heading to appear
57+
self.wait_for_selector_visible("h1")
58+
self.wait_for_selector_visible(".monaco-editor")
59+
60+
def save_tool(self) -> str:
61+
self.components.custom_tools.save_button.wait_for_and_click()
62+
# Wait for save operation to complete
63+
self.sleep_for(self.wait_types.UX_TRANSITION)
64+
# Verify save was successful
65+
current_url = self.driver.current_url
66+
return current_url.split("/tools/editor/")[1]
67+
68+
def paste_tool(self):
69+
# Define a simple custom tool YAML
70+
tool_yaml_one = """class: GalaxyUserTool
71+
id: test_cat_tool
72+
name: Test Cat Tool
73+
version: "0.1"
74+
description: Concatenate test files
75+
container: busybox
76+
shell_command: |
77+
cat $(inputs.datasets.map((input) => input.path).join(' ')) > output.txt
78+
79+
"""
80+
81+
tool_yaml_two = """
82+
inputs:
83+
- name: datasets
84+
multiple: true
85+
type: data
86+
87+
"""
88+
tool_yaml_three = (
89+
"""
90+
outputs:
91+
- name: output1
92+
type: data
93+
format_source: datasets
94+
from_work_dir: output.txt
95+
"""
96+
""
97+
)
98+
# Try finding Monaco editor textarea and replace skeleton content
99+
self.sleep_for(self.wait_types.UX_RENDER) # Allow editor to focus
100+
editor = self.wait_for_selector_visible(".monaco-editor div.view-line")
101+
102+
# Focus the editor first
103+
editor.click()
104+
self.sleep_for(self.wait_types.UX_RENDER) # Allow editor to focus
105+
106+
is_mac = platform.system() == "Darwin"
107+
modifier_key = Keys.COMMAND if is_mac else Keys.CONTROL
108+
109+
action_chains = ActionChains(self.driver)
110+
111+
# Select all content
112+
action_chains.key_down(modifier_key)
113+
action_chains.send_keys("a")
114+
action_chains.key_up(modifier_key)
115+
action_chains.perform()
116+
117+
# Delete selected content
118+
action_chains = ActionChains(self.driver)
119+
action_chains.send_keys(Keys.DELETE)
120+
action_chains.perform()
121+
122+
# Now insert the new content
123+
# yaml is split in funky was to accomodate guided yaml text input in monaco
124+
action_chains = ActionChains(self.driver)
125+
action_chains.send_keys(tool_yaml_one)
126+
action_chains.send_keys(Keys.BACKSPACE)
127+
action_chains.send_keys(tool_yaml_two)
128+
action_chains.send_keys(Keys.BACKSPACE)
129+
action_chains.send_keys(tool_yaml_three)
130+
action_chains.perform()

0 commit comments

Comments
 (0)