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