playwright_core/protocol/
root.rs

1// Copyright 2024 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// Root - Internal object for sending initialize message
5//
6// Reference:
7// - Python: playwright-python/playwright/_impl/_connection.py (RootChannelOwner)
8// - Java: playwright-java/.../impl/Connection.java (Root inner class)
9// - .NET: playwright-dotnet/src/Playwright/Transport/Connection.cs (InitializePlaywrightAsync)
10
11use crate::channel::Channel;
12use crate::channel_owner::{ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection};
13use crate::connection::ConnectionLike;
14use crate::error::Result;
15use serde_json::Value;
16use std::any::Any;
17use std::sync::Arc;
18
19/// Root object for sending the initialize message to the Playwright server
20///
21/// This is an internal object not exposed to end users. It exists solely to
22/// send the `initialize` message to the server during connection setup.
23///
24/// # Protocol Flow
25///
26/// When `initialize()` is called:
27/// 1. Sends `initialize` message with `sdkLanguage: "rust"`
28/// 2. Server creates BrowserType objects (sends `__create__` messages)
29/// 3. Server creates Playwright object (sends `__create__` message)
30/// 4. Server responds with Playwright GUID: `{ "playwright": { "guid": "..." } }`
31/// 5. All objects are now in the connection's object registry
32///
33/// The Root object has an empty GUID (`""`) and is not registered in the
34/// object registry. It's discarded after initialization completes.
35///
36/// # Example
37///
38/// ```ignore
39/// # use playwright_core::protocol::Root;
40/// # use playwright_core::connection::ConnectionLike;
41/// # use std::sync::Arc;
42/// # async fn example(connection: Arc<dyn ConnectionLike>) -> Result<(), Box<dyn std::error::Error>> {
43/// // Create root object with connection
44/// let root = Root::new(connection.clone());
45///
46/// // Send initialize message to server
47/// let response = root.initialize().await?;
48///
49/// // Verify Playwright GUID is returned
50/// let playwright_guid = response["playwright"]["guid"]
51///     .as_str()
52///     .expect("Missing playwright.guid");
53/// assert!(!playwright_guid.is_empty());
54/// assert!(playwright_guid.contains("playwright"));
55///
56/// // Verify response contains BrowserType objects
57/// assert!(response["playwright"].is_object());
58/// # Ok(())
59/// # }
60/// ```
61///
62/// See:
63/// - Python: <https://github.com/microsoft/playwright-python/blob/main/playwright/_impl/_connection.py>
64/// - Java: <https://github.com/microsoft/playwright-java>
65pub struct Root {
66    /// Base ChannelOwner implementation
67    base: ChannelOwnerImpl,
68}
69
70impl Root {
71    /// Creates a new Root object
72    ///
73    /// # Arguments
74    ///
75    /// * `connection` - The connection to the Playwright server
76    pub fn new(connection: Arc<dyn ConnectionLike>) -> Self {
77        Self {
78            base: ChannelOwnerImpl::new(
79                ParentOrConnection::Connection(connection),
80                "Root".to_string(),
81                Arc::from(""), // Empty GUID - Root is not registered in object map
82                Value::Null,
83            ),
84        }
85    }
86
87    /// Send the initialize message to the Playwright server
88    ///
89    /// This is a synchronous request that blocks until the server responds.
90    /// By the time the response arrives, all protocol objects (Playwright,
91    /// BrowserType, etc.) will have been created and registered.
92    ///
93    /// # Returns
94    ///
95    /// The server response containing the Playwright object GUID:
96    /// ```json
97    /// {
98    ///   "playwright": {
99    ///     "guid": "playwright"
100    ///   }
101    /// }
102    /// ```
103    ///
104    /// # Errors
105    ///
106    /// Returns error if:
107    /// - Message send fails
108    /// - Server returns protocol error
109    /// - Connection is closed
110    pub async fn initialize(&self) -> Result<Value> {
111        self.channel()
112            .send(
113                "initialize",
114                serde_json::json!({
115                    // TODO: Use "rust" once upstream Playwright accepts it
116                    // Current issue: Playwright v1.49.0 protocol validator only accepts:
117                    // (javascript|python|java|csharp)
118                    //
119                    // Using "python" because:
120                    // - Closest async/await patterns to Rust
121                    // - sdkLanguage only affects CLI error messages and codegen
122                    // - Does NOT affect core protocol functionality
123                    // - Python error messages are appropriate ("playwright install")
124                    //
125                    // Plan: Contribute to microsoft/playwright to add 'rust' to Language enum
126                    // See: packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts
127                    "sdkLanguage": "python"
128                }),
129            )
130            .await
131    }
132}
133
134impl ChannelOwner for Root {
135    fn guid(&self) -> &str {
136        self.base.guid()
137    }
138
139    fn type_name(&self) -> &str {
140        self.base.type_name()
141    }
142
143    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
144        self.base.parent()
145    }
146
147    fn connection(&self) -> Arc<dyn ConnectionLike> {
148        self.base.connection()
149    }
150
151    fn initializer(&self) -> &Value {
152        self.base.initializer()
153    }
154
155    fn channel(&self) -> &Channel {
156        self.base.channel()
157    }
158
159    fn dispose(&self, reason: DisposeReason) {
160        self.base.dispose(reason)
161    }
162
163    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
164        self.base.adopt(child)
165    }
166
167    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
168        self.base.add_child(guid, child)
169    }
170
171    fn remove_child(&self, guid: &str) {
172        self.base.remove_child(guid)
173    }
174
175    fn on_event(&self, method: &str, params: Value) {
176        self.base.on_event(method, params)
177    }
178
179    fn was_collected(&self) -> bool {
180        self.base.was_collected()
181    }
182
183    fn as_any(&self) -> &dyn Any {
184        self
185    }
186}
187
188impl std::fmt::Debug for Root {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        f.debug_struct("Root")
191            .field("guid", &self.guid())
192            .field("type_name", &self.type_name())
193            .finish()
194    }
195}
196
197// Note: Root object testing is done via integration tests since it requires:
198// - A real Connection to send messages
199// - A real Playwright server to respond
200// See: crates/playwright-core/tests/initialization_integration.rs