playwright_rs/protocol/
resource_timing.rs1#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub struct ResourceTiming {
18 pub start_time: f64,
20 pub domain_lookup_start: f64,
24 pub domain_lookup_end: f64,
28 pub connect_start: f64,
31 pub secure_connection_start: f64,
34 pub connect_end: f64,
37 pub request_start: f64,
40 pub response_start: f64,
43 pub response_end: f64,
46}
47
48impl ResourceTiming {
49 pub(crate) fn merge_response_end(
57 timing: &mut serde_json::Value,
58 response_end_timing: Option<f64>,
59 ) {
60 if let (Some(end), Some(obj)) = (response_end_timing, timing.as_object_mut())
61 && let Some(n) = serde_json::Number::from_f64(end)
62 {
63 obj.insert("responseEnd".to_string(), serde_json::Value::Number(n));
64 }
65 }
66
67 pub(crate) fn from_protocol(value: &serde_json::Value) -> Option<Self> {
77 use serde::Deserialize;
78
79 #[derive(Deserialize)]
80 #[serde(rename_all = "camelCase")]
81 struct RawTiming {
82 start_time: Option<f64>,
83 domain_lookup_start: Option<f64>,
84 domain_lookup_end: Option<f64>,
85 connect_start: Option<f64>,
86 connect_end: Option<f64>,
87 secure_connection_start: Option<f64>,
88 request_start: Option<f64>,
89 response_start: Option<f64>,
90 response_end: Option<f64>,
91 }
92
93 let raw: RawTiming = serde_json::from_value(value.clone()).ok()?;
94
95 Some(Self {
96 start_time: raw.start_time.unwrap_or(-1.0),
97 domain_lookup_start: raw.domain_lookup_start.unwrap_or(-1.0),
98 domain_lookup_end: raw.domain_lookup_end.unwrap_or(-1.0),
99 connect_start: raw.connect_start.unwrap_or(-1.0),
100 connect_end: raw.connect_end.unwrap_or(-1.0),
101 secure_connection_start: raw.secure_connection_start.unwrap_or(-1.0),
102 request_start: raw.request_start.unwrap_or(-1.0),
103 response_start: raw.response_start.unwrap_or(-1.0),
104 response_end: raw.response_end.unwrap_or(-1.0),
105 })
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::ResourceTiming;
112 use serde_json::json;
113
114 #[test]
115 fn absent_phases_become_minus_one() {
116 let timing = ResourceTiming::from_protocol(&json!({ "startTime": 1000.0 }))
119 .expect("an object with only startTime is still a timing");
120
121 assert_eq!(timing.start_time, 1000.0);
122 assert_eq!(timing.domain_lookup_start, -1.0);
123 assert_eq!(timing.domain_lookup_end, -1.0);
124 assert_eq!(timing.connect_start, -1.0);
125 assert_eq!(timing.connect_end, -1.0);
126 assert_eq!(timing.secure_connection_start, -1.0);
127 assert_eq!(timing.request_start, -1.0);
128 assert_eq!(timing.response_start, -1.0);
129 assert_eq!(timing.response_end, -1.0);
130 }
131
132 #[test]
133 fn every_phase_is_read_from_its_own_wire_name() {
134 let timing = ResourceTiming::from_protocol(&json!({
137 "startTime": 1.0,
138 "domainLookupStart": 2.0,
139 "domainLookupEnd": 3.0,
140 "connectStart": 4.0,
141 "connectEnd": 5.0,
142 "secureConnectionStart": 6.0,
143 "requestStart": 7.0,
144 "responseStart": 8.0,
145 "responseEnd": 9.0,
146 }))
147 .expect("full timing parses");
148
149 assert_eq!(timing.start_time, 1.0);
150 assert_eq!(timing.domain_lookup_start, 2.0);
151 assert_eq!(timing.domain_lookup_end, 3.0);
152 assert_eq!(timing.connect_start, 4.0);
153 assert_eq!(timing.connect_end, 5.0);
154 assert_eq!(timing.secure_connection_start, 6.0);
155 assert_eq!(timing.request_start, 7.0);
156 assert_eq!(timing.response_start, 8.0);
157 assert_eq!(timing.response_end, 9.0);
158 }
159
160 #[test]
161 fn an_empty_timing_defaults_every_phase_including_start() {
162 let timing = ResourceTiming::from_protocol(&json!({})).expect("empty object parses");
165 assert_eq!(timing.start_time, -1.0);
166 }
167
168 #[test]
169 fn a_non_object_is_not_a_timing() {
170 assert!(ResourceTiming::from_protocol(&json!("nope")).is_none());
171 assert!(ResourceTiming::from_protocol(&json!(null)).is_none());
172 }
173}
174
175#[cfg(test)]
176mod merge_tests {
177 use super::ResourceTiming;
178 use serde_json::json;
179
180 #[test]
181 fn response_end_is_folded_into_the_timing() {
182 let mut timing = json!({ "startTime": 100.0, "requestStart": 5.0 });
186 ResourceTiming::merge_response_end(&mut timing, Some(42.0));
187
188 let parsed = ResourceTiming::from_protocol(&timing).expect("parses");
189 assert_eq!(parsed.response_end, 42.0);
190 }
191
192 #[test]
193 fn without_a_reported_end_the_phase_stays_unreached() {
194 let mut timing = json!({ "startTime": 100.0 });
195 ResourceTiming::merge_response_end(&mut timing, None);
196
197 let parsed = ResourceTiming::from_protocol(&timing).expect("parses");
198 assert_eq!(parsed.response_end, -1.0);
199 }
200
201 #[test]
202 fn a_non_object_timing_is_left_alone() {
203 let mut timing = json!("not a timing");
204 ResourceTiming::merge_response_end(&mut timing, Some(42.0));
205 assert_eq!(timing, json!("not a timing"));
206 }
207}