Files
sanderling/examples/sample-app/spec.ts
T
pj 16e55086d8 fix(runner): surface focus-tap errors in InputText (#12)
* fix(runner): surface focus-tap errors in InputText action

A failed Tap/TapSelector before InputText was swallowed, so text typed
into the wrong field (or no field) still reported success. Return the
error so the step fails explicitly.

* feat(sample-app): add username EditText and snapshot

Gives the spec a real EditText target (content-desc: username_field)
so the InputText action path can be exercised end-to-end. The typed
value is mirrored into MainActivity.username and surfaced as the
"username" snapshot for spec assertions.

* feat(sample-app): exercise InputText action against username field

Adds typeUsername action and usernameNeverShrinks property to the
sample spec, and extends the integration test to assert the bundled
spec emits an InputText(desc:username_field, "alice") action and that
the property correctly violates when a snapshot reports a shorter
string.
2026-04-18 16:51:35 +07:00

62 lines
2.2 KiB
TypeScript

import {
extract,
always,
actions,
weighted,
Tap,
InputText,
taps,
swipes,
} from "@uatu/spec";
// ── Snapshot extractors (fed by SampleApplication.kt) ──────────
// See ./android/src/main/kotlin/dev/uatu/sample/SampleApplication.kt
const appState = extract<string>(
(state) => (state.snapshots.app_state as string) ?? "",
);
const clickCount = extract<number>(
(state) => (state.snapshots.click_count as number) ?? 0,
);
const username = extract<string>(
(state) => (state.snapshots.username as string) ?? "",
);
// ── UI elements ────────────────────────────────────────────────
const clickButton = extract((state) => state.ax.find("text:Click me"));
const usernameField = extract((state) => state.ax.find("desc:username_field"));
// ── Properties ─────────────────────────────────────────────────
export const properties = {
appIsRunning: always(() => appState.current === "running"),
clickCountNonNegative: always(() => clickCount.current >= 0),
clickCountNeverDecreases: always(() => {
const previous = clickCount.previous;
return previous === undefined || clickCount.current >= previous;
}),
usernameNeverShrinks: always(() => {
const previous = username.previous;
return previous === undefined || username.current.length >= previous.length;
}),
};
// ── Actions ────────────────────────────────────────────────────
const tapClickMe = actions(() => {
return clickButton.current ? [Tap({ on: clickButton.current })] : [];
});
const typeUsername = actions(() => {
return usernameField.current
? [InputText({ into: usernameField.current, text: "alice" })]
: [];
});
export const actionsRoot = weighted(
[50, tapClickMe],
[50, typeUsername],
[10, taps],
[2, swipes],
);
(globalThis as { actions?: unknown; properties?: unknown }).actions = actionsRoot;
(globalThis as { properties?: unknown }).properties = properties;