makepad/libs/makepad_test
Kevin Boos 0c38e3b081
makepad_test: let a suite choose parallelism and the tick pump (#1176)
Two knobs, both defaulting to exactly what happens today.

MAKEPAD_TEST_PARALLEL opts out of the global TEST_MUTEX. Every test
currently takes that lock for its whole body, so `--test-threads=N` has no
effect at all and there is nothing in the API that says so. Serial is the
right default — each test drives a whole app process, and oversubscribing
the machine makes timing-sensitive assertions flaky — but it should be the
suite's call.

MAKEPAD_TEST_PUMP_TICKS sets how many Ticks are forwarded before each
query. Each one costs the app a full rendered frame whenever anything is
dirty, so the hardcoded 3 is a 3x multiplier on the cost of every
`widget_snapshot()`, which is the single most common thing a test does.

Reporting the measurements honestly, from a 55-test suite downstream:

- Parallel at 4-way took it from 67 min to 11-20 min, but 2-3 tests failed
  per run and the SET changed between runs — load-induced, not specific
  tests. Useful for local iteration, not something to turn on by default,
  which is why it is opt-in and documented as such rather than flipped.
- PUMP_TICKS=1 measured 1.47x on a fixed 10-test slice with no failures,
  but broke one drag-and-drop test elsewhere in a way I could not explain,
  so treat it as a tuning knob to try rather than a free win.

The flakiness above is a property of tests that wait by counting polls: how
much wall clock and how many frames a poll buys both change under load. That
is worth fixing in the tests, not by keeping the lock.
2026-08-13 10:03:47 +02:00
..
macros ft makepad_test (#974) 2026-03-25 16:09:03 +01:00
src makepad_test: let a suite choose parallelism and the tick pump (#1176) 2026-08-13 10:03:47 +02:00
Cargo.toml ft makepad_test (#974) 2026-03-25 16:09:03 +01:00
GUIDE.md ft makepad_test (#974) 2026-03-25 16:09:03 +01:00
README.md ft makepad_test (#974) 2026-03-25 16:09:03 +01:00

makepad_test

makepad_test provides Rust-native UI regression tests for Makepad apps. Tests live next to the package they exercise, run through normal cargo test, and drive the app through the existing Studio protocol in headless mode.

Quick Start

Add this to the package under test:

[dev-dependencies]
makepad-test = { path = "../../libs/makepad_test", version = "0.1.0" }

Create an integration test:

use makepad_test::{makepad_test, Selector, TestApp};

#[makepad_test]
fn fill_and_submit(app: TestApp) {
    app.locator(Selector::id("input_singleline"))
        .wait_visible()
        .fill("hello")
        .wait_value("hello");
    app.press_return();
    app.locator(Selector::id("status_label"))
        .wait_text("Returned from singleline: \"hello\"");
}

Run a package-local suite with:

cargo test -p makepad-example-text-input --test ui -- --test-threads=1

Run the same test visibly inside a running Makepad Studio session with:

MAKEPAD_TEST_VISIBLE=1 cargo test -p makepad-example-counter --test ui -- --test-threads=1

Visible mode expects Studio to already be running at 127.0.0.1:8001. Set MAKEPAD_TEST_STUDIO=<ip:port> to override the address.

To make the run easy to watch inside Studio, add pacing:

MAKEPAD_TEST_VISIBLE=1 \
MAKEPAD_TEST_STARTUP_DELAY_MS=1000 \
MAKEPAD_TEST_ACTION_DELAY_MS=750 \
MAKEPAD_TEST_KEEP_OPEN_MS=3000 \
cargo test -p makepad-example-counter --test ui -- --test-threads=1

Run the curated repo UI suites serially on macOS with:

tools/run_ui_tests.sh

Surface Area

  • #[makepad_test] for current-package UI tests
  • TestApp for app-scoped input, waits, logs, screenshots, and raw protocol forwarding
  • Selector for structured snapshot matching
  • Locator for strict single-widget interaction and assertions

Structured selectors support:

  • Selector::all()
  • Selector::id("...")
  • Selector::widget_type("...")
  • Selector::raw("...")
  • builder filters: .text_exact(...), .text_contains(...), .nth(...), .window(...), .window_index(...), .any_window()

Common locator actions:

  • click, type_text, fill, clear
  • press_key, press_key_with_modifiers
  • scroll, drag_by

Common waits and assertions:

  • wait_visible, wait_hidden, wait_count
  • wait_text, wait_value, wait_checked, wait_enabled
  • assert_text, assert_value, assert_checked, assert_enabled

Inspection helpers:

  • widget_snapshot()
  • widget_dump()
  • screenshot()
  • wait_for_log_contains(...)

Failure Artifacts

Failed tests write artifacts under:

target/makepad_test/<package>/<test>/

The runtime captures:

  • failure.txt
  • logs.txt
  • widget-snapshot.json
  • widget-tree.txt or widget-tree-error.txt
  • failure-screenshot.png or failure-screenshot-error.txt

Current Constraints

  • synchronous API only
  • current-package targeting only
  • milestone-1 repo suite is validated on macOS first
  • no visual diffing or trace viewer yet

Guide

For the full authoring model, runtime behavior, and troubleshooting notes, see GUIDE.md.