Documentation Application testing
Testing an application#
blitsen/test drives a packaged desktop export from Node or Bun without a display
server, GPU, or OS input injection. Build the application with blitsen build,
then launch the executable from a test:
import { launch } from 'blitsen/test';
const app = await launch('./MyApp', {
width: 1180,
height: 780,
env: { APP_TEST_DATA: '/tmp/test-data' },
artifactsDir: './test-results',
});
try {
await app.waitFor(() => document.querySelector('form'));
await app.click({ role: 'textbox', name: 'Local folder' });
await app.type('/path/to/repository');
await app.wheel({ x: 400, y: 500, deltaY: 500 });
await app.click({ role: 'button', name: 'Save repository' });
await app.assert(() => document.querySelector('[data-saved]') !== null);
} finally {
await app.close();
}The application runs in its own Bun process, including when the test driver
is Bun. Use the executable inside a macOS .app, or the exported .exe on
Windows. During
runtime development, launch(runtimePath, { args: [builtDirectory] }) runs a
built directory through the same path. CI runs the packaged React acceptance
fixture with both drivers on Linux, macOS and Windows (bun run --cwd
packages/blitsen test:ui).
Input enters through the native host's retained pointer, keyboard and IME callbacks. Pointer commands hit-test coordinates against the live, flushed layout; they do not dispatch to the element returned by a query. The application loader, DOM, focus on mousedown, click activation, editing, default scrolling, asynchronous handoff and layout are shared with native windows. Presentation uses Blitz's CPU rasterizer, replacing the OS window and GPU surface. This does not test the platform compositor, native dialogs or OS input translation.
click(locator) requires exactly one enabled element whose box center is in the
viewport and is not covered. It sends pointer move/down/up at that center, with a
frame turn between them. Scroll explicitly before clicking below the fold. A
locator can be a CSS selector, { selector, text }, or { role, name }. Text
matches normalize whitespace. Names recognize aria-labelledby, aria-label,
labels, element text, image alt text and button values. Roles cover explicit
role attributes and common HTML controls; this is not a complete accessibility
tree or implementation of the accessible-name specification.
query(locator) returns detached JSON snapshots: layout boxes in window CSS
pixels, device pixel ratio, viewport size, disabled/value/checked state, focus,
and scroll offsets. The headless surface currently uses device pixel ratio 1.
evaluate(expression) evaluates an assertion expression in the live document
and returns its JSON value. Expressions and predicates must be synchronous;
use waitFor to observe asynchronous work. Functions passed to evaluate, assert and
waitFor are serialized into that process and cannot capture driver variables.
Inspection through query is read-only; assertion scripts are trusted test code
and can modify the document.
For lower-level input use pointer('pointerdown', { x, y, button, ...modifiers }),
pointer('pointerup', ...), key('keydown', { key, code, ...modifiers }), and
key('keyup', ...). type(text) commits text through the native IME path into
the focused control. Pointer buttons use DOM numbering (primary 0, auxiliary 1,
secondary 2); modifiers are ctrlKey, shiftKey, altKey, and metaKey.
Wheel deltas are CSS pixels, positive down/right.
settle(ms = 50) advances timers, microtasks, native completions, animation frames
and layout for a bounded duration. It does not promise network idleness or wait
for repeating timers to disappear. Use waitFor(predicate, { timeout }) for
application-specific readiness. The process continues frame turns while waiting
for the next driver command. Commands time out and terminate the child instead
of allowing a late action to affect another step. Always close the session.
screenshot(path?) returns PNG bytes and optionally writes them.
eventLog() returns and drains the last 1,000 dispatched events, including type,
target, coordinates and defaultPrevented after listeners. Failed interaction
and assertion steps attach screenshot and events to their Error; supplying
artifactsDir also writes a PNG and JSON log. screenshots: 'steps' additionally
records successful interaction and assertion steps into that directory.
launch explicitly sets BLITSEN_TEST_MODE=1 on the child process. Only this
mode reads the private stdin command protocol; normal launches install no test
injection or inspection globals. Test mode supplies an in-memory window fake
before startup scripts run. Size changes update the live viewport; fullscreen,
decoration and maximization setters round-trip their state. Other window actions
are no-ops. File/folder dialogs resolve to null; message dialogs resolve to
'cancel'. No native dialog opens. Application environment and storage are real;
set isolated data directories in env when a test needs separate state.
Document-script checks#
BLITSEN_STANDALONE_CHECK=1 ./MyApp remains a lightweight document-script harness
on Bun exports. It loads scripts, advances asynchronous
work, optionally evaluates BLITSEN_STANDALONE_CHECK_SCRIPT and
BLITSEN_STANDALONE_CHECK_ASSERT, and renders a frame. It does not prove a
native click, hit testing, focus, or scroll-dependent interaction. CLI help and
each check's stderr state this limitation.
Window and dialog members are absent in this mode, allowing feature detection:
import windowApi from 'blitsen/window';
import dialog from 'blitsen/dialog';
if (windowApi.setDecorations) windowApi.setDecorations(false);
if (dialog.openFolder) await dialog.openFolder();element.click() and dispatched MouseEvent('click', ...) run activation on
both hosts: submit buttons submit, checkables preactivate before listeners and
restore their state on cancellation, and labels forward clicks. Native clicks
use that same dispatch algorithm. Other script-dispatched input-like events do
not run native default actions; a document check warns once per event type.
Dispatching submit directly proves only the submit handler. Use the UI driver
to prove the interaction that reaches it.