f7a720204a
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
167 lines
7.6 KiB
Rust
167 lines
7.6 KiB
Rust
//! **P2 D2:navigate settle 升级端到端集成**(`#[ignore]`,本机/打包 chrome)。
|
||
//!
|
||
//! 验证 D2 的成熟导航判定(DESIGN §12 + 裁决⑤):
|
||
//! - 普通页 navigate → load_state 达 Load/NetworkIdle(视页面是否真静默)。
|
||
//! - networkidle 永不空闲页(长轮询)→ networkidle 短 cap(~4s)降级返 Load,**不卡 30s**。
|
||
//! - SPA history.pushState 软导航 → navigatedWithinDocument 降级判定(不重新等 load)。
|
||
//! - 真站(HTTP)→ http_status==200 填充;redirect 用 URL-normalize 不误判 trailing-slash。
|
||
//!
|
||
//! 复用 `tests/common` 的 `build_backend_for_fixture`(勿再复制契约母本)。
|
||
//!
|
||
//! 手动跑(本机 Windows 有系统 Chrome):
|
||
//! set NOMIFUN_CHROME_BINARY=...\chrome.exe
|
||
//! cargo nextest run -p nomi-browser-engine --run-ignored all -E 'test(nav)'
|
||
//! 跑完核对任务管理器无残留 chrome(Builder kill_on_drop 应自动清)。
|
||
//!
|
||
//! 真实结果(本机首跑会 eprintln 出 load_state/http_status/耗时——填回任务汇报)。
|
||
|
||
use std::time::Instant;
|
||
|
||
use nomi_browser_engine::{BrowserEngine, LoadState};
|
||
|
||
mod common;
|
||
|
||
/// 普通 file:// 页 navigate:settle 阶梯走通,load_state 达 Load(file:// 无 HTTP 故 http_status
|
||
/// 可能为 None),耗时远小于 30s 总超时。act-c1.html 是静态页(脚本只挂事件,无持续网络)→ 应在
|
||
/// networkidle 短 cap 内达到 NetworkIdle,或至少 Load。
|
||
#[tokio::test]
|
||
#[ignore = "需本机/打包 chrome:set NOMIFUN_CHROME_BINARY 后 --run-ignored all"]
|
||
async fn nav_normal_page_reaches_load_or_networkidle() {
|
||
let backend = common::build_backend_for_fixture("nav-normal").await;
|
||
|
||
let t0 = Instant::now();
|
||
let nav = backend
|
||
.navigate(&common::fixture_url("act-c1.html"), false)
|
||
.await
|
||
.expect("navigate act-c1.html");
|
||
let elapsed = t0.elapsed();
|
||
|
||
eprintln!(
|
||
"=== nav_normal_page === final_url={} http_status={:?} redirected={} load_state={} elapsed={:?}",
|
||
nav.final_url, nav.http_status, nav.redirected, nav.load_state, elapsed
|
||
);
|
||
|
||
// 静态页:应达 Load 或 NetworkIdle(不会停在 commit/DCL——本页极快)。
|
||
assert!(
|
||
matches!(nav.load_state, LoadState::Load | LoadState::NetworkIdle),
|
||
"static page should reach Load/NetworkIdle, got {}",
|
||
nav.load_state
|
||
);
|
||
// file:// 自身不算 redirect(归一化比较:请求 url == final url)。
|
||
assert!(!nav.redirected, "file:// self-nav must not be flagged redirect");
|
||
// 整个 navigate 应远小于 30s(即便 networkidle cap 触发也只 +4s)。
|
||
assert!(
|
||
elapsed.as_secs() < 15,
|
||
"navigate took too long ({elapsed:?}); networkidle cap must not blow up nav timeout"
|
||
);
|
||
}
|
||
|
||
/// networkidle 永不空闲页(长轮询 fixture):networkidle 短 cap(~4s)到点降级返 Load,**绝不**卡到
|
||
/// 30s。这是裁决⑤ 的核心不变量——长轮询/SSE/WS 站永不 idle 也不能拖垮 navigate。
|
||
#[tokio::test]
|
||
#[ignore = "需本机/打包 chrome:set NOMIFUN_CHROME_BINARY 后 --run-ignored all"]
|
||
async fn nav_never_idle_page_degrades_to_load_within_cap() {
|
||
let backend = common::build_backend_for_fixture("nav-never-idle").await;
|
||
|
||
let t0 = Instant::now();
|
||
let nav = backend
|
||
.navigate(&common::fixture_url("never-idle.html"), false)
|
||
.await
|
||
.expect("navigate never-idle.html");
|
||
let elapsed = t0.elapsed();
|
||
|
||
eprintln!(
|
||
"=== nav_never_idle === load_state={} elapsed={:?} (networkidle cap degrade expected)",
|
||
nav.load_state, elapsed
|
||
);
|
||
|
||
// 永不空闲 → networkidle 等不到 → 降级返 Load(良性,不报错)。
|
||
assert_eq!(
|
||
nav.load_state,
|
||
LoadState::Load,
|
||
"never-idle page must degrade to Load (not NetworkIdle)"
|
||
);
|
||
// 关键不变量:cap 独立,总耗时 ~ load + 4s cap,远小于 30s。给宽松上限 20s 防慢机器 flaky,
|
||
// 但必须显著小于 30s 才能证明「cap 没并入 nav 超时」。
|
||
assert!(
|
||
elapsed.as_secs() < 20,
|
||
"networkidle cap must be independent of 30s nav timeout; elapsed={elapsed:?}"
|
||
);
|
||
}
|
||
|
||
/// SPA 软导航:本 fixture load 后自动 history.pushState 改 URL(navigatedWithinDocument,无新文档)。
|
||
/// navigate 应识别软导航降级路径(不重新等 load 超时),良性返回成功。
|
||
///
|
||
/// 注意:navigate 的初始文档 load 与之后的软导航是两件事——navigate 返回时通常已达 Load(初始文档
|
||
/// 的 load 先到)。本测试主要验证「不报错、final_url 反映软导航后的 URL(若软导航在 navigate 返回
|
||
/// 前发生)」+ 不卡死。软导航的「不重新等 load」降级路径在 run_settle 内(若软导航先于 load 到达)。
|
||
#[tokio::test]
|
||
#[ignore = "需本机/打包 chrome:set NOMIFUN_CHROME_BINARY 后 --run-ignored all"]
|
||
async fn nav_spa_soft_navigation_is_benign() {
|
||
let backend = common::build_backend_for_fixture("nav-spa").await;
|
||
|
||
let t0 = Instant::now();
|
||
let nav = backend
|
||
.navigate(&common::fixture_url("spa-softnav.html"), false)
|
||
.await
|
||
.expect("navigate spa-softnav.html");
|
||
let elapsed = t0.elapsed();
|
||
|
||
eprintln!(
|
||
"=== nav_spa === final_url={} load_state={} redirected={} elapsed={:?}",
|
||
nav.final_url, nav.load_state, nav.redirected, elapsed
|
||
);
|
||
|
||
// 软导航是良性态:navigate 成功(不 Err),不卡死。
|
||
assert!(
|
||
elapsed.as_secs() < 15,
|
||
"SPA nav must not hang on load timeout; elapsed={elapsed:?}"
|
||
);
|
||
// load_state 至少达 DOMContentLoaded(DOM 已构建);多数情况达 Load(初始文档 load 先到)。
|
||
assert!(
|
||
matches!(
|
||
nav.load_state,
|
||
LoadState::DomContentLoaded | LoadState::Load | LoadState::NetworkIdle
|
||
),
|
||
"SPA page should reach at least DOMContentLoaded, got {}",
|
||
nav.load_state
|
||
);
|
||
}
|
||
|
||
/// 真站(HTTP):http_status==200 填充 + redirect URL-normalize 不误判。仅当设了
|
||
/// `NOMIFUN_NAV_HTTP_TEST=1`(避免离线环境 flaky)才真跑——否则 eprintln 跳过原因后返回。
|
||
///
|
||
/// 用 https://example.com(稳定、无重定向、明确 200)。验证:
|
||
/// - http_status == Some(200)(D2 从主帧 Document responseReceived 取到)。
|
||
/// - example.com → example.com/(trailing-slash)归一化后**不**算 redirect(裸 != 会误报)。
|
||
#[tokio::test]
|
||
#[ignore = "需本机/打包 chrome + 网络:set NOMIFUN_CHROME_BINARY + NOMIFUN_NAV_HTTP_TEST=1 后 --run-ignored all"]
|
||
async fn nav_http_status_and_redirect_normalize() {
|
||
if std::env::var("NOMIFUN_NAV_HTTP_TEST").ok().as_deref() != Some("1") {
|
||
eprintln!("=== nav_http_status === SKIPPED (set NOMIFUN_NAV_HTTP_TEST=1 to run online)");
|
||
return;
|
||
}
|
||
let backend = common::build_backend_for_fixture("nav-http").await;
|
||
|
||
let t0 = Instant::now();
|
||
let nav = backend
|
||
.navigate("https://example.com", false)
|
||
.await
|
||
.expect("navigate example.com");
|
||
let elapsed = t0.elapsed();
|
||
|
||
eprintln!(
|
||
"=== nav_http_status === final_url={} http_status={:?} redirected={} load_state={} elapsed={:?}",
|
||
nav.final_url, nav.http_status, nav.redirected, nav.load_state, elapsed
|
||
);
|
||
|
||
assert_eq!(nav.http_status, Some(200), "example.com should return HTTP 200");
|
||
// example.com → example.com/(浏览器补 trailing slash):归一化比较**不**算 redirect。
|
||
assert!(
|
||
!nav.redirected,
|
||
"trailing-slash difference must not be flagged as redirect (URL-normalize), final_url={}",
|
||
nav.final_url
|
||
);
|
||
assert!(nav.final_url.contains("example.com"));
|
||
}
|