leptos-browser-test 0.2.0

Leptos test-app launcher for browser-driven integration tests.
Documentation

leptos-browser-test

Leptos test-app launcher for browser-driven integration tests.

This crate owns the cargo leptos serve / cargo leptos watch process management for a local Leptos test app:

  • starts cargo-leptos for you, letting it serve your application
  • waits until the app is fully started
  • keeps a recent tail of stdout/stderr logs for failure diagnostics
  • provides a handle to the launched application
  • terminates the started cargo-leptos process gracefully when the handle is dropped, which in turn gracefully terminates the Leptos app
  • the handle provides .base_url(), conveniently telling you where (randomized port) the app is reachable

Test orchestration is intentionally left to the consumer. Take a look at browser-test, for a convenient Rust-native integration test runner using thirtyfour.

Installation

[dev-dependencies]
leptos-browser-test = "0.2.0"

cargo-leptos requirement

leptos-browser-test v0.2.0 sets LEPTOS_GRACEFUL_SHUTDOWN_TIMEOUT_SECS and LEPTOS_GRACEFUL_SHUTDOWN_UNIX_SIGNAL on the managed cargo-leptos child to drive the Leptos app's graceful-shutdown path on drop. These env vars require cargo-leptos PR #648, which has not yet shipped. Until it lands upstream, install cargo-leptos from here:

cargo install --locked --git https://github.com/lpotthast/cargo-leptos --branch graceful-shutdown-v2 cargo-leptos

Runtime requirements

LeptosTestApp terminates the managed cargo leptos process when it is dropped, using tokio_process_tools::TerminateOnDrop. This requires an active multithreaded Tokio runtime, so browser tests must use:

#[tokio::test(flavor = "multi_thread")]

Usage

Starting an app

use leptos_browser_test::{LeptosTestAppConfig, Report};

#[tokio::main]
async fn main() -> Result<(), Report> {
    let app = LeptosTestAppConfig::new("testing/test-app")
        .with_app_name("my test app")
        .start()
        .await
        .map_err(Report::into_dynamic)?;

    let url = app.base_url();
    // run tests...
    Ok(())
}

Report::into_dynamic erases the typed Report<LeptosBrowserTestError> into a generic Report so it composes with the test harness's own error type.

Running browser-test

Start the app, pass its base URL into the runner context, and let drop-based cleanup handle the app process after BrowserTestRunner::run(...) returns:

use browser_test::{BrowserTestRunner, BrowserTests};
use leptos_browser_test::{LeptosTestAppConfig, Report};

struct Context {
    base_url: String,
}

#[tokio::test(flavor = "multi_thread")]
async fn browser_tests() -> Result<(), Report> {
    let app = LeptosTestAppConfig::new("testing/test-app")
        .with_app_name("my test app")
        .start()
        .await
        .map_err(Report::into_dynamic)?;

    let context = Context {
        base_url: app.base_url().to_owned(),
    };

    BrowserTestRunner::new()
        .run(&context, BrowserTests::new().with(MyFirstTest))
        .await
        .map_err(Report::into_dynamic)?;

    Ok(())
}
pub struct MyFirstTest {}

#[async_trait]
impl BrowserTest<Context> for MyFirstTest {
    fn name(&self) -> Cow<'_, str> {
        "classes_tests".into()
    }

    async fn run(&self, driver: &WebDriver, ctx: &Context) -> Result<(), Report> {
        // TODO: Use `driver` to query the page and run assertions.
        Ok(())
    }
}

HTTPS

For apps served over HTTPS, override the URL scheme before starting:

use leptos_browser_test::{LeptosTestAppConfig, SiteScheme};

async fn example() -> Result<(), leptos_browser_test::Report> {
    let app = LeptosTestAppConfig::new("testing/test-app")
        .with_site_scheme(SiteScheme::Https)
        .start()
        .await
        .map_err(leptos_browser_test::Report::into_dynamic)?;
    let _ = app;
    Ok(())
}

SiteScheme only affects the base_url() returned to your harness. Configuring TLS on the served app itself is the app's responsibility.

Tuning graceful shutdown

By default, the managed Leptos app gets 10 seconds to shut down on drop and is signalled with SIGINT on Unix (always CTRL_BREAK_EVENT on Windows). Override this via:

use std::time::Duration;
use leptos_browser_test::{LeptosTestAppConfig, UnixGracefulSignal};

async fn example() -> Result<(), leptos_browser_test::Report> {
    let app = LeptosTestAppConfig::new("testing/test-app")
        .with_graceful_shutdown_timeout(Duration::from_secs(30))
        .with_graceful_shutdown_unix_signal(UnixGracefulSignal::Terminate)
        .start()
        .await
        .map_err(leptos_browser_test::Report::into_dynamic)?;
    let _ = app;
    Ok(())
}

The timeout is forwarded to cargo leptos via LEPTOS_GRACEFUL_SHUTDOWN_TIMEOUT_SECS, the signal via LEPTOS_GRACEFUL_SHUTDOWN_UNIX_SIGNAL. The signal selector is ignored on Windows.

Manual debugging

Run the integration-test target from the consuming crate with --nocapture so the managed app's forwarded stdout/stderr stays visible:

cargo test --test your_integration_test -- --nocapture