#![allow(
clippy::too_many_lines,
clippy::doc_markdown,
clippy::uninlined_format_args,
clippy::unwrap_used,
clippy::expect_used,
clippy::needless_pass_by_value
)]
use super::client::McpClient;
fn urlencoding(s: &str) -> String {
s.replace(' ', "%20").replace('#', "%23").replace('"', "%22")
}
pub fn test_page_error_is_native_error(c: &mut McpClient) {
let html = "<!doctype html><html><body><h1>wait-pageerror</h1></body></html>";
let url = format!("data:text/html,{}", urlencoding(html));
let script = format!(
r"
await page.goto({url});
await page.evaluate(() => {{
setTimeout(() => {{
const e = new Error('boom');
window.dispatchEvent(new ErrorEvent('error', {{ error: e, message: e.message }}));
throw e;
}}, 10);
}});
const deadline = Date.now() + 5000;
let match = null;
while (Date.now() < deadline) {{
const remaining = deadline - Date.now();
if (remaining <= 0) break;
const err = await page.waitForEvent('pageerror', remaining);
// Playwright parity: `err` is a native JS Error, not a wrapper.
if (err && err.message && err.message.indexOf('boom') !== -1) {{
match = {{
isError: err instanceof Error,
name: err.name,
message: err.message,
stackIsString: typeof err.stack === 'string',
}};
break;
}}
}}
return match;
",
url = serde_json::to_string(&url).unwrap()
);
let v = c.script_value(&script);
assert!(!v.is_null(), "expected a pageerror with 'boom' message: {v}");
assert_eq!(
v["isError"].as_bool(),
Some(true),
"page.waitForEvent('pageerror') should resolve to `instanceof Error`: {v}"
);
assert_eq!(
v["name"].as_str(),
Some("Error"),
"pageerror name should be 'Error': {v}"
);
assert!(
v["message"].as_str().unwrap_or("").contains("boom"),
"pageerror message should contain 'boom': {v}"
);
assert_eq!(
v["stackIsString"].as_bool(),
Some(true),
"pageerror stack must be a string (possibly empty on synthesised dispatches): {v}"
);
}
pub fn test_context_weberror_is_webbed_error_class(c: &mut McpClient) {
let html = "<!doctype html><html><body><h1>wait-weberror</h1></body></html>";
let url = format!("data:text/html,{}", urlencoding(html));
let script = format!(
r"
await page.goto({url});
await page.evaluate(() => {{
setTimeout(() => {{
const e = new Error('ctx-forwarded');
window.dispatchEvent(new ErrorEvent('error', {{ error: e, message: e.message }}));
throw e;
}}, 10);
}});
const deadline = Date.now() + 5000;
let match = null;
while (Date.now() < deadline) {{
const remaining = deadline - Date.now();
if (remaining <= 0) break;
const webErr = await context.waitForEvent('weberror', remaining);
// `webErr` is a WebError class instance — call .error() to
// retrieve the native JS Error.
const err = webErr && typeof webErr.error === 'function' ? webErr.error() : null;
if (err && err.message && err.message.indexOf('ctx-forwarded') !== -1) {{
match = {{
webErrorHasErrorMethod: typeof webErr.error === 'function',
errorIsError: err instanceof Error,
name: err.name,
message: err.message,
}};
break;
}}
}}
return match;
",
url = serde_json::to_string(&url).unwrap()
);
let v = c.script_value(&script);
assert!(!v.is_null(), "expected a weberror with 'ctx-forwarded' message: {v}");
assert_eq!(
v["webErrorHasErrorMethod"].as_bool(),
Some(true),
"context.waitForEvent('weberror') should resolve to a class with `.error()`: {v}"
);
assert_eq!(
v["errorIsError"].as_bool(),
Some(true),
"webError.error() should return a native JS Error: {v}"
);
assert_eq!(
v["name"].as_str(),
Some("Error"),
"webError.error().name should be 'Error': {v}"
);
assert!(
v["message"].as_str().unwrap_or("").contains("ctx-forwarded"),
"webError.error().message should contain 'ctx-forwarded': {v}"
);
}