viewpoint_core/page/page_error/
mod.rs

1//! Page error types and event handling.
2//!
3//! This module provides types for capturing uncaught JavaScript exceptions
4//! and errors from the page.
5
6use viewpoint_cdp::protocol::runtime::{ExceptionDetails, ExceptionThrownEvent};
7
8/// An uncaught exception from the page.
9///
10/// Page errors are emitted when JavaScript code throws an uncaught exception.
11/// These correspond to the 'pageerror' event in Playwright.
12///
13/// # Example
14///
15/// ```
16/// # #[cfg(feature = "integration")]
17/// # tokio_test::block_on(async {
18/// # use viewpoint_core::Browser;
19/// # let browser = Browser::launch().headless(true).launch().await.unwrap();
20/// # let context = browser.new_context().await.unwrap();
21/// # let page = context.new_page().await.unwrap();
22///
23/// page.on_pageerror(|error| async move {
24///     println!("Page error: {}", error.message());
25/// }).await;
26/// # });
27/// ```
28#[derive(Debug, Clone)]
29pub struct PageError {
30    /// Exception details.
31    exception_details: ExceptionDetails,
32    /// Timestamp when the error occurred.
33    timestamp: f64,
34}
35
36impl PageError {
37    /// Create a new page error from a CDP event.
38    pub(crate) fn from_event(event: ExceptionThrownEvent) -> Self {
39        Self {
40            exception_details: event.exception_details,
41            timestamp: event.timestamp,
42        }
43    }
44
45    /// Get the error message.
46    pub fn message(&self) -> String {
47        // Try to get the exception message first
48        if let Some(ref exception) = self.exception_details.exception {
49            if let Some(ref description) = exception.description {
50                return description.clone();
51            }
52            if let Some(ref value) = exception.value {
53                if let Some(s) = value.as_str() {
54                    return s.to_string();
55                }
56                return value.to_string();
57            }
58        }
59
60        // Fall back to the exception text
61        self.exception_details.text.clone()
62    }
63
64    /// Get the full stack trace as a string.
65    pub fn stack(&self) -> Option<String> {
66        self.exception_details
67            .exception
68            .as_ref()
69            .and_then(|exc| exc.description.clone())
70    }
71
72    /// Get the error name (e.g., "`TypeError`", "`ReferenceError`").
73    pub fn name(&self) -> Option<String> {
74        self.exception_details
75            .exception
76            .as_ref()
77            .and_then(|exc| exc.class_name.clone())
78    }
79
80    /// Get the URL where the error occurred.
81    pub fn url(&self) -> Option<&str> {
82        self.exception_details.url.as_deref()
83    }
84
85    /// Get the line number where the error occurred.
86    pub fn line_number(&self) -> i64 {
87        self.exception_details.line_number
88    }
89
90    /// Get the column number where the error occurred.
91    pub fn column_number(&self) -> i64 {
92        self.exception_details.column_number
93    }
94
95    /// Get the timestamp when the error occurred.
96    pub fn timestamp(&self) -> f64 {
97        self.timestamp
98    }
99}
100
101impl std::fmt::Display for PageError {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        if let Some(name) = self.name() {
104            write!(f, "{}: {}", name, self.message())
105        } else {
106            write!(f, "{}", self.message())
107        }
108    }
109}
110
111impl std::error::Error for PageError {}
112
113/// An error from any page in a browser context.
114///
115/// Web errors are emitted at the context level when any page has an uncaught
116/// exception. They include a reference to the page where the error occurred.
117///
118/// # Example
119///
120/// ```
121/// # #[cfg(feature = "integration")]
122/// # tokio_test::block_on(async {
123/// # use viewpoint_core::Browser;
124/// # let browser = Browser::launch().headless(true).launch().await.unwrap();
125/// # let context = browser.new_context().await.unwrap();
126///
127/// context.on_weberror(|error| async move {
128///     println!("Error: {}", error.message());
129/// }).await;
130/// # });
131/// ```
132#[derive(Debug, Clone)]
133pub struct WebError {
134    /// The underlying page error.
135    error: PageError,
136    /// Target ID of the page where the error occurred.
137    target_id: String,
138    /// Session ID of the page.
139    session_id: String,
140}
141
142impl WebError {
143    /// Create a new web error.
144    pub(crate) fn new(error: PageError, target_id: String, session_id: String) -> Self {
145        Self {
146            error,
147            target_id,
148            session_id,
149        }
150    }
151
152    /// Get the error message.
153    pub fn message(&self) -> String {
154        self.error.message()
155    }
156
157    /// Get the full stack trace as a string.
158    pub fn stack(&self) -> Option<String> {
159        self.error.stack()
160    }
161
162    /// Get the error name.
163    pub fn name(&self) -> Option<String> {
164        self.error.name()
165    }
166
167    /// Get the target ID of the page where the error occurred.
168    pub fn target_id(&self) -> &str {
169        &self.target_id
170    }
171
172    /// Get the session ID of the page.
173    pub fn session_id(&self) -> &str {
174        &self.session_id
175    }
176
177    /// Get the underlying page error.
178    pub fn page_error(&self) -> &PageError {
179        &self.error
180    }
181}
182
183impl std::fmt::Display for WebError {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        write!(f, "{}", self.error)
186    }
187}
188
189impl std::error::Error for WebError {}