playwright_rs/protocol/tracing.rs
1// Copyright 2026 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// Tracing — Playwright trace recording
5//
6// Architecture Reference:
7// - Python: playwright-python/playwright/_impl/_tracing.py
8// - JavaScript: playwright/packages/playwright-core/src/client/tracing.ts
9// - Docs: https://playwright.dev/docs/api/class-tracing
10
11//! Tracing — record Playwright traces for debugging
12//!
13//! Tracing is a per-context feature. Access the Tracing object via
14//! [`BrowserContext::tracing`](crate::protocol::BrowserContext::tracing).
15//!
16//! # Example
17//!
18//! ```ignore
19//! use playwright_rs::protocol::{Playwright, TracingStartOptions};
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//!
27//! let tracing = context.tracing()?;
28//!
29//! // Start tracing with options
30//! tracing.start(Some(TracingStartOptions {
31//! name: Some("my-trace".to_string()),
32//! screenshots: Some(true),
33//! snapshots: Some(true),
34//! })).await?;
35//!
36//! let page = context.new_page().await?;
37//! page.goto("https://example.com", None).await?;
38//!
39//! // Stop and save the trace
40//! use playwright_rs::protocol::TracingStopOptions;
41//! tracing.stop(Some(TracingStopOptions {
42//! path: Some("/tmp/trace.zip".to_string()),
43//! })).await?;
44//!
45//! context.close().await?;
46//! browser.close().await?;
47//! Ok(())
48//! }
49//! ```
50//!
51//! See: <https://playwright.dev/docs/api/class-tracing>
52
53use crate::error::Result;
54use crate::server::channel::Channel;
55use crate::server::channel_owner::{
56 ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
57};
58use crate::server::connection::ConnectionLike;
59use serde_json::Value;
60use std::any::Any;
61use std::sync::Arc;
62
63/// Options for starting a trace recording.
64///
65/// See: <https://playwright.dev/docs/api/class-tracing#tracing-start>
66#[derive(Debug, Clone, Default)]
67pub struct TracingStartOptions {
68 /// Custom name for the trace. Shown in trace viewer as the trace title.
69 pub name: Option<String>,
70 /// Whether to capture screenshots during tracing. Screenshots are used as
71 /// a timeline preview in the trace viewer.
72 pub screenshots: Option<bool>,
73 /// Whether to capture DOM snapshots on each action.
74 pub snapshots: Option<bool>,
75}
76
77/// Options for stopping a trace recording.
78///
79/// See: <https://playwright.dev/docs/api/class-tracing#tracing-stop>
80#[derive(Debug, Clone, Default)]
81pub struct TracingStopOptions {
82 /// Path to export the trace file to. If not provided, the trace is discarded.
83 /// The file is written as a `.zip` archive.
84 pub path: Option<String>,
85}
86
87/// Tracing — records Playwright traces for debugging and inspection.
88///
89/// Trace files can be opened in the Playwright Trace Viewer.
90/// This is a Chromium-only feature; calling tracing methods on Firefox or
91/// WebKit contexts will fail.
92///
93/// See: <https://playwright.dev/docs/api/class-tracing>
94#[derive(Clone)]
95pub struct Tracing {
96 base: ChannelOwnerImpl,
97}
98
99impl Tracing {
100 /// Creates a new Tracing from protocol initialization.
101 ///
102 /// Called by the object factory when the server sends a `__create__` message.
103 pub fn new(
104 parent: ParentOrConnection,
105 type_name: String,
106 guid: Arc<str>,
107 initializer: Value,
108 ) -> Result<Self> {
109 Ok(Self {
110 base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
111 })
112 }
113
114 /// Start tracing.
115 ///
116 /// Playwright implements tracing as a two-step process: `tracingStart` to
117 /// configure the trace, then `tracingStartChunk` to begin recording.
118 ///
119 /// # Arguments
120 ///
121 /// * `options` - Optional trace configuration (name, screenshots, snapshots)
122 ///
123 /// # Errors
124 ///
125 /// Returns error if:
126 /// - Tracing is already active
127 /// - Communication with browser process fails
128 ///
129 /// See: <https://playwright.dev/docs/api/class-tracing#tracing-start>
130 pub async fn start(&self, options: Option<TracingStartOptions>) -> Result<()> {
131 let opts = options.unwrap_or_default();
132
133 // Step 1: tracingStart — configure the trace
134 let mut start_params = serde_json::json!({});
135 if let Some(ref name) = opts.name {
136 start_params["name"] = serde_json::Value::String(name.clone());
137 }
138 if let Some(screenshots) = opts.screenshots {
139 start_params["screenshots"] = serde_json::Value::Bool(screenshots);
140 }
141 if let Some(snapshots) = opts.snapshots {
142 start_params["snapshots"] = serde_json::Value::Bool(snapshots);
143 }
144
145 self.channel()
146 .send_no_result("tracingStart", start_params)
147 .await?;
148
149 // Step 2: tracingStartChunk — begin the chunk/recording
150 let mut chunk_params = serde_json::json!({});
151 if let Some(name) = opts.name {
152 chunk_params["name"] = serde_json::Value::String(name);
153 }
154
155 self.channel()
156 .send_no_result("tracingStartChunk", chunk_params)
157 .await
158 }
159
160 /// Stop tracing.
161 ///
162 /// Playwright implements stopping as a two-step process: `tracingStopChunk`
163 /// to finalize the recording, then `tracingStop` to tear down.
164 ///
165 /// If `options.path` is provided, the trace is exported to that file as a
166 /// `.zip` archive. If no path is provided, the trace is discarded.
167 ///
168 /// # Arguments
169 ///
170 /// * `options` - Optional stop options; set `path` to save the trace to a file
171 ///
172 /// # Errors
173 ///
174 /// Returns error if:
175 /// - Tracing was not active
176 /// - Communication with browser process fails
177 ///
178 /// See: <https://playwright.dev/docs/api/class-tracing#tracing-stop>
179 pub async fn stop(&self, options: Option<TracingStopOptions>) -> Result<()> {
180 let path = options.and_then(|o| o.path);
181
182 // Step 1: tracingStopChunk — mode "entries" collects trace data
183 // mode "archive" or "compressedTrace" would export, but "entries" is simpler
184 let mode = if path.is_some() { "archive" } else { "discard" };
185 let stop_chunk_params = serde_json::json!({ "mode": mode });
186
187 let chunk_result: Value = self
188 .channel()
189 .send("tracingStopChunk", stop_chunk_params)
190 .await?;
191
192 // Step 2: tracingStop — tear down
193 self.channel()
194 .send_no_result("tracingStop", serde_json::json!({}))
195 .await?;
196
197 // If a path was requested, save the artifact
198 if let Some(dest_path) = path
199 && let Some(artifact_guid) = chunk_result
200 .get("artifact")
201 .and_then(|a| a.get("guid"))
202 .and_then(|g| g.as_str())
203 {
204 // Resolve the artifact and save it
205 self.save_artifact(artifact_guid, &dest_path).await?;
206 }
207
208 Ok(())
209 }
210
211 /// Save a trace artifact to a file path.
212 async fn save_artifact(&self, artifact_guid: &str, dest_path: &str) -> Result<()> {
213 use crate::protocol::artifact::Artifact;
214 use crate::server::connection::ConnectionExt;
215
216 let artifact = self
217 .connection()
218 .get_typed::<Artifact>(artifact_guid)
219 .await?;
220
221 artifact.save_as(dest_path).await
222 }
223}
224
225impl ChannelOwner for Tracing {
226 fn guid(&self) -> &str {
227 self.base.guid()
228 }
229
230 fn type_name(&self) -> &str {
231 self.base.type_name()
232 }
233
234 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
235 self.base.parent()
236 }
237
238 fn connection(&self) -> Arc<dyn ConnectionLike> {
239 self.base.connection()
240 }
241
242 fn initializer(&self) -> &Value {
243 self.base.initializer()
244 }
245
246 fn channel(&self) -> &Channel {
247 self.base.channel()
248 }
249
250 fn dispose(&self, reason: DisposeReason) {
251 self.base.dispose(reason)
252 }
253
254 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
255 self.base.adopt(child)
256 }
257
258 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
259 self.base.add_child(guid, child)
260 }
261
262 fn remove_child(&self, guid: &str) {
263 self.base.remove_child(guid)
264 }
265
266 fn on_event(&self, method: &str, params: Value) {
267 self.base.on_event(method, params)
268 }
269
270 fn was_collected(&self) -> bool {
271 self.base.was_collected()
272 }
273
274 fn as_any(&self) -> &dyn Any {
275 self
276 }
277}
278
279impl std::fmt::Debug for Tracing {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 f.debug_struct("Tracing")
282 .field("guid", &self.guid())
283 .finish()
284 }
285}