diff --git a/CHANGELOG.md b/CHANGELOG.md index cf21387..b6c7e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,40 @@ together). ### Added +- **Selecting four options selected one, and said nothing.** `Select` commits + through the control's value setter, which is correct and is also a + *replacement*. So the obvious spelling — four `Select` steps at one + `, so it has no options to select" + ))) + } + "not_multiple" => { + return Err(DriverError::Browser(format!( + "[{selector}] does not allow multiple options - select one option instead" + ))) + } + s if s.starts_with("no_option:") => { + return Err(DriverError::Browser(format!( + "no option matching '{}' in [{selector}] - the selection was left untouched", + &s["no_option:".len()..] + ))) + } + s if s.starts_with("ok:") => s["ok:".len()..].to_string(), + other => { + return Err(DriverError::Browser(format!( + "selecting options in [{selector}] returned no answer ({other:?})" + ))) + } + }; + // Verify the state took, exactly as `Check` does. A control that + // accepted the assignment and then re-derived its own selection + // (a framework re-render) must fail here rather than pass on the + // strength of having been asked. + let got: Vec<&str> = if selected.is_empty() { + Vec::new() + } else { + selected.split('\u{1f}').collect() + }; + if got.len() != values.len() { + return Err(DriverError::Browser(format!( + "selecting in [{selector}] did not take: asked for {} option(s), \ + the control now has {} ({})", + values.len(), + got.len(), + got.join(", ") + ))); + } + Ok(()) + } + fn clear_text(&mut self, selector: &UiaSelector) -> Result<(), DriverError> { let locator = Self::locator(selector)?; // Go through the native value setter so framework-controlled inputs diff --git a/crates/flowproof-agent/src/recorder.rs b/crates/flowproof-agent/src/recorder.rs index e82ea1f..387ff07 100644 --- a/crates/flowproof-agent/src/recorder.rs +++ b/crates/flowproof-agent/src/recorder.rs @@ -595,6 +595,21 @@ fn step_for(id: usize, intent: &str, app: &str, action: &ResolvedAction) -> Step selector_ref: Some(0), }), ), + ResolvedAction::SelectOptions { target, values } => { + let mut params = flowproof_trace::format::TypeTextParams { + // `text` keeps the FIRST option so the step still names + // something concrete in a reader that shows only text. + // `values` is authoritative wherever it is present - see + // docs/trace-format.md. + text: values.first().cloned().unwrap_or_default(), + submit: None, + extra: Default::default(), + }; + params + .extra + .insert("values".into(), serde_json::json!(values)); + (selectors_for(app, target, None), Action::TypeText(params)) + } ResolvedAction::Capture { target, name, @@ -929,6 +944,7 @@ fn action_selector(action: &ResolvedAction) -> Option { let target = match action { ResolvedAction::Press { target, .. } | ResolvedAction::TypeText { target, .. } + | ResolvedAction::SelectOptions { target, .. } | ResolvedAction::Upload { target, .. } | ResolvedAction::ContextClick { target, .. } | ResolvedAction::DoubleClick { target, .. } @@ -1940,6 +1956,11 @@ pub fn record_with_reuse( } match &action { ResolvedAction::Press { .. } => driver.invoke(targeted())?, + ResolvedAction::SelectOptions { values, .. } => { + // One commit for the whole set. A missing option fails + // the step in the driver, before anything is selected. + driver.select_options(targeted(), values)?; + } ResolvedAction::TypeText { text, .. } => { // `${captured.x}` and `${VAR}` both resolve at the moment // of typing; the trace only ever stores the reference diff --git a/crates/flowproof-agent/src/rules.rs b/crates/flowproof-agent/src/rules.rs index ec437eb..fb8c9a3 100644 --- a/crates/flowproof-agent/src/rules.rs +++ b/crates/flowproof-agent/src/rules.rs @@ -201,6 +201,10 @@ pub enum ResolvedAction { /// Read a target's text into a flow-scoped name for later comparison /// (`Remember the "Balance" as balance`). The VALUE never enters the /// trace - only the name does, exactly like a `${VAR}` secret. + /// Drive a ``: committed via the value setter, fires `input`+`change` (React-safe). `in the` and `… dropdown` also accepted | +| `Select "", "" and "" from the [2nd ]"