You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Build the e2e test infrastructure: a BrowserHandle that drives the headless browser via the JSON protocol from Step 6, a local test server for fixtures, and a full suite of e2e tests covering navigation, tabs, and bookmarks.
Prerequisites
Step 6 (headless navigation pipeline with interactive JSON protocol)
File Structure
tests/
├── harness/
│ ├── mod.rs # BrowserHandle — spawns and drives headless browser
│ └── server.rs # Local HTTP test server for fixtures
├── fixtures/
│ ├── hello.html # Simple page
│ ├── titled.html # Page with specific <title>
│ └── large.html # ~100KB page for basic perf sanity
├── navigation.rs # Navigation e2e tests
├── tabs.rs # Tab lifecycle e2e tests
└── bookmarks.rs # Bookmark e2e tests
Parent: #2
Goal
Build the e2e test infrastructure: a
BrowserHandlethat drives the headless browser via the JSON protocol from Step 6, a local test server for fixtures, and a full suite of e2e tests covering navigation, tabs, and bookmarks.Prerequisites
File Structure
Implementation
BrowserHandle (
tests/harness/mod.rs)BrowserHandlestruct:BrowserHandle::spawn() -> Result<Self>:TempDirfor data isolationcargo run -p ie-shell -- --headless --allow-http --data-dir <tempdir>as child process--allow-httpis required because the test server is HTTPasync fn send_command(&mut self, cmd: serde_json::Value) -> Result<serde_json::Value>:send_command):async fn navigate(&mut self, url: &str) -> Result<NavigateResponse>{"cmd": "navigate", "url": "..."}, assertsok: true, returns parsed dataasync fn get_source(&mut self) -> Result<String>{"cmd": "get_source"}, returns source stringasync fn get_tabs(&mut self) -> Result<Vec<TabInfo>>{"cmd": "get_tabs"}, returns parsed tab listasync fn new_tab(&mut self) -> Result<TabId>{"cmd": "new_tab"}, returns new tab idasync fn close_tab(&mut self, id: TabId) -> Result<()>{"cmd": "close_tab", "id": N}async fn switch_tab(&mut self, id: TabId) -> Result<()>{"cmd": "switch_tab", "id": N}async fn go_back(&mut self) -> Result<serde_json::Value>{"cmd": "go_back"}, returns response (may be ok or error)async fn go_forward(&mut self) -> Result<serde_json::Value>{"cmd": "go_forward"}, returns response (may be ok or error)async fn bookmark_add(&mut self, url: &str, title: &str) -> Result<()>{"cmd": "bookmark_add", "url": "...", "title": "..."}async fn bookmark_list(&mut self) -> Result<Vec<BookmarkInfo>>{"cmd": "bookmark_list"}async fn quit(&mut self) -> Result<()>{"cmd": "quit"}, waits for process exitDropimpl:Response types for harness
NavigateResponse { status: u16, url: String }TabInfo { id: u64, url: Option<String>, title: String, state: String }BookmarkInfo { url: String, title: String }Test server (
tests/harness/server.rs)TestServerstruct:TestServer::start() -> Self:127.0.0.1:0tests/fixtures/based on URL path/hello.html→ read and returntests/fixtures/hello.htmlwithContent-Type: text/html/titled.html→ return the titled fixture/large.html→ return the large fixtureTestServer::start_with_handler(handler: impl Fn(Request) -> Response)— custom handler for special cases (redirects, slow responses, errors)TestServer::url(&self, path: &str) -> String— helper:format!("http://127.0.0.1:{}{}", self.addr.port(), path)Dropimpl: send shutdown signal, join handleTest fixtures
tests/fixtures/hello.html:tests/fixtures/titled.html:tests/fixtures/large.html:E2E Tests
Navigation tests (
tests/navigation.rs)test_navigate_and_get_source:/hello.htmlget_source()→ contains "Hello World"test_navigate_status_code:/hello.htmltest_navigate_404:/nonexistenttest_navigate_updates_url:/hello.htmlget_tabs()→ active tab URL matches server URLtest_navigate_redirect:/redirect→ 302 to/hello.html/redirectget_source()→ contains "Hello World" (followed the redirect)test_navigate_twice:/hello.html, verify source/titled.html, verify source changedtest_navigate_large_page:/large.htmlget_source()→ length matches fixturetest_navigate_invalid_url:ok: falsetest_navigate_connection_refused:http://127.0.0.1:1(nothing listening)ok: falsewith error messageNavigation history tests (
tests/navigation.rs)test_navigate_go_back:/hello.html(A), navigate to/titled.html(B)go_back()→ response ok, source is A's content ("Hello World")test_navigate_go_forward:go_back(),go_forward()test_go_back_at_start:go_back()→ error response (no back history)test_go_forward_at_end:go_forward()→ error response (no forward history)HTTPS-only tests (
tests/navigation.rs)test_https_only_blocks_http:--allow-httphttp://127.0.0.1:PORT/hello.htmlok: falsewith error about HTTP being blockedTab tests (
tests/tabs.rs)test_starts_with_one_tab:get_tabs()→ 1 tab, state is "blank"test_new_tab:new_tab(),get_tabs()→ 2 tabstest_close_tab:new_tab()(now 2),close_tab(id),get_tabs()→ 1 tabtest_close_nonexistent_tab:close_tab(9999)→ error responsetest_switch_tab:new_tab()→ tab 2.switch_tab(tab_1_id).get_tabs()→ verify tab 1 is now active.test_navigate_different_tabs:/hello.htmlnew_tab()→ tab 2/titled.htmlget_source()→ contains "Titled Page" (active is tab 2)switch_tab(tab_1_id),get_source()→ contains "Hello World"test_close_all_tabs:get_tabs()→ empty listBookmark tests (
tests/bookmarks.rs)test_bookmark_add_and_list:bookmark_add("https://example.com", "Example")bookmark_list()→ 1 bookmark with correct URL and titletest_bookmark_multiple:bookmark_list()→ 3 entriestest_bookmark_persists_across_restart:bookmark_list()→ bookmark still presenttest_bookmark_isolation:bookmark_list()→ emptyAcceptance Criteria
cargo test --test navigation— all navigation e2e tests passcargo test --test tabs— all tab e2e tests passcargo test --test bookmarks— all bookmark e2e tests passmise run testruns all tests (unit + integration + e2e)