browser_automation_cli/
constants.rs1#[derive(Debug, Clone, Copy)]
5pub struct NetworkPreset {
6 pub name: &'static str,
8 pub offline: bool,
10 pub latency_ms: f64,
12 pub download_throughput: f64,
14 pub upload_throughput: f64,
16}
17
18pub const NETWORK_PRESETS: &[NetworkPreset] = &[
20 NetworkPreset {
21 name: "No throttling",
22 offline: false,
23 latency_ms: 0.0,
24 download_throughput: -1.0,
25 upload_throughput: -1.0,
26 },
27 NetworkPreset {
28 name: "Offline",
29 offline: true,
30 latency_ms: 0.0,
31 download_throughput: 0.0,
32 upload_throughput: 0.0,
33 },
34 NetworkPreset {
35 name: "Slow 3G",
36 offline: false,
37 latency_ms: 400.0,
38 download_throughput: (500.0 * 1024.0) / 8.0 * 0.8,
39 upload_throughput: (500.0 * 1024.0) / 8.0 * 0.8,
40 },
41 NetworkPreset {
42 name: "Fast 3G",
43 offline: false,
44 latency_ms: 150.0,
45 download_throughput: (1.6 * 1024.0 * 1024.0) / 8.0 * 0.9,
46 upload_throughput: (750.0 * 1024.0) / 8.0 * 0.9,
47 },
48 NetworkPreset {
49 name: "Slow 4G",
50 offline: false,
51 latency_ms: 20.0,
52 download_throughput: (1.6 * 1024.0 * 1024.0) / 8.0,
53 upload_throughput: (750.0 * 1024.0) / 8.0,
54 },
55 NetworkPreset {
56 name: "Fast 4G",
57 offline: false,
58 latency_ms: 20.0,
59 download_throughput: (9.0 * 1024.0 * 1024.0) / 8.0,
60 upload_throughput: (1.5 * 1024.0 * 1024.0) / 8.0,
61 },
62];
63
64pub fn network_preset_by_name(name: &str) -> Option<&'static NetworkPreset> {
66 let n = name.trim();
67 NETWORK_PRESETS
68 .iter()
69 .find(|p| p.name.eq_ignore_ascii_case(n))
70}
71
72pub fn network_preset_names() -> Vec<&'static str> {
74 NETWORK_PRESETS.iter().map(|p| p.name).collect()
75}
76
77#[derive(Debug, Clone, PartialEq)]
79pub struct ViewportSpec {
80 pub width: i32,
82 pub height: i32,
84 pub device_scale_factor: f64,
86 pub mobile: bool,
88 pub has_touch: bool,
90 pub is_landscape: bool,
92}
93
94pub fn parse_viewport_spec(raw: &str) -> Result<ViewportSpec, String> {
96 let mut parts = raw.split(',').map(|s| s.trim()).filter(|s| !s.is_empty());
97 let dims = parts
98 .next()
99 .ok_or_else(|| "viewport empty; expected WxHxDPR".to_string())?;
100 let mut nums = dims.split('x').map(|s| s.trim());
101 let width: i32 = nums
102 .next()
103 .ok_or("viewport missing width")?
104 .parse()
105 .map_err(|_| "viewport width must be integer")?;
106 let height: i32 = nums
107 .next()
108 .ok_or("viewport missing height")?
109 .parse()
110 .map_err(|_| "viewport height must be integer")?;
111 let device_scale_factor: f64 = nums
112 .next()
113 .map(|s| {
114 s.parse()
115 .map_err(|_| "viewport dpr must be number".to_string())
116 })
117 .transpose()?
118 .unwrap_or(1.0);
119 let mut mobile = false;
120 let mut has_touch = false;
121 let mut is_landscape = false;
122 for flag in parts {
123 match flag.to_ascii_lowercase().as_str() {
124 "mobile" => mobile = true,
125 "touch" => has_touch = true,
126 "landscape" => is_landscape = true,
127 other => return Err(format!("unknown viewport flag: {other}")),
128 }
129 }
130 Ok(ViewportSpec {
131 width,
132 height,
133 device_scale_factor,
134 mobile,
135 has_touch,
136 is_landscape,
137 })
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn preset_lookup() {
146 assert!(network_preset_by_name("Slow 3G").is_some());
147 assert!(network_preset_by_name("offline").is_some());
148 assert!(network_preset_by_name("nope").is_none());
149 }
150
151 #[test]
152 fn viewport_parse() {
153 let v = parse_viewport_spec("412x823x1.75,mobile,touch").unwrap();
154 assert_eq!(v.width, 412);
155 assert_eq!(v.height, 823);
156 assert!((v.device_scale_factor - 1.75).abs() < 0.001);
157 assert!(v.mobile && v.has_touch);
158 }
159}