Skip to content

Commit 3ce98a0

Browse files
committed
Preserve falsy workflow parameter values on workflow rerun
Re-running a workflow invocation through the simple run form silently discarded `false`, `0`, and `""` values for `parameter_input` steps and fell back to the workflow's default. The rerun-state hydration in WorkflowRunFormSimple.vue used truthiness checks (`if (requestState[k])` and `if (value)`) where it should have checked for key presence and `!== undefined`. As a result a boolean parameter set to `false` on the original run came back as the default `true` on rerun, with no "Changed Input" badge to flag the discrepancy. Switch the lookup to `key in requestState` and the assignment guard to `value !== undefined` so falsy-but-defined values are preserved. Add a selenium regression test that runs gx_boolean with the parameter toggled to false, reruns, and asserts both the form state (preserved false, no changed-input badge; toggling to true raises the badge) and the new invocation's Inputs tab.
1 parent feccf57 commit 3ce98a0

5 files changed

Lines changed: 105 additions & 7 deletions

File tree

client/src/components/Workflow/Run/WorkflowRunFormSimple.vue

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,18 @@ const formInputs = computed(() => {
161161
if (props.requestState) {
162162
if (props.isRerun) {
163163
const requestStateKeys = Object.keys(props.requestState);
164+
const stateKey = String(rerunStateIndex);
164165
165166
let value;
166-
if (props.requestState[rerunStateIndex]) {
167+
if (stateKey in props.requestState) {
167168
// request state has the step_label as key
168-
value = props.requestState[rerunStateIndex];
169-
} else if (requestStateKeys[i] !== undefined && requestStateKeys[i] === "") {
169+
value = props.requestState[stateKey];
170+
} else if (requestStateKeys[i] === "") {
170171
// request state has "" as key on the `i` position
171172
value = Object.values(props.requestState)[i];
172173
}
173174
174-
if (value) {
175+
if (value !== undefined) {
175176
if (stepType === "data_input" || stepType === "data_collection_input") {
176177
// Note: This is different from workflow landings because `WorkflowInvocationRequestModel`
177178
// does not provide an object with `values` property.
@@ -182,9 +183,8 @@ const formInputs = computed(() => {
182183
stepAsInput.value = value;
183184
}
184185
}
185-
} else if (props.requestState[stepLabel]) {
186-
const value = props.requestState[stepLabel];
187-
stepAsInput.value = value;
186+
} else if (String(stepLabel) in props.requestState) {
187+
stepAsInput.value = props.requestState[String(stepLabel)];
188188
}
189189
}
190190

client/src/utils/navigation/navigation.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ tool_form:
648648
parameter_div: 'div.ui-form-element[id="form-element-${parameter}"]'
649649
parameter_error: 'div.ui-form-element[id="form-element-${parameter}"] .ui-form-error-text'
650650
parameter_checkbox: 'div.ui-form-element[id="form-element-${parameter}"] .ui-switch'
651+
parameter_checkbox_input: 'div.ui-form-element[id="form-element-${parameter}"] input[type="checkbox"]'
651652
parameter_select: 'div.ui-form-element[id="form-element-${parameter}"] .multiselect'
652653
parameter_input: 'div.ui-form-element[id="form-element-${parameter}"] .ui-input'
653654
parameter_textarea: 'div.ui-form-element[id="form-element-${parameter}"] textarea'

lib/galaxy/selenium/playwright_element.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ def is_enabled(self) -> bool:
110110
"""
111111
return self._element.is_enabled()
112112

113+
def is_selected(self) -> bool:
114+
"""
115+
Check if a checkbox, radio button, or option is selected.
116+
117+
Playwright's is_checked() only handles checkbox/radio inputs and raises
118+
for <option> elements, so fall back to evaluating the element's
119+
``checked``/``selected`` property to mirror Selenium's behavior.
120+
"""
121+
return bool(self._element.evaluate("(el) => !!(el.checked || el.selected)"))
122+
113123
def submit(self) -> None:
114124
"""
115125
Submit a form element.

lib/galaxy/selenium/web_element_protocol.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ def is_enabled(self) -> bool:
6262
"""Check if the element is enabled (not disabled)."""
6363
...
6464

65+
def is_selected(self) -> bool:
66+
"""Check if a checkbox, radio, or option element is selected."""
67+
...
68+
6569
def submit(self) -> None:
6670
"""Submit a form element."""
6771
...

lib/galaxy_test/selenium/test_workflow_rerun.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@
99
UsesHistoryItemAssertions,
1010
)
1111

12+
WORKFLOW_BOOLEAN_PARAMETER_DEFAULT_TRUE = """
13+
class: GalaxyWorkflow
14+
inputs:
15+
bool_param:
16+
type: boolean
17+
default: true
18+
steps:
19+
echo_bool:
20+
tool_id: gx_boolean
21+
in:
22+
parameter: bool_param
23+
"""
24+
1225

1326
class TestWorkflowRun(SeleniumTestCase, UsesHistoryItemAssertions, RunsWorkflows):
1427
ensure_registered = True
@@ -65,6 +78,76 @@ def test_workflow_rerun(self):
6578
form_element.changed_value_badge.wait_for_visible()
6679
self.screenshot("workflow_rerun_changed_input")
6780

81+
@selenium_test
82+
@managed_history
83+
def test_workflow_rerun_preserves_false_boolean_parameter(self):
84+
"""Regression test for boolean workflow parameters reverting to default on rerun.
85+
86+
Setting a boolean ``parameter_input`` to ``false`` (overriding a
87+
``default: true``), running the workflow, and re-running the invocation
88+
previously caused the form to silently fall back to the default value
89+
because of a falsy-value check in WorkflowRunFormSimple.vue. The form
90+
should instead show the value the user actually used.
91+
"""
92+
invocations = self.components.invocations
93+
94+
self.workflow_run_open_workflow(WORKFLOW_BOOLEAN_PARAMETER_DEFAULT_TRUE)
95+
96+
# Override the boolean default of true to false before submitting.
97+
# The boolean is the workflow's only input, hence step_index 0.
98+
self._toggle_workflow_run_boolean(parameter="0", target=False)
99+
self.screenshot("workflow_run_boolean_false_before_submit")
100+
101+
self.workflow_run_submit()
102+
self.sleep_for(self.wait_types.UX_TRANSITION)
103+
self.workflow_run_wait_for_ok(hid=1)
104+
105+
# Submitting lands us on the new invocation page; the rerun button is
106+
# right there on the invocation state details. We're still in the
107+
# invocation's history, so no "switch history" confirmation appears.
108+
invocations.state_details.wait_for_visible()
109+
invocations.workflow_rerun_button.wait_for_and_click()
110+
self.sleep_for(self.wait_types.UX_TRANSITION)
111+
112+
# The boolean should be preserved as false on the rerun form rather than
113+
# silently reset to the workflow's default of true.
114+
checkbox = self.components.tool_form.parameter_checkbox_input(parameter="0").wait_for_present()
115+
assert (
116+
not checkbox.is_selected()
117+
), "Boolean parameter input was reset to its default 'true' instead of preserving the previous run's 'false' value"
118+
119+
# Without changes, the boolean should not be flagged as a changed input.
120+
form_element = self.components.workflow_run.form_element._(index=0)
121+
form_element.changed_value_badge.assert_absent()
122+
self.screenshot("workflow_rerun_boolean_preserved_false")
123+
124+
# Toggling the value back to true should flag it as changed.
125+
self._toggle_workflow_run_boolean(parameter="0", target=True)
126+
form_element.changed_value_badge.wait_for_visible()
127+
self.screenshot("workflow_rerun_boolean_changed_input")
128+
129+
# Submit the rerun and verify the new invocation actually recorded the
130+
# value shown in the form (true) on its Inputs tab.
131+
self.workflow_run_submit()
132+
self.sleep_for(self.wait_types.UX_TRANSITION)
133+
self.workflow_run_wait_for_ok(hid=2)
134+
invocations.state_details.wait_for_visible()
135+
invocations.invocation_tab(label="Inputs").wait_for_and_click()
136+
self._assert_input_table_has_parameter("bool_param", "true")
137+
self.screenshot("workflow_rerun_boolean_inputs_tab")
138+
139+
@retry_assertion_during_transitions
140+
def _assert_input_table_has_parameter(self, label: str, value: str):
141+
table = self.wait_for_selector('[data-description="input table"]')
142+
text = table.text
143+
assert label in text, f"Expected input label '{label}' in inputs table, got: {text!r}"
144+
assert value in text, f"Expected input value '{value}' in inputs table, got: {text!r}"
145+
146+
def _toggle_workflow_run_boolean(self, parameter: str, target: bool):
147+
checkbox = self.components.tool_form.parameter_checkbox_input(parameter=parameter).wait_for_present()
148+
if checkbox.is_selected() != target:
149+
self.execute_script("arguments[0].click();", checkbox)
150+
68151
@retry_assertion_during_transitions
69152
def _assert_history_name_is(self, expected_name=None):
70153
name = self.history_panel_name()

0 commit comments

Comments
 (0)