playwright_rs/protocol/cdp_session.rs
1// Copyright 2026 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// CDPSession — Chrome DevTools Protocol session object
5//
6// Architecture Reference:
7// - Python: playwright-python/playwright/_impl/_cdp_session.py
8// - JavaScript: playwright/packages/playwright-core/src/client/cdpSession.ts
9// - Docs: https://playwright.dev/docs/api/class-cdpsession
10
11//! CDPSession — Chrome DevTools Protocol session
12//!
13//! Provides access to the Chrome DevTools Protocol for Chromium-based browsers.
14//! CDPSession is created via [`BrowserContext::new_cdp_session`](crate::protocol::BrowserContext::new_cdp_session).
15//!
16//! # Example
17//!
18//! ```ignore
19//! use playwright_rs::protocol::Playwright;
20//!
21//! #[tokio::main]
22//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! let playwright = Playwright::launch().await?;
24//! let browser = playwright.chromium().launch().await?;
25//! let context = browser.new_context().await?;
26//! let page = context.new_page().await?;
27//!
28//! // Create a CDP session for the page
29//! let session = context.new_cdp_session(&page).await?;
30//!
31//! // Send a CDP command
32//! let result = session
33//! .send("Runtime.evaluate", Some(serde_json::json!({ "expression": "1+1" })))
34//! .await?;
35//!
36//! println!("Result: {:?}", result);
37//!
38//! session.detach().await?;
39//! context.close().await?;
40//! browser.close().await?;
41//! Ok(())
42//! }
43//! ```
44//!
45//! See: <https://playwright.dev/docs/api/class-cdpsession>
46
47use crate::error::Result;
48use crate::server::channel::Channel;
49use crate::server::channel_owner::{
50 ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
51};
52use crate::server::connection::ConnectionLike;
53use serde_json::Value;
54use std::any::Any;
55use std::sync::Arc;
56
57/// A Chrome DevTools Protocol session for a page or browser context.
58///
59/// CDPSession is only available in Chromium-based browsers.
60///
61/// See: <https://playwright.dev/docs/api/class-cdpsession>
62#[derive(Clone)]
63pub struct CDPSession {
64 base: ChannelOwnerImpl,
65}
66
67impl CDPSession {
68 /// Creates a new CDPSession from protocol initialization.
69 ///
70 /// Called by the object factory when the server sends a `__create__` message.
71 pub fn new(
72 parent: ParentOrConnection,
73 type_name: String,
74 guid: Arc<str>,
75 initializer: Value,
76 ) -> Result<Self> {
77 Ok(Self {
78 base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
79 })
80 }
81
82 /// Send a CDP command and return the result.
83 ///
84 /// # Arguments
85 ///
86 /// * `method` - The CDP method name (e.g., `"Runtime.evaluate"`)
87 /// * `params` - Optional JSON parameters for the method
88 ///
89 /// # Errors
90 ///
91 /// Returns error if:
92 /// - The session has been detached
93 /// - The CDP method fails
94 /// - Communication with browser process fails
95 ///
96 /// See: <https://playwright.dev/docs/api/class-cdpsession#cdp-session-send>
97 pub async fn send(&self, method: &str, params: Option<Value>) -> Result<Value> {
98 let params = serde_json::json!({
99 "method": method,
100 "params": params.unwrap_or(serde_json::json!({})),
101 });
102 self.channel().send("send", params).await
103 }
104
105 /// Detach the CDP session from the target.
106 ///
107 /// After detaching, the session can no longer be used to send commands.
108 ///
109 /// # Errors
110 ///
111 /// Returns error if:
112 /// - The session has already been detached
113 /// - Communication with browser process fails
114 ///
115 /// See: <https://playwright.dev/docs/api/class-cdpsession#cdp-session-detach>
116 pub async fn detach(&self) -> Result<()> {
117 self.channel()
118 .send_no_result("detach", serde_json::json!({}))
119 .await
120 }
121}
122
123impl ChannelOwner for CDPSession {
124 fn guid(&self) -> &str {
125 self.base.guid()
126 }
127
128 fn type_name(&self) -> &str {
129 self.base.type_name()
130 }
131
132 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
133 self.base.parent()
134 }
135
136 fn connection(&self) -> Arc<dyn ConnectionLike> {
137 self.base.connection()
138 }
139
140 fn initializer(&self) -> &Value {
141 self.base.initializer()
142 }
143
144 fn channel(&self) -> &Channel {
145 self.base.channel()
146 }
147
148 fn dispose(&self, reason: DisposeReason) {
149 self.base.dispose(reason)
150 }
151
152 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
153 self.base.adopt(child)
154 }
155
156 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
157 self.base.add_child(guid, child)
158 }
159
160 fn remove_child(&self, guid: &str) {
161 self.base.remove_child(guid)
162 }
163
164 fn on_event(&self, method: &str, params: Value) {
165 self.base.on_event(method, params)
166 }
167
168 fn was_collected(&self) -> bool {
169 self.base.was_collected()
170 }
171
172 fn as_any(&self) -> &dyn Any {
173 self
174 }
175}
176
177impl std::fmt::Debug for CDPSession {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 f.debug_struct("CDPSession")
180 .field("guid", &self.guid())
181 .finish()
182 }
183}