browser_commander/core/
navigation.rs1use thiserror::Error;
7
8#[derive(Debug, Error)]
10pub enum NavigationError {
11 #[error("Page navigated away during operation")]
13 PageNavigatedAway,
14
15 #[error("Navigation was interrupted: {0}")]
17 Interrupted(String),
18
19 #[error("Navigation timed out after {0}ms")]
21 Timeout(u64),
22
23 #[error("Target was detached")]
25 TargetDetached,
26
27 #[error("Execution context was destroyed")]
29 ExecutionContextDestroyed,
30}
31
32const NAVIGATION_ERROR_PATTERNS: &[&str] = &[
34 "navigat", "detached",
36 "context was destroyed",
37 "execution context was destroyed",
38 "frame was detached",
39 "target closed",
40 "page has been closed",
41 "session closed",
42 "cannot find context",
43 "protocol error",
44];
45
46const TIMEOUT_ERROR_PATTERNS: &[&str] = &["timed out", "timeout", "exceeded", "waiting for"];
48
49pub fn is_navigation_error(error_message: &str) -> bool {
63 NAVIGATION_ERROR_PATTERNS.iter().any(|pattern| {
64 error_message
65 .to_lowercase()
66 .contains(&pattern.to_lowercase())
67 })
68}
69
70pub fn is_timeout_error(error_message: &str) -> bool {
80 TIMEOUT_ERROR_PATTERNS.iter().any(|pattern| {
81 error_message
82 .to_lowercase()
83 .contains(&pattern.to_lowercase())
84 })
85}
86
87pub async fn safe_operation<F, T, E>(operation: F, default: T) -> T
101where
102 F: std::future::Future<Output = Result<T, E>>,
103 E: std::fmt::Display,
104{
105 match operation.await {
106 Ok(value) => value,
107 Err(e) if is_navigation_error(&e.to_string()) => default,
108 Err(_) => default, }
110}
111
112#[derive(Debug)]
114pub struct SafeResult<T> {
115 pub value: T,
117 pub success: bool,
119 pub navigation_error: bool,
121}
122
123impl<T: Default> SafeResult<T> {
124 pub fn success(value: T) -> Self {
126 Self {
127 value,
128 success: true,
129 navigation_error: false,
130 }
131 }
132
133 pub fn navigation_error() -> Self {
135 Self {
136 value: T::default(),
137 success: false,
138 navigation_error: true,
139 }
140 }
141
142 pub fn error(value: T) -> Self {
144 Self {
145 value,
146 success: false,
147 navigation_error: false,
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn is_navigation_error_detects_common_patterns() {
158 assert!(is_navigation_error("Page navigated away"));
159 assert!(is_navigation_error("Target was detached"));
160 assert!(is_navigation_error("Execution context was destroyed"));
161 assert!(is_navigation_error("frame was detached"));
162 assert!(is_navigation_error("Target closed"));
163 assert!(is_navigation_error("page has been closed"));
164 assert!(is_navigation_error(
165 "Session closed. Most likely the page has been closed."
166 ));
167 assert!(is_navigation_error("Cannot find context with specified id"));
168 assert!(is_navigation_error("Protocol error: Target closed"));
169 }
170
171 #[test]
172 fn is_navigation_error_case_insensitive() {
173 assert!(is_navigation_error("NAVIGATION ERROR"));
174 assert!(is_navigation_error("Target Detached"));
175 assert!(is_navigation_error("CONTEXT WAS DESTROYED"));
176 }
177
178 #[test]
179 fn is_navigation_error_false_for_other_errors() {
180 assert!(!is_navigation_error("Element not found"));
181 assert!(!is_navigation_error("Invalid selector"));
182 assert!(!is_navigation_error("Network error"));
183 }
184
185 #[test]
186 fn is_timeout_error_detects_timeout_patterns() {
187 assert!(is_timeout_error("Operation timed out"));
188 assert!(is_timeout_error("Timeout exceeded"));
189 assert!(is_timeout_error("waiting for selector"));
190 assert!(is_timeout_error("Timeout"));
191 }
192
193 #[test]
194 fn is_timeout_error_false_for_other_errors() {
195 assert!(!is_timeout_error("Element not found"));
196 assert!(!is_timeout_error("Navigation error"));
197 }
198
199 #[test]
200 fn safe_result_success() {
201 let result = SafeResult::success(42);
202 assert_eq!(result.value, 42);
203 assert!(result.success);
204 assert!(!result.navigation_error);
205 }
206
207 #[test]
208 fn safe_result_navigation_error() {
209 let result: SafeResult<i32> = SafeResult::navigation_error();
210 assert_eq!(result.value, 0);
211 assert!(!result.success);
212 assert!(result.navigation_error);
213 }
214
215 #[test]
216 fn safe_result_error() {
217 let result: SafeResult<String> = SafeResult::error("fallback".to_string());
218 assert_eq!(result.value, "fallback");
219 assert!(!result.success);
220 assert!(!result.navigation_error);
221 }
222}