PICK_VALUES_E2E_PLAN

Pick Value Module — E2E Test Plan

Approach

Incremental tests, each building on the previous. Use existing Selenium/Playwright E2E infrastructure in lib/galaxy_test/selenium/test_workflow_editor.py. Tests go from simple (module exists in UI) to complex (full conditional workflow execution).

All tests added to TestWorkflowEditor class. Use @selenium_test decorator. All tests must work with both Selenium and Playwright backends — no @selenium_only.


Test 1: Add pick_value from palette

Goal: Module appears in palette and creates a node.

@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()

Validates: Palette entry, module registration, node rendering, icon.


Test 2: Mode selector in form panel

Goal: Clicking the node shows the mode dropdown, changing mode persists to saved workflow.

@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()
    # Change mode via vue-multiselect: click input, type label, ENTER
    mode_input = self.find_element_by_selector(
        "div.ui-form-element[id='form-element-mode'] input[type='text']"
    )
    mode_input.click()
    mode_input.send_keys("All non-null")
    self.send_enter(mode_input)
    self.sleep_for(self.wait_types.UX_RENDER)
    # Save and verify mode persisted
    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"

Validates: Form rendering, mode persistence, save round-trip.


Test 3: Input terminals present

Goal: Node shows at least 2 input terminals + 1 output terminal.

@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()

Validates: get_all_inputs() and get_all_outputs() via build_module.


Test 4: Connections to pick_value inputs

Goal: Can connect tool outputs to pick_value input terminals.

@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("cat")
    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")

Validates: Terminal connection mechanics, connector rendering.


Test 5: Grow-on-connect (dynamic terminal creation)

Goal: Connecting to the last empty terminal creates a new one.

@selenium_test
def test_pick_value_grow_on_connect(self):
    # Load a workflow with pick_value + 2 tool branches via YAML
    name = self.open_in_workflow_editor("""
class: GalaxyWorkflow
inputs:
  input_data: data
steps:
  branch_a:
    tool_id: cat1
    in:
      input1: input_data
  branch_b:
    tool_id: cat1
    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")
    # With 2 connections, there should be a 3rd empty terminal (input_2)
    pick_node.input_terminal(name="input_0").wait_for_present()
    pick_node.input_terminal(name="input_1").wait_for_present()
    pick_node.input_terminal(name="input_2").wait_for_present()

Validates: grow-on-connect watcher, build_module round-trip for terminal updates.


Test 6: Pick_value in conditional workflow (save + reload)

Goal: Full conditional workflow with pick_value saves and reloads correctly.

@selenium_test
def test_pick_value_conditional_workflow_roundtrip(self):
    yaml_content = """
class: GalaxyWorkflow
inputs:
  input_data: data
steps:
  branch_a:
    tool_id: cat1
    in:
      input1: input_data
    when: $(true)
  branch_b:
    tool_id: cat1
    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
"""
    name = self.open_in_workflow_editor(yaml_content)
    editor = self.components.workflow_editor
    pick_node = editor.node._(label="pick")
    pick_node.wait_for_present()
    # Verify connections survived import
    self.assert_connected("branch_a#out_file1", "pick#input_0")
    self.assert_connected("branch_b#out_file1", "pick#input_1")
    # Verify output terminal
    pick_node.output_terminal(name="output").wait_for_present()
    # Download and verify structure
    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

Validates: gxformat2 import, editor rendering, native format export, full round-trip.


Test 7: Output type changes with mode

Goal: Switching mode between scalar and collection changes the output terminal type.

@selenium_test
def test_pick_value_output_type_changes_with_mode(self):
    name = self.open_in_workflow_editor("""
class: GalaxyWorkflow
inputs:
  input_data: data
steps:
  branch_a:
    tool_id: cat1
    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()
    # Change mode to all_non_null (output becomes collection)
    mode_input = self.find_element_by_selector(
        "div.ui-form-element[id='form-element-mode'] input[type='text']"
    )
    mode_input.click()
    mode_input.send_keys("All non-null")
    self.send_enter(mode_input)
    self.sleep_for(self.wait_types.UX_RENDER)
    # Save and verify output type changed
    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"

Validates: Dynamic output type switching, build_module re-fetch on mode change.


Implementation Order

#TestDepends onComplexity
1Add from paletteLow
2Mode selection1Low
3Terminals present1Low
4Connect inputs3Medium
5Grow-on-connect4Medium
6Conditional roundtrip5Medium
7Output type changes2Medium

Tests 1-3 are independent and can be implemented first. Tests 4-7 build on earlier tests and exercise increasingly complex interactions.

Notes

Resolved Questions

1. Does workflow_editor_add_input(item_name="pick_value") work?

Yes. The selector chain:

The selector .workflow-input-button[data-id='pick_value'] matches.

2. How to interact with the mode <select> dropdown in E2E?

FormElement with type="select" renders FormSelection which uses vue-multiselect. Works with both Selenium and Playwright using the click + type

# Find the multiselect input inside the mode form element
mode_input = self.find_element_by_selector(
    "div.ui-form-element[id='form-element-mode'] input[type='text']"
)
mode_input.click()
mode_input.send_keys("All non-null")
self.send_enter(mode_input)

Playwright’s send_keys() focuses the element, positions cursor at end, and types — matching Selenium behavior. The vue-multiselect filters options as you type, and ENTER selects the filtered result. No action_chains needed.

3. Should E2E tests also verify execution?

No — keep E2E tests focused on editor interactions. The 9 API tests in test_workflows.py already comprehensively cover all execution modes and edge cases (all 4 modes, error paths, ordering, collection output). E2E execution tests would be slow, fragile, and redundant. The roundtrip test (Test 6) already validates that the workflow structure survives save/reload, which is the editor’s responsibility.