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 => {
74                        "\n  Hint: method not found — the tool may be disabled by privacy profile"
75                    }
76                    -32602 => "\n  Hint: invalid params — check the tool's expected arguments",
77                    -32603 => "\n  Hint: internal error — check the Tauri app's stderr for details",
78                    _ => "",
79                };
80                write!(f, "MCP error {code}: {message}{hint}")
81            }
82            Self::ToolError(msg) => write!(f, "tool call failed: {msg}"),
83            Self::Assertion(msg) => write!(f, "assertion failed: {msg}"),
84            Self::Timeout(msg) => {
85                write!(
86                    f,
87                    "timeout: {msg}\n\
88                     \n  Possible fixes:\n\
89                     \x20 - Increase timeout: .timeout_ms(10_000) or wait_for(..., Some(15_000), ...)\n\
90                     \x20 - Check that the expected condition can actually be met\n\
91                     \x20 - Look for JS errors: client.get_console_logs().await"
92                )
93            }
94            Self::ElementNotFound(msg) => {
95                write!(
96                    f,
97                    "element not found: {msg}\n\
98                     \n  Possible fixes:\n\
99                     \x20 - Take a DOM snapshot to see what's on the page: client.dom_snapshot().await\n\
100                     \x20 - The element may not have rendered yet — use expect().to_be_visible().await\n\
101                     \x20 - Check for typos in the locator query"
102                )
103            }
104            Self::VisualRegression(msg) => {
105                write!(
106                    f,
107                    "visual regression: {msg}\n\
108                     \n  If the change is intentional, delete the baseline image to regenerate it.\n\
109                     \x20 Use ThresholdPreset::Relaxed for cross-platform tolerance."
110                )
111            }
112            Self::Other(msg) => write!(f, "{msg}"),
113        }
114    }
115}
116
117impl std::error::Error for TestError {
118    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
119        match self {
120            Self::Request(e) => Some(e),
121            _ => None,
122        }
123    }
124}