1use serde::Serialize;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
14#[serde(rename_all = "lowercase")]
15#[non_exhaustive]
16pub enum Animations {
17 Allow,
19 Disabled,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
33#[serde(rename_all = "lowercase")]
34#[non_exhaustive]
35pub enum ScreenshotType {
36 Png,
38 Jpeg,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
46#[serde(rename_all = "lowercase")]
47#[non_exhaustive]
48pub enum Caret {
49 Hide,
51 Initial,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
59#[serde(rename_all = "lowercase")]
60#[non_exhaustive]
61pub enum Scale {
62 Css,
64 Device,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
85pub struct ScreenshotClip {
86 pub x: f64,
88 pub y: f64,
90 pub width: f64,
92 pub height: f64,
94}
95
96#[derive(Debug, Clone, Default)]
122#[non_exhaustive]
123pub struct ScreenshotOptions {
124 pub screenshot_type: Option<ScreenshotType>,
126 pub quality: Option<u8>,
128 pub full_page: Option<bool>,
130 pub clip: Option<ScreenshotClip>,
132 pub omit_background: Option<bool>,
134 pub animations: Option<Animations>,
136 pub caret: Option<Caret>,
138 pub scale: Option<Scale>,
140 pub style: Option<String>,
142 pub mask: Option<Vec<serde_json::Value>>,
145 pub mask_color: Option<String>,
147 pub timeout: Option<f64>,
149}
150
151impl ScreenshotOptions {
152 pub fn builder() -> ScreenshotOptionsBuilder {
154 ScreenshotOptionsBuilder::default()
155 }
156
157 pub(crate) fn to_json(&self) -> serde_json::Value {
159 let mut json = serde_json::json!({});
160
161 if let Some(screenshot_type) = &self.screenshot_type {
162 json["type"] = serde_json::to_value(screenshot_type)
163 .expect("serialization of ScreenshotType cannot fail");
164 }
165
166 if let Some(quality) = self.quality {
167 json["quality"] = serde_json::json!(quality);
168 }
169
170 if let Some(full_page) = self.full_page {
171 json["fullPage"] = serde_json::json!(full_page);
172 }
173
174 if let Some(clip) = &self.clip {
175 json["clip"] = serde_json::to_value(clip).expect("serialization of clip cannot fail");
176 }
177
178 if let Some(omit_background) = self.omit_background {
179 json["omitBackground"] = serde_json::json!(omit_background);
180 }
181
182 if let Some(animations) = &self.animations {
183 json["animations"] =
184 serde_json::to_value(animations).expect("serialization of Animations cannot fail");
185 }
186
187 if let Some(caret) = &self.caret {
188 json["caret"] =
189 serde_json::to_value(caret).expect("serialization of Caret cannot fail");
190 }
191
192 if let Some(scale) = &self.scale {
193 json["scale"] =
194 serde_json::to_value(scale).expect("serialization of Scale cannot fail");
195 }
196
197 if let Some(style) = &self.style {
198 json["style"] = serde_json::json!(style);
199 }
200
201 if let Some(mask) = &self.mask {
202 json["mask"] = serde_json::Value::Array(mask.clone());
203 }
204
205 if let Some(mask_color) = &self.mask_color {
206 json["maskColor"] = serde_json::json!(mask_color);
207 }
208
209 if let Some(timeout) = self.timeout {
211 json["timeout"] = serde_json::json!(timeout);
212 } else {
213 json["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
214 }
215
216 json
217 }
218}
219
220#[derive(Debug, Clone, Default)]
224pub struct ScreenshotOptionsBuilder {
225 screenshot_type: Option<ScreenshotType>,
226 quality: Option<u8>,
227 full_page: Option<bool>,
228 clip: Option<ScreenshotClip>,
229 omit_background: Option<bool>,
230 animations: Option<Animations>,
231 caret: Option<Caret>,
232 scale: Option<Scale>,
233 style: Option<String>,
234 mask: Option<Vec<serde_json::Value>>,
235 mask_color: Option<String>,
236 timeout: Option<f64>,
237}
238
239impl ScreenshotOptionsBuilder {
240 pub fn screenshot_type(mut self, screenshot_type: ScreenshotType) -> Self {
242 self.screenshot_type = Some(screenshot_type);
243 self
244 }
245
246 pub fn quality(mut self, quality: u8) -> Self {
250 self.quality = Some(quality);
251 self
252 }
253
254 pub fn full_page(mut self, full_page: bool) -> Self {
256 self.full_page = Some(full_page);
257 self
258 }
259
260 pub fn clip(mut self, clip: ScreenshotClip) -> Self {
262 self.clip = Some(clip);
263 self
264 }
265
266 pub fn omit_background(mut self, omit_background: bool) -> Self {
268 self.omit_background = Some(omit_background);
269 self
270 }
271
272 pub fn animations(mut self, animations: Animations) -> Self {
277 self.animations = Some(animations);
278 self
279 }
280
281 pub fn caret(mut self, caret: Caret) -> Self {
283 self.caret = Some(caret);
284 self
285 }
286
287 pub fn scale(mut self, scale: Scale) -> Self {
289 self.scale = Some(scale);
290 self
291 }
292
293 pub fn style(mut self, style: impl Into<String>) -> Self {
295 self.style = Some(style.into());
296 self
297 }
298
299 pub fn mask(mut self, mask: Vec<crate::protocol::Locator>) -> Self {
303 self.mask = Some(mask.iter().map(|l| l.mask_json()).collect());
304 self
305 }
306
307 pub fn mask_color(mut self, mask_color: impl Into<String>) -> Self {
310 self.mask_color = Some(mask_color.into());
311 self
312 }
313
314 pub fn timeout(mut self, timeout: f64) -> Self {
316 self.timeout = Some(timeout);
317 self
318 }
319
320 pub fn build(self) -> ScreenshotOptions {
322 ScreenshotOptions {
323 screenshot_type: self.screenshot_type,
324 quality: self.quality,
325 full_page: self.full_page,
326 clip: self.clip,
327 omit_background: self.omit_background,
328 animations: self.animations,
329 caret: self.caret,
330 scale: self.scale,
331 style: self.style,
332 mask: self.mask,
333 mask_color: self.mask_color,
334 timeout: self.timeout,
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_screenshot_type_serialization() {
345 assert_eq!(
346 serde_json::to_string(&ScreenshotType::Png).unwrap(),
347 "\"png\""
348 );
349 assert_eq!(
350 serde_json::to_string(&ScreenshotType::Jpeg).unwrap(),
351 "\"jpeg\""
352 );
353 }
354
355 #[test]
356 fn test_builder_jpeg_with_quality() {
357 let options = ScreenshotOptions::builder()
358 .screenshot_type(ScreenshotType::Jpeg)
359 .quality(80)
360 .build();
361
362 let json = options.to_json();
363 assert_eq!(json["type"], "jpeg");
364 assert_eq!(json["quality"], 80);
365 }
366
367 #[test]
368 fn test_builder_full_page() {
369 let options = ScreenshotOptions::builder().full_page(true).build();
370
371 let json = options.to_json();
372 assert_eq!(json["fullPage"], true);
373 }
374
375 #[test]
376 fn test_builder_clip() {
377 let clip = ScreenshotClip {
378 x: 10.0,
379 y: 20.0,
380 width: 300.0,
381 height: 200.0,
382 };
383 let options = ScreenshotOptions::builder().clip(clip).build();
384
385 let json = options.to_json();
386 assert_eq!(json["clip"]["x"], 10.0);
387 assert_eq!(json["clip"]["y"], 20.0);
388 assert_eq!(json["clip"]["width"], 300.0);
389 assert_eq!(json["clip"]["height"], 200.0);
390 }
391
392 #[test]
393 fn test_builder_omit_background() {
394 let options = ScreenshotOptions::builder().omit_background(true).build();
395
396 let json = options.to_json();
397 assert_eq!(json["omitBackground"], true);
398 }
399
400 #[test]
401 fn test_builder_animations() {
402 let json = ScreenshotOptions::builder()
403 .animations(Animations::Disabled)
404 .build()
405 .to_json();
406 assert_eq!(json["animations"], "disabled");
407
408 let json = ScreenshotOptions::builder()
409 .animations(Animations::Allow)
410 .build()
411 .to_json();
412 assert_eq!(json["animations"], "allow");
413 }
414
415 #[test]
416 fn test_builder_caret() {
417 let json = ScreenshotOptions::builder()
418 .caret(Caret::Hide)
419 .build()
420 .to_json();
421 assert_eq!(json["caret"], "hide");
422 }
423
424 #[test]
425 fn test_builder_scale() {
426 let json = ScreenshotOptions::builder()
427 .scale(Scale::Device)
428 .build()
429 .to_json();
430 assert_eq!(json["scale"], "device");
431 }
432
433 #[test]
434 fn test_builder_style() {
435 let json = ScreenshotOptions::builder()
436 .style(".flaky { visibility: hidden; }")
437 .build()
438 .to_json();
439 assert_eq!(json["style"], ".flaky { visibility: hidden; }");
440 }
441
442 #[test]
443 fn test_builder_mask_color() {
444 let json = ScreenshotOptions::builder()
445 .mask_color("#FF00FF")
446 .build()
447 .to_json();
448 assert_eq!(json["maskColor"], "#FF00FF");
449 }
450
451 #[test]
452 fn test_mask_serializes_to_array() {
453 let options = ScreenshotOptions {
456 mask: Some(vec![serde_json::json!({
457 "frame": { "guid": "frame@1" },
458 "selector": "h1",
459 })]),
460 ..Default::default()
461 };
462 let json = options.to_json();
463 assert_eq!(json["mask"][0]["frame"]["guid"], "frame@1");
464 assert_eq!(json["mask"][0]["selector"], "h1");
465 }
466
467 #[test]
468 fn test_unset_options_absent() {
469 let json = ScreenshotOptions::builder().build().to_json();
470 assert!(json.get("animations").is_none());
471 assert!(json.get("caret").is_none());
472 assert!(json.get("scale").is_none());
473 assert!(json.get("style").is_none());
474 assert!(json.get("mask").is_none());
475 assert!(json.get("maskColor").is_none());
476 }
477
478 #[test]
479 fn test_builder_multiple_options() {
480 let options = ScreenshotOptions::builder()
481 .screenshot_type(ScreenshotType::Jpeg)
482 .quality(90)
483 .full_page(true)
484 .timeout(5000.0)
485 .build();
486
487 let json = options.to_json();
488 assert_eq!(json["type"], "jpeg");
489 assert_eq!(json["quality"], 90);
490 assert_eq!(json["fullPage"], true);
491 assert_eq!(json["timeout"], 5000.0);
492 }
493}