use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Scenario {
#[serde(default)]
pub config: ScenarioConfig,
#[serde(default)]
pub definitions: Vec<AssertDefinition>,
#[serde(default)]
pub test: Vec<TestGroup>,
}
#[derive(Debug, Deserialize, Default, Clone)]
pub struct ScenarioConfig {
pub base_url: Option<String>,
pub llm_url: Option<String>,
pub llm_model: Option<String>,
#[serde(default)]
pub llm_api_key: Option<String>,
#[serde(default, deserialize_with = "deserialize_headers")]
pub llm_headers: HashMap<String, String>,
pub browser_headless: Option<bool>,
pub timeout_secs: Option<u64>,
pub viewport_width: Option<u32>,
pub viewport_height: Option<u32>,
pub start_url: Option<String>,
#[serde(default = "default_auto_navigate")]
pub auto_navigate: bool,
#[serde(default = "default_temperature")]
pub temperature: f64,
#[serde(default = "default_thinking")]
pub thinking: bool,
}
fn deserialize_headers<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw: Option<serde_json::Value> = Option::deserialize(deserializer)?;
let Some(json) = raw else {
return Ok(HashMap::new());
};
let serde_json::Value::Object(obj) = json else {
return Ok(HashMap::new());
};
Ok(obj
.into_iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k, s.to_owned())))
.collect())
}
const fn default_auto_navigate() -> bool {
true
}
const fn default_temperature() -> f64 {
0.0
}
const fn default_thinking() -> bool {
false
}
#[derive(Debug, Deserialize, Clone)]
pub struct AssertDefinition {
pub name: String,
#[serde(default)]
pub preset: Option<String>,
#[serde(default)]
pub prompt: Option<String>,
#[serde(default)]
pub assert_text: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct TestGroup {
pub name: String,
#[serde(default)]
pub start_url: Option<String>,
#[serde(default)]
pub auto_navigate: Option<bool>,
#[serde(default)]
pub base_url: Option<String>,
#[serde(default)]
pub timeout_secs: Option<u64>,
#[serde(default)]
pub browser_headless: Option<bool>,
#[serde(default)]
pub steps: Vec<TestStep>,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "kind")]
pub enum TestStep {
#[serde(rename = "navigate")]
Navigate {
url: String,
#[serde(default)]
wait_after_ms: Option<u64>,
},
#[serde(rename = "click")]
Click {
target: String,
#[serde(default)]
selector: Option<String>,
#[serde(default)]
wait_after_ms: Option<u64>,
},
#[serde(rename = "type")]
Type {
target: String,
text: String,
#[serde(default)]
selector: Option<String>,
#[serde(default)]
wait_after_ms: Option<u64>,
},
#[serde(rename = "wait")]
Wait {
target: String,
#[serde(default)]
selector: Option<String>,
#[serde(default)]
timeout_ms: Option<u64>,
},
#[serde(rename = "assert")]
Assert {
#[serde(default)]
definition: Option<String>,
#[serde(default)]
preset: Option<String>,
#[serde(default)]
prompt: Option<String>,
#[serde(default)]
assert_text: Option<String>,
},
#[serde(rename = "screenshot")]
Screenshot {
#[serde(default)]
path: Option<String>,
},
}