use browser_oxide::Page;
async fn evaluate(js: &str) -> String {
let mut page = Page::from_html(
"<!DOCTYPE html><html><body></body></html>",
None::<browser_oxide::stealth::StealthProfile>,
)
.await
.unwrap();
page.evaluate(js).unwrap_or_else(|e| format!("ERROR: {e}"))
}
async fn evaluate_secure(js: &str) -> String {
let mut page = Page::from_html_with_url(
"<!DOCTYPE html><html><body></body></html>",
"https://example.com/",
None::<browser_oxide::stealth::StealthProfile>,
)
.await
.unwrap();
page.evaluate(js).unwrap_or_else(|e| format!("ERROR: {e}"))
}
#[tokio::test]
async fn parity_eval_to_string_length() {
let r = evaluate("eval.toString().length").await;
assert_eq!(r, "33", "Chrome 147: eval.toString().length === 33");
}
#[tokio::test]
async fn parity_eval_to_string_exact() {
let r = evaluate("eval.toString()").await;
assert_eq!(
r, "function eval() { [native code] }",
"Chrome 147: eval.toString() === 'function eval() {{ [native code] }}'"
);
}
#[tokio::test]
#[allow(non_snake_case, reason = "mirrors JS API name under test")]
async fn parity_function_toString_self_native() {
let r = evaluate("Function.prototype.toString.call(Function.prototype.toString)").await;
assert_eq!(
r, "function toString() { [native code] }",
"Chrome 147: Function.prototype.toString.toString() === 'function toString() {{ [native code] }}'"
);
}
#[tokio::test]
async fn parity_math_sin_to_string() {
let r = evaluate("Function.prototype.toString.call(Math.sin)").await;
assert_eq!(r, "function sin() { [native code] }");
}
#[tokio::test]
async fn parity_math_cos_13e() {
let r = evaluate("Math.cos(13 * Math.E)").await;
assert_eq!(
r, "-0.7108118501064332",
"Math.cos(13*Math.E) must match Chrome 147 V8 byte-for-byte"
);
}
#[tokio::test]
async fn parity_math_cos_57e() {
let r = evaluate("Math.cos(57 * Math.E)").await;
assert_eq!(r, "-0.536911695749024");
}
#[tokio::test]
async fn parity_math_acos_0_123() {
let r = evaluate("Math.acos(0.123)").await;
assert_eq!(r, "1.4474840516030247");
}
#[tokio::test]
async fn parity_math_acosh_1e308() {
let r = evaluate("Math.acosh(1e308)").await;
assert_eq!(r, "709.889355822726");
}
#[tokio::test]
async fn parity_math_atan_2() {
let r = evaluate("Math.atan(2)").await;
assert_eq!(r, "1.1071487177940904");
}
#[tokio::test]
async fn parity_math_tan_neg1e300() {
let r = evaluate("Math.tan(-1e300)").await;
assert_eq!(r, "-1.4214488238747245");
}
#[tokio::test]
async fn parity_rect_width_1_3px() {
let r = evaluate(
"
const d = document.createElement('div');
d.style.cssText = 'width:1.3px;height:0.5px;position:absolute;';
document.body.appendChild(d);
const w = d.getBoundingClientRect().width;
// Either exact match (1.296875) or a multiple of 1/64 close enough
Number.isInteger(Math.round(w * 64))
",
)
.await;
assert_eq!(
r, "true",
"rect width must be a multiple of 1/64 px (LayoutUnit)"
);
}
#[tokio::test]
async fn parity_rect_height_0_5px() {
let r = evaluate(
"
const d = document.createElement('div');
d.style.cssText = 'width:1.3px;height:0.5px;position:absolute;';
document.body.appendChild(d);
const h = d.getBoundingClientRect().height;
Number.isInteger(Math.round(h * 64))
",
)
.await;
assert_eq!(r, "true");
}
#[tokio::test]
#[ignore = "not yet implemented: per-realm constructor identity for iframe contexts"]
async fn parity_iframe_navigator_distinct() {
let r = evaluate(
"(()=>{ const f = document.createElement('iframe'); document.body.appendChild(f); return f.contentWindow.Navigator !== Navigator; })()",
)
.await;
assert_eq!(r, "true", "Chrome 147 returns true; engine must match");
}
#[tokio::test]
async fn parity_iframe_array_distinct() {
let r = evaluate(
"(()=>{ const f = document.createElement('iframe'); document.body.appendChild(f); return f.contentWindow.Array !== Array; })()",
)
.await;
assert_eq!(r, "true");
}
#[tokio::test]
async fn parity_iframe_array_instanceof_is_false() {
let r = evaluate(
"(()=>{ const f = document.createElement('iframe'); document.body.appendChild(f); return ([] instanceof f.contentWindow.Array); })()",
)
.await;
assert_eq!(
r, "false",
"Chrome 147 returns false; cross-realm instanceof must be false"
);
}
#[tokio::test]
#[allow(non_snake_case, reason = "mirrors JS API name under test")]
async fn parity_iframe_function_toString_native() {
let r = evaluate(
"(()=>{ const f = document.createElement('iframe'); document.body.appendChild(f); return f.contentWindow.Function.prototype.toString.call(window.fetch).includes('[native code]'); })()",
)
.await;
assert_eq!(r, "true");
}
#[tokio::test]
async fn parity_navigator_vendor() {
let r = evaluate("navigator.vendor").await;
assert_eq!(r, "Google Inc.");
}
#[tokio::test]
async fn parity_navigator_product_sub() {
let r = evaluate("navigator.productSub").await;
assert_eq!(r, "20030107");
}
#[tokio::test]
async fn parity_navigator_product() {
let r = evaluate("navigator.product").await;
assert_eq!(r, "Gecko");
}
#[tokio::test]
async fn parity_navigator_app_code_name() {
let r = evaluate("navigator.appCodeName").await;
assert_eq!(r, "Mozilla");
}
#[tokio::test]
async fn parity_navigator_webdriver_false() {
let r = evaluate("navigator.webdriver").await;
assert_eq!(r, "false");
}
#[tokio::test]
async fn parity_chrome_app_exists() {
let r = evaluate("typeof chrome !== 'undefined' && typeof chrome.app === 'object'").await;
assert_eq!(r, "true");
}
#[tokio::test]
async fn parity_chrome_runtime_absent_like_user_session() {
let r = evaluate("typeof chrome !== 'undefined' && typeof chrome.runtime").await;
assert_eq!(
r, "undefined",
"chrome.runtime must be absent (presence is automation-mode tell)"
);
}
#[tokio::test]
async fn parity_chrome_app_own_property_names() {
let r = evaluate("Object.getOwnPropertyNames(chrome.app).sort().join(',')").await;
assert_eq!(
r,
"InstallState,RunningState,getDetails,getIsInstalled,installState,isInstalled,runningState"
);
}
#[tokio::test]
async fn parity_chrome_app_get_details_returns_null() {
let r = evaluate("chrome.app.getDetails()").await;
assert_eq!(r, "null", "getDetails() off-CWS must return null");
}
#[tokio::test]
async fn parity_chrome_app_get_is_installed_returns_false() {
let r = evaluate("chrome.app.getIsInstalled()").await;
assert_eq!(r, "false");
}
#[tokio::test]
async fn parity_chrome_app_running_state_returns_cannot_run() {
let r = evaluate("chrome.app.runningState()").await;
assert_eq!(r, "cannot_run");
}
#[tokio::test]
async fn parity_chrome_app_install_state_async_callback_fires() {
let mut page = Page::from_html(
"<!DOCTYPE html><html><body></body></html>",
None::<browser_oxide::stealth::StealthProfile>,
)
.await
.unwrap();
page.evaluate(
r#"window.__installStateResult = null;
chrome.app.installState(s => { window.__installStateResult = s; });"#,
)
.unwrap();
page.evaluate_async("void 0", std::time::Duration::from_millis(200))
.await
.ok();
let r = page.evaluate("window.__installStateResult").unwrap();
assert_eq!(
r, "not_installed",
"installState callback must fire with 'not_installed'"
);
}
#[tokio::test]
async fn parity_chrome_app_install_state_dict() {
let r = evaluate(
"JSON.stringify([chrome.app.InstallState.DISABLED, chrome.app.InstallState.INSTALLED, chrome.app.InstallState.NOT_INSTALLED])",
)
.await;
assert_eq!(r, "[\"disabled\",\"installed\",\"not_installed\"]");
}
#[tokio::test]
async fn parity_chrome_app_running_state_dict() {
let r = evaluate(
"JSON.stringify([chrome.app.RunningState.CANNOT_RUN, chrome.app.RunningState.READY_TO_RUN, chrome.app.RunningState.RUNNING])",
)
.await;
assert_eq!(r, "[\"cannot_run\",\"ready_to_run\",\"running\"]");
}
#[tokio::test]
async fn parity_notification_permission() {
let r = evaluate_secure("Notification.permission").await;
assert_eq!(r, "default");
}
#[tokio::test]
async fn parity_promise_with_resolvers() {
let r = evaluate("typeof Promise.withResolvers").await;
assert_eq!(r, "function");
}
#[tokio::test]
async fn parity_structured_clone() {
let r = evaluate("typeof structuredClone").await;
assert_eq!(r, "function");
}
#[tokio::test]
async fn parity_globals_count_in_range() {
let r = evaluate("Object.getOwnPropertyNames(globalThis).length").await;
let n: usize = r.parse().unwrap_or(0);
assert!(
n >= 200,
"globals count must be ≥ 200 (real Chrome 147 headless: 980); got {n}"
);
}