viewpoint_core/page/locator/builders/
fill.rs

1//! Fill builder for locator actions.
2
3use tracing::{debug, instrument};
4use viewpoint_cdp::protocol::input::DispatchKeyEventParams;
5
6use super::super::Locator;
7use crate::error::LocatorError;
8use crate::wait::NavigationWaiter;
9
10/// Builder for fill operations with configurable options.
11///
12/// Created via [`Locator::fill`].
13#[derive(Debug)]
14pub struct FillBuilder<'l, 'a> {
15    locator: &'l Locator<'a>,
16    text: String,
17    /// Whether to skip waiting for navigation after the action.
18    no_wait_after: bool,
19}
20
21impl<'l, 'a> FillBuilder<'l, 'a> {
22    pub(crate) fn new(locator: &'l Locator<'a>, text: &str) -> Self {
23        Self {
24            locator,
25            text: text.to_string(),
26            no_wait_after: false,
27        }
28    }
29
30    /// Whether to skip waiting for navigation after the fill.
31    ///
32    /// By default, the fill will wait for any triggered navigation to complete.
33    /// Set to `true` to return immediately after the text is filled.
34    #[must_use]
35    pub fn no_wait_after(mut self, no_wait_after: bool) -> Self {
36        self.no_wait_after = no_wait_after;
37        self
38    }
39
40    /// Execute the fill operation.
41    #[instrument(level = "debug", skip(self), fields(selector = ?self.locator.selector))]
42    pub async fn send(self) -> Result<(), LocatorError> {
43        // Set up navigation waiter before the action if needed
44        let navigation_waiter = if self.no_wait_after {
45            None
46        } else {
47            Some(NavigationWaiter::new(
48                self.locator.page.connection().subscribe_events(),
49                self.locator.page.session_id().to_string(),
50                self.locator.page.frame_id().to_string(),
51            ))
52        };
53
54        // Perform the fill action
55        self.perform_fill().await?;
56
57        // Wait for navigation if triggered
58        if let Some(waiter) = navigation_waiter {
59            if let Err(e) = waiter.wait_for_navigation_if_triggered().await {
60                debug!(error = ?e, "Navigation wait failed after fill");
61                return Err(LocatorError::WaitError(e));
62            }
63        }
64
65        Ok(())
66    }
67
68    /// Perform the actual fill without navigation waiting.
69    async fn perform_fill(&self) -> Result<(), LocatorError> {
70        self.locator.wait_for_actionable().await?;
71
72        debug!(text = %self.text, "Filling element");
73
74        // Focus the element
75        self.locator.focus_element().await?;
76
77        // Select all and delete (clear)
78        self.locator
79            .dispatch_key_event(DispatchKeyEventParams::key_down("a"))
80            .await?;
81        // Send Ctrl+A
82        let mut select_all = DispatchKeyEventParams::key_down("a");
83        select_all.modifiers = Some(viewpoint_cdp::protocol::input::modifiers::CTRL);
84        self.locator.dispatch_key_event(select_all).await?;
85
86        // Delete selected text
87        self.locator
88            .dispatch_key_event(DispatchKeyEventParams::key_down("Backspace"))
89            .await?;
90
91        // Insert the new text
92        self.locator.insert_text(&self.text).await?;
93
94        Ok(())
95    }
96}
97
98impl<'l> std::future::IntoFuture for FillBuilder<'l, '_> {
99    type Output = Result<(), LocatorError>;
100    type IntoFuture =
101        std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send + 'l>>;
102
103    fn into_future(self) -> Self::IntoFuture {
104        Box::pin(self.send())
105    }
106}