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, serde::Serialize)]
122#[serde(rename_all = "camelCase")]
123#[non_exhaustive]
124pub struct ScreenshotOptions {
125 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
127 pub screenshot_type: Option<ScreenshotType>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub quality: Option<u8>,
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub full_page: Option<bool>,
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub clip: Option<ScreenshotClip>,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub omit_background: Option<bool>,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub animations: Option<Animations>,
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub caret: Option<Caret>,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub scale: Option<Scale>,
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub style: Option<String>,
152 #[serde(skip_serializing_if = "Option::is_none")]
155 pub mask: Option<Vec<serde_json::Value>>,
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub mask_color: Option<String>,
159 #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
161 pub timeout: Option<f64>,
162}
163
164impl ScreenshotOptions {
165 pub fn builder() -> ScreenshotOptionsBuilder {
167 ScreenshotOptionsBuilder::default()
168 }
169
170 pub(crate) fn to_json(&self) -> serde_json::Value {
172 serde_json::to_value(self).expect("ScreenshotOptions serialization cannot fail")
173 }
174}
175
176#[derive(Debug, Clone, Default)]
180pub struct ScreenshotOptionsBuilder {
181 screenshot_type: Option<ScreenshotType>,
182 quality: Option<u8>,
183 full_page: Option<bool>,
184 clip: Option<ScreenshotClip>,
185 omit_background: Option<bool>,
186 animations: Option<Animations>,
187 caret: Option<Caret>,
188 scale: Option<Scale>,
189 style: Option<String>,
190 mask: Option<Vec<serde_json::Value>>,
191 mask_color: Option<String>,
192 timeout: Option<f64>,
193}
194
195impl ScreenshotOptionsBuilder {
196 pub fn screenshot_type(mut self, screenshot_type: ScreenshotType) -> Self {
198 self.screenshot_type = Some(screenshot_type);
199 self
200 }
201
202 pub fn quality(mut self, quality: u8) -> Self {
206 self.quality = Some(quality);
207 self
208 }
209
210 pub fn full_page(mut self, full_page: bool) -> Self {
212 self.full_page = Some(full_page);
213 self
214 }
215
216 pub fn clip(mut self, clip: ScreenshotClip) -> Self {
218 self.clip = Some(clip);
219 self
220 }
221
222 pub fn omit_background(mut self, omit_background: bool) -> Self {
224 self.omit_background = Some(omit_background);
225 self
226 }
227
228 pub fn animations(mut self, animations: Animations) -> Self {
233 self.animations = Some(animations);
234 self
235 }
236
237 pub fn caret(mut self, caret: Caret) -> Self {
239 self.caret = Some(caret);
240 self
241 }
242
243 pub fn scale(mut self, scale: Scale) -> Self {
245 self.scale = Some(scale);
246 self
247 }
248
249 pub fn style(mut self, style: impl Into<String>) -> Self {
251 self.style = Some(style.into());
252 self
253 }
254
255 pub fn mask(mut self, mask: Vec<crate::protocol::Locator>) -> Self {
259 self.mask = Some(mask.iter().map(|l| l.mask_json()).collect());
260 self
261 }
262
263 pub fn mask_color(mut self, mask_color: impl Into<String>) -> Self {
266 self.mask_color = Some(mask_color.into());
267 self
268 }
269
270 pub fn timeout(mut self, timeout: f64) -> Self {
272 self.timeout = Some(timeout);
273 self
274 }
275
276 pub fn build(self) -> ScreenshotOptions {
278 ScreenshotOptions {
279 screenshot_type: self.screenshot_type,
280 quality: self.quality,
281 full_page: self.full_page,
282 clip: self.clip,
283 omit_background: self.omit_background,
284 animations: self.animations,
285 caret: self.caret,
286 scale: self.scale,
287 style: self.style,
288 mask: self.mask,
289 mask_color: self.mask_color,
290 timeout: self.timeout,
291 }
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_screenshot_type_serialization() {
301 assert_eq!(
302 serde_json::to_string(&ScreenshotType::Png).unwrap(),
303 "\"png\""
304 );
305 assert_eq!(
306 serde_json::to_string(&ScreenshotType::Jpeg).unwrap(),
307 "\"jpeg\""
308 );
309 }
310
311 #[test]
312 fn test_builder_jpeg_with_quality() {
313 let options = ScreenshotOptions::builder()
314 .screenshot_type(ScreenshotType::Jpeg)
315 .quality(80)
316 .build();
317
318 let json = options.to_json();
319 assert_eq!(json["type"], "jpeg");
320 assert_eq!(json["quality"], 80);
321 }
322
323 #[test]
324 fn test_builder_full_page() {
325 let options = ScreenshotOptions::builder().full_page(true).build();
326
327 let json = options.to_json();
328 assert_eq!(json["fullPage"], true);
329 }
330
331 #[test]
332 fn test_builder_clip() {
333 let clip = ScreenshotClip {
334 x: 10.0,
335 y: 20.0,
336 width: 300.0,
337 height: 200.0,
338 };
339 let options = ScreenshotOptions::builder().clip(clip).build();
340
341 let json = options.to_json();
342 assert_eq!(json["clip"]["x"], 10.0);
343 assert_eq!(json["clip"]["y"], 20.0);
344 assert_eq!(json["clip"]["width"], 300.0);
345 assert_eq!(json["clip"]["height"], 200.0);
346 }
347
348 #[test]
349 fn test_builder_omit_background() {
350 let options = ScreenshotOptions::builder().omit_background(true).build();
351
352 let json = options.to_json();
353 assert_eq!(json["omitBackground"], true);
354 }
355
356 #[test]
357 fn test_builder_animations() {
358 let json = ScreenshotOptions::builder()
359 .animations(Animations::Disabled)
360 .build()
361 .to_json();
362 assert_eq!(json["animations"], "disabled");
363
364 let json = ScreenshotOptions::builder()
365 .animations(Animations::Allow)
366 .build()
367 .to_json();
368 assert_eq!(json["animations"], "allow");
369 }
370
371 #[test]
372 fn test_builder_caret() {
373 let json = ScreenshotOptions::builder()
374 .caret(Caret::Hide)
375 .build()
376 .to_json();
377 assert_eq!(json["caret"], "hide");
378 }
379
380 #[test]
381 fn test_builder_scale() {
382 let json = ScreenshotOptions::builder()
383 .scale(Scale::Device)
384 .build()
385 .to_json();
386 assert_eq!(json["scale"], "device");
387 }
388
389 #[test]
390 fn test_builder_style() {
391 let json = ScreenshotOptions::builder()
392 .style(".flaky { visibility: hidden; }")
393 .build()
394 .to_json();
395 assert_eq!(json["style"], ".flaky { visibility: hidden; }");
396 }
397
398 #[test]
399 fn test_builder_mask_color() {
400 let json = ScreenshotOptions::builder()
401 .mask_color("#FF00FF")
402 .build()
403 .to_json();
404 assert_eq!(json["maskColor"], "#FF00FF");
405 }
406
407 #[test]
408 fn test_mask_serializes_to_array() {
409 let options = ScreenshotOptions {
412 mask: Some(vec![serde_json::json!({
413 "frame": { "guid": "frame@1" },
414 "selector": "h1",
415 })]),
416 ..Default::default()
417 };
418 let json = options.to_json();
419 assert_eq!(json["mask"][0]["frame"]["guid"], "frame@1");
420 assert_eq!(json["mask"][0]["selector"], "h1");
421 }
422
423 #[test]
424 fn test_unset_options_absent() {
425 let json = ScreenshotOptions::builder().build().to_json();
426 assert!(json.get("animations").is_none());
427 assert!(json.get("caret").is_none());
428 assert!(json.get("scale").is_none());
429 assert!(json.get("style").is_none());
430 assert!(json.get("mask").is_none());
431 assert!(json.get("maskColor").is_none());
432 }
433
434 #[test]
435 fn test_builder_multiple_options() {
436 let options = ScreenshotOptions::builder()
437 .screenshot_type(ScreenshotType::Jpeg)
438 .quality(90)
439 .full_page(true)
440 .timeout(5000.0)
441 .build();
442
443 let json = options.to_json();
444 assert_eq!(json["type"], "jpeg");
445 assert_eq!(json["quality"], 90);
446 assert_eq!(json["fullPage"], true);
447 assert_eq!(json["timeout"], 5000.0);
448 }
449}