playwright_core/protocol/browser.rs
1// Browser protocol object
2//
3// Represents a browser instance created by BrowserType.launch()
4
5use crate::channel::Channel;
6use crate::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
7use crate::error::Result;
8use crate::protocol::{BrowserContext, Page};
9use serde::Deserialize;
10use serde_json::Value;
11use std::any::Any;
12use std::sync::Arc;
13
14/// Browser represents a browser instance.
15///
16/// A Browser is created when you call `BrowserType::launch()`. It provides methods
17/// to create browser contexts and pages.
18///
19/// # Example
20///
21/// ```ignore
22/// use playwright_core::protocol::Playwright;
23///
24/// #[tokio::main]
25/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
26/// let playwright = Playwright::launch().await?;
27/// let chromium = playwright.chromium();
28///
29/// // Launch browser and get info
30/// let browser = chromium.launch().await?;
31/// println!("Browser: {} version {}", browser.name(), browser.version());
32///
33/// // Create and use contexts and pages
34/// let context = browser.new_context().await?;
35/// let page = context.new_page().await?;
36///
37/// // Convenience: create page directly (auto-creates default context)
38/// let page2 = browser.new_page().await?;
39///
40/// // Cleanup
41/// browser.close().await?;
42/// Ok(())
43/// }
44/// ```
45///
46/// See: <https://playwright.dev/docs/api/class-browser>
47#[derive(Clone)]
48pub struct Browser {
49 base: ChannelOwnerImpl,
50 version: String,
51 name: String,
52}
53
54impl Browser {
55 /// Creates a new Browser from protocol initialization
56 ///
57 /// This is called by the object factory when the server sends a `__create__` message
58 /// for a Browser object.
59 ///
60 /// # Arguments
61 ///
62 /// * `parent` - The parent BrowserType object
63 /// * `type_name` - The protocol type name ("Browser")
64 /// * `guid` - The unique identifier for this browser instance
65 /// * `initializer` - The initialization data from the server
66 ///
67 /// # Errors
68 ///
69 /// Returns error if initializer is missing required fields (version, name)
70 pub fn new(
71 parent: Arc<dyn ChannelOwner>,
72 type_name: String,
73 guid: Arc<str>,
74 initializer: Value,
75 ) -> Result<Self> {
76 let base = ChannelOwnerImpl::new(
77 ParentOrConnection::Parent(parent),
78 type_name,
79 guid,
80 initializer.clone(),
81 );
82
83 let version = initializer["version"]
84 .as_str()
85 .ok_or_else(|| {
86 crate::error::Error::ProtocolError(
87 "Browser initializer missing 'version' field".to_string(),
88 )
89 })?
90 .to_string();
91
92 let name = initializer["name"]
93 .as_str()
94 .ok_or_else(|| {
95 crate::error::Error::ProtocolError(
96 "Browser initializer missing 'name' field".to_string(),
97 )
98 })?
99 .to_string();
100
101 Ok(Self {
102 base,
103 version,
104 name,
105 })
106 }
107
108 /// Returns the browser version string.
109 ///
110 /// See: <https://playwright.dev/docs/api/class-browser#browser-version>
111 pub fn version(&self) -> &str {
112 &self.version
113 }
114
115 /// Returns the browser name (e.g., "chromium", "firefox", "webkit").
116 ///
117 /// See: <https://playwright.dev/docs/api/class-browser#browser-name>
118 pub fn name(&self) -> &str {
119 &self.name
120 }
121
122 /// Returns the channel for sending protocol messages
123 ///
124 /// Used internally for sending RPC calls to the browser.
125 fn channel(&self) -> &Channel {
126 self.base.channel()
127 }
128
129 /// Creates a new browser context.
130 ///
131 /// A browser context is an isolated session within the browser instance,
132 /// similar to an incognito profile. Each context has its own cookies,
133 /// cache, and local storage.
134 ///
135 /// # Errors
136 ///
137 /// Returns error if:
138 /// - Browser has been closed
139 /// - Communication with browser process fails
140 ///
141 /// See: <https://playwright.dev/docs/api/class-browser#browser-new-context>
142 pub async fn new_context(&self) -> Result<BrowserContext> {
143 // Response contains the GUID of the created BrowserContext
144 #[derive(Deserialize)]
145 struct NewContextResponse {
146 context: GuidRef,
147 }
148
149 #[derive(Deserialize)]
150 struct GuidRef {
151 #[serde(deserialize_with = "crate::connection::deserialize_arc_str")]
152 guid: Arc<str>,
153 }
154
155 // Send newContext RPC to server with empty options for now
156 let response: NewContextResponse = self
157 .channel()
158 .send("newContext", serde_json::json!({}))
159 .await?;
160
161 // Retrieve the BrowserContext object from the connection registry
162 let context_arc = self.connection().get_object(&response.context.guid).await?;
163
164 // Downcast to BrowserContext
165 let context = context_arc
166 .as_any()
167 .downcast_ref::<BrowserContext>()
168 .ok_or_else(|| {
169 crate::error::Error::ProtocolError(format!(
170 "Expected BrowserContext object, got {}",
171 context_arc.type_name()
172 ))
173 })?;
174
175 Ok(context.clone())
176 }
177
178 /// Creates a new browser context with custom options.
179 ///
180 /// A browser context is an isolated session within the browser instance,
181 /// similar to an incognito profile. Each context has its own cookies,
182 /// cache, and local storage.
183 ///
184 /// This method allows customizing viewport, user agent, locale, timezone,
185 /// and other settings.
186 ///
187 /// # Errors
188 ///
189 /// Returns error if:
190 /// - Browser has been closed
191 /// - Communication with browser process fails
192 /// - Invalid options provided
193 ///
194 /// See: <https://playwright.dev/docs/api/class-browser#browser-new-context>
195 pub async fn new_context_with_options(
196 &self,
197 options: crate::protocol::BrowserContextOptions,
198 ) -> Result<BrowserContext> {
199 // Response contains the GUID of the created BrowserContext
200 #[derive(Deserialize)]
201 struct NewContextResponse {
202 context: GuidRef,
203 }
204
205 #[derive(Deserialize)]
206 struct GuidRef {
207 #[serde(deserialize_with = "crate::connection::deserialize_arc_str")]
208 guid: Arc<str>,
209 }
210
211 // Convert options to JSON
212 let options_json = serde_json::to_value(options).map_err(|e| {
213 crate::error::Error::ProtocolError(format!(
214 "Failed to serialize context options: {}",
215 e
216 ))
217 })?;
218
219 // Send newContext RPC to server with options
220 let response: NewContextResponse = self.channel().send("newContext", options_json).await?;
221
222 // Retrieve the BrowserContext object from the connection registry
223 let context_arc = self.connection().get_object(&response.context.guid).await?;
224
225 // Downcast to BrowserContext
226 let context = context_arc
227 .as_any()
228 .downcast_ref::<BrowserContext>()
229 .ok_or_else(|| {
230 crate::error::Error::ProtocolError(format!(
231 "Expected BrowserContext object, got {}",
232 context_arc.type_name()
233 ))
234 })?;
235
236 Ok(context.clone())
237 }
238
239 /// Creates a new page in a new browser context.
240 ///
241 /// This is a convenience method that creates a default context and then
242 /// creates a page in it. This is equivalent to calling `browser.new_context().await?.new_page().await?`.
243 ///
244 /// The created context is not directly accessible, but will be cleaned up
245 /// when the page is closed.
246 ///
247 /// # Errors
248 ///
249 /// Returns error if:
250 /// - Browser has been closed
251 /// - Communication with browser process fails
252 ///
253 /// See: <https://playwright.dev/docs/api/class-browser#browser-new-page>
254 pub async fn new_page(&self) -> Result<Page> {
255 // Create a default context and then create a page in it
256 let context = self.new_context().await?;
257 context.new_page().await
258 }
259
260 /// Closes the browser and all of its pages (if any were opened).
261 ///
262 /// This is a graceful operation that sends a close command to the browser
263 /// and waits for it to shut down properly.
264 ///
265 /// # Errors
266 ///
267 /// Returns error if:
268 /// - Browser has already been closed
269 /// - Communication with browser process fails
270 ///
271 /// See: <https://playwright.dev/docs/api/class-browser#browser-close>
272 pub async fn close(&self) -> Result<()> {
273 // Send close RPC to server
274 // The protocol expects an empty object as params
275 let result = self
276 .channel()
277 .send_no_result("close", serde_json::json!({}))
278 .await;
279
280 // Add delay on Windows CI to ensure browser process fully terminates
281 // This prevents subsequent browser launches from hanging
282 #[cfg(windows)]
283 {
284 let is_ci = std::env::var("CI").is_ok() || std::env::var("GITHUB_ACTIONS").is_ok();
285 if is_ci {
286 eprintln!("[playwright-rust] Adding Windows CI browser cleanup delay");
287 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
288 }
289 }
290
291 result
292 }
293}
294
295impl ChannelOwner for Browser {
296 fn guid(&self) -> &str {
297 self.base.guid()
298 }
299
300 fn type_name(&self) -> &str {
301 self.base.type_name()
302 }
303
304 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
305 self.base.parent()
306 }
307
308 fn connection(&self) -> Arc<dyn crate::connection::ConnectionLike> {
309 self.base.connection()
310 }
311
312 fn initializer(&self) -> &Value {
313 self.base.initializer()
314 }
315
316 fn channel(&self) -> &Channel {
317 self.base.channel()
318 }
319
320 fn dispose(&self, reason: crate::channel_owner::DisposeReason) {
321 self.base.dispose(reason)
322 }
323
324 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
325 self.base.adopt(child)
326 }
327
328 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
329 self.base.add_child(guid, child)
330 }
331
332 fn remove_child(&self, guid: &str) {
333 self.base.remove_child(guid)
334 }
335
336 fn on_event(&self, _method: &str, _params: Value) {
337 // TODO: Handle browser events in future phases
338 }
339
340 fn was_collected(&self) -> bool {
341 self.base.was_collected()
342 }
343
344 fn as_any(&self) -> &dyn Any {
345 self
346 }
347}
348
349impl std::fmt::Debug for Browser {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351 f.debug_struct("Browser")
352 .field("guid", &self.guid())
353 .field("name", &self.name)
354 .field("version", &self.version)
355 .finish()
356 }
357}
358
359// Note: Browser testing is done via integration tests since it requires:
360// - A real Connection with object registry
361// - Protocol messages from the server
362// - BrowserType.launch() to create Browser objects
363// See: crates/playwright-core/tests/browser_launch_integration.rs (Phase 2 Slice 3)