From 6bc94e01ed2c9f7d414df1f285dd7cc3918ae81c Mon Sep 17 00:00:00 2001 From: "Shahar \"Dawn\" Or" Date: Sun, 7 Dec 2025 21:02:41 +0700 Subject: [PATCH] test: end-to-end Co-authored-by: turbio Co-authored-by: shivaraj-bh --- Cargo.lock | 274 ++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 3 + tests/end-to-end.rs | 126 ++++++++++++++++++++ 3 files changed, 395 insertions(+), 8 deletions(-) create mode 100644 tests/end-to-end.rs diff --git a/Cargo.lock b/Cargo.lock index b5392b0..e506c42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,29 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "async-tungstenite" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" +dependencies = [ + "atomic-waker", + "futures-core", + "futures-io", + "futures-task", + "futures-util", + "log", + "pin-project-lite", + "tokio", + "tungstenite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -183,6 +206,9 @@ name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -196,6 +222,73 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chromiumoxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c18200611490f523adb497ddd4744d6d536e243f6add13e7eeeb1c05904fbb1" +dependencies = [ + "async-tungstenite", + "base64", + "bytes", + "cfg-if", + "chromiumoxide_cdp", + "chromiumoxide_types", + "dunce", + "fnv", + "futures", + "futures-timer", + "pin-project-lite", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", + "url", + "which", + "windows-registry", +] + +[[package]] +name = "chromiumoxide_cdp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f78027ced540595dcbaf9e2f3413cbe3708b839ff239d2858acaea73915dcb" +dependencies = [ + "chromiumoxide_pdl", + "chromiumoxide_types", + "serde", + "serde_json", +] + +[[package]] +name = "chromiumoxide_pdl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d2c7b7c6b41a0de36d00a284e619017e0f4aec5c9bc8d90614b9e1687984f20" +dependencies = [ + "chromiumoxide_types", + "either", + "heck 0.4.1", + "once_cell", + "proc-macro2", + "quote", + "regex", + "serde", + "serde_json", +] + +[[package]] +name = "chromiumoxide_types" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309ba8f378bbc093c93f06beb7bd4c5ceffdf14107ad99cacbbf063709926795" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "clap" version = "4.5.53" @@ -224,7 +317,7 @@ version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -368,12 +461,24 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "env_logger" version = "0.10.2" @@ -398,6 +503,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -408,6 +523,12 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "file-id" version = "0.2.3" @@ -512,6 +633,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -540,6 +667,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "get-port" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "888123007db34fbff15b5a347d46364dfbad531d6cb43de52cc0b62558f570e2" + [[package]] name = "getrandom" version = "0.2.16" @@ -588,6 +721,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -793,7 +932,7 @@ checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" dependencies = [ "hermit-abi", "io-lifetimes", - "rustix", + "rustix 0.36.8", "windows-sys 0.45.0", ] @@ -854,14 +993,22 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "live-server" version = "0.11.0" dependencies = [ "axum", + "chromiumoxide", "clap", "env_logger", "futures", + "get-port", "ignore", "local-ip-address", "log", @@ -871,6 +1018,7 @@ dependencies = [ "open", "path-absolutize", "reqwest", + "tempfile", "tokio", ] @@ -882,7 +1030,7 @@ checksum = "786c72d9739fc316a7acf9b22d9c2794ac9cb91074e9668feb04304ab7219783" dependencies = [ "libc", "neli", - "thiserror", + "thiserror 2.0.12", "windows-sys 0.61.2", ] @@ -1297,13 +1445,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags 1.3.2", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.4", "windows-sys 0.45.0", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.7.0", + "errno 0.3.14", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.22.4" @@ -1531,6 +1692,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1540,13 +1714,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1678,9 +1872,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -1709,7 +1915,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror", + "thiserror 2.0.12", "utf-8", ] @@ -1903,6 +2109,17 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix 1.1.2", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1934,12 +2151,47 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -1991,7 +2243,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2246,6 +2498,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index ca8f3c2..77dae54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,7 @@ ignore = "0.4.25" path-absolutize = "3.1.1" [dev-dependencies] +chromiumoxide = "0.8.0" +get-port = "4.0.0" reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls"] } +tempfile = "3.23.0" diff --git a/tests/end-to-end.rs b/tests/end-to-end.rs new file mode 100644 index 0000000..e2ced58 --- /dev/null +++ b/tests/end-to-end.rs @@ -0,0 +1,126 @@ +use std::{ + collections::BTreeSet, + ffi::OsStr, + path::{Path, PathBuf}, + sync::{LazyLock, Mutex}, + time::Duration, +}; + +use chromiumoxide::{ + Browser, BrowserConfig, cdp::browser_protocol::page::EventFrameStoppedLoading, +}; +use futures::StreamExt as _; +use get_port::{Ops, tcp::TcpPort}; +use tempfile::{self, TempDir, tempdir}; +use tokio::{ + fs, + process::{Child, Command}, +}; + +async fn with_timeout( + future: impl Future, +) -> Result { + tokio::time::timeout(Duration::from_secs(6), future).await +} + +fn subject_with(args: &[impl AsRef]) -> (Child, String) { + static TAKEN_PORTS: LazyLock>> = + LazyLock::new(|| Mutex::new(BTreeSet::new())); + + const HOST: &str = "127.0.0.1"; + let mut taken_ports = TAKEN_PORTS.lock().unwrap(); + let port = TcpPort::except(HOST, taken_ports.iter().copied().collect()).unwrap(); + taken_ports.insert(port); + + let subject = Command::new(env!("CARGO_BIN_EXE_live-server")) + .kill_on_drop(true) + .args(["--host", HOST]) + .args(["--port", &port.to_string()]) + .args(args) + .spawn() + .unwrap(); + + (subject, format!("{HOST}:{port}")) +} + +async fn fresh_browser() -> (Browser, TempDir) { + let data_dir = tempdir().unwrap(); + let (browser, handler) = Browser::launch( + BrowserConfig::builder() + .user_data_dir(data_dir.path()) + .build() + .unwrap(), + ) + .await + .unwrap(); + tokio::spawn(async move { + handler.for_each(async |_| {}).await; + }); + (browser, data_dir) +} + +fn index_with(title: &str) -> String { + format!( + r#"{title}"# + ) +} + +async fn fixture_with(title: &str) -> TempDir { + let dir = tempdir().unwrap(); + let index_path: PathBuf = [dir.path(), Path::new("index.html")].iter().collect(); + fs::write(&index_path, index_with(title)).await.unwrap(); + dir +} + +#[tokio::test] +async fn page_content_is_served() { + let fixture = fixture_with("some page").await; + let (_subject, authority) = subject_with(&[fixture.path()]); + let (browser, _browser_dir) = fresh_browser().await; + let title = browser + .new_page(format!("http://{authority}/")) + .await + .unwrap() + .wait_for_navigation() + .await + .unwrap() + .get_title() + .await + .unwrap() + .unwrap(); + assert_eq!(title, "some page"); +} + +#[tokio::test] +async fn browser_reloads_on_file_change() { + let fixture = fixture_with("initial").await; + let (_subject, authority) = subject_with(&[fixture.path()]); + let (browser, _browser_dir) = fresh_browser().await; + + let page = browser + .new_page(format!("http://{authority}/")) + .await + .unwrap(); + + page.wait_for_navigation().await.unwrap(); + let title = page.get_title().await.unwrap().unwrap(); + assert_eq!(title, "initial"); + + let mut frame_stopped_loading_event_stream = page + .event_listener::() + .await + .unwrap(); + + let index_path = [fixture.path(), Path::new("index.html")] + .iter() + .collect::(); + fs::write(index_path, index_with("modified")).await.unwrap(); + + with_timeout(frame_stopped_loading_event_stream.next()) + .await + .unwrap() + .unwrap(); + + let title = page.get_title().await.unwrap().unwrap(); + assert_eq!(title, "modified"); +}