use chromiumoxide::cdp::browser_protocol::page::AddScriptToEvaluateOnNewDocumentParams;
use chromiumoxide::Page;
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::error::{AuditError, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsoleMessage {
pub level: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsoleErrorsAnalysis {
pub errors: Vec<ConsoleMessage>,
pub warnings: Vec<ConsoleMessage>,
pub error_count: usize,
pub warning_count: usize,
}
pub async fn prepare_console_collection(page: &Page) -> Result<()> {
let script = r#"
(function() {
window.__auditConsoleErrors = [];
var _origError = console.error;
var _origWarn = console.warn;
console.error = function() {
var msg = Array.prototype.slice.call(arguments).map(function(a) {
return typeof a === 'object' ? JSON.stringify(a) : String(a);
}).join(' ');
window.__auditConsoleErrors.push({level: 'error', msg: msg.substring(0, 200)});
_origError.apply(console, arguments);
};
console.warn = function() {
var msg = Array.prototype.slice.call(arguments).map(function(a) {
return typeof a === 'object' ? JSON.stringify(a) : String(a);
}).join(' ');
window.__auditConsoleErrors.push({level: 'warn', msg: msg.substring(0, 200)});
_origWarn.apply(console, arguments);
};
window.addEventListener('error', function(e) {
var loc = e.filename ? (' at ' + e.filename + ':' + e.lineno) : '';
window.__auditConsoleErrors.push({
level: 'error',
msg: (e.message + loc).substring(0, 200)
});
});
window.addEventListener('unhandledrejection', function(e) {
var reason = e.reason ? String(e.reason) : 'unknown';
window.__auditConsoleErrors.push({
level: 'error',
msg: ('Unhandled Promise Rejection: ' + reason).substring(0, 200)
});
});
})();
"#;
page.execute(AddScriptToEvaluateOnNewDocumentParams::new(script))
.await
.map_err(|e| AuditError::CdpError(format!("Console collection setup failed: {e}")))?;
Ok(())
}
pub async fn take_console_results(page: &Page) -> Result<ConsoleErrorsAnalysis> {
info!("Taking console error results...");
let js = r#"
(() => {
var items = window.__auditConsoleErrors || [];
return JSON.stringify(items);
})()
"#;
let result = page
.evaluate(js)
.await
.map_err(|e| AuditError::CdpError(format!("Console results JS failed: {e}")))?;
let json_str = result.value().and_then(|v| v.as_str()).unwrap_or("[]");
#[derive(serde::Deserialize)]
struct RawMsg {
level: String,
msg: String,
}
let raw: Vec<RawMsg> = serde_json::from_str(json_str).unwrap_or_default();
let mut errors: Vec<ConsoleMessage> = Vec::new();
let mut warnings: Vec<ConsoleMessage> = Vec::new();
for item in raw {
let msg = ConsoleMessage {
level: item.level.clone(),
message: item.msg,
};
if item.level == "error" {
errors.push(msg);
} else {
warnings.push(msg);
}
}
let error_count = errors.len();
let warning_count = warnings.len();
info!(
"Console: {} errors, {} warnings",
error_count, warning_count
);
Ok(ConsoleErrorsAnalysis {
errors,
warnings,
error_count,
warning_count,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_console_errors_analysis_empty() {
let analysis = ConsoleErrorsAnalysis {
errors: vec![],
warnings: vec![],
error_count: 0,
warning_count: 0,
};
assert_eq!(analysis.error_count, 0);
assert_eq!(analysis.warning_count, 0);
}
}