Skip to main content

playwright_rs/protocol/
action_options.rs

1// Action options for various Locator methods
2//
3// Provides configuration for fill, press, check, hover, and select actions.
4
5use super::click::{KeyboardModifier, Position};
6
7/// Fill options
8///
9/// Configuration options for fill() action.
10///
11/// See: <https://playwright.dev/docs/api/class-locator#locator-fill>
12#[derive(Debug, Clone, Default, serde::Serialize)]
13#[serde(rename_all = "camelCase")]
14#[non_exhaustive]
15pub struct FillOptions {
16    /// Whether to bypass actionability checks
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub force: Option<bool>,
19    /// Maximum time in milliseconds
20    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
21    pub timeout: Option<f64>,
22}
23
24impl FillOptions {
25    /// Create a new builder for FillOptions
26    pub fn builder() -> FillOptionsBuilder {
27        FillOptionsBuilder::default()
28    }
29
30    /// Convert options to JSON value for protocol
31    pub(crate) fn to_json(&self) -> serde_json::Value {
32        serde_json::to_value(self).expect("FillOptions serialization cannot fail")
33    }
34}
35
36/// Builder for FillOptions
37#[derive(Debug, Clone, Default)]
38pub struct FillOptionsBuilder {
39    force: Option<bool>,
40    timeout: Option<f64>,
41}
42
43impl FillOptionsBuilder {
44    /// Bypass actionability checks
45    pub fn force(mut self, force: bool) -> Self {
46        self.force = Some(force);
47        self
48    }
49
50    /// Set timeout in milliseconds
51    pub fn timeout(mut self, timeout: f64) -> Self {
52        self.timeout = Some(timeout);
53        self
54    }
55
56    /// Build the FillOptions
57    pub fn build(self) -> FillOptions {
58        FillOptions {
59            force: self.force,
60            timeout: self.timeout,
61        }
62    }
63}
64
65/// Press options
66///
67/// Configuration options for press() action.
68///
69/// See: <https://playwright.dev/docs/api/class-locator#locator-press>
70#[derive(Debug, Clone, Default, serde::Serialize)]
71#[serde(rename_all = "camelCase")]
72#[non_exhaustive]
73pub struct PressOptions {
74    /// Time to wait between keydown and keyup in milliseconds
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub delay: Option<f64>,
77    /// Maximum time in milliseconds
78    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
79    pub timeout: Option<f64>,
80}
81
82impl PressOptions {
83    /// Create a new builder for PressOptions
84    pub fn builder() -> PressOptionsBuilder {
85        PressOptionsBuilder::default()
86    }
87
88    /// Convert options to JSON value for protocol
89    pub(crate) fn to_json(&self) -> serde_json::Value {
90        serde_json::to_value(self).expect("PressOptions serialization cannot fail")
91    }
92}
93
94/// Builder for PressOptions
95#[derive(Debug, Clone, Default)]
96pub struct PressOptionsBuilder {
97    delay: Option<f64>,
98    timeout: Option<f64>,
99}
100
101impl PressOptionsBuilder {
102    /// Set delay between keydown and keyup in milliseconds
103    pub fn delay(mut self, delay: f64) -> Self {
104        self.delay = Some(delay);
105        self
106    }
107
108    /// Set timeout in milliseconds
109    pub fn timeout(mut self, timeout: f64) -> Self {
110        self.timeout = Some(timeout);
111        self
112    }
113
114    /// Build the PressOptions
115    pub fn build(self) -> PressOptions {
116        PressOptions {
117            delay: self.delay,
118            timeout: self.timeout,
119        }
120    }
121}
122
123/// Check options
124///
125/// Configuration options for check() and uncheck() actions.
126///
127/// See: <https://playwright.dev/docs/api/class-locator#locator-check>
128#[derive(Debug, Clone, Default, serde::Serialize)]
129#[serde(rename_all = "camelCase")]
130#[non_exhaustive]
131pub struct CheckOptions {
132    /// Whether to bypass actionability checks
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub force: Option<bool>,
135    /// Position to click relative to element top-left corner
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub position: Option<Position>,
138    /// Maximum time in milliseconds
139    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
140    pub timeout: Option<f64>,
141    /// Perform actionability checks without checking
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub trial: Option<bool>,
144    /// Whether the action may scroll the element into view first
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub scroll: Option<Scroll>,
147}
148
149impl CheckOptions {
150    /// Create a new builder for CheckOptions
151    pub fn builder() -> CheckOptionsBuilder {
152        CheckOptionsBuilder::default()
153    }
154
155    /// Convert options to JSON value for protocol
156    pub(crate) fn to_json(&self) -> serde_json::Value {
157        serde_json::to_value(self).expect("CheckOptions serialization cannot fail")
158    }
159}
160
161/// Builder for CheckOptions
162#[derive(Debug, Clone, Default)]
163pub struct CheckOptionsBuilder {
164    force: Option<bool>,
165    position: Option<Position>,
166    timeout: Option<f64>,
167    trial: Option<bool>,
168    scroll: Option<Scroll>,
169}
170
171impl CheckOptionsBuilder {
172    /// Bypass actionability checks
173    pub fn force(mut self, force: bool) -> Self {
174        self.force = Some(force);
175        self
176    }
177
178    /// Set position to click relative to element top-left corner
179    pub fn position(mut self, position: Position) -> Self {
180        self.position = Some(position);
181        self
182    }
183
184    /// Set timeout in milliseconds
185    pub fn timeout(mut self, timeout: f64) -> Self {
186        self.timeout = Some(timeout);
187        self
188    }
189
190    /// Perform actionability checks without checking
191    pub fn trial(mut self, trial: bool) -> Self {
192        self.trial = Some(trial);
193        self
194    }
195
196    /// Opt out of scrolling the element into view (`Scroll::None`), or keep
197    /// Playwright's default (`Scroll::Auto`)
198    pub fn scroll(mut self, scroll: Scroll) -> Self {
199        self.scroll = Some(scroll);
200        self
201    }
202
203    /// Build the CheckOptions
204    pub fn build(self) -> CheckOptions {
205        CheckOptions {
206            force: self.force,
207            position: self.position,
208            timeout: self.timeout,
209            trial: self.trial,
210            scroll: self.scroll,
211        }
212    }
213}
214
215/// Hover options
216///
217/// Configuration options for hover() action.
218///
219/// See: <https://playwright.dev/docs/api/class-locator#locator-hover>
220#[derive(Debug, Clone, Default, serde::Serialize)]
221#[serde(rename_all = "camelCase")]
222#[non_exhaustive]
223pub struct HoverOptions {
224    /// Whether to bypass actionability checks
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub force: Option<bool>,
227    /// Modifier keys to press during hover
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub modifiers: Option<Vec<KeyboardModifier>>,
230    /// Position to hover relative to element top-left corner
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub position: Option<Position>,
233    /// Maximum time in milliseconds
234    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
235    pub timeout: Option<f64>,
236    /// Perform actionability checks without hovering
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub trial: Option<bool>,
239    /// Whether the action may scroll the element into view first
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub scroll: Option<Scroll>,
242}
243
244impl HoverOptions {
245    /// Create a new builder for HoverOptions
246    pub fn builder() -> HoverOptionsBuilder {
247        HoverOptionsBuilder::default()
248    }
249
250    /// Convert options to JSON value for protocol
251    pub(crate) fn to_json(&self) -> serde_json::Value {
252        serde_json::to_value(self).expect("HoverOptions serialization cannot fail")
253    }
254}
255
256/// Builder for HoverOptions
257#[derive(Debug, Clone, Default)]
258pub struct HoverOptionsBuilder {
259    force: Option<bool>,
260    modifiers: Option<Vec<KeyboardModifier>>,
261    position: Option<Position>,
262    timeout: Option<f64>,
263    trial: Option<bool>,
264    scroll: Option<Scroll>,
265}
266
267impl HoverOptionsBuilder {
268    /// Bypass actionability checks
269    pub fn force(mut self, force: bool) -> Self {
270        self.force = Some(force);
271        self
272    }
273
274    /// Set modifier keys to press during hover
275    pub fn modifiers(mut self, modifiers: Vec<KeyboardModifier>) -> Self {
276        self.modifiers = Some(modifiers);
277        self
278    }
279
280    /// Set position to hover relative to element top-left corner
281    pub fn position(mut self, position: Position) -> Self {
282        self.position = Some(position);
283        self
284    }
285
286    /// Set timeout in milliseconds
287    pub fn timeout(mut self, timeout: f64) -> Self {
288        self.timeout = Some(timeout);
289        self
290    }
291
292    /// Perform actionability checks without hovering
293    pub fn trial(mut self, trial: bool) -> Self {
294        self.trial = Some(trial);
295        self
296    }
297
298    /// Opt out of scrolling the element into view (`Scroll::None`), or keep
299    /// Playwright's default (`Scroll::Auto`)
300    pub fn scroll(mut self, scroll: Scroll) -> Self {
301        self.scroll = Some(scroll);
302        self
303    }
304
305    /// Build the HoverOptions
306    pub fn build(self) -> HoverOptions {
307        HoverOptions {
308            force: self.force,
309            modifiers: self.modifiers,
310            position: self.position,
311            timeout: self.timeout,
312            trial: self.trial,
313            scroll: self.scroll,
314        }
315    }
316}
317
318/// Options for [`Locator::press_sequentially()`](crate::protocol::Locator::press_sequentially).
319///
320/// Controls timing between key presses when typing characters one by one.
321///
322/// See: <https://playwright.dev/docs/api/class-locator#locator-press-sequentially>
323#[derive(Debug, Clone, Default)]
324#[non_exhaustive]
325pub struct PressSequentiallyOptions {
326    /// Delay between key presses in milliseconds. Defaults to 0.
327    pub delay: Option<f64>,
328}
329
330impl PressSequentiallyOptions {
331    /// Create a new builder for PressSequentiallyOptions
332    pub fn builder() -> PressSequentiallyOptionsBuilder {
333        PressSequentiallyOptionsBuilder::default()
334    }
335
336    /// Convert options to JSON value for protocol
337    pub(crate) fn to_json(&self) -> serde_json::Value {
338        let mut json = serde_json::json!({});
339
340        if let Some(delay) = self.delay {
341            json["delay"] = serde_json::json!(delay);
342        }
343
344        // Timeout is required in Playwright 1.56.1+
345        json["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
346
347        json
348    }
349}
350
351/// Builder for PressSequentiallyOptions
352#[derive(Debug, Clone, Default)]
353pub struct PressSequentiallyOptionsBuilder {
354    delay: Option<f64>,
355}
356
357impl PressSequentiallyOptionsBuilder {
358    /// Set delay between key presses in milliseconds
359    pub fn delay(mut self, delay: f64) -> Self {
360        self.delay = Some(delay);
361        self
362    }
363
364    /// Build the PressSequentiallyOptions
365    pub fn build(self) -> PressSequentiallyOptions {
366        PressSequentiallyOptions { delay: self.delay }
367    }
368}
369
370/// Select options
371///
372/// Configuration options for select_option() action.
373///
374/// See: <https://playwright.dev/docs/api/class-locator#locator-select-option>
375#[derive(Debug, Clone, Default, serde::Serialize)]
376#[serde(rename_all = "camelCase")]
377#[non_exhaustive]
378pub struct SelectOptions {
379    /// Whether to bypass actionability checks
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub force: Option<bool>,
382    /// Maximum time in milliseconds
383    #[serde(serialize_with = "crate::protocol::serialize_timeout_or_default")]
384    pub timeout: Option<f64>,
385}
386
387impl SelectOptions {
388    /// Create a new builder for SelectOptions
389    pub fn builder() -> SelectOptionsBuilder {
390        SelectOptionsBuilder::default()
391    }
392
393    /// Convert options to JSON value for protocol
394    pub(crate) fn to_json(&self) -> serde_json::Value {
395        serde_json::to_value(self).expect("SelectOptions serialization cannot fail")
396    }
397}
398
399/// Builder for SelectOptions
400#[derive(Debug, Clone, Default)]
401pub struct SelectOptionsBuilder {
402    force: Option<bool>,
403    timeout: Option<f64>,
404}
405
406impl SelectOptionsBuilder {
407    /// Bypass actionability checks
408    pub fn force(mut self, force: bool) -> Self {
409        self.force = Some(force);
410        self
411    }
412
413    /// Set timeout in milliseconds
414    pub fn timeout(mut self, timeout: f64) -> Self {
415        self.timeout = Some(timeout);
416        self
417    }
418
419    /// Build the SelectOptions
420    pub fn build(self) -> SelectOptions {
421        SelectOptions {
422            force: self.force,
423            timeout: self.timeout,
424        }
425    }
426}
427
428/// Keyboard options
429///
430/// Configuration options for keyboard.press() and keyboard.type_text() methods.
431///
432/// See: <https://playwright.dev/docs/api/class-keyboard#keyboard-press>
433#[derive(Debug, Clone, Default, serde::Serialize)]
434#[serde(rename_all = "camelCase")]
435#[non_exhaustive]
436pub struct KeyboardOptions {
437    /// Time to wait between key presses in milliseconds
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub delay: Option<f64>,
440}
441
442impl KeyboardOptions {
443    /// Create a new builder for KeyboardOptions
444    pub fn builder() -> KeyboardOptionsBuilder {
445        KeyboardOptionsBuilder::default()
446    }
447
448    /// Convert options to JSON value for protocol
449    pub(crate) fn to_json(&self) -> serde_json::Value {
450        serde_json::to_value(self).expect("KeyboardOptions serialization cannot fail")
451    }
452}
453
454/// Builder for KeyboardOptions
455#[derive(Debug, Clone, Default)]
456pub struct KeyboardOptionsBuilder {
457    delay: Option<f64>,
458}
459
460impl KeyboardOptionsBuilder {
461    /// Set delay between key presses in milliseconds
462    pub fn delay(mut self, delay: f64) -> Self {
463        self.delay = Some(delay);
464        self
465    }
466
467    /// Build the KeyboardOptions
468    pub fn build(self) -> KeyboardOptions {
469        KeyboardOptions { delay: self.delay }
470    }
471}
472
473/// Mouse options
474///
475/// Configuration options for mouse methods.
476///
477/// See: <https://playwright.dev/docs/api/class-mouse>
478#[derive(Debug, Clone, Default, serde::Serialize)]
479#[serde(rename_all = "camelCase")]
480#[non_exhaustive]
481pub struct MouseOptions {
482    /// Mouse button to use
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub button: Option<super::click::MouseButton>,
485    /// Number of clicks
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub click_count: Option<u32>,
488    /// Time to wait between mousedown and mouseup in milliseconds
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub delay: Option<f64>,
491    /// Number of intermediate mousemove events (for move operations)
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub steps: Option<u32>,
494}
495
496impl MouseOptions {
497    /// Create a new builder for MouseOptions
498    pub fn builder() -> MouseOptionsBuilder {
499        MouseOptionsBuilder::default()
500    }
501
502    /// Convert options to JSON value for protocol
503    pub(crate) fn to_json(&self) -> serde_json::Value {
504        serde_json::to_value(self).expect("MouseOptions serialization cannot fail")
505    }
506}
507
508/// Builder for MouseOptions
509#[derive(Debug, Clone, Default)]
510pub struct MouseOptionsBuilder {
511    button: Option<super::click::MouseButton>,
512    click_count: Option<u32>,
513    delay: Option<f64>,
514    steps: Option<u32>,
515}
516
517impl MouseOptionsBuilder {
518    /// Set the mouse button
519    pub fn button(mut self, button: super::click::MouseButton) -> Self {
520        self.button = Some(button);
521        self
522    }
523
524    /// Set the number of clicks
525    pub fn click_count(mut self, click_count: u32) -> Self {
526        self.click_count = Some(click_count);
527        self
528    }
529
530    /// Set delay between mousedown and mouseup in milliseconds
531    pub fn delay(mut self, delay: f64) -> Self {
532        self.delay = Some(delay);
533        self
534    }
535
536    /// Set number of intermediate mousemove events
537    pub fn steps(mut self, steps: u32) -> Self {
538        self.steps = Some(steps);
539        self
540    }
541
542    /// Build the MouseOptions
543    pub fn build(self) -> MouseOptions {
544        MouseOptions {
545            button: self.button,
546            click_count: self.click_count,
547            delay: self.delay,
548            steps: self.steps,
549        }
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556    use crate::protocol::click::MouseButton;
557
558    #[test]
559    fn test_fill_options_builder() {
560        let options = FillOptions::builder().force(true).timeout(5000.0).build();
561
562        let json = options.to_json();
563        assert_eq!(json["force"], true);
564        assert_eq!(json["timeout"], 5000.0);
565    }
566
567    #[test]
568    fn test_press_options_builder() {
569        let options = PressOptions::builder().delay(100.0).timeout(3000.0).build();
570
571        let json = options.to_json();
572        assert_eq!(json["delay"], 100.0);
573        assert_eq!(json["timeout"], 3000.0);
574    }
575
576    #[test]
577    fn test_check_options_builder() {
578        let options = CheckOptions::builder()
579            .force(true)
580            .position(Position { x: 5.0, y: 10.0 })
581            .timeout(2000.0)
582            .trial(true)
583            .build();
584
585        let json = options.to_json();
586        assert_eq!(json["force"], true);
587        assert_eq!(json["position"]["x"], 5.0);
588        assert_eq!(json["position"]["y"], 10.0);
589        assert_eq!(json["timeout"], 2000.0);
590        assert_eq!(json["trial"], true);
591    }
592
593    #[test]
594    fn test_hover_options_builder() {
595        let options = HoverOptions::builder()
596            .force(true)
597            .modifiers(vec![KeyboardModifier::Shift])
598            .position(Position { x: 10.0, y: 20.0 })
599            .timeout(4000.0)
600            .trial(false)
601            .build();
602
603        let json = options.to_json();
604        assert_eq!(json["force"], true);
605        assert_eq!(json["modifiers"], serde_json::json!(["Shift"]));
606        assert_eq!(json["position"]["x"], 10.0);
607        assert_eq!(json["position"]["y"], 20.0);
608        assert_eq!(json["timeout"], 4000.0);
609        assert_eq!(json["trial"], false);
610    }
611
612    #[test]
613    fn test_select_options_builder() {
614        let options = SelectOptions::builder().force(true).timeout(6000.0).build();
615
616        let json = options.to_json();
617        assert_eq!(json["force"], true);
618        assert_eq!(json["timeout"], 6000.0);
619    }
620
621    #[test]
622    fn test_keyboard_options_builder() {
623        let options = KeyboardOptions::builder().delay(50.0).build();
624
625        let json = options.to_json();
626        assert_eq!(json["delay"], 50.0);
627    }
628
629    #[test]
630    fn test_mouse_options_builder() {
631        let options = MouseOptions::builder()
632            .button(MouseButton::Right)
633            .click_count(2)
634            .delay(100.0)
635            .steps(10)
636            .build();
637
638        let json = options.to_json();
639        assert_eq!(json["button"], "right");
640        assert_eq!(json["clickCount"], 2);
641        assert_eq!(json["delay"], 100.0);
642        assert_eq!(json["steps"], 10);
643    }
644}
645
646/// Whether an action may scroll the element into view first.
647///
648/// Playwright's actionability checks scroll the target into the viewport
649/// before acting. `None` opts out, so the action fails instead of scrolling
650/// when the element is out of view. Useful for asserting that something is
651/// already visible, and for pages where scrolling itself changes layout.
652///
653/// See: <https://playwright.dev/docs/api/class-locator#locator-click-option-scroll>
654#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
655#[serde(rename_all = "lowercase")]
656#[non_exhaustive]
657pub enum Scroll {
658    /// Scroll the element into view if needed (Playwright's default).
659    Auto,
660    /// Never scroll; act only if the element is already in view.
661    None,
662}
663
664#[cfg(test)]
665mod scroll_tests {
666    use super::{CheckOptions, HoverOptions, Scroll};
667
668    #[test]
669    fn scroll_serializes_lowercase() {
670        assert_eq!(serde_json::to_string(&Scroll::Auto).unwrap(), "\"auto\"");
671        assert_eq!(serde_json::to_string(&Scroll::None).unwrap(), "\"none\"");
672    }
673
674    #[test]
675    fn scroll_is_omitted_unless_set() {
676        // The driver defaults to "auto"; sending nothing keeps that, so an
677        // unset builder must not put the key on the wire.
678        let json = CheckOptions::builder().build().to_json();
679        assert!(json.get("scroll").is_none());
680
681        let json = HoverOptions::builder()
682            .scroll(Scroll::None)
683            .build()
684            .to_json();
685        assert_eq!(json["scroll"], "none");
686    }
687}