Skip to main content

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}