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 Webp,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
50#[serde(rename_all = "lowercase")]
51#[non_exhaustive]
52pub enum Caret {
53 Hide,
55 Initial,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
63#[serde(rename_all = "lowercase")]
64#[non_exhaustive]
65pub enum Scale {
66 Css,
68 Device,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
89pub struct ScreenshotClip {
90 pub x: f64,
92 pub y: f64,
94 pub width: f64,
96 pub height: f64,
98}
99
100#[derive(Debug, Clone, Default, serde::Serialize)]
126#[serde(rename_all = "camelCase")]
127#[non_exhaustive]
128pub struct ScreenshotOptions {
129 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
131 pub screenshot_type: Option<ScreenshotType>,
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub quality: Option<u8>,
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub full_page: Option<bool>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub clip: Option<ScreenshotClip>,
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub omit_background: Option<bool>,
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub animations: Option<Animations>,
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub caret: Option<Caret>,
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub scale: Option<Scale>,
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub style: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
159 pub mask: Option<Vec<serde_json::Value>>,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub mask_color: Option<String>,
163 #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
165 pub timeout: Option<f64>,
166}
167
168impl ScreenshotOptions {
169 pub fn builder() -> ScreenshotOptionsBuilder {
171 ScreenshotOptionsBuilder::default()
172 }
173
174 pub(crate) fn to_json(&self) -> serde_json::Value {
176 serde_json::to_value(self).expect("ScreenshotOptions serialization cannot fail")
177 }
178}
179
180#[derive(Debug, Clone, Default)]
184pub struct ScreenshotOptionsBuilder {
185 screenshot_type: Option<ScreenshotType>,
186 quality: Option<u8>,
187 full_page: Option<bool>,
188 clip: Option<ScreenshotClip>,
189 omit_background: Option<bool>,
190 animations: Option<Animations>,
191 caret: Option<Caret>,
192 scale: Option<Scale>,
193 style: Option<String>,
194 mask: Option<Vec<serde_json::Value>>,
195 mask_color: Option<String>,
196 timeout: Option<f64>,
197}
198
199impl ScreenshotOptionsBuilder {
200 pub fn screenshot_type(mut self, screenshot_type: ScreenshotType) -> Self {
202 self.screenshot_type = Some(screenshot_type);
203 self
204 }
205
206 pub fn quality(mut self, quality: u8) -> Self {
210 self.quality = Some(quality);
211 self
212 }
213
214 pub fn full_page(mut self, full_page: bool) -> Self {
216 self.full_page = Some(full_page);
217 self
218 }
219
220 pub fn clip(mut self, clip: ScreenshotClip) -> Self {
222 self.clip = Some(clip);
223 self
224 }
225
226 pub fn omit_background(mut self, omit_background: bool) -> Self {
228 self.omit_background = Some(omit_background);
229 self
230 }
231
232 pub fn animations(mut self, animations: Animations) -> Self {
237 self.animations = Some(animations);
238 self
239 }
240
241 pub fn caret(mut self, caret: Caret) -> Self {
243 self.caret = Some(caret);
244 self
245 }
246
247 pub fn scale(mut self, scale: Scale) -> Self {
249 self.scale = Some(scale);
250 self
251 }
252
253 pub fn style(mut self, style: impl Into<String>) -> Self {
255 self.style = Some(style.into());
256 self
257 }
258
259 pub fn mask(mut self, mask: Vec<crate::protocol::Locator>) -> Self {
263 self.mask = Some(mask.iter().map(|l| l.mask_json()).collect());
264 self
265 }
266
267 pub fn mask_color(mut self, mask_color: impl Into<String>) -> Self {
270 self.mask_color = Some(mask_color.into());
271 self
272 }
273
274 pub fn timeout(mut self, timeout: f64) -> Self {
276 self.timeout = Some(timeout);
277 self
278 }
279
280 pub fn build(self) -> ScreenshotOptions {
282 ScreenshotOptions {
283 screenshot_type: self.screenshot_type,
284 quality: self.quality,
285 full_page: self.full_page,
286 clip: self.clip,
287 omit_background: self.omit_background,
288 animations: self.animations,
289 caret: self.caret,
290 scale: self.scale,
291 style: self.style,
292 mask: self.mask,
293 mask_color: self.mask_color,
294 timeout: self.timeout,
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_screenshot_type_serialization() {
305 assert_eq!(
306 serde_json::to_string(&ScreenshotType::Png).unwrap(),
307 "\"png\""
308 );
309 assert_eq!(
310 serde_json::to_string(&ScreenshotType::Jpeg).unwrap(),
311 "\"jpeg\""
312 );
313 assert_eq!(
314 serde_json::to_string(&ScreenshotType::Webp).unwrap(),
315 "\"webp\""
316 );
317 }
318
319 #[test]
320 fn test_builder_webp_with_quality() {
321 let options = ScreenshotOptions::builder()
322 .screenshot_type(ScreenshotType::Webp)
323 .quality(70)
324 .build();
325
326 let json = options.to_json();
327 assert_eq!(json["type"], "webp");
328 assert_eq!(json["quality"], 70);
329 }
330
331 #[test]
332 fn test_builder_jpeg_with_quality() {
333 let options = ScreenshotOptions::builder()
334 .screenshot_type(ScreenshotType::Jpeg)
335 .quality(80)
336 .build();
337
338 let json = options.to_json();
339 assert_eq!(json["type"], "jpeg");
340 assert_eq!(json["quality"], 80);
341 }
342
343 #[test]
344 fn test_builder_full_page() {
345 let options = ScreenshotOptions::builder().full_page(true).build();
346
347 let json = options.to_json();
348 assert_eq!(json["fullPage"], true);
349 }
350
351 #[test]
352 fn test_builder_clip() {
353 let clip = ScreenshotClip {
354 x: 10.0,
355 y: 20.0,
356 width: 300.0,
357 height: 200.0,
358 };
359 let options = ScreenshotOptions::builder().clip(clip).build();
360
361 let json = options.to_json();
362 assert_eq!(json["clip"]["x"], 10.0);
363 assert_eq!(json["clip"]["y"], 20.0);
364 assert_eq!(json["clip"]["width"], 300.0);
365 assert_eq!(json["clip"]["height"], 200.0);
366 }
367
368 #[test]
369 fn test_builder_omit_background() {
370 let options = ScreenshotOptions::builder().omit_background(true).build();
371
372 let json = options.to_json();
373 assert_eq!(json["omitBackground"], true);
374 }
375
376 #[test]
377 fn test_builder_animations() {
378 let json = ScreenshotOptions::builder()
379 .animations(Animations::Disabled)
380 .build()
381 .to_json();
382 assert_eq!(json["animations"], "disabled");
383
384 let json = ScreenshotOptions::builder()
385 .animations(Animations::Allow)
386 .build()
387 .to_json();
388 assert_eq!(json["animations"], "allow");
389 }
390
391 #[test]
392 fn test_builder_caret() {
393 let json = ScreenshotOptions::builder()
394 .caret(Caret::Hide)
395 .build()
396 .to_json();
397 assert_eq!(json["caret"], "hide");
398 }
399
400 #[test]
401 fn test_builder_scale() {
402 let json = ScreenshotOptions::builder()
403 .scale(Scale::Device)
404 .build()
405 .to_json();
406 assert_eq!(json["scale"], "device");
407 }
408
409 #[test]
410 fn test_builder_style() {
411 let json = ScreenshotOptions::builder()
412 .style(".flaky { visibility: hidden; }")
413 .build()
414 .to_json();
415 assert_eq!(json["style"], ".flaky { visibility: hidden; }");
416 }
417
418 #[test]
419 fn test_builder_mask_color() {
420 let json = ScreenshotOptions::builder()
421 .mask_color("#FF00FF")
422 .build()
423 .to_json();
424 assert_eq!(json["maskColor"], "#FF00FF");
425 }
426
427 #[test]
428 fn test_mask_serializes_to_array() {
429 let options = ScreenshotOptions {
432 mask: Some(vec![serde_json::json!({
433 "frame": { "guid": "frame@1" },
434 "selector": "h1",
435 })]),
436 ..Default::default()
437 };
438 let json = options.to_json();
439 assert_eq!(json["mask"][0]["frame"]["guid"], "frame@1");
440 assert_eq!(json["mask"][0]["selector"], "h1");
441 }
442
443 #[test]
444 fn test_unset_options_absent() {
445 let json = ScreenshotOptions::builder().build().to_json();
446 assert!(json.get("animations").is_none());
447 assert!(json.get("caret").is_none());
448 assert!(json.get("scale").is_none());
449 assert!(json.get("style").is_none());
450 assert!(json.get("mask").is_none());
451 assert!(json.get("maskColor").is_none());
452 }
453
454 #[test]
455 fn test_builder_multiple_options() {
456 let options = ScreenshotOptions::builder()
457 .screenshot_type(ScreenshotType::Jpeg)
458 .quality(90)
459 .full_page(true)
460 .timeout(5000.0)
461 .build();
462
463 let json = options.to_json();
464 assert_eq!(json["type"], "jpeg");
465 assert_eq!(json["quality"], 90);
466 assert_eq!(json["fullPage"], true);
467 assert_eq!(json["timeout"], 5000.0);
468 }
469}