Skip to main content

playwright_rs/protocol/
root.rs

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