use crate::protocol::proxy::ProxySettings;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::collections::HashMap;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LaunchOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub channel: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chromium_sandbox: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub devtools: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub downloads_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub env: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub executable_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firefox_user_prefs: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle_sighup: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle_sigint: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle_sigterm: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headless: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore_default_args: Option<IgnoreDefaultArgs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy: Option<ProxySettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_mo: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub traces_dir: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum IgnoreDefaultArgs {
Bool(bool),
Array(Vec<String>),
}
impl LaunchOptions {
pub fn new() -> Self {
Self::default()
}
pub fn args(mut self, args: Vec<String>) -> Self {
self.args = Some(args);
self
}
pub fn channel(mut self, channel: String) -> Self {
self.channel = Some(channel);
self
}
pub fn chromium_sandbox(mut self, enabled: bool) -> Self {
self.chromium_sandbox = Some(enabled);
self
}
pub fn devtools(mut self, enabled: bool) -> Self {
self.devtools = Some(enabled);
self
}
pub fn downloads_path(mut self, path: String) -> Self {
self.downloads_path = Some(path);
self
}
pub fn env(mut self, env: HashMap<String, String>) -> Self {
self.env = Some(env);
self
}
pub fn executable_path(mut self, path: String) -> Self {
self.executable_path = Some(path);
self
}
pub fn firefox_user_prefs(mut self, prefs: HashMap<String, Value>) -> Self {
self.firefox_user_prefs = Some(prefs);
self
}
pub fn handle_sighup(mut self, enabled: bool) -> Self {
self.handle_sighup = Some(enabled);
self
}
pub fn handle_sigint(mut self, enabled: bool) -> Self {
self.handle_sigint = Some(enabled);
self
}
pub fn handle_sigterm(mut self, enabled: bool) -> Self {
self.handle_sigterm = Some(enabled);
self
}
pub fn headless(mut self, enabled: bool) -> Self {
self.headless = Some(enabled);
self
}
pub fn ignore_default_args(mut self, args: IgnoreDefaultArgs) -> Self {
self.ignore_default_args = Some(args);
self
}
pub fn proxy(mut self, proxy: ProxySettings) -> Self {
self.proxy = Some(proxy);
self
}
pub fn slow_mo(mut self, ms: f64) -> Self {
self.slow_mo = Some(ms);
self
}
pub fn timeout(mut self, ms: f64) -> Self {
self.timeout = Some(ms);
self
}
pub fn traces_dir(mut self, path: String) -> Self {
self.traces_dir = Some(path);
self
}
pub(crate) fn normalize(self) -> Value {
let mut value =
serde_json::to_value(&self).expect("serialization of LaunchOptions cannot fail");
if value.get("timeout").is_none() {
value["timeout"] = json!(crate::DEFAULT_TIMEOUT_MS);
}
if let Some(env_map) = value.get_mut("env")
&& let Some(map) = env_map.as_object()
{
let env_array: Vec<_> = map
.iter()
.map(|(k, v)| json!({"name": k, "value": v}))
.collect();
*env_map = json!(env_array);
}
if let Some(ignore) = value.get("ignoreDefaultArgs")
&& let Some(b) = ignore.as_bool()
{
if b {
value["ignoreAllDefaultArgs"] = json!(true);
}
value
.as_object_mut()
.expect("params is a JSON object")
.remove("ignoreDefaultArgs");
}
value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_launch_options_default() {
let opts = LaunchOptions::default();
assert!(opts.headless.is_none());
assert!(opts.args.is_none());
}
#[test]
fn test_launch_options_builder() {
let opts = LaunchOptions::default()
.headless(false)
.slow_mo(100.0)
.args(vec!["--no-sandbox".to_string()]);
assert_eq!(opts.headless, Some(false));
assert_eq!(opts.slow_mo, Some(100.0));
assert_eq!(opts.args, Some(vec!["--no-sandbox".to_string()]));
}
#[test]
fn test_launch_options_normalize_env() {
let opts = LaunchOptions::default().env(HashMap::from([
("FOO".to_string(), "bar".to_string()),
("BAZ".to_string(), "qux".to_string()),
]));
let normalized = opts.normalize();
assert!(normalized["env"].is_array());
let env_array = normalized["env"].as_array().unwrap();
assert_eq!(env_array.len(), 2);
let names: Vec<_> = env_array
.iter()
.map(|v| v["name"].as_str().unwrap())
.collect();
assert!(names.contains(&"FOO"));
assert!(names.contains(&"BAZ"));
}
#[test]
fn test_launch_options_normalize_ignore_default_args_bool() {
let opts = LaunchOptions::default().ignore_default_args(IgnoreDefaultArgs::Bool(true));
let normalized = opts.normalize();
assert!(normalized["ignoreAllDefaultArgs"].as_bool().unwrap());
assert!(normalized.get("ignoreDefaultArgs").is_none());
}
#[test]
fn test_launch_options_normalize_ignore_default_args_array() {
let opts = LaunchOptions::default()
.ignore_default_args(IgnoreDefaultArgs::Array(vec!["--foo".to_string()]));
let normalized = opts.normalize();
assert!(normalized["ignoreDefaultArgs"].is_array());
assert_eq!(
normalized["ignoreDefaultArgs"][0].as_str().unwrap(),
"--foo"
);
}
#[test]
fn test_proxy_settings() {
let proxy = ProxySettings {
server: "http://proxy:8080".to_string(),
bypass: Some("localhost,127.0.0.1".to_string()),
username: Some("user".to_string()),
password: Some("pass".to_string()),
};
let opts = LaunchOptions::default().proxy(proxy);
assert!(opts.proxy.is_some());
}
#[test]
fn test_builder_pattern_chaining() {
let opts = LaunchOptions::new()
.headless(true)
.slow_mo(50.0)
.timeout(60000.0)
.args(vec![
"--no-sandbox".to_string(),
"--disable-gpu".to_string(),
])
.channel("chrome".to_string());
assert_eq!(opts.headless, Some(true));
assert_eq!(opts.slow_mo, Some(50.0));
assert_eq!(opts.timeout, Some(60000.0));
assert_eq!(opts.args.as_ref().unwrap().len(), 2);
assert_eq!(opts.channel, Some("chrome".to_string()));
}
}