diff --git a/.github/workflows/cd-android.yml b/.github/workflows/cd.yml similarity index 72% rename from .github/workflows/cd-android.yml rename to .github/workflows/cd.yml index 1a62e39..8f65268 100644 --- a/.github/workflows/cd-android.yml +++ b/.github/workflows/cd.yml @@ -1,9 +1,5 @@ -name: CD – Android +name: CD -# Triggers: -# • push to main → builds and updates the rolling "latest-android" pre-release -# • release event → attaches the artifacts to the newly published GitHub release -# • manual → same as push to main on: push: branches: [main] @@ -12,10 +8,50 @@ on: workflow_dispatch: permissions: - contents: write # required to create/update releases and upload assets + contents: write + pages: write + id-token: write jobs: - # ── Job 1: build arm64 APK (for distribution) + all-arch AAB ────────────── + deploy-web: + name: Deploy to GitHub Pages + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + concurrency: + group: "pages" + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - uses: swatinem/rust-cache@v2 + + - name: Install Dioxus CLI + uses: taiki-e/install-action@v2 + with: + tool: dioxus-cli + + - name: Build + run: dx build --release --platform web --base-path /LogOut + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./target/dx/log-workout/release/web/public + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + # ── Build arm64 APK (for distribution) + all-arch AAB ───────────────────── build-android: name: Build Android APK (arm64) & AAB (all archs) runs-on: ubuntu-latest @@ -30,7 +66,7 @@ jobs: java-version: "17" distribution: temurin - # ── Step 1: Build arm64-only APK ─────────────────────────────────────── + # ── Step 1: Build arm64-only APK ────────────────────────────────────── # Installing only the arm64 target ensures dx produces a single-ABI APK. - name: Install arm64 Rust target uses: dtolnay/rust-toolchain@stable @@ -62,7 +98,7 @@ jobs: cp "$SRC" logout-android-arm64.apk echo "path=logout-android-arm64.apk" >> "$GITHUB_OUTPUT" - # ── Step 2: Add remaining targets and build all-arch AAB ─────────────── + # ── Step 2: Add remaining targets and build all-arch AAB ────────────── # The arm64 Rust artifacts are cached; only the three extra targets compile. - name: Add remaining Android Rust targets run: rustup target add armv7-linux-androideabi i686-linux-android x86_64-linux-android @@ -146,67 +182,3 @@ jobs: --notes "$NOTES" \ --repo "$GITHUB_REPOSITORY" fi - - # ── Job 2: Android E2E tests using Maestro on an x86_64 emulator ────────── - e2e-android: - name: Android E2E Tests (Maestro) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: "17" - distribution: temurin - - # Build for x86_64 so the APK runs on the CI emulator. - - name: Install x86_64 Android Rust target - uses: dtolnay/rust-toolchain@stable - with: - targets: x86_64-linux-android - - - uses: swatinem/rust-cache@v2 - - - name: Install Dioxus CLI - uses: taiki-e/install-action@v2 - with: - tool: dioxus-cli - - - name: Build x86_64 APK for emulator testing - run: >- - dx build - --platform android - --release - --no-default-features - --features mobile-platform - - - name: Locate x86_64 APK - id: apk - run: | - SRC=$(find target/dx -name "*.apk" -maxdepth 10 | head -1) - [ -n "$SRC" ] || { echo "::error::No APK found under target/dx"; exit 1; } - echo "Found APK: $SRC" - echo "path=$(realpath "$SRC")" >> "$GITHUB_OUTPUT" - - # Enable KVM for hardware-accelerated Android emulation on ubuntu-latest. - - 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 - - - name: Install Maestro - run: | - curl -Ls "https://get.maestro.mobile.dev" | bash - echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - - - name: Run Maestro flows on Android emulator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 33 - arch: x86_64 - script: | - adb install "${{ steps.apk.outputs.path }}" - maestro test maestro/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a42ad4..5559567 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -289,3 +289,96 @@ jobs: - name: Fail if any E2E test failed if: steps.e2e.outcome == 'failure' run: exit 1 + + e2e-android: + name: Android E2E Tests (Maestro) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: "17" + distribution: temurin + + # Build for x86_64 so the APK runs on the CI emulator. + # Omit --release so Rust compiles with debug_assertions=true. wry 0.53+ + # defaults devtools=true when debug_assertions is on, which calls + # WebView.setWebContentsDebuggingEnabled(true) — enabling the Chrome + # DevTools Protocol (CDP) socket that Maestro uses to read WebView content. + - name: Install x86_64 Android Rust target + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-linux-android + + - uses: swatinem/rust-cache@v2 + + - name: Install Dioxus CLI + uses: taiki-e/install-action@v2 + with: + tool: dioxus-cli + + - name: Build x86_64 APK for emulator testing + run: >- + dx build + --platform android + --target x86_64-linux-android + --no-default-features + --features mobile-platform + + - name: Locate x86_64 APK + id: apk + run: | + SRC=$(find target/dx -name "*.apk" -maxdepth 10 | head -1) + [ -n "$SRC" ] || { echo "::error::No APK found under target/dx"; exit 1; } + echo "Found APK: $SRC" + echo "path=$(realpath "$SRC")" >> "$GITHUB_OUTPUT" + + # Enable KVM for hardware-accelerated Android emulation on ubuntu-latest. + - 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 + + - name: Install Maestro + run: | + curl -Ls "https://get.maestro.mobile.dev" | bash + echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + + - name: Run Maestro flows on Android emulator + id: maestro + # continue-on-error so the artifact upload step below always runs + continue-on-error: true + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 33 + arch: x86_64 + # google_apis provides the Google Chrome WebView (chromium-based) with + # full Chrome DevTools Protocol (CDP) support. The default AOSP system + # image ships a stripped-down WebView that Maestro cannot inspect via + # CDP, causing all text assertions to time out. + target: google_apis + script: | + adb install "${{ steps.apk.outputs.path }}" + # Pre-launch the app so the JVM and Chrome WebView warm up before Maestro starts. + # Without this, Maestro's UIAutomator2 polling during extendedWaitUntil slows + # cold-start rendering from ~22 s to ~60 s, hitting the timeout boundary. + adb shell monkey -p com.gfaure.logworkout -c android.intent.category.LAUNCHER 1 + sleep 60 + MAESTRO_CLI_NO_ANALYTICS=1 maestro test maestro/ + + - name: Upload diagnostics + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-e2e-diagnostics + path: | + ~/.maestro/tests/ + if-no-files-found: ignore + retention-days: 7 + + - name: Fail if Maestro tests failed + if: steps.maestro.outcome == 'failure' + run: exit 1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 88cdbe1..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Deploy to GitHub Pages - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: wasm32-unknown-unknown - override: true - - - name: Install Dioxus CLI - uses: taiki-e/install-action@v2 - with: - tool: dioxus-cli - - - name: Build - run: dx build --release --platform web --base-path /LogOut - - - name: Debug Output Content - run: ls -R target/dx - - - name: Setup Pages - uses: actions/configure-pages@v4 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./target/dx/log-workout/release/web/public - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/assets/styles.css b/assets/styles.css index 7d2a245..4cb5f36 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -1,3 +1,9 @@ +/* ===== Base / Reset ===== */ +body { + background-color: #111; + color: #e0e0e0; +} + /* ===== Global / Shared ===== */ .container { padding: 20px; diff --git a/maestro/launch.yaml b/maestro/launch.yaml index 1798739..45619b2 100644 --- a/maestro/launch.yaml +++ b/maestro/launch.yaml @@ -1,9 +1,8 @@ # Smoke test: the app launches and displays the home screen. appId: com.gfaure.logworkout --- -- launchApp - extendedWaitUntil: visible: text: "LogOut" - timeout: 15000 + timeout: 30000 - assertVisible: "Log your workOut" diff --git a/maestro/navigation.yaml b/maestro/navigation.yaml index 69a5319..f2e2ce1 100644 --- a/maestro/navigation.yaml +++ b/maestro/navigation.yaml @@ -1,11 +1,10 @@ # Test: navigate to the exercise browser via the bottom navigation bar. appId: com.gfaure.logworkout --- -- launchApp - extendedWaitUntil: visible: text: "LogOut" - timeout: 15000 + timeout: 30000 - tapOn: "📚" - assertVisible: "Exercise Database" - tapOn: "💪" diff --git a/maestro/session.yaml b/maestro/session.yaml index 4684e57..466e9e3 100644 --- a/maestro/session.yaml +++ b/maestro/session.yaml @@ -1,11 +1,10 @@ # Test: start a new workout session then cancel it. appId: com.gfaure.logworkout --- -- launchApp - extendedWaitUntil: visible: text: "LogOut" - timeout: 15000 + timeout: 30000 - tapOn: "+" - assertVisible: "Active Session" - tapOn: "Cancel Session" diff --git a/src/components/active_session.rs b/src/components/active_session.rs index e52a164..b04a6f4 100644 --- a/src/components/active_session.rs +++ b/src/components/active_session.rs @@ -281,7 +281,7 @@ pub fn SessionView() -> Element { header { class: "session-header", div { - h2 { class: "session-header__title", "⏱️ Active Session" } + h2 { class: "session-header__title", tabindex: 0, "⏱️ Active Session" } p { class: "session-header__timer", onclick: move |_| { diff --git a/src/components/exercise_list.rs b/src/components/exercise_list.rs index e74a835..3cacc07 100644 --- a/src/components/exercise_list.rs +++ b/src/components/exercise_list.rs @@ -136,7 +136,7 @@ pub fn ExerciseListPage() -> Element { header { class: "page-header", - h1 { class: "page-title", "📚 Exercise Database" } + h1 { class: "page-title", tabindex: 0, "📚 Exercise Database" } p { class: "page-subtitle", "Browse {total} exercises" } diff --git a/src/components/home.rs b/src/components/home.rs index 68a4567..f301b9b 100644 --- a/src/components/home.rs +++ b/src/components/home.rs @@ -33,8 +33,8 @@ pub fn HomePage() -> Element { } else { section { class: "sessions-tab", header { class: "sessions-tab__header", - h1 { class: "app-title", "💪 LogOut" } - p { class: "app-tagline", "Turn off your computer, Log your workOut" } + h1 { class: "app-title", tabindex: 0, "💪 LogOut" } + p { class: "app-tagline", tabindex: 0, "Turn off your computer, Log your workOut" } } if completed_sessions().is_empty() { div { class: "sessions-empty",