playwright_rs/protocol/
element_handle.rs

1// ElementHandle protocol object
2//
3// Represents a DOM element in the page. Supports element-specific operations like screenshots.
4// ElementHandles are created via query_selector methods and are protocol objects with GUIDs.
5
6use crate::error::Result;
7use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
8use base64::Engine;
9use serde::Deserialize;
10use serde_json::Value;
11use std::any::Any;
12use std::sync::Arc;
13
14/// ElementHandle represents a DOM element in the page.
15///
16/// ElementHandles are created via `page.query_selector()` or `frame.query_selector()`.
17/// They are protocol objects that allow element-specific operations like taking screenshots.
18///
19/// See: <https://playwright.dev/docs/api/class-elementhandle>
20#[derive(Clone)]
21pub struct ElementHandle {
22    base: ChannelOwnerImpl,
23}
24
25impl ElementHandle {
26    /// Creates a new ElementHandle from protocol initialization
27    ///
28    /// This is called by the object factory when the server sends a `__create__` message
29    /// for an ElementHandle object.
30    pub fn new(
31        parent: Arc<dyn ChannelOwner>,
32        type_name: String,
33        guid: Arc<str>,
34        initializer: Value,
35    ) -> Result<Self> {
36        let base = ChannelOwnerImpl::new(
37            ParentOrConnection::Parent(parent),
38            type_name,
39            guid,
40            initializer,
41        );
42
43        Ok(Self { base })
44    }
45
46    /// Takes a screenshot of the element and returns the image bytes.
47    ///
48    /// The screenshot is captured as PNG by default.
49    ///
50    /// # Example
51    ///
52    /// ```ignore
53    /// # use playwright_rs::protocol::Playwright;
54    /// # #[tokio::main]
55    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
56    /// let playwright = Playwright::launch().await?;
57    /// let browser = playwright.chromium().launch().await?;
58    /// let page = browser.new_page().await?;
59    /// page.goto("https://example.com", None).await?;
60    ///
61    /// let element = page.query_selector("h1").await?.expect("h1 not found");
62    /// let screenshot_bytes = element.screenshot(None).await?;
63    /// # Ok(())
64    /// # }
65    /// ```
66    ///
67    /// See: <https://playwright.dev/docs/api/class-elementhandle#element-handle-screenshot>
68    pub async fn screenshot(
69        &self,
70        options: Option<crate::protocol::ScreenshotOptions>,
71    ) -> Result<Vec<u8>> {
72        let params = if let Some(opts) = options {
73            opts.to_json()
74        } else {
75            // Default to PNG with required timeout
76            serde_json::json!({
77                "type": "png",
78                "timeout": crate::DEFAULT_TIMEOUT_MS
79            })
80        };
81
82        #[derive(Deserialize)]
83        struct ScreenshotResponse {
84            binary: String,
85        }
86
87        let response: ScreenshotResponse = self.base.channel().send("screenshot", params).await?;
88
89        // Decode base64 to bytes
90        let bytes = base64::prelude::BASE64_STANDARD
91            .decode(&response.binary)
92            .map_err(|e| {
93                crate::error::Error::ProtocolError(format!(
94                    "Failed to decode element screenshot: {}",
95                    e
96                ))
97            })?;
98
99        Ok(bytes)
100    }
101}
102
103impl ChannelOwner for ElementHandle {
104    fn guid(&self) -> &str {
105        self.base.guid()
106    }
107
108    fn type_name(&self) -> &str {
109        self.base.type_name()
110    }
111
112    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
113        self.base.parent()
114    }
115
116    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
117        self.base.connection()
118    }
119
120    fn initializer(&self) -> &Value {
121        self.base.initializer()
122    }
123
124    fn channel(&self) -> &crate::server::channel::Channel {
125        self.base.channel()
126    }
127
128    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
129        self.base.dispose(reason)
130    }
131
132    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
133        self.base.adopt(child)
134    }
135
136    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
137        self.base.add_child(guid, child)
138    }
139
140    fn remove_child(&self, guid: &str) {
141        self.base.remove_child(guid)
142    }
143
144    fn on_event(&self, _method: &str, _params: Value) {
145        // ElementHandle events will be handled in future phases if needed
146    }
147
148    fn was_collected(&self) -> bool {
149        self.base.was_collected()
150    }
151
152    fn as_any(&self) -> &dyn Any {
153        self
154    }
155}
156
157impl std::fmt::Debug for ElementHandle {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        f.debug_struct("ElementHandle")
160            .field("guid", &self.guid())
161            .finish()
162    }
163}