Squash of 55 work commits (Sep 1–12):0fd356dwindows: the vendored bindings are generated from a checked-in filter79882b5fabric: a photo or a live camera to a fitted sewing pattern1e93309AGENTS.md: designs stay local; no OS screenshots; focus and hidden-window lawsaa96dbdcargo-makepad wasm: package the bin target's wasm and create dirs before minifying14d0723cargo-makepad wasm: production packaging — strip, small profile LTO, optional binaryen, size report79e7526sqlite: a page-store seam — the file backend as before, an in-memory backend, and open_memory / open_with60ee978cargo-makepad: package artifacts carry a content hash so a re-upload is a new URL611c9ebcargo-makepad: production packaging stays off fat LTO; script VM under LTO investigatedab8febccargo-makepad: fonts packaged from the app's font manifestf6bbee9wasm bridge: shared memory asks for the 4 GiB wasm32 ceiling and steps down where the engine refusesfb1416dcargo-makepad: the threaded wasm module is linked with the 4 GiB wasm32 memory ceilingfd5d70ccargo-makepad: the app's own resources are packaged under its bin name, which is how self:// resolvesf51ca07workspace: the wasm interpreter's tests build at opt-level 1 — its own profile setting is ignored inside a workspace, and opt-level 0 overflowed the script eval stack942ff86sqlite: the browser store has one owner — its locks never wait on a clock82d0cfaweb path: the trace helper keeps its doc, the journal nonce steps a counter where there is no clock or pid6f8c08fweb-server: POST /api/crash stores crash reports in a rotating log on both servers106b38cwasm bridge: the imported memory honours the module's declared limits94b8726dj-pack: tracks in, stems through the hub, a site store snapshot outd9f04fcdj-pack: pack reads caches, never creates them; dry-run writes nothinge6a305cai-hub + dj-pack: a whole track fits a stems job; long tracks split into spansd1910b8web-server: a store snapshot's extensionless routes are served with the types the exporter recordedc1f7b53asset-client + dj-pack: a long description never rejects a snapshot; the packer writes one bounded line453197ddj-pack: analyse produces the beat grid, overview and loop-splat caches the demo cache ships9b4a3canetwork: every completion raises the UI signalfad1c49web server: audio and text files are served, and models/ is immutable like maps/569b4a7dj-pack: every CC BY and CC BY-SA version and the public domain mark are redistributable licencesdc9cce6workspace: no std clock on the web in any crate the web apps link — the last start-up worker death is gonec7639f0clippy: timed std waits (sleep, recv_timeout, wait_timeout, park_timeout) are disallowed — they read the std clock and panic on wasm workersd748753wasm bridge: the page environment carries js_worker_wait so the module links — the pool landing added the import for workers onlyec40fdbvj + widgets: double-click a knob or fader to reset it to its default — the Slider handles tap_count 2 and emits its normal Slide action; the deck controls carry their neutral defaults (pitch 0, gain/EQ/stems 1, filter centre, crossfader centre)fe23e06AGENTS.md: the execution policy — zero locking on the UI thread as one mechanism for native and wasm, no temporary threads (the pool), the standard operating flow (Codex codes, Fable designs and reviews, Grok tests), and the tweaker on Shift+F10e689aeaweb_server + geodata + route: live radar, wind and weather on makepad.nl/api — one bounded poller per feed, hourly, disk-backed cache served from an Arc snapshot (a restart never re-polls early), 503 warming until the first result, health reports ok/warming/unavailable with timestamps; KNMI key from --knmi-key-file, the documented anonymous open-data key otherwise; libs/geodata fetches through the platform HTTP client instead of shelling out to curl; the client retries 503 after 30 s and disables a layer only on 404cd33943web_server: radar and weather run on KNMI's documented anonymous key when no --knmi-key-file is given; without --live-cache the pollers keep an in-memory cache and say so once2f3e393web_server: a directory path without its trailing slash (/score) redirects to /score/ instead of 404, query preserved9a31d21flow-ui + widgets: a chosen model shows no node list, and a closed ComboBox shows the start of a long label 22b2c78 docs: streamline agent runbook and extract reference guides 6058284 terminal: add hostable session multiplexing via tools/screen 8a9345b tools: migrate Cargo.toml lookups to segment-path keys 8749b91 counter: keep app state across Splash reloads 3ffd485 tools: add agent launcher and AIHub node update and smoke scripts c33a9d3 screen: add bypass and resume menu options c40d234 AGENTS.md: current delegation hierarchy (Grok mechanical, Codex hard, Fable manages) 4184455 Workspace: scope lives at apps/scope (clone of makepad/scope) 27844c9 makepad arch usb builder dd967eb Workspace: drop nine members that are not in the repository 1961768 AGENTS.md: hierarchy 2026-09-11 — Fable builds, Codex reviews, Grok proves 9cd9f94 AGENTS.md: rendering is verified on the real GPU backend, headless is for logic tests only 5f53254 AGENTS.md: GPU proofs may run hidden; only the headless CPU backend is out e950b08 docs: app-remote — hidden GPU runs vs the simulated-GPU backend, measured grab cadence, remote hazards 9c94a77 arch: the platform plan names the simulated-GPU backend gpusim 3009257 micro_serde: serde_json-style JsonValue accessors, pretty printer, depth limit and strict parse 134cd76 gitignore: alternate target directories, root scratch dirs and stray logs are never source 60ba86e arch: the render node refs name platform/src/os/gpusim/mod.rs 1dc571a tools/arch_usb: Wi-Fi, Intel GPU firmware, the AI hub service and game-hardware udev rules on the Arch image; the WM session script picks the saved compositor GPU cc3b05a tools/arch_usb: a polkit rule lets the arch account start, stop and restart the WM service Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
277 lines
8.6 KiB
Markdown
277 lines
8.6 KiB
Markdown
# makepad_test Guide
|
|
|
|
This guide covers how to write, run, and debug UI tests with `makepad_test`.
|
|
|
|
## Authoring Model
|
|
|
|
Tests live beside the package they exercise, usually under `tests/`.
|
|
|
|
```text
|
|
examples/text_input/
|
|
├── Cargo.toml
|
|
├── src/main.rs
|
|
└── tests/ui.rs
|
|
```
|
|
|
|
`#[makepad_test]` is current-package oriented by default:
|
|
|
|
- `env!("CARGO_MANIFEST_DIR")` provides the package directory
|
|
- `env!("CARGO_PKG_NAME")` provides the package to run
|
|
|
|
That keeps the normal Rust workflow intact: add a dev-dependency, write `tests/*.rs`, and run `cargo test --release -p <package>`.
|
|
|
|
## Macro Behavior
|
|
|
|
`#[makepad_test]` expands to a normal `#[test]` wrapper that:
|
|
|
|
1. runs `cargo build --release -p <package>` from the package directory
|
|
2. selects the standalone executable from Cargo's compiler-artifact messages
|
|
3. launches that executable with `--remote`, hidden unless visible mode is requested
|
|
4. reads the owned PID and endpoint and waits for the app's first window
|
|
5. passes a `TestApp` into your test body
|
|
6. captures failure artifacts on returned errors or panics
|
|
7. requests `/gq`, falls back to `/quit` if needed, and confirms the owned child exits
|
|
|
|
Cargo's owning-workspace target is reused, including explicit `CARGO_TARGET_DIR`
|
|
overrides. No build target is forced beneath each package. Tests keep their
|
|
small artifact directories beneath the package directory.
|
|
|
|
Supported signatures:
|
|
|
|
```rust
|
|
#[makepad_test]
|
|
fn smoke(app: TestApp) {
|
|
// ...
|
|
}
|
|
|
|
#[makepad_test]
|
|
fn smoke(app: TestApp) -> Result<(), TestError> {
|
|
// ...
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Unsupported:
|
|
|
|
- async tests
|
|
- methods with `self`
|
|
- generic test functions
|
|
- macro arguments
|
|
|
|
## Runtime Defaults
|
|
|
|
The runtime is synchronous and serial-first:
|
|
|
|
- action timeout: `10s`
|
|
- poll interval: `50ms`
|
|
- artifacts: `<manifest_dir>/target/makepad_test/<package>/<test>/`
|
|
|
|
The runner serializes app sessions within each test executable. Use
|
|
`--test-threads=1` for predictable suite order. `MAKEPAD_TEST_PARALLEL=1` opts out
|
|
of the runner lock when a suite is designed for concurrent owned instances.
|
|
|
|
## Visible Mode and Configuration
|
|
|
|
The default is a hidden native window (`MAKEPAD_HIDE_WINDOWS=1`). To watch a
|
|
standalone test window without focusing it:
|
|
|
|
```bash
|
|
MAKEPAD_TEST_VISIBLE=1 cargo test --release -p makepad-example-text-input --test ui -- --test-threads=1
|
|
```
|
|
|
|
Visible mode uses the same owned-process transport and never connects to an
|
|
existing app or Studio session. The harness removes `MAKEPAD_FOCUS` from the
|
|
child's environment.
|
|
|
|
Pacing variables:
|
|
|
|
- `MAKEPAD_TEST_STARTUP_DELAY_MS=1000` waits after startup before the test starts
|
|
- `MAKEPAD_TEST_ACTION_DELAY_MS=750` waits after each interaction
|
|
- `MAKEPAD_TEST_KEEP_OPEN_MS=3000` pauses briefly before shutdown
|
|
|
|
For explicit configuration, construct `TestConfig::new` or
|
|
`TestConfig::current_package`, adjust it, and pass it to `run_with_config`.
|
|
`bin_name` selects a target in a package with several binaries; `app_args` adds
|
|
arguments after `--remote`; `visible` controls visibility. `env` supplies app
|
|
environment variables. Its `CARGO_TARGET_DIR`, when present, also applies to the
|
|
build. Otherwise Cargo's inherited environment and configuration apply.
|
|
|
|
The former `mount_name` and `listen_address` fields and
|
|
`MAKEPAD_TEST_STUDIO`/`MAKEPAD_TEST_STUDIO_MOUNT` settings no longer apply. There
|
|
is no hub or mount to configure.
|
|
|
|
## Selectors
|
|
|
|
Selectors are snapshot-based. They match structured widget state instead of only relying on geometry query strings.
|
|
|
|
Constructors:
|
|
|
|
- `Selector::all()`
|
|
- `Selector::id("widget_id")`
|
|
- `Selector::widget_type("TextInput")`
|
|
- `Selector::raw("text:hello")`
|
|
|
|
Builder filters:
|
|
|
|
- `.text_exact("...")`
|
|
- `.text_contains("...")`
|
|
- `.nth(index)`
|
|
- `.window("panel_window")`
|
|
- `.window_index(1)`
|
|
- `.any_window()`
|
|
|
|
Selectors default to the primary window. That keeps single-window tests terse while still allowing explicit multi-window targeting.
|
|
|
|
## Locators
|
|
|
|
`Locator` methods require exactly one visible match for interaction. That strictness is intentional: it keeps tests from silently clicking the wrong widget.
|
|
|
|
Common actions:
|
|
|
|
```rust
|
|
app.locator(Selector::id("panel_input"))
|
|
.wait_visible()
|
|
.fill("hello")
|
|
.wait_value("hello")
|
|
.press_key(KeyCode::ReturnKey);
|
|
```
|
|
|
|
Available interaction helpers:
|
|
|
|
- `click`
|
|
- `type_text`
|
|
- `fill`
|
|
- `clear`
|
|
- `press_key`
|
|
- `press_key_with_modifiers`
|
|
- `scroll`
|
|
- `drag_by`
|
|
|
|
Available waits and assertions:
|
|
|
|
- `wait_visible`
|
|
- `wait_hidden`
|
|
- `wait_count`
|
|
- `wait_text` / `assert_text`
|
|
- `wait_value` / `assert_value`
|
|
- `wait_checked` / `assert_checked`
|
|
- `wait_enabled` / `assert_enabled`
|
|
|
|
Inspection helpers:
|
|
|
|
- `snapshot()`
|
|
- `count()`
|
|
- `widget_snapshot()`
|
|
- `widget_dump()`
|
|
- `screenshot()`
|
|
- `wait_for_log_contains(...)`
|
|
|
|
Lower-level escape hatch:
|
|
|
|
```rust
|
|
app.forward(vec![/* pointer, scroll, key, or text StudioToApp events */]);
|
|
```
|
|
|
|
`forward` translates supported input events to HTTP input routes. Other legacy
|
|
protocol variants return an explicit error. Native timestamps are assigned at
|
|
injection; key repeat and IME metadata are not forwarded. It does not provide live reload,
|
|
window resizing, swapchains, clipboard forwarding, or hub control. Prefer the
|
|
regular `TestApp` methods for snapshots, grabs, and logs.
|
|
|
|
## Structured Widget State
|
|
|
|
Each snapshot record exposes:
|
|
|
|
- widget id
|
|
- widget type
|
|
- bounds
|
|
- window id and window index
|
|
- visible/enabled state
|
|
- widget-specific state when available:
|
|
- `text`
|
|
- `value`
|
|
- `checked`
|
|
- `selected`
|
|
|
|
Window names, enabled flags, and selections come from the actual widget
|
|
snapshot. Missing required fields fail decoding instead of fabricating state.
|
|
Optional state is absent when the widget does not expose it; empty labels may
|
|
omit `text`, while an empty input `value` remains an empty string.
|
|
|
|
Interactions require visible geometry. State reads prefer visible matches and
|
|
can fall back to a uniquely matched clipped widget, such as a label below a
|
|
scrolling page.
|
|
|
|
## Failure Artifacts
|
|
|
|
Failed tests write to:
|
|
|
|
```text
|
|
<manifest_dir>/target/makepad_test/<package>/<test>/
|
|
```
|
|
|
|
Builds retain stderr; launched sessions also retain app stdout/stderr and a
|
|
`shutdown.txt` record with the owned PID and shutdown result. Test-body failures
|
|
additionally capture:
|
|
|
|
- `failure.txt`
|
|
- `logs.txt`
|
|
- `widget-snapshot.json`
|
|
- `widget-tree.txt` or `widget-tree-error.txt`
|
|
- `failure-screenshot.png` or `failure-screenshot-error.txt`
|
|
|
|
If a capture step fails, the runtime writes a `*-error.txt` file instead of silently dropping the artifact.
|
|
|
|
## Running Tests
|
|
|
|
```bash
|
|
cargo test --release -p makepad-test
|
|
cargo test --release -p makepad-example-text-input --test ui -- --test-threads=1
|
|
```
|
|
|
|
## Standalone Transport and Ownership
|
|
|
|
The runtime uses the app's documented [HTTP remote surface](../../docs/agents/app-remote.md):
|
|
`/s`, `/snap`, `/d`, `/g`, `/log`, and input routes. Input requests wait for a
|
|
resulting frame. Rectangles are window-local layout points; do not apply DPI
|
|
conversion to clicks.
|
|
|
|
Screenshots are captured from the app's own drawable. Setting
|
|
`TestConfig::env["MAKEPAD_GPUSIM_DPI"]` to a positive number scales screenshots
|
|
to that pixel density for existing suites; it does not select a software
|
|
renderer or change the native window's DPI.
|
|
|
|
Cleanup first uses `/gq` to save a final frame and quit. If capture is unavailable
|
|
or the app remains alive, it sends `/quit`. A dropped response is not treated as
|
|
proof that the process failed to exit. The runner waits for its owned `Child`
|
|
and kills only that child after graceful shutdown times out. This cleanup also
|
|
runs after test panics. It never stops, replaces, or drives user-owned instances.
|
|
|
|
## Troubleshooting
|
|
|
|
If a test times out or fails to resolve a widget:
|
|
|
|
1. inspect `target/makepad_test/.../logs.txt`
|
|
2. inspect `widget-snapshot.json` for text/value/checked/selected state
|
|
3. inspect `widget-tree.txt` for the raw compact tree
|
|
4. verify the selector is scoped tightly enough
|
|
|
|
For startup failures, inspect `build-stderr.txt`, `app-stdout.txt`, and
|
|
`app-stderr.txt`. `shutdown.txt` records whether the child exited normally or
|
|
required the exact-PID fallback. For live diagnostics inside a test,
|
|
`app.pid()`, `app.remote_endpoint()`, and `app.grab_dir()` identify only that
|
|
owned instance.
|
|
|
|
A `closed by user` response is preserved as an error; the runner does not
|
|
relaunch a dismissed app. Grabs have a longer request timeout because the
|
|
backend must finish readback and PNG encoding.
|
|
|
|
## Current Limitations
|
|
|
|
- the macro targets its current package; explicit configuration can select another
|
|
- synchronous API only
|
|
- no visual diffing or trace viewer yet
|
|
- some complex widgets still need more structured state over time
|
|
|
|
The native runtime and lifecycle fixtures are currently validated on macOS.
|
|
Other platform backends may differ in capture support.
|