playwright_rs/protocol/browser_context.rs
1// BrowserContext protocol object
2//
3// Represents an isolated browser context (session) within a browser instance.
4// Multiple contexts can exist in a single browser, each with its own cookies,
5// cache, and local storage.
6
7use crate::error::Result;
8use crate::protocol::{Browser, Page};
9use crate::server::channel::Channel;
10use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::any::Any;
14use std::collections::HashMap;
15use std::sync::{Arc, Mutex};
16
17/// BrowserContext represents an isolated browser session.
18///
19/// Contexts are isolated environments within a browser instance. Each context
20/// has its own cookies, cache, and local storage, enabling independent sessions
21/// without interference.
22///
23/// # Example
24///
25/// ```ignore
26/// use playwright_rs::protocol::Playwright;
27///
28/// #[tokio::main]
29/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
30/// let playwright = Playwright::launch().await?;
31/// let browser = playwright.chromium().launch().await?;
32///
33/// // Create isolated contexts
34/// let context1 = browser.new_context().await?;
35/// let context2 = browser.new_context().await?;
36///
37/// // Create pages in each context
38/// let page1 = context1.new_page().await?;
39/// let page2 = context2.new_page().await?;
40///
41/// // Access all pages in a context
42/// let pages = context1.pages();
43/// assert_eq!(pages.len(), 1);
44///
45/// // Access the browser from a context
46/// let ctx_browser = context1.browser().unwrap();
47/// assert_eq!(ctx_browser.name(), browser.name());
48///
49/// // App mode: access initial page created automatically
50/// let chromium = playwright.chromium();
51/// let app_context = chromium
52/// .launch_persistent_context_with_options(
53/// "/tmp/app-data",
54/// playwright_rs::protocol::BrowserContextOptions::builder()
55/// .args(vec!["--app=https://example.com".to_string()])
56/// .headless(true)
57/// .build()
58/// )
59/// .await?;
60///
61/// // Get the initial page (don't create a new one!)
62/// let app_pages = app_context.pages();
63/// if !app_pages.is_empty() {
64/// let initial_page = &app_pages[0];
65/// // Use the initial page...
66/// }
67///
68/// // Cleanup
69/// context1.close().await?;
70/// context2.close().await?;
71/// app_context.close().await?;
72/// browser.close().await?;
73/// Ok(())
74/// }
75/// ```
76///
77/// See: <https://playwright.dev/docs/api/class-browsercontext>
78#[derive(Clone)]
79pub struct BrowserContext {
80 base: ChannelOwnerImpl,
81 /// Browser instance that owns this context (None for persistent contexts)
82 browser: Option<Browser>,
83 /// All open pages in this context
84 pages: Arc<Mutex<Vec<Page>>>,
85}
86
87impl BrowserContext {
88 /// Creates a new BrowserContext from protocol initialization
89 ///
90 /// This is called by the object factory when the server sends a `__create__` message
91 /// for a BrowserContext object.
92 ///
93 /// # Arguments
94 ///
95 /// * `parent` - The parent Browser object
96 /// * `type_name` - The protocol type name ("BrowserContext")
97 /// * `guid` - The unique identifier for this context
98 /// * `initializer` - The initialization data from the server
99 ///
100 /// # Errors
101 ///
102 /// Returns error if initializer is malformed
103 pub fn new(
104 parent: Arc<dyn ChannelOwner>,
105 type_name: String,
106 guid: Arc<str>,
107 initializer: Value,
108 ) -> Result<Self> {
109 let base = ChannelOwnerImpl::new(
110 ParentOrConnection::Parent(parent.clone()),
111 type_name,
112 guid,
113 initializer,
114 );
115
116 // Store browser reference if parent is a Browser
117 // Returns None only for special contexts (Android, Electron) where parent is not a Browser
118 // For both regular contexts and persistent contexts, parent is a Browser instance
119 let browser = parent.as_any().downcast_ref::<Browser>().cloned();
120
121 let context = Self {
122 base,
123 browser,
124 pages: Arc::new(Mutex::new(Vec::new())),
125 };
126
127 // Enable dialog event subscription
128 // Dialog events need to be explicitly subscribed to via updateSubscription command
129 let channel = context.channel().clone();
130 tokio::spawn(async move {
131 let _ = channel
132 .send_no_result(
133 "updateSubscription",
134 serde_json::json!({
135 "event": "dialog",
136 "enabled": true
137 }),
138 )
139 .await;
140 });
141
142 Ok(context)
143 }
144
145 /// Returns the channel for sending protocol messages
146 ///
147 /// Used internally for sending RPC calls to the context.
148 fn channel(&self) -> &Channel {
149 self.base.channel()
150 }
151
152 /// Adds a script which would be evaluated in one of the following scenarios:
153 ///
154 /// - Whenever a page is created in the browser context or is navigated.
155 /// - Whenever a child frame is attached or navigated in any page in the browser context.
156 ///
157 /// The script is evaluated after the document was created but before any of its scripts
158 /// were run. This is useful to amend the JavaScript environment, e.g. to seed Math.random.
159 ///
160 /// # Arguments
161 ///
162 /// * `script` - Script to be evaluated in all pages in the browser context.
163 ///
164 /// # Errors
165 ///
166 /// Returns error if:
167 /// - Context has been closed
168 /// - Communication with browser process fails
169 ///
170 /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script>
171 pub async fn add_init_script(&self, script: &str) -> Result<()> {
172 self.channel()
173 .send_no_result("addInitScript", serde_json::json!({ "source": script }))
174 .await
175 }
176
177 /// Creates a new page in this browser context.
178 ///
179 /// Pages are isolated tabs/windows within a context. Each page starts
180 /// at "about:blank" and can be navigated independently.
181 ///
182 /// # Errors
183 ///
184 /// Returns error if:
185 /// - Context has been closed
186 /// - Communication with browser process fails
187 ///
188 /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-new-page>
189 pub async fn new_page(&self) -> Result<Page> {
190 // Response contains the GUID of the created Page
191 #[derive(Deserialize)]
192 struct NewPageResponse {
193 page: GuidRef,
194 }
195
196 #[derive(Deserialize)]
197 struct GuidRef {
198 #[serde(deserialize_with = "crate::server::connection::deserialize_arc_str")]
199 guid: Arc<str>,
200 }
201
202 // Send newPage RPC to server
203 let response: NewPageResponse = self
204 .channel()
205 .send("newPage", serde_json::json!({}))
206 .await?;
207
208 // Retrieve the Page object from the connection registry
209 let page_arc = self.connection().get_object(&response.page.guid).await?;
210
211 // Downcast to Page
212 let page = page_arc.as_any().downcast_ref::<Page>().ok_or_else(|| {
213 crate::error::Error::ProtocolError(format!(
214 "Expected Page object, got {}",
215 page_arc.type_name()
216 ))
217 })?;
218
219 // Note: Don't track the page here - it will be tracked via the "page" event
220 // that Playwright server sends automatically when a page is created.
221 // Tracking it here would create duplicates.
222
223 Ok(page.clone())
224 }
225
226 /// Returns all open pages in the context.
227 ///
228 /// This method provides a snapshot of all currently active pages that belong
229 /// to this browser context instance. Pages created via `new_page()` and popup
230 /// pages opened through user interactions are included.
231 ///
232 /// In persistent contexts launched with `--app=url`, this will include the
233 /// initial page created automatically by Playwright.
234 ///
235 /// # Errors
236 ///
237 /// This method does not return errors. It provides a snapshot of pages at
238 /// the time of invocation.
239 ///
240 /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-pages>
241 pub fn pages(&self) -> Vec<Page> {
242 self.pages.lock().unwrap().clone()
243 }
244
245 /// Returns the browser instance that owns this context.
246 ///
247 /// Returns `None` only for contexts created outside of normal browser
248 /// (e.g., Android or Electron contexts). For both regular contexts and
249 /// persistent contexts, this returns the owning Browser instance.
250 ///
251 /// # Errors
252 ///
253 /// This method does not return errors.
254 ///
255 /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-browser>
256 pub fn browser(&self) -> Option<Browser> {
257 self.browser.clone()
258 }
259
260 /// Closes the browser context and all its pages.
261 ///
262 /// This is a graceful operation that sends a close command to the context
263 /// and waits for it to shut down properly.
264 ///
265 /// # Errors
266 ///
267 /// Returns error if:
268 /// - Context has already been closed
269 /// - Communication with browser process fails
270 ///
271 /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-close>
272 pub async fn close(&self) -> Result<()> {
273 // Send close RPC to server
274 self.channel()
275 .send_no_result("close", serde_json::json!({}))
276 .await
277 }
278
279 /// Pauses the browser context.
280 ///
281 /// This pauses the execution of all pages in the context.
282 pub async fn pause(&self) -> Result<()> {
283 self.channel()
284 .send_no_result("pause", serde_json::Value::Null)
285 .await
286 }
287
288 /// Returns storage state for this browser context.
289 ///
290 /// Contains current cookies and local storage snapshots.
291 ///
292 /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state>
293 pub async fn storage_state(&self) -> Result<StorageState> {
294 let response: StorageState = self
295 .channel()
296 .send("storageState", serde_json::json!({}))
297 .await?;
298 Ok(response)
299 }
300
301 /// Adds cookies into this browser context.
302 ///
303 /// All pages within this context will have these cookies installed. Cookies can be granularly specified
304 /// with `name`, `value`, `url`, `domain`, `path`, `expires`, `httpOnly`, `secure`, `sameSite`.
305 ///
306 /// See: <https://playwright.dev/docs/api/class-browsercontext#browser-context-add-cookies>
307 pub async fn add_cookies(&self, cookies: &[Cookie]) -> Result<()> {
308 self.channel()
309 .send_no_result(
310 "addCookies",
311 serde_json::json!({
312 "cookies": cookies
313 }),
314 )
315 .await
316 }
317}
318
319impl ChannelOwner for BrowserContext {
320 fn guid(&self) -> &str {
321 self.base.guid()
322 }
323
324 fn type_name(&self) -> &str {
325 self.base.type_name()
326 }
327
328 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
329 self.base.parent()
330 }
331
332 fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
333 self.base.connection()
334 }
335
336 fn initializer(&self) -> &Value {
337 self.base.initializer()
338 }
339
340 fn channel(&self) -> &Channel {
341 self.base.channel()
342 }
343
344 fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
345 self.base.dispose(reason)
346 }
347
348 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
349 self.base.adopt(child)
350 }
351
352 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
353 self.base.add_child(guid, child)
354 }
355
356 fn remove_child(&self, guid: &str) {
357 self.base.remove_child(guid)
358 }
359
360 fn on_event(&self, method: &str, params: Value) {
361 match method {
362 "page" => {
363 // Page events are triggered when pages are created, including:
364 // - Initial page in persistent context with --app mode
365 // - Popup pages opened through user interactions
366 // Event format: {page: {guid: "..."}}
367 if let Some(page_guid) = params
368 .get("page")
369 .and_then(|v| v.get("guid"))
370 .and_then(|v| v.as_str())
371 {
372 let connection = self.connection();
373 let page_guid_owned = page_guid.to_string();
374 let pages = self.pages.clone();
375
376 tokio::spawn(async move {
377 // Get the Page object
378 let page_arc = match connection.get_object(&page_guid_owned).await {
379 Ok(obj) => obj,
380 Err(_) => return,
381 };
382
383 // Downcast to Page
384 let page = match page_arc.as_any().downcast_ref::<Page>() {
385 Some(p) => p.clone(),
386 None => return,
387 };
388
389 // Track the page
390 pages.lock().unwrap().push(page);
391 });
392 }
393 }
394 "dialog" => {
395 // Dialog events come to BrowserContext, need to forward to the associated Page
396 // Event format: {dialog: {guid: "..."}}
397 // The Dialog protocol object has the Page as its parent
398 if let Some(dialog_guid) = params
399 .get("dialog")
400 .and_then(|v| v.get("guid"))
401 .and_then(|v| v.as_str())
402 {
403 let connection = self.connection();
404 let dialog_guid_owned = dialog_guid.to_string();
405
406 tokio::spawn(async move {
407 // Get the Dialog object
408 let dialog_arc = match connection.get_object(&dialog_guid_owned).await {
409 Ok(obj) => obj,
410 Err(_) => return,
411 };
412
413 // Downcast to Dialog
414 let dialog = match dialog_arc
415 .as_any()
416 .downcast_ref::<crate::protocol::Dialog>()
417 {
418 Some(d) => d.clone(),
419 None => return,
420 };
421
422 // Get the Page from the Dialog's parent
423 let page_arc = match dialog_arc.parent() {
424 Some(parent) => parent,
425 None => return,
426 };
427
428 // Downcast to Page
429 let page = match page_arc.as_any().downcast_ref::<Page>() {
430 Some(p) => p.clone(),
431 None => return,
432 };
433
434 // Forward to Page's dialog handlers
435 page.trigger_dialog_event(dialog).await;
436 });
437 }
438 }
439 _ => {
440 // Other events will be handled in future phases
441 }
442 }
443 }
444
445 fn was_collected(&self) -> bool {
446 self.base.was_collected()
447 }
448
449 fn as_any(&self) -> &dyn Any {
450 self
451 }
452}
453
454impl std::fmt::Debug for BrowserContext {
455 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
456 f.debug_struct("BrowserContext")
457 .field("guid", &self.guid())
458 .finish()
459 }
460}
461
462/// Viewport dimensions for browser context.
463///
464/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context>
465#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct Viewport {
467 /// Page width in pixels
468 pub width: u32,
469 /// Page height in pixels
470 pub height: u32,
471}
472
473/// Geolocation coordinates.
474///
475/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context>
476#[derive(Debug, Clone, Serialize, Deserialize)]
477pub struct Geolocation {
478 /// Latitude between -90 and 90
479 pub latitude: f64,
480 /// Longitude between -180 and 180
481 pub longitude: f64,
482 /// Optional accuracy in meters (default: 0)
483 #[serde(skip_serializing_if = "Option::is_none")]
484 pub accuracy: Option<f64>,
485}
486
487/// Cookie information for storage state.
488///
489/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-storage-state>
490#[derive(Debug, Clone, Serialize, Deserialize)]
491#[serde(rename_all = "camelCase")]
492pub struct Cookie {
493 /// Cookie name
494 pub name: String,
495 /// Cookie value
496 pub value: String,
497 /// Cookie domain (use dot prefix for subdomain matching, e.g., ".example.com")
498 pub domain: String,
499 /// Cookie path
500 pub path: String,
501 /// Unix timestamp in seconds; -1 for session cookies
502 pub expires: f64,
503 /// HTTP-only flag
504 pub http_only: bool,
505 /// Secure flag
506 pub secure: bool,
507 /// SameSite attribute ("Strict", "Lax", "None")
508 #[serde(skip_serializing_if = "Option::is_none")]
509 pub same_site: Option<String>,
510}
511
512/// Local storage item for storage state.
513///
514/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-storage-state>
515#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct LocalStorageItem {
517 /// Storage key
518 pub name: String,
519 /// Storage value
520 pub value: String,
521}
522
523/// Origin with local storage items for storage state.
524///
525/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-storage-state>
526#[derive(Debug, Clone, Serialize, Deserialize)]
527#[serde(rename_all = "camelCase")]
528pub struct Origin {
529 /// Origin URL (e.g., "https://example.com")
530 pub origin: String,
531 /// Local storage items for this origin
532 pub local_storage: Vec<LocalStorageItem>,
533}
534
535/// Storage state containing cookies and local storage.
536///
537/// Used to populate a browser context with saved authentication state,
538/// enabling session persistence across context instances.
539///
540/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-storage-state>
541#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct StorageState {
543 /// List of cookies
544 pub cookies: Vec<Cookie>,
545 /// List of origins with local storage
546 pub origins: Vec<Origin>,
547}
548
549/// Options for recording HAR.
550///
551/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har>
552#[derive(Debug, Clone, Serialize, Default)]
553#[serde(rename_all = "camelCase")]
554pub struct RecordHar {
555 /// Path on the filesystem to write the HAR file to.
556 pub path: String,
557 /// Optional setting to control whether to omit request content from the HAR.
558 #[serde(skip_serializing_if = "Option::is_none")]
559 pub omit_content: Option<bool>,
560 /// Optional setting to control resource content management.
561 /// "omit" | "embed" | "attach"
562 #[serde(skip_serializing_if = "Option::is_none")]
563 pub content: Option<String>,
564 /// "full" | "minimal"
565 #[serde(skip_serializing_if = "Option::is_none")]
566 pub mode: Option<String>,
567 /// A glob or regex pattern to filter requests that are stored in the HAR.
568 #[serde(skip_serializing_if = "Option::is_none")]
569 pub url_filter: Option<String>,
570}
571
572/// Options for recording video.
573///
574/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-video>
575#[derive(Debug, Clone, Serialize, Default)]
576pub struct RecordVideo {
577 /// Path to the directory to put videos into.
578 pub dir: String,
579 /// Optional dimensions of the recorded videos.
580 #[serde(skip_serializing_if = "Option::is_none")]
581 pub size: Option<Viewport>,
582}
583
584/// Options for creating a new browser context.
585///
586/// Allows customizing viewport, user agent, locale, timezone, geolocation,
587/// permissions, and other browser context settings.
588///
589/// See: <https://playwright.dev/docs/api/class-browser#browser-new-context>
590#[derive(Debug, Clone, Default, Serialize)]
591#[serde(rename_all = "camelCase")]
592pub struct BrowserContextOptions {
593 /// Sets consistent viewport for all pages in the context.
594 /// Set to null via `no_viewport(true)` to disable viewport emulation.
595 #[serde(skip_serializing_if = "Option::is_none")]
596 pub viewport: Option<Viewport>,
597
598 /// Disables viewport emulation when set to true.
599 #[serde(skip_serializing_if = "Option::is_none")]
600 pub no_viewport: Option<bool>,
601
602 /// Custom user agent string
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub user_agent: Option<String>,
605
606 /// Locale for the context (e.g., "en-GB", "de-DE", "fr-FR")
607 #[serde(skip_serializing_if = "Option::is_none")]
608 pub locale: Option<String>,
609
610 /// Timezone identifier (e.g., "America/New_York", "Europe/Berlin")
611 #[serde(skip_serializing_if = "Option::is_none")]
612 pub timezone_id: Option<String>,
613
614 /// Geolocation coordinates
615 #[serde(skip_serializing_if = "Option::is_none")]
616 pub geolocation: Option<Geolocation>,
617
618 /// List of permissions to grant (e.g., "geolocation", "notifications")
619 #[serde(skip_serializing_if = "Option::is_none")]
620 pub permissions: Option<Vec<String>>,
621
622 /// Emulates 'prefers-colors-scheme' media feature ("light", "dark", "no-preference")
623 #[serde(skip_serializing_if = "Option::is_none")]
624 pub color_scheme: Option<String>,
625
626 /// Whether the viewport supports touch events
627 #[serde(skip_serializing_if = "Option::is_none")]
628 pub has_touch: Option<bool>,
629
630 /// Whether the meta viewport tag is respected
631 #[serde(skip_serializing_if = "Option::is_none")]
632 pub is_mobile: Option<bool>,
633
634 /// Whether JavaScript is enabled in the context
635 #[serde(skip_serializing_if = "Option::is_none")]
636 pub javascript_enabled: Option<bool>,
637
638 /// Emulates network being offline
639 #[serde(skip_serializing_if = "Option::is_none")]
640 pub offline: Option<bool>,
641
642 /// Whether to automatically download attachments
643 #[serde(skip_serializing_if = "Option::is_none")]
644 pub accept_downloads: Option<bool>,
645
646 /// Whether to bypass Content-Security-Policy
647 #[serde(skip_serializing_if = "Option::is_none")]
648 pub bypass_csp: Option<bool>,
649
650 /// Whether to ignore HTTPS errors
651 #[serde(skip_serializing_if = "Option::is_none")]
652 pub ignore_https_errors: Option<bool>,
653
654 /// Device scale factor (default: 1)
655 #[serde(skip_serializing_if = "Option::is_none")]
656 pub device_scale_factor: Option<f64>,
657
658 /// Extra HTTP headers to send with every request
659 #[serde(skip_serializing_if = "Option::is_none")]
660 pub extra_http_headers: Option<HashMap<String, String>>,
661
662 /// Base URL for relative navigation
663 #[serde(skip_serializing_if = "Option::is_none")]
664 pub base_url: Option<String>,
665
666 /// Storage state to populate the context (cookies, localStorage, sessionStorage).
667 /// Can be an inline StorageState object or a file path string.
668 /// Use builder methods `storage_state()` for inline or `storage_state_path()` for file path.
669 #[serde(skip_serializing_if = "Option::is_none")]
670 pub storage_state: Option<StorageState>,
671
672 /// Storage state file path (alternative to inline storage_state).
673 /// This is handled by the builder and converted to storage_state during serialization.
674 #[serde(skip_serializing_if = "Option::is_none")]
675 pub storage_state_path: Option<String>,
676
677 // Launch options (for launch_persistent_context)
678 /// Additional arguments to pass to browser instance
679 #[serde(skip_serializing_if = "Option::is_none")]
680 pub args: Option<Vec<String>>,
681
682 /// Browser distribution channel (e.g., "chrome", "msedge")
683 #[serde(skip_serializing_if = "Option::is_none")]
684 pub channel: Option<String>,
685
686 /// Enable Chromium sandboxing (default: false on Linux)
687 #[serde(skip_serializing_if = "Option::is_none")]
688 pub chromium_sandbox: Option<bool>,
689
690 /// Auto-open DevTools (deprecated, default: false)
691 #[serde(skip_serializing_if = "Option::is_none")]
692 pub devtools: Option<bool>,
693
694 /// Directory to save downloads
695 #[serde(skip_serializing_if = "Option::is_none")]
696 pub downloads_path: Option<String>,
697
698 /// Path to custom browser executable
699 #[serde(skip_serializing_if = "Option::is_none")]
700 pub executable_path: Option<String>,
701
702 /// Firefox user preferences (Firefox only)
703 #[serde(skip_serializing_if = "Option::is_none")]
704 pub firefox_user_prefs: Option<HashMap<String, serde_json::Value>>,
705
706 /// Run in headless mode (default: true unless devtools=true)
707 #[serde(skip_serializing_if = "Option::is_none")]
708 pub headless: Option<bool>,
709
710 /// Slow down operations by N milliseconds
711 #[serde(skip_serializing_if = "Option::is_none")]
712 pub slow_mo: Option<f64>,
713
714 /// Timeout for browser launch in milliseconds
715 #[serde(skip_serializing_if = "Option::is_none")]
716 pub timeout: Option<f64>,
717
718 /// Directory to save traces
719 #[serde(skip_serializing_if = "Option::is_none")]
720 pub traces_dir: Option<String>,
721
722 /// Check if strict selectors mode is enabled
723 #[serde(skip_serializing_if = "Option::is_none")]
724 pub strict_selectors: Option<bool>,
725
726 /// Emulates 'prefers-reduced-motion' media feature
727 #[serde(skip_serializing_if = "Option::is_none")]
728 pub reduced_motion: Option<String>,
729
730 /// Emulates 'forced-colors' media feature
731 #[serde(skip_serializing_if = "Option::is_none")]
732 pub forced_colors: Option<String>,
733
734 /// Whether to allow sites to register Service workers
735 #[serde(skip_serializing_if = "Option::is_none")]
736 pub service_workers: Option<String>,
737
738 /// Options for recording HAR
739 #[serde(skip_serializing_if = "Option::is_none")]
740 pub record_har: Option<RecordHar>,
741
742 /// Options for recording video
743 #[serde(skip_serializing_if = "Option::is_none")]
744 pub record_video: Option<RecordVideo>,
745}
746
747impl BrowserContextOptions {
748 /// Creates a new builder for BrowserContextOptions
749 pub fn builder() -> BrowserContextOptionsBuilder {
750 BrowserContextOptionsBuilder::default()
751 }
752}
753
754/// Builder for BrowserContextOptions
755#[derive(Debug, Clone, Default)]
756pub struct BrowserContextOptionsBuilder {
757 viewport: Option<Viewport>,
758 no_viewport: Option<bool>,
759 user_agent: Option<String>,
760 locale: Option<String>,
761 timezone_id: Option<String>,
762 geolocation: Option<Geolocation>,
763 permissions: Option<Vec<String>>,
764 color_scheme: Option<String>,
765 has_touch: Option<bool>,
766 is_mobile: Option<bool>,
767 javascript_enabled: Option<bool>,
768 offline: Option<bool>,
769 accept_downloads: Option<bool>,
770 bypass_csp: Option<bool>,
771 ignore_https_errors: Option<bool>,
772 device_scale_factor: Option<f64>,
773 extra_http_headers: Option<HashMap<String, String>>,
774 base_url: Option<String>,
775 storage_state: Option<StorageState>,
776 storage_state_path: Option<String>,
777 // Launch options
778 args: Option<Vec<String>>,
779 channel: Option<String>,
780 chromium_sandbox: Option<bool>,
781 devtools: Option<bool>,
782 downloads_path: Option<String>,
783 executable_path: Option<String>,
784 firefox_user_prefs: Option<HashMap<String, serde_json::Value>>,
785 headless: Option<bool>,
786 slow_mo: Option<f64>,
787 timeout: Option<f64>,
788 traces_dir: Option<String>,
789 strict_selectors: Option<bool>,
790 reduced_motion: Option<String>,
791 forced_colors: Option<String>,
792 service_workers: Option<String>,
793 record_har: Option<RecordHar>,
794 record_video: Option<RecordVideo>,
795}
796
797impl BrowserContextOptionsBuilder {
798 /// Sets the viewport dimensions
799 pub fn viewport(mut self, viewport: Viewport) -> Self {
800 self.viewport = Some(viewport);
801 self.no_viewport = None; // Clear no_viewport if setting viewport
802 self
803 }
804
805 /// Disables viewport emulation
806 pub fn no_viewport(mut self, no_viewport: bool) -> Self {
807 self.no_viewport = Some(no_viewport);
808 if no_viewport {
809 self.viewport = None; // Clear viewport if setting no_viewport
810 }
811 self
812 }
813
814 /// Sets the user agent string
815 pub fn user_agent(mut self, user_agent: String) -> Self {
816 self.user_agent = Some(user_agent);
817 self
818 }
819
820 /// Sets the locale
821 pub fn locale(mut self, locale: String) -> Self {
822 self.locale = Some(locale);
823 self
824 }
825
826 /// Sets the timezone identifier
827 pub fn timezone_id(mut self, timezone_id: String) -> Self {
828 self.timezone_id = Some(timezone_id);
829 self
830 }
831
832 /// Sets the geolocation
833 pub fn geolocation(mut self, geolocation: Geolocation) -> Self {
834 self.geolocation = Some(geolocation);
835 self
836 }
837
838 /// Sets the permissions to grant
839 pub fn permissions(mut self, permissions: Vec<String>) -> Self {
840 self.permissions = Some(permissions);
841 self
842 }
843
844 /// Sets the color scheme preference
845 pub fn color_scheme(mut self, color_scheme: String) -> Self {
846 self.color_scheme = Some(color_scheme);
847 self
848 }
849
850 /// Sets whether the viewport supports touch events
851 pub fn has_touch(mut self, has_touch: bool) -> Self {
852 self.has_touch = Some(has_touch);
853 self
854 }
855
856 /// Sets whether this is a mobile viewport
857 pub fn is_mobile(mut self, is_mobile: bool) -> Self {
858 self.is_mobile = Some(is_mobile);
859 self
860 }
861
862 /// Sets whether JavaScript is enabled
863 pub fn javascript_enabled(mut self, javascript_enabled: bool) -> Self {
864 self.javascript_enabled = Some(javascript_enabled);
865 self
866 }
867
868 /// Sets whether to emulate offline network
869 pub fn offline(mut self, offline: bool) -> Self {
870 self.offline = Some(offline);
871 self
872 }
873
874 /// Sets whether to automatically download attachments
875 pub fn accept_downloads(mut self, accept_downloads: bool) -> Self {
876 self.accept_downloads = Some(accept_downloads);
877 self
878 }
879
880 /// Sets whether to bypass Content-Security-Policy
881 pub fn bypass_csp(mut self, bypass_csp: bool) -> Self {
882 self.bypass_csp = Some(bypass_csp);
883 self
884 }
885
886 /// Sets whether to ignore HTTPS errors
887 pub fn ignore_https_errors(mut self, ignore_https_errors: bool) -> Self {
888 self.ignore_https_errors = Some(ignore_https_errors);
889 self
890 }
891
892 /// Sets the device scale factor
893 pub fn device_scale_factor(mut self, device_scale_factor: f64) -> Self {
894 self.device_scale_factor = Some(device_scale_factor);
895 self
896 }
897
898 /// Sets extra HTTP headers
899 pub fn extra_http_headers(mut self, extra_http_headers: HashMap<String, String>) -> Self {
900 self.extra_http_headers = Some(extra_http_headers);
901 self
902 }
903
904 /// Sets the base URL for relative navigation
905 pub fn base_url(mut self, base_url: String) -> Self {
906 self.base_url = Some(base_url);
907 self
908 }
909
910 /// Sets the storage state inline (cookies, localStorage).
911 ///
912 /// Populates the browser context with the provided storage state, including
913 /// cookies and local storage. This is useful for initializing a context with
914 /// a saved authentication state.
915 ///
916 /// Mutually exclusive with `storage_state_path()`.
917 ///
918 /// # Example
919 ///
920 /// ```rust
921 /// use playwright_rs::protocol::{BrowserContextOptions, Cookie, StorageState, Origin, LocalStorageItem};
922 ///
923 /// let storage_state = StorageState {
924 /// cookies: vec![Cookie {
925 /// name: "session_id".to_string(),
926 /// value: "abc123".to_string(),
927 /// domain: ".example.com".to_string(),
928 /// path: "/".to_string(),
929 /// expires: -1.0,
930 /// http_only: true,
931 /// secure: true,
932 /// same_site: Some("Lax".to_string()),
933 /// }],
934 /// origins: vec![Origin {
935 /// origin: "https://example.com".to_string(),
936 /// local_storage: vec![LocalStorageItem {
937 /// name: "user_prefs".to_string(),
938 /// value: "{\"theme\":\"dark\"}".to_string(),
939 /// }],
940 /// }],
941 /// };
942 ///
943 /// let options = BrowserContextOptions::builder()
944 /// .storage_state(storage_state)
945 /// .build();
946 /// ```
947 ///
948 /// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-storage-state>
949 pub fn storage_state(mut self, storage_state: StorageState) -> Self {
950 self.storage_state = Some(storage_state);
951 self.storage_state_path = None; // Clear path if setting inline
952 self
953 }
954
955 /// Sets the storage state from a file path.
956 ///
957 /// The file should contain a JSON representation of StorageState with cookies
958 /// and origins. This is useful for loading authentication state saved from a
959 /// previous session.
960 ///
961 /// Mutually exclusive with `storage_state()`.
962 ///
963 /// # Example
964 ///
965 /// ```rust
966 /// use playwright_rs::protocol::BrowserContextOptions;
967 ///
968 /// let options = BrowserContextOptions::builder()
969 /// .storage_state_path("auth.json".to_string())
970 /// .build();
971 /// ```
972 ///
973 /// The file should have this format:
974 /// ```json
975 /// {
976 /// "cookies": [{
977 /// "name": "session_id",
978 /// "value": "abc123",
979 /// "domain": ".example.com",
980 /// "path": "/",
981 /// "expires": -1,
982 /// "httpOnly": true,
983 /// "secure": true,
984 /// "sameSite": "Lax"
985 /// }],
986 /// "origins": [{
987 /// "origin": "https://example.com",
988 /// "localStorage": [{
989 /// "name": "user_prefs",
990 /// "value": "{\"theme\":\"dark\"}"
991 /// }]
992 /// }]
993 /// }
994 /// ```
995 ///
996 /// See: <https://playwright.dev/docs/api/class-browser#browser-new-context-option-storage-state>
997 pub fn storage_state_path(mut self, path: String) -> Self {
998 self.storage_state_path = Some(path);
999 self.storage_state = None; // Clear inline if setting path
1000 self
1001 }
1002
1003 /// Sets additional arguments to pass to browser instance (for launch_persistent_context)
1004 pub fn args(mut self, args: Vec<String>) -> Self {
1005 self.args = Some(args);
1006 self
1007 }
1008
1009 /// Sets browser distribution channel (for launch_persistent_context)
1010 pub fn channel(mut self, channel: String) -> Self {
1011 self.channel = Some(channel);
1012 self
1013 }
1014
1015 /// Enables or disables Chromium sandboxing (for launch_persistent_context)
1016 pub fn chromium_sandbox(mut self, enabled: bool) -> Self {
1017 self.chromium_sandbox = Some(enabled);
1018 self
1019 }
1020
1021 /// Auto-open DevTools (for launch_persistent_context)
1022 pub fn devtools(mut self, enabled: bool) -> Self {
1023 self.devtools = Some(enabled);
1024 self
1025 }
1026
1027 /// Sets directory to save downloads (for launch_persistent_context)
1028 pub fn downloads_path(mut self, path: String) -> Self {
1029 self.downloads_path = Some(path);
1030 self
1031 }
1032
1033 /// Sets path to custom browser executable (for launch_persistent_context)
1034 pub fn executable_path(mut self, path: String) -> Self {
1035 self.executable_path = Some(path);
1036 self
1037 }
1038
1039 /// Sets Firefox user preferences (for launch_persistent_context, Firefox only)
1040 pub fn firefox_user_prefs(mut self, prefs: HashMap<String, serde_json::Value>) -> Self {
1041 self.firefox_user_prefs = Some(prefs);
1042 self
1043 }
1044
1045 /// Run in headless mode (for launch_persistent_context)
1046 pub fn headless(mut self, enabled: bool) -> Self {
1047 self.headless = Some(enabled);
1048 self
1049 }
1050
1051 /// Slow down operations by N milliseconds (for launch_persistent_context)
1052 pub fn slow_mo(mut self, ms: f64) -> Self {
1053 self.slow_mo = Some(ms);
1054 self
1055 }
1056
1057 /// Set timeout for browser launch in milliseconds (for launch_persistent_context)
1058 pub fn timeout(mut self, ms: f64) -> Self {
1059 self.timeout = Some(ms);
1060 self
1061 }
1062
1063 /// Set directory to save traces (for launch_persistent_context)
1064 pub fn traces_dir(mut self, path: String) -> Self {
1065 self.traces_dir = Some(path);
1066 self
1067 }
1068
1069 /// Check if strict selectors mode is enabled
1070 pub fn strict_selectors(mut self, enabled: bool) -> Self {
1071 self.strict_selectors = Some(enabled);
1072 self
1073 }
1074
1075 /// Emulates 'prefers-reduced-motion' media feature
1076 pub fn reduced_motion(mut self, value: String) -> Self {
1077 self.reduced_motion = Some(value);
1078 self
1079 }
1080
1081 /// Emulates 'forced-colors' media feature
1082 pub fn forced_colors(mut self, value: String) -> Self {
1083 self.forced_colors = Some(value);
1084 self
1085 }
1086
1087 /// Whether to allow sites to register Service workers ("allow" | "block")
1088 pub fn service_workers(mut self, value: String) -> Self {
1089 self.service_workers = Some(value);
1090 self
1091 }
1092
1093 /// Sets options for recording HAR
1094 pub fn record_har(mut self, record_har: RecordHar) -> Self {
1095 self.record_har = Some(record_har);
1096 self
1097 }
1098
1099 /// Sets options for recording video
1100 pub fn record_video(mut self, record_video: RecordVideo) -> Self {
1101 self.record_video = Some(record_video);
1102 self
1103 }
1104
1105 /// Builds the BrowserContextOptions
1106 pub fn build(self) -> BrowserContextOptions {
1107 BrowserContextOptions {
1108 viewport: self.viewport,
1109 no_viewport: self.no_viewport,
1110 user_agent: self.user_agent,
1111 locale: self.locale,
1112 timezone_id: self.timezone_id,
1113 geolocation: self.geolocation,
1114 permissions: self.permissions,
1115 color_scheme: self.color_scheme,
1116 has_touch: self.has_touch,
1117 is_mobile: self.is_mobile,
1118 javascript_enabled: self.javascript_enabled,
1119 offline: self.offline,
1120 accept_downloads: self.accept_downloads,
1121 bypass_csp: self.bypass_csp,
1122 ignore_https_errors: self.ignore_https_errors,
1123 device_scale_factor: self.device_scale_factor,
1124 extra_http_headers: self.extra_http_headers,
1125 base_url: self.base_url,
1126 storage_state: self.storage_state,
1127 storage_state_path: self.storage_state_path,
1128 // Launch options
1129 args: self.args,
1130 channel: self.channel,
1131 chromium_sandbox: self.chromium_sandbox,
1132 devtools: self.devtools,
1133 downloads_path: self.downloads_path,
1134 executable_path: self.executable_path,
1135 firefox_user_prefs: self.firefox_user_prefs,
1136 headless: self.headless,
1137 slow_mo: self.slow_mo,
1138 timeout: self.timeout,
1139 traces_dir: self.traces_dir,
1140 strict_selectors: self.strict_selectors,
1141 reduced_motion: self.reduced_motion,
1142 forced_colors: self.forced_colors,
1143 service_workers: self.service_workers,
1144 record_har: self.record_har,
1145 record_video: self.record_video,
1146 }
1147 }
1148}