Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
78e4eb1
run test on github action
AnonymousWalker Aug 6, 2026
e9e5948
set up gradle
AnonymousWalker Aug 6, 2026
0bf5bc9
fix test
AnonymousWalker Aug 6, 2026
4c153e4
capture screenshots during test
AnonymousWalker Aug 6, 2026
ad1823d
screenshot timing
AnonymousWalker Aug 6, 2026
979f4b2
fix
AnonymousWalker Aug 6, 2026
3fc1797
fix screenshot artifact
AnonymousWalker Aug 6, 2026
5e719a3
increase timeout
AnonymousWalker Aug 6, 2026
c2db3ac
use androidx test storage for screenshot
AnonymousWalker Aug 6, 2026
7aeb189
revert debug screenshots
AnonymousWalker Aug 6, 2026
aa59a4a
increase timeout for other test
AnonymousWalker Aug 6, 2026
77384f6
prebuild apk for test
AnonymousWalker Aug 7, 2026
9804bc3
assemble APKs before emulator
AnonymousWalker Aug 7, 2026
84f1131
harden test
AnonymousWalker Aug 7, 2026
b827f08
upload apk when build fails
AnonymousWalker Aug 7, 2026
e07b455
ATD flakiness
AnonymousWalker Aug 7, 2026
3a9a4d2
try headed environment
AnonymousWalker Aug 7, 2026
b3a49a9
add dep
AnonymousWalker Aug 7, 2026
4d65515
upload screenshot to tmpfiles link
AnonymousWalker Aug 7, 2026
65f51aa
try with maestro
AnonymousWalker Aug 7, 2026
2e404b2
fix flow
AnonymousWalker Aug 11, 2026
b5c2d76
screenshot
AnonymousWalker Aug 11, 2026
bad0399
extend timeout for splash
AnonymousWalker Aug 11, 2026
2c750c7
suppress Not Responding dialogs
AnonymousWalker Aug 11, 2026
c072c18
migrate to maestro like btt writer
AnonymousWalker Aug 11, 2026
b769c88
harden ANR skip
AnonymousWalker Aug 11, 2026
5f54d57
fix retries
AnonymousWalker Aug 11, 2026
bcd187a
refactor
AnonymousWalker Aug 11, 2026
f8d8830
Merge branch 'main' into tony/ci-run-test
AnonymousWalker Aug 11, 2026
3c9e714
fix gradle issue
AnonymousWalker Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# Auto detect text files and perform LF normalization
* text=auto

# Gradle wrapper jar is binary; `* text=auto` would otherwise let Git
# normalize line endings inside the ZIP and strip Main-Class from the
# manifest (CI then fails with "no main manifest attribute").
/gradle/wrapper/gradle-wrapper.jar binary
52 changes: 52 additions & 0 deletions .github/scripts/dismiss-anr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Dismiss system ANR dialogs (e.g. "Pixel Launcher isn't responding") via ui dump + tap.
# Maestro optional taps often miss these system windows on CI.
set -eu

DUMP_REMOTE="/sdcard/window_dump.xml"
DUMP_LOCAL="$(mktemp)"
cleanup() { rm -f "${DUMP_LOCAL}"; }
trap cleanup EXIT

adb shell uiautomator dump "${DUMP_REMOTE}" >/dev/null 2>&1 || true
adb pull "${DUMP_REMOTE}" "${DUMP_LOCAL}" >/dev/null 2>&1 || true

if [[ ! -s "${DUMP_LOCAL}" ]]; then
echo "ANR dismiss: no ui dump"
exit 0
fi

if ! grep -Eqi "isn't responding|aerr_wait|aerr_close|Application Not Responding" "${DUMP_LOCAL}"; then
echo "ANR dismiss: no ANR dialog in hierarchy"
exit 0
fi

echo "ANR dismiss: dialog detected, tapping Wait if present"

# Prefer android:id/aerr_wait, then text="Wait", then aerr_close (launcher only).
tap_bounds() {
local line="$1"
if [[ "${line}" =~ bounds=\"\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\]\" ]]; then
local x=$(( (BASH_REMATCH[1] + BASH_REMATCH[3]) / 2 ))
local y=$(( (BASH_REMATCH[2] + BASH_REMATCH[4]) / 2 ))
echo "ANR dismiss: input tap ${x} ${y}"
adb shell input tap "${x}" "${y}" || true
return 0
fi
return 1
}

line="$(grep -E 'resource-id="android:id/aerr_wait"' "${DUMP_LOCAL}" | head -n 1 || true)"
if [[ -n "${line}" ]] && tap_bounds "${line}"; then
exit 0
fi

line="$(grep -E 'text="Wait"' "${DUMP_LOCAL}" | head -n 1 || true)"
if [[ -n "${line}" ]] && tap_bounds "${line}"; then
exit 0
fi

# Fallback: DPAD + ENTER (often lands on Wait).
echo "ANR dismiss: falling back to DPAD_DOWN + ENTER"
adb shell input keyevent KEYCODE_DPAD_DOWN || true
adb shell input keyevent KEYCODE_ENTER || true
61 changes: 61 additions & 0 deletions .github/scripts/run-android-e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Install Maestro, install the debug APK, grant mic, run the smoke orchestrator.
set -eu

APP_ID="${APP_ID:-org.bibletranslationtools.recorder2}"
APK_PATH="${APK_PATH:-}"
if [[ -z "${APK_PATH}" ]]; then
APK_PATH="$(ls -1 app-recorder/build/outputs/apk/debug/*.apk | head -n 1)"
fi
if [[ -z "${APK_PATH}" || ! -f "${APK_PATH}" ]]; then
echo "Debug APK not found under app-recorder/build/outputs/apk/debug/" >&2
exit 1
fi

echo "Waiting for emulator"
bash .github/scripts/wait-for-emulator-ready.sh

echo "Installing Maestro CLI"
curl -Ls "https://get.maestro.mobile.dev" | bash
export PATH="${HOME}/.maestro/bin:${PATH}"
maestro --version

echo "Installing ${APK_PATH}"
adb install -r -t "${APK_PATH}"

echo "Granting RECORD_AUDIO to ${APP_ID}"
adb shell pm grant "${APP_ID}" android.permission.RECORD_AUDIO || true

# Re-wake after install; headed CI emulators can still lose focus before Maestro starts.
adb shell input keyevent KEYCODE_WAKEUP || true
adb shell wm dismiss-keyguard 2>/dev/null || true
adb shell settings put global hide_error_dialogs 1 || true
adb shell settings put global anr_show_background 0 || true
bash .github/scripts/dismiss-anr.sh || true

mkdir -p maestro-results
set +e
# Single orchestrator (like BTT-Writer); --flatten-debug-output keeps artifacts shallow.
maestro test \
--test-output-dir maestro-results \
--debug-output maestro-results \
--flatten-debug-output \
.maestro/flows/smoke.yaml
STATUS=$?
set -e

if [[ "${STATUS}" -ne 0 ]]; then
echo "Maestro failed — dumping app logcat (InitializeApp / splash)"
adb logcat -d -t 400 \
'*:S' \
'InitializeApp:V' \
'InitializeUlb:V' \
'InitializeLanguages:V' \
'InitializeSources:V' \
'AndroidRuntime:E' \
'System.err:W' \
> maestro-results/logcat-splash.txt 2>&1 || true
adb logcat -d -t 800 > maestro-results/logcat-full.txt 2>&1 || true
fi

exit "$STATUS"
11 changes: 11 additions & 0 deletions .github/scripts/setup-android-emulator.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail

mkdir -p "${HOME}/.android"
cat > "${HOME}/.android/advancedFeatures.ini" <<'EOF'
Vulkan = off
GLDirectMem = on
EOF

echo "Configured ${HOME}/.android/advancedFeatures.ini:"
cat "${HOME}/.android/advancedFeatures.ini"
21 changes: 21 additions & 0 deletions .github/scripts/wait-for-emulator-ready.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail

adb wait-for-device
adb shell 'until [[ "$(getprop sys.boot_completed)" == "1" ]]; do sleep 2; done'

# Let system services settle; reduces cold-start ANRs on CI.
sleep 20

adb shell input keyevent KEYCODE_WAKEUP
adb shell wm dismiss-keyguard 2>/dev/null || true
# Do NOT send KEYCODE_HOME — that foregrounds Pixel Launcher, which often ANRs on CI
# and leaves "isn't responding" on top of the app under test.

# Suppress ANR / crash dialogs where the platform honors these globals.
adb shell settings put global hide_error_dialogs 1 || true
adb shell settings put global anr_show_background 0 || true

bash "$(dirname "$0")/dismiss-anr.sh" || true

echo "Emulator boot completed and keyguard dismissed"
84 changes: 84 additions & 0 deletions .github/workflows/recorder-android-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Recorder Android E2E

on:
push:

concurrency:
group: recorder-android-e2e-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
maestro-smoke:
name: Maestro smoke (API 34 google_apis)
runs-on: ubuntu-latest
timeout-minutes: 90

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
ls -l /dev/kvm

# Headed emulator (Xvfb): -no-window failed to surface main-menu a11y ("Files") on CI
# even though the same flows pass on a local headed emulator.
- name: Start Xvfb and install emulator deps
run: |
sudo apt-get update
sudo apt-get install -y xvfb libpulse0
Xvfb :99 -screen 0 1280x800x24 -ac +extension GLX +render -noreset &
echo "DISPLAY=:99" >> "$GITHUB_ENV"
sleep 1

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
# No gradle-wrapper.jar in git (*.jar ignored); install Gradle directly.
gradle-version: 9.3.1

- name: Cache en_ulb GL source
uses: actions/cache@v4
with:
path: shared/src/commonMain/composeResources/files/content/en_ulb.zip
key: en-ulb-${{ hashFiles('shared/src/commonMain/composeResources/files/gl_sources.json') }}

# Assemble before the emulator so Gradle is not contending with the AVD.
- name: Assemble debug APK
run: gradle :app-recorder:assembleDebug -PminimalGlSources=true --stacktrace

- name: Run Maestro smoke
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
target: google_apis
profile: pixel_6
disable-animations: true
enable-hw-keyboard: true
disk-size: 6000M
ram-size: 4096M
heap-size: 512M
emulator-boot-timeout: 900
pre-emulator-launch-script: bash .github/scripts/setup-android-emulator.sh
# Headed (needs DISPLAY/Xvfb). Extra RAM reduces Pixel Launcher ANRs on CI.
emulator-options: -no-snapshot -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096
script: bash .github/scripts/run-android-e2e.sh

- name: Upload Maestro results
if: failure()
uses: actions/upload-artifact@v4
with:
name: recorder-maestro-results
path: maestro-results/
if-no-files-found: ignore
retention-days: 14
13 changes: 13 additions & 0 deletions .maestro/flows/dismiss-anr-if-visible.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
appId: org.bibletranslationtools.recorder2
---
# Dismiss "… isn't responding" by tapping Wait (system ANR dialog).
- runFlow:
when:
visible: ".*isn't responding.*"
commands:
- tapOn:
id: "android:id/aerr_wait"
optional: true
- tapOn:
text: "Wait"
optional: true
54 changes: 54 additions & 0 deletions .maestro/flows/shared/create_project_genesis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
appId: org.bibletranslationtools.recorder2
---
# Files → New Project → English → Afar → Genesis → Project Management.
# Paths are relative to this file (shared/), not the workspace root.
- runFlow: open_files.yaml
- tapOn: "New Project"
- extendedWaitUntil:
visible: "New Project|Select Source"
timeout: 3000

# Prefer en_ulb, else English, else search "en".
- runFlow:
when:
visible: ".*en_ulb.*"
commands:
- tapOn: ".*en_ulb.*"
- runFlow:
when:
notVisible: "Choose Target Language"
visible: ".*English.*"
commands:
- tapOn: ".*English.*"
- runFlow:
when:
notVisible: "Choose Target Language"
commands:
- runFlow:
file: search_and_tap.yaml
env:
QUERY: "en"
RESULT: ".*English.*"

- extendedWaitUntil:
visible: "Choose Target Language"
timeout: 3000
- runFlow:
file: search_and_tap.yaml
env:
QUERY: "aa"
RESULT: ".*Afar.*"

- extendedWaitUntil:
visible: "Choose a Book"
timeout: 3000
- runFlow:
file: search_and_tap.yaml
env:
QUERY: "gen"
RESULT: ".*Genesis.*"

- extendedWaitUntil:
visible: "Project Management"
timeout: 3000
- assertVisible: ".*Genesis.*"
10 changes: 10 additions & 0 deletions .maestro/flows/shared/open_files.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
appId: org.bibletranslationtools.recorder2
---
# Retry tap — Compose clickables can miss on first gesture (API 34).
- repeat:
times: 20
while:
notVisible: "Project Management"
commands:
- tapOn: "Files"
- assertVisible: "Project Management"
14 changes: 14 additions & 0 deletions .maestro/flows/shared/search_and_tap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
appId: org.bibletranslationtools.recorder2
---
# Env: QUERY (search string), RESULT (regex for row text / contentDescription).
- repeat:
times: 10
while:
notVisible: "Close search"
commands:
- tapOn: "Search"
- inputText: ${QUERY}
- extendedWaitUntil:
visible: ${RESULT}
timeout: 3000
- tapOn: ${RESULT}
8 changes: 8 additions & 0 deletions .maestro/flows/shared/wait_main_menu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
appId: org.bibletranslationtools.recorder2
---
# Used when already past splash; keep in sync with smoke-launch wait.
- extendedWaitUntil:
visible: "Files"
timeout: 60000
- assertVisible: "Files"
- assertVisible: "Record"
6 changes: 6 additions & 0 deletions .maestro/flows/smoke-create-project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
appId: org.bibletranslationtools.recorder2
---
# CreateProjectWizardFlowTest.createNewProjectViaWizard
# Assumes main menu (or Project Management). Leaves Genesis on Project Management.
- runFlow: dismiss-anr-if-visible.yaml
- runFlow: shared/create_project_genesis.yaml
28 changes: 28 additions & 0 deletions .maestro/flows/smoke-files-settings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
appId: org.bibletranslationtools.recorder2
---
# MainMenuNavigationFlowTest.filesOpensProjectManagementAndSettings
# Assumes main menu. Leaves app on main menu.
- runFlow: dismiss-anr-if-visible.yaml
- runFlow: shared/open_files.yaml

- tapOn: "More options"
- extendedWaitUntil:
visible: "Settings"
timeout: 3000
- tapOn: "Settings"
- extendedWaitUntil:
visible: "Audio"
timeout: 3000
- assertVisible: "Audio"

- tapOn: "Back"
- extendedWaitUntil:
visible: "Project Management"
timeout: 3000
- assertVisible: "Project Management"

# Project Management has no toolbar back; system back returns to main menu.
- pressKey: back
- extendedWaitUntil:
visible: "Files"
timeout: 3000
Loading
Loading