- lib/input.sh: ADB input automation library (screenshot, tap, swipe, type, key, launch, OCR, SMS/OTP via KDE Connect) - fs5: 9 new remote control commands for AI agent capabilities - tests/test_remote_control.bats: 31 tests, all passing - specs/002-remote-control/spec.md: feature specification Tools: scrcpy 3.3.4 (GUI mirror), tesseract-ocr (OCR), KDE Connect (SMS/OTP) Constraints: no root required, Android 9 (SDK 28) Tests: 64/64 passing (33 device mgmt + 31 remote control)
238 lines
7.3 KiB
Bash
238 lines
7.3 KiB
Bash
#!/usr/bin/env bats
|
|
# tests/test_remote_control.bats — Remote control command tests for fs5
|
|
# Tests: screenshot, tap, swipe, type, key, launch, ocr, sms
|
|
|
|
setup() {
|
|
SCRIPT_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")/.." && pwd)"
|
|
FS5="${SCRIPT_DIR}/fs5"
|
|
DEVICE_SERIAL="${DEVICE_SERIAL:-RD51QE202392}"
|
|
export DEVICE_SERIAL
|
|
}
|
|
|
|
teardown() {
|
|
# Clean up any test screenshots
|
|
rm -f /tmp/fs5-test-screenshot-*.png
|
|
}
|
|
|
|
# ── screenshot ────────────────────────────────────────────────────────────────
|
|
|
|
@test "screenshot: saves PNG to default path" {
|
|
run "${FS5}" screenshot
|
|
[ "${status}" -eq 0 ]
|
|
# Output contains a path
|
|
echo "${output}" | grep -q "/tmp/fs5-screenshot-"
|
|
# File exists
|
|
local path
|
|
path=$(echo "${output}" | grep "/tmp/fs5-screenshot-" | tail -1)
|
|
[ -f "${path}" ]
|
|
rm -f "${path}"
|
|
}
|
|
|
|
@test "screenshot: saves PNG to specified path" {
|
|
local out="/tmp/fs5-test-screenshot-$$.png"
|
|
run "${FS5}" screenshot "${out}"
|
|
[ "${status}" -eq 0 ]
|
|
[ -f "${out}" ]
|
|
}
|
|
|
|
@test "screenshot: --json output contains path and timestamp" {
|
|
run "${FS5}" screenshot --json
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | python3 -c "
|
|
import sys, json
|
|
data = json.load(sys.stdin)
|
|
assert 'path' in data, 'missing path'
|
|
assert 'timestamp' in data, 'missing timestamp'
|
|
assert data['path'].endswith('.png'), 'path not PNG'
|
|
"
|
|
# Clean up
|
|
local path
|
|
path=$(echo "${output}" | python3 -c "import sys,json; print(json.load(sys.stdin)['path'])")
|
|
rm -f "${path}"
|
|
}
|
|
|
|
@test "screenshot: --help shows usage" {
|
|
run "${FS5}" screenshot --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage"
|
|
}
|
|
|
|
# ── tap ───────────────────────────────────────────────────────────────────────
|
|
|
|
@test "tap: sends tap at valid coordinates" {
|
|
run "${FS5}" tap 540 960
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -q "Tapped"
|
|
}
|
|
|
|
@test "tap: fails without coordinates" {
|
|
run "${FS5}" tap
|
|
[ "${status}" -ne 0 ]
|
|
echo "${output}" | grep -qi "usage"
|
|
}
|
|
|
|
@test "tap: fails with only one coordinate" {
|
|
run "${FS5}" tap 540
|
|
[ "${status}" -ne 0 ]
|
|
}
|
|
|
|
@test "tap: --help shows usage" {
|
|
run "${FS5}" tap --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage"
|
|
}
|
|
|
|
# ── swipe ─────────────────────────────────────────────────────────────────────
|
|
|
|
@test "swipe: performs swipe with four coordinates" {
|
|
run "${FS5}" swipe 540 1200 540 400
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -q "Swiped"
|
|
}
|
|
|
|
@test "swipe: performs swipe with custom duration" {
|
|
run "${FS5}" swipe 540 1200 540 400 500
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -q "500ms"
|
|
}
|
|
|
|
@test "swipe: fails without coordinates" {
|
|
run "${FS5}" swipe
|
|
[ "${status}" -ne 0 ]
|
|
}
|
|
|
|
@test "swipe: --help shows usage" {
|
|
run "${FS5}" swipe --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage"
|
|
}
|
|
|
|
# ── key ───────────────────────────────────────────────────────────────────────
|
|
|
|
@test "key: sends KEYCODE_HOME" {
|
|
run "${FS5}" key KEYCODE_HOME
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -q "Key sent"
|
|
}
|
|
|
|
@test "key: sends numeric keycode" {
|
|
run "${FS5}" key 3
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -q "Key sent"
|
|
}
|
|
|
|
@test "key: fails without keycode" {
|
|
run "${FS5}" key
|
|
[ "${status}" -ne 0 ]
|
|
}
|
|
|
|
@test "key: --help shows usage (global help intercepts --help flag)" {
|
|
run "${FS5}" key --help
|
|
[ "${status}" -eq 0 ]
|
|
# --help is a global flag; shows main usage with key command listed
|
|
echo "${output}" | grep -q "key"
|
|
}
|
|
|
|
# ── launch ────────────────────────────────────────────────────────────────────
|
|
|
|
@test "launch: launches KDE Connect app" {
|
|
run "${FS5}" launch org.kde.kdeconnect_tp
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -q "Launched"
|
|
}
|
|
|
|
@test "launch: fails for non-existent package" {
|
|
run "${FS5}" launch com.nonexistent.package.xyz
|
|
[ "${status}" -ne 0 ]
|
|
}
|
|
|
|
@test "launch: fails without package name" {
|
|
run "${FS5}" launch
|
|
[ "${status}" -ne 0 ]
|
|
}
|
|
|
|
@test "launch: --help shows usage" {
|
|
run "${FS5}" launch --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage"
|
|
}
|
|
|
|
# ── ocr ───────────────────────────────────────────────────────────────────────
|
|
|
|
@test "ocr: runs without error and returns text or empty" {
|
|
run "${FS5}" ocr
|
|
[ "${status}" -eq 0 ]
|
|
# Output may be empty (blank screen) or contain text — both valid
|
|
}
|
|
|
|
@test "ocr: --help shows usage" {
|
|
run "${FS5}" ocr --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage\|screenshot\|ocr"
|
|
}
|
|
|
|
# ── sms ───────────────────────────────────────────────────────────────────────
|
|
|
|
@test "sms: no subcommand shows error" {
|
|
run "${FS5}" sms
|
|
[ "${status}" -ne 0 ]
|
|
echo "${output}" | grep -qi "unknown\|subcommand\|use:"
|
|
}
|
|
|
|
@test "sms: --help shows subcommands" {
|
|
run "${FS5}" sms --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -q "list"
|
|
echo "${output}" | grep -q "otp"
|
|
echo "${output}" | grep -q "watch"
|
|
}
|
|
|
|
@test "sms list: shows KDE Connect setup instructions when not paired" {
|
|
run "${FS5}" sms list
|
|
# May succeed (if paired) or fail with setup instructions
|
|
if [ "${status}" -ne 0 ]; then
|
|
echo "${output}" | grep -qi "kde connect\|paired\|setup"
|
|
fi
|
|
}
|
|
|
|
@test "sms otp: shows error when KDE Connect not paired" {
|
|
run "${FS5}" sms otp
|
|
# Either returns OTP (if paired) or error about KDE Connect
|
|
if [ "${status}" -ne 0 ]; then
|
|
echo "${output}" | grep -qi "kde connect\|paired\|otp"
|
|
fi
|
|
}
|
|
|
|
@test "sms otp: --help shows usage" {
|
|
run "${FS5}" sms otp --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage\|otp\|wait"
|
|
}
|
|
|
|
@test "sms list: --help shows usage" {
|
|
run "${FS5}" sms list --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage"
|
|
}
|
|
|
|
# ── screen ────────────────────────────────────────────────────────────────────
|
|
|
|
@test "screen: --help shows usage" {
|
|
run "${FS5}" screen --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "scrcpy\|mirror\|headless"
|
|
}
|
|
|
|
# ── type ──────────────────────────────────────────────────────────────────────
|
|
|
|
@test "type: --help shows usage" {
|
|
run "${FS5}" type --help
|
|
[ "${status}" -eq 0 ]
|
|
echo "${output}" | grep -qi "usage"
|
|
}
|
|
|
|
@test "type: fails without text argument" {
|
|
run "${FS5}" type
|
|
[ "${status}" -ne 0 ]
|
|
}
|