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 an `sdkLanguage` the driver accepts
30/// (see [`Root::initialize`] for why it is not `"rust"`)
31/// 2. Server creates BrowserType objects (sends `__create__` messages)
32/// 3. Server creates Playwright object (sends `__create__` message)
33/// 4. Server responds with Playwright GUID: `{ "playwright": { "guid": "..." } }`
34/// 5. All objects are now in the connection's object registry
35///
36/// The Root object has an empty GUID (`""`) and is not registered in the
37/// object registry. It's discarded after initialization completes.
38///
39/// # Example
40///
41/// ```no_run
42/// # use playwright_rs::protocol::Root;
43/// # use playwright_rs::server::connection::ConnectionLike;
44/// # use std::sync::Arc;
45/// # async fn example(connection: Arc<dyn ConnectionLike>) -> Result<(), Box<dyn std::error::Error>> {
46/// // Create root object with connection
47/// let root = Root::new(connection.clone());
48///
49/// // Send initialize message to server
50/// let response = root.initialize().await?;
51///
52/// // Verify Playwright GUID is returned
53/// let playwright_guid = response["playwright"]["guid"]
54/// .as_str()
55/// .expect("Missing playwright.guid");
56/// assert!(!playwright_guid.is_empty());
57/// assert!(playwright_guid.contains("playwright"));
58///
59/// // Verify response contains BrowserType objects
60/// assert!(response["playwright"].is_object());
61/// # Ok(())
62/// # }
63/// ```
64///
65/// See:
66/// - Python: <https://github.com/microsoft/playwright-python/blob/main/playwright/_impl/_connection.py>
67/// - Java: <https://github.com/microsoft/playwright-java>
68#[derive(Clone)]
69pub struct Root {
70 /// Base ChannelOwner implementation
71 base: ChannelOwnerImpl,
72}
73
74impl Root {
75 /// Creates a new Root object
76 ///
77 /// # Arguments
78 ///
79 /// * `connection` - The connection to the Playwright server
80 pub fn new(connection: Arc<dyn ConnectionLike>) -> Self {
81 Self {
82 base: ChannelOwnerImpl::new(
83 ParentOrConnection::Connection(connection),
84 "Root".to_string(),
85 Arc::from(""), // Empty GUID - Root is not registered in object map
86 Value::Null,
87 ),
88 }
89 }
90
91 /// Send the initialize message to the Playwright server
92 ///
93 /// This is a synchronous request that blocks until the server responds.
94 /// By the time the response arrives, all protocol objects (Playwright,
95 /// BrowserType, etc.) will have been created and registered.
96 ///
97 /// # Why `sdkLanguage` is `"python"`, not `"rust"`
98 ///
99 /// The driver's protocol validator accepts only
100 /// `javascript`, `python`, `java`, and `csharp`, so `"rust"` is rejected
101 /// outright. `"python"` is the closest fit: its async/await shape matches
102 /// this crate's, and its error text (`playwright install`) reads correctly.
103 ///
104 /// One visible consequence: the driver records this value in trace files,
105 /// so the Playwright trace viewer labels traces produced by this crate as
106 /// Python and renders action snippets in Python syntax. Protocol behavior
107 /// is unaffected.
108 ///
109 /// # Returns
110 ///
111 /// The server response containing the Playwright object GUID:
112 /// ```json
113 /// {
114 /// "playwright": {
115 /// "guid": "playwright"
116 /// }
117 /// }
118 /// ```
119 ///
120 /// # Errors
121 ///
122 /// Returns error if:
123 /// - Message send fails
124 /// - Server returns protocol error
125 /// - Connection is closed
126 pub async fn initialize(&self) -> Result<Value> {
127 self.channel()
128 .send(
129 "initialize",
130 serde_json::json!({
131 // See this method's rustdoc for why this is not "rust".
132 // Validator enum last confirmed against playwright@1.62.1.
133 "sdkLanguage": "python"
134 }),
135 )
136 .await
137 }
138}
139
140impl ChannelOwner for Root {
141 fn guid(&self) -> &str {
142 self.base.guid()
143 }
144
145 fn type_name(&self) -> &str {
146 self.base.type_name()
147 }
148
149 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
150 self.base.parent()
151 }
152
153 fn connection(&self) -> Arc<dyn ConnectionLike> {
154 self.base.connection()
155 }
156
157 fn initializer(&self) -> &Value {
158 self.base.initializer()
159 }
160
161 fn channel(&self) -> &Channel {
162 self.base.channel()
163 }
164
165 fn dispose(&self, reason: DisposeReason) {
166 self.base.dispose(reason)
167 }
168
169 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
170 self.base.adopt(child)
171 }
172
173 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
174 self.base.add_child(guid, child)
175 }
176
177 fn remove_child(&self, guid: &str) {
178 self.base.remove_child(guid)
179 }
180
181 fn on_event(&self, method: &str, params: Value) {
182 self.base.on_event(method, params)
183 }
184
185 fn was_collected(&self) -> bool {
186 self.base.was_collected()
187 }
188
189 fn as_any(&self) -> &dyn Any {
190 self
191 }
192}
193
194impl std::fmt::Debug for Root {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.debug_struct("Root")
197 .field("guid", &self.guid())
198 .field("type_name", &self.type_name())
199 .finish()
200 }
201}
202
203// Note: Root object testing is done via integration tests since it requires:
204// - A real Connection to send messages
205// - A real Playwright server to respond
206// See: crates/playwright-core/tests/initialization_integration.rs