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