From 27d200e939a6cb8d952835e63fd1e5cbdda46fe2 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 11 Mar 2026 08:41:05 -0400 Subject: [PATCH] Add E2E tests for PickValueModule workflow editor 7 Selenium/Playwright tests: palette add, mode selection, terminals, connections, grow-on-connect, conditional roundtrip, output type change. Co-Authored-By: Claude Opus 4.6 --- .../Workflow/Editor/Forms/FormPickValue.vue | 8 +- .../selenium/test_workflow_editor.py | 172 ++++++++++++++++++ 2 files changed, 176 insertions(+), 4 deletions(-) diff --git a/client/src/components/Workflow/Editor/Forms/FormPickValue.vue b/client/src/components/Workflow/Editor/Forms/FormPickValue.vue index c626bc81d31..de379589288 100644 --- a/client/src/components/Workflow/Editor/Forms/FormPickValue.vue +++ b/client/src/components/Workflow/Editor/Forms/FormPickValue.vue @@ -37,10 +37,10 @@ function cleanToolState(): ToolState { const emit = defineEmits(["onChange"]); const modeOptions = [ - { value: "first_non_null", label: "First non-null (error if all null)" }, - { value: "first_or_skip", label: "First non-null (skip if all null)" }, - { value: "the_only_non_null", label: "The only non-null (error if != 1)" }, - { value: "all_non_null", label: "All non-null (as collection)" }, + ["First non-null (error if all null)", "first_non_null"], + ["First non-null (skip if all null)", "first_or_skip"], + ["The only non-null (error if != 1)", "the_only_non_null"], + ["All non-null (as collection)", "all_non_null"], ]; function onMode(newMode: string) { diff --git a/lib/galaxy_test/selenium/test_workflow_editor.py b/lib/galaxy_test/selenium/test_workflow_editor.py index 0c791d161cb..fa8b8325f23 100644 --- a/lib/galaxy_test/selenium/test_workflow_editor.py +++ b/lib/galaxy_test/selenium/test_workflow_editor.py @@ -946,6 +946,178 @@ steps: workflow = self.workflow_populator.download_workflow(workflow_id) return workflow + def _pick_value_select_mode(self, label): + mode_selector = "div.ui-form-element[id='form-element-mode']" + container = self.wait_for_selector(mode_selector) + trigger = container.find_element(By.CSS_SELECTOR, ".multiselect__select") + trigger.click() + self.sleep_for(self.wait_types.UX_RENDER) + js = """ + var label = arguments[0]; + var container = document.querySelector('#form-element-mode'); + var items = container.querySelectorAll('.multiselect__element'); + for (var i = 0; i < items.length; i++) { + if (items[i].textContent.trim() === label) { + items[i].querySelector('.multiselect__option').click(); + return true; + } + } + return false; + """ + result = self.execute_script(js, label) + assert result, f"Mode option '{label}' not found" + + @selenium_test + def test_pick_value_add_from_palette(self): + self.workflow_create_new(annotation="pick value test") + self.workflow_editor_add_input(item_name="pick_value") + editor = self.components.workflow_editor + editor.node._(label="Pick Value").wait_for_present() + + @selenium_test + def test_pick_value_mode_selection(self): + self.workflow_create_new(annotation="pick value mode test") + self.workflow_editor_add_input(item_name="pick_value") + editor = self.components.workflow_editor + node = editor.node._(label="Pick Value") + node.wait_for_and_click() + self._pick_value_select_mode("All non-null (as collection)") + self.sleep_for(self.wait_types.UX_RENDER) + self.assert_workflow_has_changes_and_save() + workflow = self._download_current_workflow() + pick_step = [s for s in workflow["steps"].values() if s["type"] == "pick_value"][0] + tool_state = json.loads(pick_step["tool_state"]) + assert tool_state["mode"] == "all_non_null" + + @selenium_test + def test_pick_value_terminals(self): + self.workflow_create_new(annotation="pick value terminals test") + self.workflow_editor_add_input(item_name="pick_value") + editor = self.components.workflow_editor + node = editor.node._(label="Pick Value") + node.input_terminal(name="input_0").wait_for_present() + node.input_terminal(name="input_1").wait_for_present() + node.output_terminal(name="output").wait_for_present() + + @selenium_test + def test_pick_value_connect_inputs(self): + self.workflow_create_new(annotation="pick value connections test") + self.workflow_editor_add_input(item_name="data_input") + editor = self.components.workflow_editor + editor.label_input.wait_for_and_send_keys("input_data") + self.tool_open("cat1") + self.sleep_for(self.wait_types.UX_RENDER) + editor.label_input.wait_for_and_send_keys("branch_a") + self.workflow_editor_add_input(item_name="pick_value") + editor.label_input.wait_for_and_send_keys("pick") + self.components.workflow_editor.tool_bar.auto_layout.wait_for_and_click() + self.sleep_for(self.wait_types.UX_RENDER) + self.workflow_editor_connect("input_data#output", "branch_a#input1") + self.workflow_editor_connect("branch_a#out_file1", "pick#input_0") + self.assert_connected("branch_a#out_file1", "pick#input_0") + + @selenium_test + def test_pick_value_grow_on_connect(self): + self.open_in_workflow_editor( + """ +class: GalaxyWorkflow +inputs: + input_data: data +steps: + branch_a: + tool_id: cat + in: + input1: input_data + branch_b: + tool_id: cat + in: + input1: input_data + pick: + type: pick_value + state: + mode: first_non_null + in: + input_0: branch_a/out_file1 + input_1: branch_b/out_file1 +""" + ) + editor = self.components.workflow_editor + pick_node = editor.node._(label="pick") + pick_node.input_terminal(name="input_0").wait_for_present() + pick_node.input_terminal(name="input_1").wait_for_present() + # With 2 connections, grow-on-connect should have created a 3rd empty terminal + pick_node.input_terminal(name="input_2").wait_for_present() + + @selenium_test + def test_pick_value_conditional_workflow_roundtrip(self): + self.open_in_workflow_editor( + """ +class: GalaxyWorkflow +inputs: + input_data: data +steps: + branch_a: + tool_id: cat + in: + input1: input_data + when: $(true) + branch_b: + tool_id: cat + in: + input1: input_data + when: $(false) + pick: + type: pick_value + state: + mode: first_non_null + in: + input_0: branch_a/out_file1 + input_1: branch_b/out_file1 +""" + ) + editor = self.components.workflow_editor + pick_node = editor.node._(label="pick") + pick_node.wait_for_present() + self.assert_connected("branch_a#out_file1", "pick#input_0") + self.assert_connected("branch_b#out_file1", "pick#input_1") + pick_node.output_terminal(name="output").wait_for_present() + workflow = self._download_current_workflow() + pick_step = [s for s in workflow["steps"].values() if s["type"] == "pick_value"][0] + tool_state = json.loads(pick_step["tool_state"]) + assert tool_state["mode"] == "first_non_null" + assert len(pick_step["input_connections"]) == 2 + + @selenium_test + def test_pick_value_output_type_changes_with_mode(self): + self.open_in_workflow_editor( + """ +class: GalaxyWorkflow +inputs: + input_data: data +steps: + branch_a: + tool_id: cat + in: + input1: input_data + pick: + type: pick_value + state: + mode: first_non_null + in: + input_0: branch_a/out_file1 +""" + ) + editor = self.components.workflow_editor + pick_node = editor.node._(label="pick") + pick_node.wait_for_and_click() + self._pick_value_select_mode("All non-null (as collection)") + self.sleep_for(self.wait_types.UX_RENDER) + self.assert_workflow_has_changes_and_save() + workflow = self._download_current_workflow() + pick_step = [s for s in workflow["steps"].values() if s["type"] == "pick_value"][0] + tool_state = json.loads(pick_step["tool_state"]) + assert tool_state["mode"] == "all_non_null" + @selenium_test def test_editor_create_conditional_step(self): editor = self.components.workflow_editor