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