1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// HAR recording options for Tracing::start_har.
//
// Kept in its own module (rather than in tracing.rs) so the pure
// RecordHarOptions serialization is covered by mutation testing, while the
// integration-only start_har/stop_har stay out of scope.
use serde::Serialize;
use serde_json::Value;
/// How resource bodies are stored in a recorded HAR.
///
/// See: <https://playwright.dev/docs/api/class-tracing#tracing-start-har-option-content>
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum HarContent {
/// Do not store bodies (smallest HAR).
Omit,
/// Inline bodies into the HAR as base64 (the default for a non-`.zip` path).
Embed,
/// Store bodies as separate files / zip entries (the default for a `.zip` path).
Attach,
}
/// Level of detail recorded in a HAR.
///
/// See: <https://playwright.dev/docs/api/class-tracing#tracing-start-har-option-mode>
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum HarMode {
/// Record everything (default).
Full,
/// Record only essentials (size, timing) and omit headers/bodies/cookies.
Minimal,
}
/// Options for [`Tracing::start_har`](crate::protocol::Tracing::start_har).
///
/// # Example
///
/// ```
/// use playwright_rs::{StartHarOptions, HarContent, HarMode};
///
/// let opts = StartHarOptions::default()
/// .content(HarContent::Attach)
/// .mode(HarMode::Minimal)
/// .url_filter("**/api/**");
/// ```
///
/// See: <https://playwright.dev/docs/api/class-tracing#tracing-start-har>
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct StartHarOptions {
/// How resource bodies are stored. Defaults to `Attach` for a `.zip` path,
/// `Embed` otherwise.
pub content: Option<HarContent>,
/// Level of detail. Defaults to [`HarMode::Full`].
pub mode: Option<HarMode>,
/// Glob pattern; only requests whose URL matches are recorded.
pub url_filter: Option<String>,
/// Directory to store `attach`-mode resource files in (for non-zip paths).
pub resources_dir: Option<String>,
}
impl StartHarOptions {
/// How resource bodies are stored (`Attach` / `Embed` / `Omit`).
pub fn content(mut self, content: HarContent) -> Self {
self.content = Some(content);
self
}
/// Level of detail (`Full` / `Minimal`).
pub fn mode(mut self, mode: HarMode) -> Self {
self.mode = Some(mode);
self
}
/// Glob pattern; only requests whose URL matches are recorded.
pub fn url_filter(mut self, url_filter: impl Into<String>) -> Self {
self.url_filter = Some(url_filter.into());
self
}
/// Directory to store `attach`-mode resource files in (for non-zip paths).
pub fn resources_dir(mut self, resources_dir: impl Into<String>) -> Self {
self.resources_dir = Some(resources_dir.into());
self
}
/// Build the protocol `RecordHarOptions` object for the given output path.
///
/// `harPath` is intentionally omitted: setting it makes the driver write its
/// own archive at that path (appending `.zip`), which would duplicate the
/// file we already produce via `harExport` + unzip in `stop_har`. The path
/// here only selects the default `content` mode.
pub(crate) fn to_record_har_json(&self, path: &str) -> Value {
let is_zip = path.ends_with(".zip");
let content = self.content.unwrap_or(if is_zip {
HarContent::Attach
} else {
HarContent::Embed
});
let mode = self.mode.unwrap_or(HarMode::Full);
let mut o = serde_json::json!({});
o["content"] = serde_json::to_value(content).expect("serialize HarContent cannot fail");
o["mode"] = serde_json::to_value(mode).expect("serialize HarMode cannot fail");
if let Some(glob) = &self.url_filter {
o["urlGlob"] = serde_json::json!(glob);
}
if let Some(dir) = &self.resources_dir {
o["resourcesDir"] = serde_json::json!(dir);
}
o
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_start_har_options_zip_defaults_to_attach() {
let json = StartHarOptions::default().to_record_har_json("run.har.zip");
assert_eq!(json["content"], "attach");
assert_eq!(json["mode"], "full");
// harPath is deliberately not sent (avoids the driver double-writing).
assert!(json.get("harPath").is_none());
}
#[test]
fn test_start_har_options_plain_defaults_to_embed() {
let json = StartHarOptions::default().to_record_har_json("run.har");
assert_eq!(json["content"], "embed");
}
#[test]
fn test_start_har_options_setters() {
let opts = StartHarOptions::default()
.content(HarContent::Omit)
.mode(HarMode::Minimal)
.url_filter("**/api/**");
let json = opts.to_record_har_json("run.har");
assert_eq!(json["content"], "omit");
assert_eq!(json["mode"], "minimal");
assert_eq!(json["urlGlob"], "**/api/**");
assert!(json.get("resourcesDir").is_none());
}
#[test]
fn test_start_har_options_resources_dir_setter() {
let json = StartHarOptions::default()
.resources_dir("/tmp/har-resources")
.to_record_har_json("run.har");
assert_eq!(json["resourcesDir"], "/tmp/har-resources");
}
}