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