playwright_rs/protocol/
frame.rs

1// Frame protocol object
2//
3// Represents a frame within a page. Pages have a main frame, and can have child frames (iframes).
4// Navigation and DOM operations happen on frames, not directly on pages.
5
6use crate::error::{Error, Result};
7use crate::protocol::page::{GotoOptions, Response};
8use crate::protocol::{parse_result, serialize_argument, serialize_null};
9use crate::server::channel::Channel;
10use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
11use serde::Deserialize;
12use serde_json::Value;
13use std::any::Any;
14use std::sync::Arc;
15
16/// Frame represents a frame within a page.
17///
18/// Every page has a main frame, and pages can have additional child frames (iframes).
19/// Frame is where navigation, selector queries, and DOM operations actually happen.
20///
21/// In Playwright's architecture, Page delegates navigation and interaction methods to Frame.
22///
23/// See: <https://playwright.dev/docs/api/class-frame>
24#[derive(Clone)]
25pub struct Frame {
26    base: ChannelOwnerImpl,
27}
28
29impl Frame {
30    /// Creates a new Frame from protocol initialization
31    ///
32    /// This is called by the object factory when the server sends a `__create__` message
33    /// for a Frame object.
34    pub fn new(
35        parent: Arc<dyn ChannelOwner>,
36        type_name: String,
37        guid: Arc<str>,
38        initializer: Value,
39    ) -> Result<Self> {
40        let base = ChannelOwnerImpl::new(
41            ParentOrConnection::Parent(parent),
42            type_name,
43            guid,
44            initializer,
45        );
46
47        Ok(Self { base })
48    }
49
50    /// Returns the channel for sending protocol messages
51    fn channel(&self) -> &Channel {
52        self.base.channel()
53    }
54
55    /// Navigates the frame to the specified URL.
56    ///
57    /// This is the actual protocol method for navigation. Page.goto() delegates to this.
58    ///
59    /// Returns `None` when navigating to URLs that don't produce responses (e.g., data URLs,
60    /// about:blank). This matches Playwright's behavior across all language bindings.
61    ///
62    /// # Arguments
63    ///
64    /// * `url` - The URL to navigate to
65    /// * `options` - Optional navigation options (timeout, wait_until)
66    ///
67    /// See: <https://playwright.dev/docs/api/class-frame#frame-goto>
68    pub async fn goto(&self, url: &str, options: Option<GotoOptions>) -> Result<Option<Response>> {
69        // Build params manually using json! macro
70        let mut params = serde_json::json!({
71            "url": url,
72        });
73
74        // Add optional parameters
75        if let Some(opts) = options {
76            if let Some(timeout) = opts.timeout {
77                params["timeout"] = serde_json::json!(timeout.as_millis() as u64);
78            } else {
79                // Default timeout required in Playwright 1.56.1+
80                params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
81            }
82            if let Some(wait_until) = opts.wait_until {
83                params["waitUntil"] = serde_json::json!(wait_until.as_str());
84            }
85        } else {
86            // No options provided, set default timeout (required in Playwright 1.56.1+)
87            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
88        }
89
90        // Send goto RPC to Frame
91        // The server returns { "response": { "guid": "..." } } or null
92        #[derive(Deserialize)]
93        struct GotoResponse {
94            response: Option<ResponseReference>,
95        }
96
97        #[derive(Deserialize)]
98        struct ResponseReference {
99            #[serde(deserialize_with = "crate::server::connection::deserialize_arc_str")]
100            guid: Arc<str>,
101        }
102
103        let goto_result: GotoResponse = self.channel().send("goto", params).await?;
104
105        // If navigation returned a response, get the Response object from the connection
106        if let Some(response_ref) = goto_result.response {
107            // The server returns a Response GUID, but the __create__ message might not have
108            // arrived yet. Retry a few times to wait for the object to be created.
109            // TODO(Phase 4+): Implement proper GUID replacement like Python's _replace_guids_with_channels
110            //   - Eliminates retry loop for better performance
111            //   - See: playwright-python's _replace_guids_with_channels method
112            let response_arc = {
113                let mut attempts = 0;
114                let max_attempts = 20; // 20 * 50ms = 1 second max wait
115                loop {
116                    match self.connection().get_object(&response_ref.guid).await {
117                        Ok(obj) => break obj,
118                        Err(_) if attempts < max_attempts => {
119                            attempts += 1;
120                            tokio::time::sleep(std::time::Duration::from_millis(50)).await;
121                        }
122                        Err(e) => return Err(e),
123                    }
124                }
125            };
126
127            // Note: ResponseObject protocol object exists (crates/playwright-core/src/protocol/response.rs)
128            // We extract Response data from its initializer rather than wrapping the protocol object
129            let initializer = response_arc.initializer();
130
131            // Extract response data from initializer
132            let status = initializer["status"].as_u64().ok_or_else(|| {
133                crate::error::Error::ProtocolError("Response missing status".to_string())
134            })? as u16;
135
136            // Convert headers from array format to HashMap
137            let headers = initializer["headers"]
138                .as_array()
139                .ok_or_else(|| {
140                    crate::error::Error::ProtocolError("Response missing headers".to_string())
141                })?
142                .iter()
143                .filter_map(|h| {
144                    let name = h["name"].as_str()?;
145                    let value = h["value"].as_str()?;
146                    Some((name.to_string(), value.to_string()))
147                })
148                .collect();
149
150            Ok(Some(Response {
151                url: initializer["url"]
152                    .as_str()
153                    .ok_or_else(|| {
154                        crate::error::Error::ProtocolError("Response missing url".to_string())
155                    })?
156                    .to_string(),
157                status,
158                status_text: initializer["statusText"].as_str().unwrap_or("").to_string(),
159                ok: (200..300).contains(&status), // Compute ok from status code
160                headers,
161            }))
162        } else {
163            // Navigation returned null (e.g., data URLs, about:blank)
164            // This is a valid result, not an error
165            Ok(None)
166        }
167    }
168
169    /// Returns the frame's title.
170    ///
171    /// See: <https://playwright.dev/docs/api/class-frame#frame-title>
172    pub async fn title(&self) -> Result<String> {
173        #[derive(Deserialize)]
174        struct TitleResponse {
175            value: String,
176        }
177
178        let response: TitleResponse = self.channel().send("title", serde_json::json!({})).await?;
179        Ok(response.value)
180    }
181
182    /// Returns the first element matching the selector, or None if not found.
183    ///
184    /// See: <https://playwright.dev/docs/api/class-frame#frame-query-selector>
185    pub async fn query_selector(
186        &self,
187        selector: &str,
188    ) -> Result<Option<Arc<crate::protocol::ElementHandle>>> {
189        let response: serde_json::Value = self
190            .channel()
191            .send(
192                "querySelector",
193                serde_json::json!({
194                    "selector": selector
195                }),
196            )
197            .await?;
198
199        // Check if response is empty (no element found)
200        if response.as_object().map(|o| o.is_empty()).unwrap_or(true) {
201            return Ok(None);
202        }
203
204        // Try different possible field names
205        let element_value = if let Some(elem) = response.get("element") {
206            elem
207        } else if let Some(elem) = response.get("handle") {
208            elem
209        } else {
210            // Maybe the response IS the guid object itself
211            &response
212        };
213
214        if element_value.is_null() {
215            return Ok(None);
216        }
217
218        // Element response contains { guid: "elementHandle@123" }
219        let guid = element_value["guid"].as_str().ok_or_else(|| {
220            crate::error::Error::ProtocolError("Element GUID missing".to_string())
221        })?;
222
223        // Look up the ElementHandle object in the connection's object registry
224        let connection = self.base.connection();
225        let element = connection.get_object(guid).await?;
226
227        // Downcast to ElementHandle
228        let handle = element
229            .as_any()
230            .downcast_ref::<crate::protocol::ElementHandle>()
231            .map(|e| Arc::new(e.clone()))
232            .ok_or_else(|| {
233                crate::error::Error::ProtocolError(format!(
234                    "Object {} is not an ElementHandle",
235                    guid
236                ))
237            })?;
238
239        Ok(Some(handle))
240    }
241
242    /// Returns all elements matching the selector.
243    ///
244    /// See: <https://playwright.dev/docs/api/class-frame#frame-query-selector-all>
245    pub async fn query_selector_all(
246        &self,
247        selector: &str,
248    ) -> Result<Vec<Arc<crate::protocol::ElementHandle>>> {
249        #[derive(Deserialize)]
250        struct QueryAllResponse {
251            elements: Vec<serde_json::Value>,
252        }
253
254        let response: QueryAllResponse = self
255            .channel()
256            .send(
257                "querySelectorAll",
258                serde_json::json!({
259                    "selector": selector
260                }),
261            )
262            .await?;
263
264        // Convert GUID responses to ElementHandle objects
265        let connection = self.base.connection();
266        let mut handles = Vec::new();
267
268        for element_value in response.elements {
269            let guid = element_value["guid"].as_str().ok_or_else(|| {
270                crate::error::Error::ProtocolError("Element GUID missing".to_string())
271            })?;
272
273            let element = connection.get_object(guid).await?;
274
275            let handle = element
276                .as_any()
277                .downcast_ref::<crate::protocol::ElementHandle>()
278                .map(|e| Arc::new(e.clone()))
279                .ok_or_else(|| {
280                    crate::error::Error::ProtocolError(format!(
281                        "Object {} is not an ElementHandle",
282                        guid
283                    ))
284                })?;
285
286            handles.push(handle);
287        }
288
289        Ok(handles)
290    }
291
292    // Locator delegate methods
293    // These are called by Locator to perform actual queries
294
295    /// Returns the number of elements matching the selector.
296    pub(crate) async fn locator_count(&self, selector: &str) -> Result<usize> {
297        // Use querySelectorAll which returns array of element handles
298        #[derive(Deserialize)]
299        struct QueryAllResponse {
300            elements: Vec<serde_json::Value>,
301        }
302
303        let response: QueryAllResponse = self
304            .channel()
305            .send(
306                "querySelectorAll",
307                serde_json::json!({
308                    "selector": selector
309                }),
310            )
311            .await?;
312
313        Ok(response.elements.len())
314    }
315
316    /// Returns the text content of the element.
317    pub(crate) async fn locator_text_content(&self, selector: &str) -> Result<Option<String>> {
318        #[derive(Deserialize)]
319        struct TextContentResponse {
320            value: Option<String>,
321        }
322
323        let response: TextContentResponse = self
324            .channel()
325            .send(
326                "textContent",
327                serde_json::json!({
328                    "selector": selector,
329                    "strict": true,
330                    "timeout": crate::DEFAULT_TIMEOUT_MS
331                }),
332            )
333            .await?;
334
335        Ok(response.value)
336    }
337
338    /// Returns the inner text of the element.
339    pub(crate) async fn locator_inner_text(&self, selector: &str) -> Result<String> {
340        #[derive(Deserialize)]
341        struct InnerTextResponse {
342            value: String,
343        }
344
345        let response: InnerTextResponse = self
346            .channel()
347            .send(
348                "innerText",
349                serde_json::json!({
350                    "selector": selector,
351                    "strict": true,
352                    "timeout": crate::DEFAULT_TIMEOUT_MS
353                }),
354            )
355            .await?;
356
357        Ok(response.value)
358    }
359
360    /// Returns the inner HTML of the element.
361    pub(crate) async fn locator_inner_html(&self, selector: &str) -> Result<String> {
362        #[derive(Deserialize)]
363        struct InnerHTMLResponse {
364            value: String,
365        }
366
367        let response: InnerHTMLResponse = self
368            .channel()
369            .send(
370                "innerHTML",
371                serde_json::json!({
372                    "selector": selector,
373                    "strict": true,
374                    "timeout": crate::DEFAULT_TIMEOUT_MS
375                }),
376            )
377            .await?;
378
379        Ok(response.value)
380    }
381
382    /// Returns the value of the specified attribute.
383    pub(crate) async fn locator_get_attribute(
384        &self,
385        selector: &str,
386        name: &str,
387    ) -> Result<Option<String>> {
388        #[derive(Deserialize)]
389        struct GetAttributeResponse {
390            value: Option<String>,
391        }
392
393        let response: GetAttributeResponse = self
394            .channel()
395            .send(
396                "getAttribute",
397                serde_json::json!({
398                    "selector": selector,
399                    "name": name,
400                    "strict": true,
401                    "timeout": crate::DEFAULT_TIMEOUT_MS
402                }),
403            )
404            .await?;
405
406        Ok(response.value)
407    }
408
409    /// Returns whether the element is visible.
410    pub(crate) async fn locator_is_visible(&self, selector: &str) -> Result<bool> {
411        #[derive(Deserialize)]
412        struct IsVisibleResponse {
413            value: bool,
414        }
415
416        let response: IsVisibleResponse = self
417            .channel()
418            .send(
419                "isVisible",
420                serde_json::json!({
421                    "selector": selector,
422                    "strict": true,
423                    "timeout": crate::DEFAULT_TIMEOUT_MS
424                }),
425            )
426            .await?;
427
428        Ok(response.value)
429    }
430
431    /// Returns whether the element is enabled.
432    pub(crate) async fn locator_is_enabled(&self, selector: &str) -> Result<bool> {
433        #[derive(Deserialize)]
434        struct IsEnabledResponse {
435            value: bool,
436        }
437
438        let response: IsEnabledResponse = self
439            .channel()
440            .send(
441                "isEnabled",
442                serde_json::json!({
443                    "selector": selector,
444                    "strict": true,
445                    "timeout": crate::DEFAULT_TIMEOUT_MS
446                }),
447            )
448            .await?;
449
450        Ok(response.value)
451    }
452
453    /// Returns whether the checkbox or radio button is checked.
454    pub(crate) async fn locator_is_checked(&self, selector: &str) -> Result<bool> {
455        #[derive(Deserialize)]
456        struct IsCheckedResponse {
457            value: bool,
458        }
459
460        let response: IsCheckedResponse = self
461            .channel()
462            .send(
463                "isChecked",
464                serde_json::json!({
465                    "selector": selector,
466                    "strict": true,
467                    "timeout": crate::DEFAULT_TIMEOUT_MS
468                }),
469            )
470            .await?;
471
472        Ok(response.value)
473    }
474
475    /// Returns whether the element is editable.
476    pub(crate) async fn locator_is_editable(&self, selector: &str) -> Result<bool> {
477        #[derive(Deserialize)]
478        struct IsEditableResponse {
479            value: bool,
480        }
481
482        let response: IsEditableResponse = self
483            .channel()
484            .send(
485                "isEditable",
486                serde_json::json!({
487                    "selector": selector,
488                    "strict": true,
489                    "timeout": crate::DEFAULT_TIMEOUT_MS
490                }),
491            )
492            .await?;
493
494        Ok(response.value)
495    }
496
497    /// Returns whether the element is focused (currently has focus).
498    ///
499    /// This implementation checks if the element is the activeElement in the DOM
500    /// using JavaScript evaluation, since Playwright doesn't expose isFocused() at
501    /// the protocol level.
502    pub(crate) async fn locator_is_focused(&self, selector: &str) -> Result<bool> {
503        #[derive(Deserialize)]
504        struct EvaluateResult {
505            value: serde_json::Value,
506        }
507
508        // Use JavaScript to check if the element is the active element
509        // The script queries the DOM and returns true/false
510        let script = r#"selector => {
511                const elements = document.querySelectorAll(selector);
512                if (elements.length === 0) return false;
513                const element = elements[0];
514                return document.activeElement === element;
515            }"#;
516
517        let params = serde_json::json!({
518            "expression": script,
519            "arg": {
520                "value": {"s": selector},
521                "handles": []
522            }
523        });
524
525        let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
526
527        // Playwright protocol returns booleans as {"b": true} or {"b": false}
528        if let serde_json::Value::Object(map) = &result.value {
529            if let Some(b) = map.get("b").and_then(|v| v.as_bool()) {
530                return Ok(b);
531            }
532        }
533
534        // Fallback: check if the string representation is "true"
535        Ok(result.value.to_string().to_lowercase().contains("true"))
536    }
537
538    // Action delegate methods
539
540    /// Clicks the element matching the selector.
541    pub(crate) async fn locator_click(
542        &self,
543        selector: &str,
544        options: Option<crate::protocol::ClickOptions>,
545    ) -> Result<()> {
546        let mut params = serde_json::json!({
547            "selector": selector,
548            "strict": true
549        });
550
551        if let Some(opts) = options {
552            let opts_json = opts.to_json();
553            if let Some(obj) = params.as_object_mut() {
554                if let Some(opts_obj) = opts_json.as_object() {
555                    obj.extend(opts_obj.clone());
556                }
557            }
558        } else {
559            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
560        }
561
562        self.channel()
563            .send_no_result("click", params)
564            .await
565            .map_err(|e| match e {
566                Error::Timeout(msg) => {
567                    Error::Timeout(format!("{} (selector: '{}')", msg, selector))
568                }
569                other => other,
570            })
571    }
572
573    /// Double clicks the element matching the selector.
574    pub(crate) async fn locator_dblclick(
575        &self,
576        selector: &str,
577        options: Option<crate::protocol::ClickOptions>,
578    ) -> Result<()> {
579        let mut params = serde_json::json!({
580            "selector": selector,
581            "strict": true
582        });
583
584        if let Some(opts) = options {
585            let opts_json = opts.to_json();
586            if let Some(obj) = params.as_object_mut() {
587                if let Some(opts_obj) = opts_json.as_object() {
588                    obj.extend(opts_obj.clone());
589                }
590            }
591        } else {
592            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
593        }
594
595        self.channel().send_no_result("dblclick", params).await
596    }
597
598    /// Fills the element with text.
599    pub(crate) async fn locator_fill(
600        &self,
601        selector: &str,
602        text: &str,
603        options: Option<crate::protocol::FillOptions>,
604    ) -> Result<()> {
605        let mut params = serde_json::json!({
606            "selector": selector,
607            "value": text,
608            "strict": true
609        });
610
611        if let Some(opts) = options {
612            let opts_json = opts.to_json();
613            if let Some(obj) = params.as_object_mut() {
614                if let Some(opts_obj) = opts_json.as_object() {
615                    obj.extend(opts_obj.clone());
616                }
617            }
618        } else {
619            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
620        }
621
622        self.channel().send_no_result("fill", params).await
623    }
624
625    /// Clears the element's value.
626    pub(crate) async fn locator_clear(
627        &self,
628        selector: &str,
629        options: Option<crate::protocol::FillOptions>,
630    ) -> Result<()> {
631        let mut params = serde_json::json!({
632            "selector": selector,
633            "value": "",
634            "strict": true
635        });
636
637        if let Some(opts) = options {
638            let opts_json = opts.to_json();
639            if let Some(obj) = params.as_object_mut() {
640                if let Some(opts_obj) = opts_json.as_object() {
641                    obj.extend(opts_obj.clone());
642                }
643            }
644        } else {
645            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
646        }
647
648        self.channel().send_no_result("fill", params).await
649    }
650
651    /// Presses a key on the element.
652    pub(crate) async fn locator_press(
653        &self,
654        selector: &str,
655        key: &str,
656        options: Option<crate::protocol::PressOptions>,
657    ) -> Result<()> {
658        let mut params = serde_json::json!({
659            "selector": selector,
660            "key": key,
661            "strict": true
662        });
663
664        if let Some(opts) = options {
665            let opts_json = opts.to_json();
666            if let Some(obj) = params.as_object_mut() {
667                if let Some(opts_obj) = opts_json.as_object() {
668                    obj.extend(opts_obj.clone());
669                }
670            }
671        } else {
672            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
673        }
674
675        self.channel().send_no_result("press", params).await
676    }
677
678    pub(crate) async fn locator_check(
679        &self,
680        selector: &str,
681        options: Option<crate::protocol::CheckOptions>,
682    ) -> Result<()> {
683        let mut params = serde_json::json!({
684            "selector": selector,
685            "strict": true
686        });
687
688        if let Some(opts) = options {
689            let opts_json = opts.to_json();
690            if let Some(obj) = params.as_object_mut() {
691                if let Some(opts_obj) = opts_json.as_object() {
692                    obj.extend(opts_obj.clone());
693                }
694            }
695        } else {
696            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
697        }
698
699        self.channel().send_no_result("check", params).await
700    }
701
702    pub(crate) async fn locator_uncheck(
703        &self,
704        selector: &str,
705        options: Option<crate::protocol::CheckOptions>,
706    ) -> Result<()> {
707        let mut params = serde_json::json!({
708            "selector": selector,
709            "strict": true
710        });
711
712        if let Some(opts) = options {
713            let opts_json = opts.to_json();
714            if let Some(obj) = params.as_object_mut() {
715                if let Some(opts_obj) = opts_json.as_object() {
716                    obj.extend(opts_obj.clone());
717                }
718            }
719        } else {
720            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
721        }
722
723        self.channel().send_no_result("uncheck", params).await
724    }
725
726    pub(crate) async fn locator_hover(
727        &self,
728        selector: &str,
729        options: Option<crate::protocol::HoverOptions>,
730    ) -> Result<()> {
731        let mut params = serde_json::json!({
732            "selector": selector,
733            "strict": true
734        });
735
736        if let Some(opts) = options {
737            let opts_json = opts.to_json();
738            if let Some(obj) = params.as_object_mut() {
739                if let Some(opts_obj) = opts_json.as_object() {
740                    obj.extend(opts_obj.clone());
741                }
742            }
743        } else {
744            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
745        }
746
747        self.channel().send_no_result("hover", params).await
748    }
749
750    pub(crate) async fn locator_input_value(&self, selector: &str) -> Result<String> {
751        #[derive(Deserialize)]
752        struct InputValueResponse {
753            value: String,
754        }
755
756        let response: InputValueResponse = self
757            .channel()
758            .send(
759                "inputValue",
760                serde_json::json!({
761                    "selector": selector,
762                    "strict": true,
763                    "timeout": crate::DEFAULT_TIMEOUT_MS  // Required in Playwright 1.56.1+
764                }),
765            )
766            .await?;
767
768        Ok(response.value)
769    }
770
771    pub(crate) async fn locator_select_option(
772        &self,
773        selector: &str,
774        value: crate::protocol::SelectOption,
775        options: Option<crate::protocol::SelectOptions>,
776    ) -> Result<Vec<String>> {
777        #[derive(Deserialize)]
778        struct SelectOptionResponse {
779            values: Vec<String>,
780        }
781
782        let mut params = serde_json::json!({
783            "selector": selector,
784            "strict": true,
785            "options": [value.to_json()]
786        });
787
788        if let Some(opts) = options {
789            let opts_json = opts.to_json();
790            if let Some(obj) = params.as_object_mut() {
791                if let Some(opts_obj) = opts_json.as_object() {
792                    obj.extend(opts_obj.clone());
793                }
794            }
795        } else {
796            // No options provided, add default timeout (required in Playwright 1.56.1+)
797            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
798        }
799
800        let response: SelectOptionResponse = self.channel().send("selectOption", params).await?;
801
802        Ok(response.values)
803    }
804
805    pub(crate) async fn locator_select_option_multiple(
806        &self,
807        selector: &str,
808        values: Vec<crate::protocol::SelectOption>,
809        options: Option<crate::protocol::SelectOptions>,
810    ) -> Result<Vec<String>> {
811        #[derive(Deserialize)]
812        struct SelectOptionResponse {
813            values: Vec<String>,
814        }
815
816        let values_array: Vec<_> = values.iter().map(|v| v.to_json()).collect();
817
818        let mut params = serde_json::json!({
819            "selector": selector,
820            "strict": true,
821            "options": values_array
822        });
823
824        if let Some(opts) = options {
825            let opts_json = opts.to_json();
826            if let Some(obj) = params.as_object_mut() {
827                if let Some(opts_obj) = opts_json.as_object() {
828                    obj.extend(opts_obj.clone());
829                }
830            }
831        } else {
832            // No options provided, add default timeout (required in Playwright 1.56.1+)
833            params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
834        }
835
836        let response: SelectOptionResponse = self.channel().send("selectOption", params).await?;
837
838        Ok(response.values)
839    }
840
841    pub(crate) async fn locator_set_input_files(
842        &self,
843        selector: &str,
844        file: &std::path::PathBuf,
845    ) -> Result<()> {
846        use base64::{engine::general_purpose, Engine as _};
847        use std::io::Read;
848
849        // Read file contents
850        let mut file_handle = std::fs::File::open(file)?;
851        let mut buffer = Vec::new();
852        file_handle.read_to_end(&mut buffer)?;
853
854        // Base64 encode the file contents
855        let base64_content = general_purpose::STANDARD.encode(&buffer);
856
857        // Get file name
858        let file_name = file
859            .file_name()
860            .and_then(|n| n.to_str())
861            .ok_or_else(|| crate::error::Error::InvalidArgument("Invalid file path".to_string()))?;
862
863        self.channel()
864            .send_no_result(
865                "setInputFiles",
866                serde_json::json!({
867                    "selector": selector,
868                    "strict": true,
869                    "timeout": crate::DEFAULT_TIMEOUT_MS,  // Required in Playwright 1.56.1+
870                    "payloads": [{
871                        "name": file_name,
872                        "buffer": base64_content
873                    }]
874                }),
875            )
876            .await
877    }
878
879    pub(crate) async fn locator_set_input_files_multiple(
880        &self,
881        selector: &str,
882        files: &[&std::path::PathBuf],
883    ) -> Result<()> {
884        use base64::{engine::general_purpose, Engine as _};
885        use std::io::Read;
886
887        // If empty array, clear the files
888        if files.is_empty() {
889            return self
890                .channel()
891                .send_no_result(
892                    "setInputFiles",
893                    serde_json::json!({
894                        "selector": selector,
895                        "strict": true,
896                        "timeout": crate::DEFAULT_TIMEOUT_MS,  // Required in Playwright 1.56.1+
897                        "payloads": []
898                    }),
899                )
900                .await;
901        }
902
903        // Read and encode each file
904        let mut file_objects = Vec::new();
905        for file_path in files {
906            let mut file_handle = std::fs::File::open(file_path)?;
907            let mut buffer = Vec::new();
908            file_handle.read_to_end(&mut buffer)?;
909
910            let base64_content = general_purpose::STANDARD.encode(&buffer);
911            let file_name = file_path
912                .file_name()
913                .and_then(|n| n.to_str())
914                .ok_or_else(|| {
915                    crate::error::Error::InvalidArgument("Invalid file path".to_string())
916                })?;
917
918            file_objects.push(serde_json::json!({
919                "name": file_name,
920                "buffer": base64_content
921            }));
922        }
923
924        self.channel()
925            .send_no_result(
926                "setInputFiles",
927                serde_json::json!({
928                    "selector": selector,
929                    "strict": true,
930                    "timeout": crate::DEFAULT_TIMEOUT_MS,  // Required in Playwright 1.56.1+
931                    "payloads": file_objects
932                }),
933            )
934            .await
935    }
936
937    pub(crate) async fn locator_set_input_files_payload(
938        &self,
939        selector: &str,
940        file: crate::protocol::FilePayload,
941    ) -> Result<()> {
942        use base64::{engine::general_purpose, Engine as _};
943
944        // Base64 encode the file contents
945        let base64_content = general_purpose::STANDARD.encode(&file.buffer);
946
947        self.channel()
948            .send_no_result(
949                "setInputFiles",
950                serde_json::json!({
951                    "selector": selector,
952                    "strict": true,
953                    "timeout": crate::DEFAULT_TIMEOUT_MS,
954                    "payloads": [{
955                        "name": file.name,
956                        "mimeType": file.mime_type,
957                        "buffer": base64_content
958                    }]
959                }),
960            )
961            .await
962    }
963
964    pub(crate) async fn locator_set_input_files_payload_multiple(
965        &self,
966        selector: &str,
967        files: &[crate::protocol::FilePayload],
968    ) -> Result<()> {
969        use base64::{engine::general_purpose, Engine as _};
970
971        // If empty array, clear the files
972        if files.is_empty() {
973            return self
974                .channel()
975                .send_no_result(
976                    "setInputFiles",
977                    serde_json::json!({
978                        "selector": selector,
979                        "strict": true,
980                        "timeout": crate::DEFAULT_TIMEOUT_MS,
981                        "payloads": []
982                    }),
983                )
984                .await;
985        }
986
987        // Encode each file
988        let file_objects: Vec<_> = files
989            .iter()
990            .map(|file| {
991                let base64_content = general_purpose::STANDARD.encode(&file.buffer);
992                serde_json::json!({
993                    "name": file.name,
994                    "mimeType": file.mime_type,
995                    "buffer": base64_content
996                })
997            })
998            .collect();
999
1000        self.channel()
1001            .send_no_result(
1002                "setInputFiles",
1003                serde_json::json!({
1004                    "selector": selector,
1005                    "strict": true,
1006                    "timeout": crate::DEFAULT_TIMEOUT_MS,
1007                    "payloads": file_objects
1008                }),
1009            )
1010            .await
1011    }
1012
1013    /// Evaluates JavaScript expression in the frame context (without return value).
1014    ///
1015    /// This is used internally by Page.evaluate().
1016    pub(crate) async fn frame_evaluate_expression(&self, expression: &str) -> Result<()> {
1017        let params = serde_json::json!({
1018            "expression": expression,
1019            "arg": {
1020                "value": {"v": "null"},
1021                "handles": []
1022            }
1023        });
1024
1025        let _: serde_json::Value = self.channel().send("evaluateExpression", params).await?;
1026        Ok(())
1027    }
1028
1029    /// Evaluates JavaScript expression and returns the result as a String.
1030    ///
1031    /// The return value is automatically converted to a string representation.
1032    ///
1033    /// # Arguments
1034    ///
1035    /// * `expression` - JavaScript code to evaluate
1036    ///
1037    /// # Returns
1038    ///
1039    /// The result as a String
1040    pub(crate) async fn frame_evaluate_expression_value(&self, expression: &str) -> Result<String> {
1041        let params = serde_json::json!({
1042            "expression": expression,
1043            "arg": {
1044                "value": {"v": "null"},
1045                "handles": []
1046            }
1047        });
1048
1049        #[derive(Deserialize)]
1050        struct EvaluateResult {
1051            value: serde_json::Value,
1052        }
1053
1054        let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
1055
1056        // Playwright protocol returns values in a wrapped format:
1057        // - String: {"s": "value"}
1058        // - Number: {"n": 123}
1059        // - Boolean: {"b": true}
1060        // - Null: {"v": "null"}
1061        // - Undefined: {"v": "undefined"}
1062        match &result.value {
1063            Value::Object(map) => {
1064                if let Some(s) = map.get("s").and_then(|v| v.as_str()) {
1065                    // String value
1066                    Ok(s.to_string())
1067                } else if let Some(n) = map.get("n") {
1068                    // Number value
1069                    Ok(n.to_string())
1070                } else if let Some(b) = map.get("b").and_then(|v| v.as_bool()) {
1071                    // Boolean value
1072                    Ok(b.to_string())
1073                } else if let Some(v) = map.get("v").and_then(|v| v.as_str()) {
1074                    // null or undefined
1075                    Ok(v.to_string())
1076                } else {
1077                    // Unknown format, return JSON
1078                    Ok(result.value.to_string())
1079                }
1080            }
1081            _ => {
1082                // Fallback for unexpected formats
1083                Ok(result.value.to_string())
1084            }
1085        }
1086    }
1087
1088    /// Evaluates a JavaScript expression in the frame context with optional arguments.
1089    ///
1090    /// Executes the provided JavaScript expression within the frame's context and returns
1091    /// the result. The return value must be JSON-serializable.
1092    ///
1093    /// # Arguments
1094    ///
1095    /// * `expression` - JavaScript code to evaluate
1096    /// * `arg` - Optional argument to pass to the expression (must implement Serialize)
1097    ///
1098    /// # Returns
1099    ///
1100    /// The result as a `serde_json::Value`
1101    ///
1102    /// # Example
1103    ///
1104    /// ```ignore
1105    /// use serde_json::json;
1106    /// use playwright_rs::protocol::Playwright;
1107    ///
1108    /// #[tokio::main]
1109    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
1110    ///     let playwright = Playwright::launch().await?;
1111    ///     let browser = playwright.chromium().launch().await?;
1112    ///     let page = browser.new_page().await?;
1113    ///     let frame = page.main_frame().await?;
1114    ///
1115    ///     // Evaluate without arguments
1116    ///     let result = frame.evaluate::<()>("1 + 1", None).await?;
1117    ///
1118    ///     // Evaluate with argument
1119    ///     let arg = json!({"x": 5, "y": 3});
1120    ///     let result = frame.evaluate::<serde_json::Value>("(arg) => arg.x + arg.y", Some(&arg)).await?;
1121    ///     assert_eq!(result, json!(8));
1122    ///     Ok(())
1123    /// }
1124    /// ```
1125    ///
1126    /// See: <https://playwright.dev/docs/api/class-frame#frame-evaluate>
1127    pub async fn evaluate<T: serde::Serialize>(
1128        &self,
1129        expression: &str,
1130        arg: Option<&T>,
1131    ) -> Result<Value> {
1132        // Serialize the argument
1133        let serialized_arg = match arg {
1134            Some(a) => serialize_argument(a),
1135            None => serialize_null(),
1136        };
1137
1138        // Build the parameters
1139        let params = serde_json::json!({
1140            "expression": expression,
1141            "arg": serialized_arg
1142        });
1143
1144        // Send the evaluateExpression command
1145        #[derive(Deserialize)]
1146        struct EvaluateResult {
1147            value: serde_json::Value,
1148        }
1149
1150        let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
1151
1152        // Deserialize the result using parse_result
1153        Ok(parse_result(&result.value))
1154    }
1155
1156    /// Adds a `<style>` tag into the page with the desired content.
1157    ///
1158    /// # Arguments
1159    ///
1160    /// * `options` - Style tag options (content, url, or path)
1161    ///
1162    /// At least one of `content`, `url`, or `path` must be specified.
1163    ///
1164    /// # Example
1165    ///
1166    /// ```no_run
1167    /// # use playwright_rs::protocol::{Playwright, AddStyleTagOptions};
1168    /// # #[tokio::main]
1169    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1170    /// # let playwright = Playwright::launch().await?;
1171    /// # let browser = playwright.chromium().launch().await?;
1172    /// # let context = browser.new_context().await?;
1173    /// # let page = context.new_page().await?;
1174    /// # let frame = page.main_frame().await?;
1175    /// use playwright_rs::protocol::AddStyleTagOptions;
1176    ///
1177    /// // With inline CSS
1178    /// frame.add_style_tag(
1179    ///     AddStyleTagOptions::builder()
1180    ///         .content("body { background-color: red; }")
1181    ///         .build()
1182    /// ).await?;
1183    ///
1184    /// // With URL
1185    /// frame.add_style_tag(
1186    ///     AddStyleTagOptions::builder()
1187    ///         .url("https://example.com/style.css")
1188    ///         .build()
1189    /// ).await?;
1190    /// # Ok(())
1191    /// # }
1192    /// ```
1193    ///
1194    /// See: <https://playwright.dev/docs/api/class-frame#frame-add-style-tag>
1195    pub async fn add_style_tag(
1196        &self,
1197        options: crate::protocol::page::AddStyleTagOptions,
1198    ) -> Result<Arc<crate::protocol::ElementHandle>> {
1199        // Validate that at least one option is provided
1200        options.validate()?;
1201
1202        // Build protocol parameters
1203        let mut params = serde_json::json!({});
1204
1205        if let Some(content) = &options.content {
1206            params["content"] = serde_json::json!(content);
1207        }
1208
1209        if let Some(url) = &options.url {
1210            params["url"] = serde_json::json!(url);
1211        }
1212
1213        if let Some(path) = &options.path {
1214            // Read file content and send as content
1215            let css_content = tokio::fs::read_to_string(path).await.map_err(|e| {
1216                Error::InvalidArgument(format!("Failed to read CSS file '{}': {}", path, e))
1217            })?;
1218            params["content"] = serde_json::json!(css_content);
1219        }
1220
1221        #[derive(Deserialize)]
1222        struct AddStyleTagResponse {
1223            element: serde_json::Value,
1224        }
1225
1226        let response: AddStyleTagResponse = self.channel().send("addStyleTag", params).await?;
1227
1228        let guid = response.element["guid"].as_str().ok_or_else(|| {
1229            Error::ProtocolError("Element GUID missing in addStyleTag response".to_string())
1230        })?;
1231
1232        let connection = self.base.connection();
1233        let element = connection.get_object(guid).await?;
1234
1235        let handle = element
1236            .as_any()
1237            .downcast_ref::<crate::protocol::ElementHandle>()
1238            .map(|e| Arc::new(e.clone()))
1239            .ok_or_else(|| {
1240                Error::ProtocolError(format!("Object {} is not an ElementHandle", guid))
1241            })?;
1242
1243        Ok(handle)
1244    }
1245}
1246
1247impl ChannelOwner for Frame {
1248    fn guid(&self) -> &str {
1249        self.base.guid()
1250    }
1251
1252    fn type_name(&self) -> &str {
1253        self.base.type_name()
1254    }
1255
1256    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
1257        self.base.parent()
1258    }
1259
1260    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
1261        self.base.connection()
1262    }
1263
1264    fn initializer(&self) -> &Value {
1265        self.base.initializer()
1266    }
1267
1268    fn channel(&self) -> &Channel {
1269        self.base.channel()
1270    }
1271
1272    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
1273        self.base.dispose(reason)
1274    }
1275
1276    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
1277        self.base.adopt(child)
1278    }
1279
1280    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
1281        self.base.add_child(guid, child)
1282    }
1283
1284    fn remove_child(&self, guid: &str) {
1285        self.base.remove_child(guid)
1286    }
1287
1288    fn on_event(&self, _method: &str, _params: Value) {
1289        // TODO: Handle frame events in future phases
1290        // Events: loadstate, navigated, etc.
1291    }
1292
1293    fn was_collected(&self) -> bool {
1294        self.base.was_collected()
1295    }
1296
1297    fn as_any(&self) -> &dyn Any {
1298        self
1299    }
1300}
1301
1302impl std::fmt::Debug for Frame {
1303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1304        f.debug_struct("Frame").field("guid", &self.guid()).finish()
1305    }
1306}