Files
castarr/tests/smoke.sh
be-nj 4cb7a6efdb
All checks were successful
Build TV app / build (push) Successful in 43s
Act on the screen-by-screen review: 17 findings
Defects. "Kopplung zurücksetzen" now asks first, naming how many phones
would have to scan again — one stray OK used to wipe a working pairing, and
the settings put the focus right on it: entering the screen landed on the
gear, one press down jumped into the right column onto that very action, and
every further press down did nothing. Focus now starts on the first row of
the left column. 1. FC Köln was labelled "EFC". An unreachable source used
to look like an evening without programmes — cached list, empty EPG, club
menus counting down to zero — so both repositories now record when they last
came through, and the top bar says "Server nicht erreichbar · Stand 14:46".
The field labels on "Erweitert" sat on the outline with the border running
through them; they sit above their field now.

Polish. The app opens on the channel list — on the channel last watched,
which survives a restart — instead of the settings gear. Rows without EPG
fill their empty half with the channel's group. The club chip says "ab
15:06" like the row next to it, the club menu carries the club's name
instead of "FCSP", and a fixture title is shortened to the fixture, so
"Pokal: Nordstadt - FC St. P…" became "Nordstadt - FC St. Pauli".
The settings are three columns and no longer scroll (CONTEXT.md's ten-foot
rule).

Features. Favourites work for a plain M3U source too, stored on the device
and mirrored to the phone; they used to exist only with a Dispatcharr login.
The channel keys jump the list by initial letter, left and right jump a
division in the club picker, and right on a channel opens its day plan —
the EPG beyond now/next, which was parsed all along and never shown. When a
club match starts elsewhere the player offers to switch. The remote asked
for a code "unten auf dem TV-Bildschirm" where none was; the welcome screen
shows it now and the wording points at where it is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 15:54:00 +02:00

173 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Smoke test on the headless Google TV emulator.
#
# Builds the debug APK, seeds the demo playlist as its source, installs it
# and walks from the channel list into the player with the D-pad. Fails on
# anything the unit tests cannot see: a crash on startup, a crash while
# navigating, an ANR, or a channel list that stayed empty. The 0.11.0
# startup crash and the rail crash on "Verein hinzufügen" would both have
# been caught here.
#
# tests/smoke.sh # build, run, leave the emulator up
# tests/smoke.sh --apk <path> # skip the build, test this APK
# tests/smoke.sh --stop # stop the emulator when done
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
HELPERS="$ROOT/tests/helpers"
RUNS="$ROOT/tests/runs"
SERIAL="${CASTARR_SERIAL:-emulator-5554}"
ADB="${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}/platform-tools/adb"
PKG="dev.castarr.tv"
DEMO_PORT="${CASTARR_DEMO_PORT:-8099}"
ACTIVITY="$PKG/.MainActivity"
APK=""
STOP_AFTER=0
while [ $# -gt 0 ]; do
case "$1" in
--apk) APK="$2"; shift 2 ;;
--stop) STOP_AFTER=1; shift ;;
*) echo "usage: $0 [--apk <path>] [--stop]" >&2; exit 2 ;;
esac
done
mkdir -p "$RUNS/screenshots"
LOG="$RUNS/smoke.log"
: > "$LOG"
# The player retries the fake demo stream for as long as it is on screen, so
# without this the request log grows by a few hundred KB per run.
: > "$RUNS/http.log"
step() { printf '\n== %s\n' "$1" | tee -a "$LOG"; }
fail() { printf '\nFAIL: %s\n' "$1" | tee -a "$LOG" >&2; exit 1; }
if [ -z "$APK" ]; then
step "Debug-APK bauen"
# shellcheck source=tests/helpers/jdk.sh
. "$HELPERS/jdk.sh"
(cd "$ROOT" && ./gradlew assembleDebug --no-daemon -q) >>"$LOG" 2>&1 ||
fail "Build fehlgeschlagen, siehe $LOG"
APK="$ROOT/app/build/outputs/apk/debug/app-debug.apk"
fi
[ -f "$APK" ] || fail "APK nicht gefunden: $APK"
step "Emulator starten"
"$HELPERS/emulator.sh" start | tee -a "$LOG"
step "Demo-Quelle bereitstellen"
# The emulator restores its snapshot with the clock frozen at save time, so
# the demo programme has to be built around *its* idea of now, not ours.
EMU_NOW="$("$ADB" -s "$SERIAL" shell date +%Y-%m-%dT%H:%M:%S | tr -d '\r')"
echo " Emulator-Uhr: $EMU_NOW" | tee -a "$LOG"
python3 "$ROOT/tests/demo/make-demo-data.py" --now "$EMU_NOW" >>"$LOG" 2>&1 ||
fail "Demo-Daten fehlgeschlagen"
# The emulator reaches the host at 10.0.2.2, which is the address baked into
# the demo playlist.
# A leftover server from an earlier run happily answers on this port while
# serving a directory that no longer exists — every request a 404, and the
# wait below would spin forever.
if ss -ltn "sport = :$DEMO_PORT" 2>/dev/null | grep -q LISTEN; then
fail "Port $DEMO_PORT ist belegt: $(ss -ltnp "sport = :$DEMO_PORT" 2>/dev/null | tail -1)"
fi
# 10.0.2.2 inside the emulator is the host loopback, so binding there is enough.
(cd "$ROOT/tests/demo" && exec python3 -m http.server "$DEMO_PORT" --bind 127.0.0.1) \
>>"$RUNS/http.log" 2>&1 &
DEMO_PID=$!
trap 'kill "$DEMO_PID" 2>/dev/null || true' EXIT
for _ in $(seq 1 50); do
curl -sf "http://127.0.0.1:$DEMO_PORT/playlist.m3u" -o /dev/null && break
sleep 0.2
done
curl -sf "http://127.0.0.1:$DEMO_PORT/playlist.m3u" -o /dev/null ||
fail "Demo-Server antwortet nicht auf Port $DEMO_PORT"
step "Installieren"
# A debug build over a signed release needs the old one gone first.
"$ADB" -s "$SERIAL" uninstall "$PKG" >/dev/null 2>&1 || true
"$ADB" -s "$SERIAL" install -r "$APK" >>"$LOG" 2>&1 || fail "Installation fehlgeschlagen"
# Onboarding runs on a phone (ADR-0007), which a test has no way to be, so
# the source is seeded straight into the preferences the first run reads.
# Only a debug build allows this, which is the build under test.
"$ADB" -s "$SERIAL" shell run-as "$PKG" sh -c "'mkdir -p shared_prefs && cat > shared_prefs/source.xml'" <<XML || fail "Quelle konnte nicht gesetzt werden"
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="m3u_url">http://10.0.2.2:$DEMO_PORT/playlist.m3u</string>
<string name="epg_url">http://10.0.2.2:$DEMO_PORT/epg.xml</string>
</map>
XML
step "Starten"
"$ADB" -s "$SERIAL" logcat -c
"$ADB" -s "$SERIAL" shell am start -W -n "$ACTIVITY" >>"$LOG" 2>&1 ||
fail "am start fehlgeschlagen"
# The first frame is not the point — the crash usually lands a moment later,
# when state loads.
sleep 6
running() { [ -n "$("$ADB" -s "$SERIAL" shell pidof "$PKG" | tr -d '\r')" ]; }
crashed() {
"$ADB" -s "$SERIAL" logcat -d -b crash,main 2>/dev/null |
grep -E "FATAL EXCEPTION|ANR in $PKG|Process $PKG .* has died" | head -20
}
check() {
local where="$1" trace
trace="$(crashed || true)"
if [ -n "$trace" ]; then
printf '%s\n' "$trace" >>"$LOG"
printf '%s\n' "$trace" | head -5
fail "Absturz $where — vollständig in $LOG"
fi
running || fail "Prozess weg $where (kein Stacktrace im Log)"
}
check "beim Start"
"$HELPERS/emulator.sh" shot smoke-start >/dev/null
# Reaching the channel list is the point: it is the screen the demo source
# feeds, and a blank one means the playlist never arrived.
step "Senderliste prüfen"
DUMP="$RUNS/ui-dump.xml"
"$ADB" -s "$SERIAL" shell uiautomator dump /sdcard/ui.xml >>"$LOG" 2>&1 || true
"$ADB" -s "$SERIAL" pull /sdcard/ui.xml "$DUMP" >>"$LOG" 2>&1 || true
if grep -q "Blau TV HD" "$DUMP" 2>/dev/null; then
echo " Sender aus der Demo-Playlist sichtbar"
else
fail "Senderliste zeigt die Demo-Playlist nicht — Dump in $DUMP"
fi
step "D-Pad-Navigation"
# Down/right walks the rail and opens whatever has focus; back returns.
for key in DPAD_DOWN DPAD_RIGHT DPAD_RIGHT DPAD_DOWN DPAD_CENTER BACK DPAD_UP; do
"$ADB" -s "$SERIAL" shell input keyevent "$key"
sleep 1
done
sleep 2
check "bei der Navigation"
"$HELPERS/emulator.sh" shot smoke-nav >/dev/null
step "Wiedereintritt"
"$ADB" -s "$SERIAL" shell am start -n "$ACTIVITY" >/dev/null 2>&1
sleep 2
check "nach dem Wiedereintritt"
if [ "$STOP_AFTER" = 1 ]; then
step "Emulator stoppen"
"$HELPERS/emulator.sh" stop | tee -a "$LOG"
fi
cat <<EOF
OK — kein Absturz, App läuft.
Screenshots: $RUNS/screenshots/smoke-start.png, smoke-nav.png
Log: $LOG
Abgedeckt: Start mit gesetzter Quelle, Senderliste aus der Demo-Playlist,
D-Pad bis in den Player, Wiedereintritt. Nicht abgedeckt: echte Wiedergabe
(die Demo-Streams sind Attrappen), Onboarding am Handy, Kopplung und alles,
was ein Dispatcharr-Backend braucht.
EOF