browser_tester 1.5.0

Deterministic lightweight browser runtime for Rust tests
Documentation

browser-tester

A deterministic browser-like testing crate implemented entirely in Rust.

Purpose

  • Run DOM and script tests deterministically in a single process.
  • Test browser-like interactions without an external browser, WebDriver, or Node.js.
  • Keep time, randomness, and test-only browser APIs under Rust-side control.

Quick Start

use browser_tester::Harness;

fn main() -> browser_tester::Result<()> {
    let html = r#"
      <input id='name' />
      <button id='submit'>Submit</button>
      <p id='result'></p>
      <script>
        document.getElementById('submit').addEventListener('click', () => {
          const name = document.getElementById('name').value;
          document.getElementById('result').textContent = `Hello, ${name}`;
        });
      </script>
    "#;

    let mut harness = Harness::from_html(html)?;
    harness.type_text("#name", "Alice")?;
    harness.click("#submit")?;
    harness.assert_text("#result", "Hello, Alice")?;
    Ok(())
}

Core API

Constructors:

  • Harness::from_html(html)
  • Harness::from_html_with_url(url, html)
  • Harness::from_html_with_local_storage(html, &[("key", "value"), ...])
  • Harness::from_html_with_url_and_local_storage(url, html, &[("key", "value"), ...])

Actions:

  • type_text
  • set_select_value
  • set_input_files
  • set_checked
  • click
  • press_enter
  • copy
  • paste
  • cut
  • focus
  • blur
  • submit
  • dispatch
  • dispatch_keyboard

Assertions:

  • assert_text
  • assert_value
  • assert_checked
  • assert_exists
  • dump_dom

Time and scheduling:

  • now_ms
  • advance_time
  • advance_time_to
  • run_due_timers
  • run_next_timer
  • run_next_due_timer
  • flush
  • pending_timers
  • clear_timer
  • clear_all_timers

Trace and diagnostics:

  • enable_trace
  • take_trace_logs
  • set_trace_stderr
  • set_trace_events
  • set_trace_timers
  • set_trace_log_limit

Test Mocks

Main mock families:

  • fetch
  • confirm / prompt / alert
  • window.print()
  • location and history-backed mock pages
  • navigator.clipboard
  • localStorage seed state
  • download capture
  • input[type="file"]
  • matchMedia

Full API details and examples are in doc/mock-guide.md.

Minimal fetch mock example:

use browser_tester::Harness;

fn main() -> browser_tester::Result<()> {
    let html = r#"
      <button id='run'>run</button>
      <p id='out'></p>
      <script>
        document.getElementById('run').addEventListener('click', () => {
          fetch('https://app.local/api/message')
            .then((res) => res.text())
            .then((text) => {
              document.getElementById('out').textContent = text;
            });
        });
      </script>
    "#;

    let mut h = Harness::from_html(html)?;
    h.set_fetch_mock("https://app.local/api/message", "hello");
    h.click("#run")?;
    h.assert_text("#out", "hello")?;
    assert_eq!(
        h.take_fetch_calls(),
        vec!["https://app.local/api/message".to_string()]
    );
    Ok(())
}

Runtime Constraints

  • eval is intentionally not implemented.
  • Date.now() and performance.now() are backed by a fake clock.
  • Math.random() is deterministic and can be reseeded with set_random_seed.
  • Real network I/O is not the target. fetch should be used with mocks.
  • Rendering, layout, style application, and the accessibility tree are intentionally partial.
  • This crate provides a deterministic browser-like subset, not full browser compatibility.

Form submission policy:

  • Harness::submit(selector) follows a user-like submission path.
  • Script-side form.requestSubmit([submitter]) also follows the user-like path.
  • Script-side form.submit() bypasses validation and does not dispatch submit.

Running Tests

Run the main test suite:

cargo test

Run by test layer:

scripts/run-test-layer.sh contract
scripts/run-test-layer.sh subsystem dom_navigation_dialog
scripts/run-test-layer.sh integration issue_174_175
scripts/run-test-layer.sh fuzz

Run repository maintenance guardrails:

scripts/check-file-size-guard.sh

Regular integration tests are grouped under a single integration_suite target to reduce Cargo's per-file test crate and link overhead:

cargo test --test integration_suite issue_174_175

Minimal public contract coverage for stable Harness APIs:

cargo test --test contract_harness_core

If you only want to confirm the contract target still builds:

scripts/run-test-layer.sh contract-build

Property and fuzz tests for parser and runtime:

# default: parser=256 cases, runtime=128 cases
cargo test --test parser_property_fuzz_test --test runtime_property_fuzz_test -- --nocapture

# quick profile
BROWSER_TESTER_PROPTEST_CASES=64 \
BROWSER_TESTER_RUNTIME_PROPTEST_CASES=64 \
cargo test --test parser_property_fuzz_test --test runtime_property_fuzz_test

# deep profile
BROWSER_TESTER_PROPTEST_CASES=1024 \
BROWSER_TESTER_RUNTIME_PROPTEST_CASES=512 \
cargo test --test parser_property_fuzz_test --test runtime_property_fuzz_test
  • BROWSER_TESTER_PROPTEST_CASES: default parser-oriented case count
  • BROWSER_TESTER_RUNTIME_PROPTEST_CASES: runtime action case count
  • shrunk failing seeds are stored in:
    • tests/proptest-regressions/parser_property_fuzz_test.txt
    • tests/proptest-regressions/runtime_property_fuzz_test.txt

Documentation