diff --git a/.config/forest.dic b/.config/forest.dic index f87d3e1e..62ee0b85 100644 --- a/.config/forest.dic +++ b/.config/forest.dic @@ -63,6 +63,7 @@ FIP FVM GC GiB +Grafana HAMT hasher healthcheck @@ -73,6 +74,7 @@ ip IPLD JSON JWT +k6 Kademlia Kubernetes Leptos diff --git a/.github/workflows/test.yml b/.github/workflows/e2e.yml similarity index 66% rename from .github/workflows/test.yml rename to .github/workflows/e2e.yml index b5790ae3..c752e906 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/e2e.yml @@ -1,4 +1,4 @@ -name: Test +name: E2E Test concurrency: group: "${{ github.workflow }}-${{ github.ref }}" @@ -20,11 +20,13 @@ env: RUSTC_WRAPPER: sccache CC: sccache clang CXX: sccache clang++ + FAUCET_TOPUP_REQ_URL: '${{ vars.FAUCET_TOPUP_REQ_URL }}' + FAUCET_TX_URL_CALIBNET: '${{ vars.FAUCET_TX_URL_CALIBNET }}' + FAUCET_TX_URL_MAINNET: '${{ vars.FAUCET_TX_URL_MAINNET }}' jobs: e2e: runs-on: ubuntu-latest - steps: - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 @@ -49,9 +51,16 @@ jobs: - name: Install worker-build run: cargo install --locked worker-build - - uses: nanasess/setup-chromedriver@v2 - - run: | - chromedriver --port=9515 & + - name: Set up k6 (with browser) + uses: grafana/setup-k6-action@v1 + with: + browser: true + + - name: Set up secrets + shell: bash + run: | + echo "SECRET_WALLET=${{ secrets.TEST_CALIBNET_PRIVATE_KEY_HEX }}" > .dev.vars + echo "SECRET_MAINNET_WALLET=${{ secrets.TEST_MAINNET_PRIVATE_KEY_HEX }}" >> .dev.vars - name: Run website run: | @@ -62,8 +71,7 @@ jobs: echo "waiting" timeout 120 sh -c 'until nc -z $0 $1; do sleep 1; done' 127.0.0.1 8787 - - name: E2E - run: | - cd e2e - cargo build - cargo run + - name: Run k6 E2E script + uses: grafana/run-k6-action@v1 + with: + path: 'e2e/script.js' diff --git a/Cargo.lock b/Cargo.lock index f4be31e8..45309422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,12 +70,6 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - [[package]] name = "arrayref" version = "0.3.9" @@ -363,7 +357,7 @@ dependencies = [ "group", "hkdf", "pairing", - "rand_core 0.6.4", + "rand_core", "rayon", "sha2 0.9.9", "subtle", @@ -380,7 +374,7 @@ dependencies = [ "ff", "group", "pairing", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -432,12 +426,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.41" @@ -865,17 +853,6 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" -[[package]] -name = "e2e" -version = "0.1.0" -dependencies = [ - "anyhow", - "reqwest", - "thirtyfour", - "tokio", - "url", -] - [[package]] name = "either" version = "1.15.0" @@ -957,7 +934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "bitvec", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -1210,11 +1187,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", ] [[package]] @@ -1276,7 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -1484,7 +1459,6 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -1830,7 +1804,7 @@ dependencies = [ "oco_ref", "or_poisoned", "paste", - "rand 0.8.5", + "rand", "reactive_graph 0.2.0", "rustc-hash", "rustc_version", @@ -2163,7 +2137,7 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.8.5", + "rand", "serde", "sha2 0.9.9", "typenum", @@ -2729,60 +2703,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quinn" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.12", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" -dependencies = [ - "bytes", - "getrandom 0.3.2", - "rand 0.9.1", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.12", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "quote" version = "1.0.40" @@ -2833,18 +2753,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_chacha", + "rand_core", ] [[package]] @@ -2854,17 +2764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core", ] [[package]] @@ -2876,15 +2776,6 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.2", -] - [[package]] name = "rayon" version = "1.10.0" @@ -3071,10 +2962,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", "rustls-pemfile", - "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", @@ -3082,14 +2970,12 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", "windows-registry", ] @@ -3172,7 +3058,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -3193,9 +3078,6 @@ name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" -dependencies = [ - "web-time", -] [[package]] name = "rustls-webpki" @@ -3363,7 +3245,6 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap", "itoa", "memchr", "ryu", @@ -3636,15 +3517,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - [[package]] name = "slab" version = "0.4.9" @@ -3691,15 +3563,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stringmatch" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aadc0801d92f0cdc26127c67c4b8766284f52a5ba22894f285e3101fa57d05d" -dependencies = [ - "regex", -] - [[package]] name = "strobe-rs" version = "0.10.0" @@ -3888,45 +3751,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "thirtyfour" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d1dd5271076f9cf5cb6a673497d1476cda2633e6cf2523edd00ac16218d651" -dependencies = [ - "arc-swap", - "async-trait", - "base64", - "bytes", - "cfg-if", - "const_format", - "futures-util", - "http", - "indexmap", - "paste", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "stringmatch", - "thirtyfour-macros", - "thiserror 1.0.69", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "thirtyfour-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf0ffc3ba4368e99597bd6afd83f4ff6febad66d9ae541ab46e697d32285fc0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -4026,21 +3850,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.45.1" @@ -4051,25 +3860,11 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", - "tokio-macros", "windows-sys 0.52.0", ] -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -4185,21 +3980,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "tracing-core" version = "0.1.33" @@ -4498,25 +4281,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.26.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 0fce5856..9fcdb2d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,6 @@ authors = ["Forest Team "] [lib] crate-type = ["cdylib"] -[workspace] -members = ["e2e"] -resolver = "2" - [dependencies] anyhow = "1" axum = { version = "0.8", default-features = false, optional = true } diff --git a/README.md b/README.md index 35f38c71..037322fc 100644 --- a/README.md +++ b/README.md @@ -105,3 +105,17 @@ secrets**, so they are attached to the correct worker: ```bash npx wrangler@latest secret put MY_SECRET --name $(git rev-parse --short HEAD) ``` + +## End-to-End Testing + +### Prerequisites + +- **Grafana k6:** Install k6 via the + [Official Grafana k6 Installation Guide](https://grafana.com/docs/k6/latest/set-up/install-k6/) +- **Faucet:** Start the faucet at `http://localhost:8787` before running tests. + +### Run Tests + +```bash +k6 run e2e/script.js +``` diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml deleted file mode 100644 index aed9b8f0..00000000 --- a/e2e/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "e2e" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1.0.98" -reqwest = "0.12.15" -thirtyfour = "0.35" -tokio = { version = "1", features = ["full"] } -url = "2.5.4" diff --git a/e2e/script.js b/e2e/script.js new file mode 100644 index 00000000..703b426d --- /dev/null +++ b/e2e/script.js @@ -0,0 +1,150 @@ +import { browser } from "k6/browser"; +import { check } from "k6"; + +export const options = { + scenarios: { + ui: { + executor: "shared-iterations", + options: { + browser: { + type: "chromium", + }, + }, + }, + }, + thresholds: { + checks: ["rate==1.0"], + }, +}; + +const BASE_URL = "http://127.0.0.1:8787"; + +// Check if the path is reachable +async function checkPath(page, path) { + const res = await page.goto(`${BASE_URL}${path}`); + check(res, { [`GET ${path} → 200`]: (r) => r && r.status() === 200 }); +} + +// Check if the button exists, is visible, and is enabled +async function checkButton(page, path, buttonText) { + const buttons = await page.$$("button"); + let btn = null; + for (const b of buttons) { + const text = await b.evaluate((el) => el.textContent.trim()); + if (text === buttonText) { + btn = b; + break; + } + } + + // Check if the button exists + const exists = btn !== null; + const existenceMsg = `Button "${buttonText}" on "${path}" ${exists ? "exists" : "does not exist"}`; + check(exists, { [existenceMsg]: () => exists }); + if (!exists) { + return; + } + + // Check if the button is visible + // Note: In some cases, the button might exist but not be visible + const isVisible = await btn.isVisible(); + check(isVisible, { + [`Button "${buttonText}" on "${path}" is visible`]: () => isVisible, + }); + + // Check if the button is enabled + // Note: In some cases, the button might be visible but not enabled + const isEnabled = await btn.isEnabled(); + check(isEnabled, { + [`Button "${buttonText}" on "${path}" is enabled`]: () => isEnabled, + }); +} + +// Check if the link exists, is visible, and has a valid href +async function checkLink(page, path, linkText) { + const links = await page.$$("a"); + let link = null; + for (const l of links) { + const text = await l.evaluate((el) => el.textContent.trim()); + if (text === linkText) { + link = l; + break; + } + } + + // Check if the link exists + const exists = link !== null; + const existenceMsg = `Link "${linkText}" on "${path}" ${exists ? "exists" : "does not exist"}`; + check(exists, { [existenceMsg]: () => exists }); + if (!exists) { + return; + } + + // Check if the link is visible + // Note: In some cases, the link might exist but not be visible + const isVisible = await link.isVisible(); + check(isVisible, { + [`Link "${linkText}" on "${path}" is visible`]: () => isVisible, + }); + + // Check if the link is enabled + // Note: In some cases, the link might be visible but not enabled + const href = await link.evaluate((el) => el.getAttribute("href")); + const hasHref = Boolean(href && href.trim()); + check(hasHref, { + [`Link "${linkText}" on "${path}" has valid href`]: () => hasHref, + }); +} + +async function checkFooter(page, path) { + await checkLink(page, path, "Forest Explorer"); + await checkLink(page, path, "ChainSafe Systems"); +} + +const PAGES = [ + { + path: "", + buttons: ["To faucet list"], + links: ["Filecoin Slack", "documentation"], + }, + { + path: "/faucet", + links: ["Calibration Network Faucet", "Mainnet Network Faucet"], + }, + { + path: "/faucet/calibnet", + buttons: ["Back to faucet list", "Transaction History", "Send"], + }, + { + path: "/faucet/mainnet", + buttons: ["Back to faucet list", "Transaction History", "Send"], + }, +]; + +// Loops through each page config, performing: +// - checkPath +// - checkButton +// - checkLink +// - checkFooter +async function runChecks(page) { + for (const { path, buttons = [], links = [] } of PAGES) { + await checkPath(page, path); + await page.goto(`${BASE_URL}${path}`, { waitUntil: "networkidle" }); + for (const btn of buttons) { + await checkButton(page, path, btn); + } + for (const lnk of links) { + await checkLink(page, path, lnk); + } + await checkFooter(page, path); + } +} + +export default async function () { + const page = await browser.newPage(); + try { + await runChecks(page); + } finally { + await page.close(); + } +} diff --git a/e2e/src/main.rs b/e2e/src/main.rs deleted file mode 100644 index 3f3450a3..00000000 --- a/e2e/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -use anyhow::{ensure, Context as _}; -use thirtyfour::prelude::*; -use url::Url; - -pub static WEBPAGE_URL: &str = "http://127.0.0.1:8787"; -pub static CHROME_DRIVER_URL: &str = "http://127.0.0.1:9515"; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let mut cap = DesiredCapabilities::chrome(); - cap.set_headless()?; - let chrome = WebDriver::new(CHROME_DRIVER_URL, cap).await?; - chrome.goto(WEBPAGE_URL).await?; - assert_faucet_reachable().await?; - - Ok(()) -} - -/// Checks if various pages of the faucet are reachable. It uses the `reqwest` library underneath, -/// given the `WebDriver` is not able to check the status of the page (the page would return 500 and -/// `WebDriver` would not report an issue). -async fn assert_faucet_reachable() -> anyhow::Result<()> { - let faucet_pages = ["/faucet", "/faucet/mainnet", "/faucet/calibnet"]; - let mut unreachable = Vec::new(); - for page in faucet_pages { - let url = Url::parse(WEBPAGE_URL)?.join(page)?; - let response = reqwest::get(url) - .await - .context("Failed to reach {page} endpoint")?; - if !response.status().is_success() { - unreachable.push(page); - } - } - - ensure!( - unreachable.is_empty(), - "The following faucet pages are unreachable: {:?}", - unreachable, - ); - Ok(()) -}