Skip to main content

victauri_test/
error.rs

1/// Errors that can occur when interacting with the Victauri MCP server from tests.
2///
3/// Each variant includes actionable context to help diagnose and fix the issue.
4#[derive(Debug)]
5#[non_exhaustive]
6pub enum TestError {
7    /// Failed to connect to the Victauri MCP server at the expected port.
8    Connection {
9        /// Host that was targeted (typically `"127.0.0.1"`).
10        host: String,
11        /// Port that was targeted.
12        port: u16,
13        /// Human-readable explanation of what went wrong.
14        reason: String,
15    },
16
17    /// An HTTP-level error occurred during an MCP request.
18    Request(reqwest::Error),
19
20    /// The MCP server returned a JSON-RPC error response.
21    Mcp {
22        /// JSON-RPC error code.
23        code: i64,
24        /// Human-readable error message from the server.
25        message: String,
26    },
27
28    /// A tool call returned `isError: true` in its result.
29    ToolError(String),
30
31    /// A test assertion evaluated to false.
32    Assertion(String),
33
34    /// A `wait_for` condition did not become true within the allowed time.
35    Timeout(String),
36
37    /// An element matching the given criteria was not found in the DOM snapshot.
38    ElementNotFound(String),
39
40    /// A visual regression was detected — screenshot differs from baseline.
41    VisualRegression(String),
42
43    /// A catch-all for errors that don't fit other variants (IO, encoding, etc.).
44    Other(String),
45}
46
47impl From<reqwest::Error> for TestError {
48    fn from(e: reqwest::Error) -> Self {
49        Self::Request(e)
50    }
51}
52
53impl std::fmt::Display for TestError {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            Self::Connection { host, port, reason } => {
57                write!(
58                    f,
59                    "connection failed ({host}:{port}): {reason}\n\
60                     \n  Possible fixes:\n\
61                     \x20 - Is your Tauri app running? Start it with: pnpm tauri dev\n\
62                     \x20 - Check that victauri-plugin is wired in your Tauri builder\n\
63                     \x20 - Try a different port: VICTAURI_PORT={port} cargo test\n\
64                     \x20 - Run `victauri doctor` for full diagnostics"
65                )
66            }
67            Self::Request(e) => {
68                write!(f, "MCP request failed: {e}")
69            }
70            Self::Mcp { code, message } => {
71                let hint = match *code {
72                    -32600 => "\n  Hint: invalid request — check your MCP protocol version",
73                    -32601 => "\n  Hint: method not found — the tool may be disabled by privacy profile",
74                    -32602 => "\n  Hint: invalid params — check the tool's expected arguments",
75                    -32603 => "\n  Hint: internal error — check the Tauri app's stderr for details",
76                    _ => "",
77                };
78                write!(f, "MCP error {code}: {message}{hint}")
79            }
80            Self::ToolError(msg) => write!(f, "tool call failed: {msg}"),
81            Self::Assertion(msg) => write!(f, "assertion failed: {msg}"),
82            Self::Timeout(msg) => {
83                write!(
84                    f,
85                    "timeout: {msg}\n\
86                     \n  Possible fixes:\n\
87                     \x20 - Increase timeout: .timeout_ms(10_000) or wait_for(..., Some(15_000), ...)\n\
88                     \x20 - Check that the expected condition can actually be met\n\
89                     \x20 - Look for JS errors: client.get_console_logs().await"
90                )
91            }
92            Self::ElementNotFound(msg) => {
93                write!(
94                    f,
95                    "element not found: {msg}\n\
96                     \n  Possible fixes:\n\
97                     \x20 - Take a DOM snapshot to see what's on the page: client.dom_snapshot().await\n\
98                     \x20 - The element may not have rendered yet — use expect().to_be_visible().await\n\
99                     \x20 - Check for typos in the locator query"
100                )
101            }
102            Self::VisualRegression(msg) => {
103                write!(
104                    f,
105                    "visual regression: {msg}\n\
106                     \n  If the change is intentional, delete the baseline image to regenerate it.\n\
107                     \x20 Use ThresholdPreset::Relaxed for cross-platform tolerance."
108                )
109            }
110            Self::Other(msg) => write!(f, "{msg}"),
111        }
112    }
113}
114
115impl std::error::Error for TestError {
116    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
117        match self {
118            Self::Request(e) => Some(e),
119            _ => None,
120        }
121    }
122}