Skip to main content

playwright_rs_trace/
event.rs

1//! Trace event types.
2//!
3//! `RawEvent` is the lossless representation — every JSONL line in
4//! `trace.trace` deserialises into one. `TraceEvent` is the typed
5//! convenience layer; unknown / unmodelled kinds fall back to
6//! `TraceEvent::Unknown(RawEvent)` so nothing is silently dropped.
7
8use serde::Deserialize;
9use serde_json::{Map, Value};
10
11/// A single event from the trace, preserved as the underlying JSON
12/// object. Forward-compat escape hatch for callers who need to dispatch
13/// on event kinds the parser doesn't model yet.
14#[derive(Debug, Clone)]
15pub struct RawEvent {
16    raw: Map<String, Value>,
17}
18
19impl RawEvent {
20    pub(crate) fn new(raw: Map<String, Value>) -> Self {
21        Self { raw }
22    }
23
24    /// Returns the value of the `"type"` field, or `None` if the event
25    /// is malformed (`type` absent or non-string). The streaming
26    /// iterators in [`crate::TraceReader`] filter out malformed events
27    /// before they reach the user, so handlers iterating on
28    /// [`TraceReader::raw_events`](crate::TraceReader::raw_events) can
29    /// generally `expect` this.
30    pub fn kind(&self) -> Option<&str> {
31        self.raw.get("type").and_then(|v| v.as_str())
32    }
33
34    /// The full underlying JSON object, including the `"type"` field.
35    pub fn as_value(&self) -> &Map<String, Value> {
36        &self.raw
37    }
38
39    /// Take ownership of the underlying JSON.
40    pub fn into_value(self) -> Value {
41        Value::Object(self.raw)
42    }
43
44    /// Materialise the typed enum. Always succeeds — recognised kinds
45    /// become typed variants; anything else (including known kinds
46    /// whose schema we fail to deserialize) becomes
47    /// [`TraceEvent::Unknown`].
48    pub fn into_typed(self) -> TraceEvent {
49        // Try to deserialize as the tagged enum. If it fails (unknown
50        // tag, or a known tag with unexpected payload shape), preserve
51        // the raw payload as `Unknown` rather than discarding it.
52        match serde_json::from_value::<TypedEnum>(Value::Object(self.raw.clone())) {
53            Ok(t) => t.into(),
54            Err(_) => TraceEvent::Unknown(self),
55        }
56    }
57}
58
59/// Strongly-typed variants for the event kinds this version of the
60/// parser models. Unknown / unmodelled kinds surface as
61/// [`TraceEvent::Unknown`] to preserve the underlying JSON.
62#[derive(Debug, Clone)]
63pub enum TraceEvent {
64    ContextOptions(ContextOptions),
65    Before(BeforeEvent),
66    Input(InputEvent),
67    Log(LogEvent),
68    After(AfterEvent),
69    Console(ConsoleEvent),
70    Event(SystemEvent),
71    FrameSnapshot(FrameSnapshotEvent),
72    ScreencastFrame(ScreencastFrameEvent),
73    /// Catch-all preserving the raw payload. Carries [`RawEvent`] so
74    /// users keep full access to the JSON for kinds we don't model.
75    Unknown(RawEvent),
76}
77
78// Internal enum used purely for serde-driven dispatch on the `type`
79// field. Public callers always see `TraceEvent`.
80#[derive(Deserialize)]
81#[serde(tag = "type", rename_all = "kebab-case")]
82enum TypedEnum {
83    ContextOptions(ContextOptions),
84    Before(BeforeEvent),
85    Input(InputEvent),
86    Log(LogEvent),
87    After(AfterEvent),
88    Console(ConsoleEvent),
89    Event(SystemEvent),
90    FrameSnapshot(FrameSnapshotEvent),
91    ScreencastFrame(ScreencastFrameEvent),
92}
93
94impl From<TypedEnum> for TraceEvent {
95    fn from(t: TypedEnum) -> Self {
96        match t {
97            TypedEnum::ContextOptions(c) => TraceEvent::ContextOptions(c),
98            TypedEnum::Before(b) => TraceEvent::Before(b),
99            TypedEnum::Input(i) => TraceEvent::Input(i),
100            TypedEnum::Log(l) => TraceEvent::Log(l),
101            TypedEnum::After(a) => TraceEvent::After(a),
102            TypedEnum::Console(c) => TraceEvent::Console(c),
103            TypedEnum::Event(e) => TraceEvent::Event(e),
104            TypedEnum::FrameSnapshot(f) => TraceEvent::FrameSnapshot(f),
105            TypedEnum::ScreencastFrame(s) => TraceEvent::ScreencastFrame(s),
106        }
107    }
108}
109
110/// Per-context metadata — appears once per trace as the first event
111/// in `trace.trace`.
112#[derive(Debug, Clone, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct ContextOptions {
115    pub version: u32,
116    #[serde(default)]
117    pub browser_name: String,
118    #[serde(default)]
119    pub playwright_version: String,
120    #[serde(default)]
121    pub platform: String,
122    #[serde(default)]
123    pub sdk_language: String,
124    #[serde(default)]
125    pub test_id_attribute_name: String,
126    #[serde(default)]
127    pub wall_time: f64,
128    #[serde(default)]
129    pub monotonic_time: f64,
130    #[serde(default)]
131    pub context_id: String,
132    /// Original `options` blob, kept as raw JSON since its shape varies
133    /// with browser type and Playwright version.
134    #[serde(default)]
135    pub options: Value,
136}
137
138/// Action-start event. Pairs with a matching [`AfterEvent`] sharing
139/// `call_id`.
140#[derive(Debug, Clone, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct BeforeEvent {
143    pub call_id: String,
144    pub start_time: f64,
145    #[serde(default)]
146    pub class: String,
147    #[serde(default)]
148    pub method: String,
149    #[serde(default)]
150    pub params: Value,
151    #[serde(default)]
152    pub title: Option<String>,
153    pub page_id: Option<String>,
154    pub before_snapshot: Option<String>,
155    pub step_id: Option<String>,
156    pub parent_id: Option<String>,
157}
158
159/// Optional input-coordinate / input-snapshot reference attached to an
160/// in-flight action.
161#[derive(Debug, Clone, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct InputEvent {
164    pub call_id: String,
165    pub point: Option<Point>,
166    pub input_snapshot: Option<String>,
167}
168
169/// Log line emitted during an in-flight action.
170#[derive(Debug, Clone, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct LogEvent {
173    pub call_id: String,
174    pub message: String,
175    pub time: f64,
176}
177
178/// Action-completion event.
179#[derive(Debug, Clone, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct AfterEvent {
182    pub call_id: String,
183    pub end_time: f64,
184    #[serde(default)]
185    pub result: Option<Value>,
186    #[serde(default)]
187    pub error: Option<ActionError>,
188    pub after_snapshot: Option<String>,
189    pub point: Option<Point>,
190}
191
192/// Browser console output captured during the trace.
193#[derive(Debug, Clone, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct ConsoleEvent {
196    /// `"log"`, `"warn"`, `"error"`, `"info"`, `"debug"`, etc. Kept as
197    /// a string because Playwright extends this set; matching at the
198    /// call site keeps us forward-compatible.
199    #[serde(rename = "type", default)]
200    pub level: String,
201    #[serde(default)]
202    pub text: String,
203    #[serde(default)]
204    pub args: Vec<Value>,
205    pub location: Option<ConsoleLocation>,
206    pub time: f64,
207    pub page_id: Option<String>,
208}
209
210#[derive(Debug, Clone, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct ConsoleLocation {
213    #[serde(default)]
214    pub url: String,
215    #[serde(default)]
216    pub line_number: u32,
217    #[serde(default)]
218    pub column_number: u32,
219}
220
221/// System events (dialog, download, page open/close). Mirrors the
222/// `event` chunk type in the trace.
223#[derive(Debug, Clone, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct SystemEvent {
226    #[serde(default)]
227    pub class: String,
228    #[serde(default)]
229    pub method: String,
230    #[serde(default)]
231    pub params: Value,
232    pub time: f64,
233    pub page_id: Option<String>,
234}
235
236/// Per-frame DOM snapshot. Includes the full HTML payload — these can
237/// be sizeable; callers iterating on snapshots for many frames should
238/// expect the per-event size to dominate the overall trace memory
239/// budget.
240#[derive(Debug, Clone, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct FrameSnapshotEvent {
243    pub call_id: String,
244    pub snapshot_name: String,
245    pub page_id: String,
246    pub frame_id: String,
247    #[serde(default)]
248    pub frame_url: String,
249    #[serde(default)]
250    pub doctype: String,
251    #[serde(default)]
252    pub html: String,
253    pub viewport: Option<Viewport>,
254    pub timestamp: f64,
255    #[serde(default)]
256    pub wall_time: f64,
257    #[serde(default)]
258    pub collection_time: f64,
259    #[serde(default)]
260    pub is_main_frame: bool,
261    #[serde(default)]
262    pub resource_overrides: Vec<ResourceOverride>,
263}
264
265#[derive(Debug, Clone, Copy, Deserialize)]
266pub struct Viewport {
267    pub width: u32,
268    pub height: u32,
269}
270
271/// External resource reference used by a snapshot. Either a SHA-1 hash
272/// (resolved through the zip's `resources/` directory) or an internal
273/// reference identifier the trace viewer reassembles.
274#[derive(Debug, Clone, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct ResourceOverride {
277    pub url: String,
278    #[serde(default)]
279    pub sha1: Option<String>,
280    #[serde(rename = "ref", default)]
281    pub reference: Option<String>,
282}
283
284/// Single screencast frame stored as a JPEG in `resources/<sha1>`.
285#[derive(Debug, Clone, Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct ScreencastFrameEvent {
288    pub page_id: String,
289    pub sha1: String,
290    pub width: u32,
291    pub height: u32,
292    pub timestamp: f64,
293}
294
295/// Failure payload attached to an [`AfterEvent`].
296#[derive(Debug, Clone, Deserialize)]
297#[serde(rename_all = "camelCase")]
298pub struct ActionError {
299    #[serde(default)]
300    pub name: String,
301    #[serde(default)]
302    pub message: String,
303}
304
305/// 2D coordinates for input events and click targets. Used in
306/// [`InputEvent::point`] and [`AfterEvent::point`].
307#[derive(Debug, Clone, Copy, Deserialize)]
308pub struct Point {
309    pub x: f64,
310    pub y: f64,
311}