viewpoint_core/page/locator/debug/
mod.rs

1//! Debug and visualization methods for locators.
2//!
3//! Methods for highlighting and debugging element selections.
4
5use std::time::Duration;
6
7use tracing::{debug, instrument};
8
9use super::Locator;
10use crate::error::LocatorError;
11
12impl Locator<'_> {
13    /// Highlight the element for debugging purposes.
14    ///
15    /// This visually highlights the element with a magenta outline for 2 seconds,
16    /// making it easy to verify which element is being targeted.
17    ///
18    /// # Example
19    ///
20    /// ```ignore
21    /// // Highlight for default duration (2 seconds)
22    /// page.locator("button").highlight().await?;
23    ///
24    /// // Highlight for custom duration
25    /// page.locator("button").highlight_for(Duration::from_secs(5)).await?;
26    /// ```
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if the element cannot be found or highlighted.
31    #[instrument(level = "debug", skip(self), fields(selector = ?self.selector))]
32    pub async fn highlight(&self) -> Result<(), LocatorError> {
33        self.highlight_for(Duration::from_secs(2)).await
34    }
35
36    /// Highlight the element for a specific duration.
37    ///
38    /// # Arguments
39    ///
40    /// * `duration` - How long to show the highlight.
41    ///
42    /// # Errors
43    ///
44    /// Returns an error if the element cannot be found or highlighted.
45    #[instrument(level = "debug", skip(self), fields(selector = ?self.selector))]
46    pub async fn highlight_for(&self, duration: Duration) -> Result<(), LocatorError> {
47        self.wait_for_actionable().await?;
48
49        debug!(?duration, "Highlighting element");
50
51        // Add highlight style
52        let js = format!(
53            r"(function() {{
54                const elements = {selector};
55                if (elements.length === 0) return {{ found: false }};
56                
57                const el = elements[0];
58                const originalOutline = el.style.outline;
59                const originalOutlineOffset = el.style.outlineOffset;
60                const originalTransition = el.style.transition;
61                
62                // Apply highlight with animation
63                el.style.transition = 'outline 0.2s ease-in-out';
64                el.style.outline = '3px solid #ff00ff';
65                el.style.outlineOffset = '2px';
66                
67                // Store original styles for restoration
68                el.__viewpoint_original_outline = originalOutline;
69                el.__viewpoint_original_outline_offset = originalOutlineOffset;
70                el.__viewpoint_original_transition = originalTransition;
71                
72                return {{ found: true }};
73            }})()",
74            selector = self.selector.to_js_expression()
75        );
76
77        let result = self.evaluate_js(&js).await?;
78        let found = result.get("found").and_then(serde_json::Value::as_bool).unwrap_or(false);
79        if !found {
80            return Err(LocatorError::NotFound(format!("{:?}", self.selector)));
81        }
82
83        // Wait for the duration
84        tokio::time::sleep(duration).await;
85
86        // Remove highlight
87        let cleanup_js = format!(
88            r"(function() {{
89                const elements = {selector};
90                if (elements.length === 0) return;
91                
92                const el = elements[0];
93                el.style.outline = el.__viewpoint_original_outline || '';
94                el.style.outlineOffset = el.__viewpoint_original_outline_offset || '';
95                el.style.transition = el.__viewpoint_original_transition || '';
96                
97                delete el.__viewpoint_original_outline;
98                delete el.__viewpoint_original_outline_offset;
99                delete el.__viewpoint_original_transition;
100            }})()",
101            selector = self.selector.to_js_expression()
102        );
103
104        // Ignore cleanup errors - element may have been removed
105        let _ = self.evaluate_js(&cleanup_js).await;
106
107        Ok(())
108    }
109}