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.
This commit is contained in:
pj authored and GitHub committed 2026-04-18 16:51:35 +07:00
1 parent b457e22569
commit 16e55086d8
6 files changed
+101 -8

No files matched your search

@@ -3,14 +3,18 @@ package dev.uatu.sample
import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.Gravity
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
class MainActivity : Activity() {
companion object {
@Volatile var clickCount: Int = 0
@Volatile var username: String = ""
}
private lateinit var label: TextView
@@ -43,6 +47,29 @@ class MainActivity : Activity() {
}
layout.addView(button)
val usernameLabel = TextView(this).apply {
text = "Username: "
textSize = 20f
setTextColor(Color.BLACK)
gravity = Gravity.CENTER
}
layout.addView(usernameLabel)
val usernameField = EditText(this).apply {
hint = "username"
contentDescription = "username_field"
textSize = 18f
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
username = s?.toString() ?: ""
usernameLabel.text = "Username: $username"
}
})
}
layout.addView(usernameField)
setContentView(layout)
}
}
@@ -9,6 +9,7 @@ class SampleApplication : Application() {
Uatu.start(this)
Uatu.extract("app_state") { "running" }
Uatu.extract("click_count") { MainActivity.clickCount }
Uatu.extract("username") { MainActivity.username }
Uatu.extract("uptime_millis") { System.currentTimeMillis() - startedAt }
}
+17 -1
View File
@@ -4,6 +4,7 @@ import {
actions,
weighted,
Tap,
InputText,
taps,
swipes,
} from "@uatu/spec";
@@ -16,9 +17,13 @@ const appState = extract<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 = {
@@ -28,6 +33,10 @@ export const properties = {
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 ────────────────────────────────────────────────────
@@ -35,8 +44,15 @@ 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(
[100, tapClickMe],
[50, tapClickMe],
[50, typeUsername],
[10, taps],
[2, swipes],
);