Skip to main content

playwright_rs/protocol/
local_utils.rs

1// Copyright 2026 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3
4use crate::error::Result;
5use crate::server::channel::Channel;
6use crate::server::channel_owner::{
7    ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
8};
9use crate::server::connection::ConnectionLike;
10use base64::Engine;
11use serde::Deserialize;
12use serde_json::Value;
13use std::any::Any;
14use std::sync::Arc;
15
16/// LocalUtils protocol object
17///
18/// Provides client-side utility operations: HAR file replay, zip, tracing helpers.
19#[derive(Clone)]
20pub struct LocalUtils {
21    base: ChannelOwnerImpl,
22}
23
24impl LocalUtils {
25    pub fn new(
26        parent: ParentOrConnection,
27        type_name: String,
28        guid: Arc<str>,
29        initializer: Value,
30    ) -> Result<Self> {
31        Ok(Self {
32            base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
33        })
34    }
35
36    /// Opens a HAR file and returns an opaque HAR ID for subsequent lookups.
37    ///
38    /// Sends `"harOpen"` RPC with the file path.
39    pub async fn har_open(&self, file: &str) -> Result<String> {
40        #[derive(Deserialize)]
41        struct HarOpenResult {
42            #[serde(rename = "harId")]
43            har_id: String,
44        }
45        let result: HarOpenResult = self
46            .channel()
47            .send("harOpen", serde_json::json!({ "file": file }))
48            .await?;
49        Ok(result.har_id)
50    }
51
52    /// Looks up a request in the opened HAR archive.
53    ///
54    /// Returns an action object describing how the request should be fulfilled,
55    /// redirected, or allowed to fall through.
56    pub async fn har_lookup(
57        &self,
58        har_id: &str,
59        url: &str,
60        method: &str,
61        headers: Vec<serde_json::Value>,
62        post_data: Option<&[u8]>,
63        is_navigation_request: bool,
64    ) -> Result<HarLookupResult> {
65        let mut params = serde_json::json!({
66            "harId": har_id,
67            "url": url,
68            "method": method,
69            "headers": headers,
70            "isNavigationRequest": is_navigation_request,
71        });
72
73        // Only include postData when present — the server rejects null.
74        if let Some(data) = post_data {
75            let encoded = base64::engine::general_purpose::STANDARD.encode(data);
76            params["postData"] = serde_json::Value::String(encoded);
77        }
78
79        self.channel().send("harLookup", params).await
80    }
81
82    /// Closes a previously opened HAR archive.
83    pub async fn har_close(&self, har_id: &str) -> Result<()> {
84        self.channel()
85            .send_no_result("harClose", serde_json::json!({ "harId": har_id }))
86            .await
87    }
88}
89
90/// Result from a `harLookup` RPC call.
91///
92/// Describes whether the request was found in the HAR and how to respond.
93#[derive(Debug, Deserialize)]
94pub struct HarLookupResult {
95    /// Action to take: `"fulfill"`, `"redirect"`, `"fallback"`, or `"error"`.
96    pub action: String,
97
98    /// For `"redirect"`: the URL to redirect to.
99    #[serde(rename = "redirectURL")]
100    pub redirect_url: Option<String>,
101
102    /// For `"fulfill"`: HTTP status code.
103    pub status: Option<u16>,
104
105    /// For `"fulfill"`: HTTP headers as `[{"name": ..., "value": ...}]`.
106    pub headers: Option<Vec<serde_json::Value>>,
107
108    /// For `"fulfill"`: Base64-encoded response body.
109    pub body: Option<String>,
110}
111
112impl ChannelOwner for LocalUtils {
113    fn guid(&self) -> &str {
114        self.base.guid()
115    }
116
117    fn type_name(&self) -> &str {
118        self.base.type_name()
119    }
120
121    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
122        self.base.parent()
123    }
124
125    fn connection(&self) -> Arc<dyn ConnectionLike> {
126        self.base.connection()
127    }
128
129    fn initializer(&self) -> &Value {
130        self.base.initializer()
131    }
132
133    fn channel(&self) -> &Channel {
134        self.base.channel()
135    }
136
137    fn dispose(&self, reason: DisposeReason) {
138        self.base.dispose(reason)
139    }
140
141    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
142        self.base.adopt(child)
143    }
144
145    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
146        self.base.add_child(guid, child)
147    }
148
149    fn remove_child(&self, guid: &str) {
150        self.base.remove_child(guid)
151    }
152
153    fn on_event(&self, method: &str, params: Value) {
154        self.base.on_event(method, params)
155    }
156
157    fn was_collected(&self) -> bool {
158        self.base.was_collected()
159    }
160
161    fn as_any(&self) -> &dyn Any {
162        self
163    }
164}
165
166impl std::fmt::Debug for LocalUtils {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        f.debug_struct("LocalUtils")
169            .field("guid", &self.guid())
170            .finish()
171    }
172}