Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

549 lines
27 KiB
YAML

name: android-device-e2e
# Real-device Android e2e: builds the WebView-debuggable APK, boots an emulator,
# and drives the real app via Playwright's Android driver + the on-device agent
# smoke. A weekly host-backed schedule gives the lane unattended cadence without
# burning an emulator on every PR; workflow_dispatch still exposes the full
# backend selector for manual cloud/local investigations.
#
# IMPORTANT coverage note: the embedded on-device agent (bun + llama) runs on
# real arm64 hardware but SIGSEGVs on a stock x86_64 emulator, so the LOCAL
# route's full chat round-trip only goes green on a self-hosted arm64 device
# runner. On a GitHub-hosted x86_64 emulator this lane proves: build + install +
# WebView attach + route navigation, and runs the local smoke as a (currently
# expected-to-surface) signal. For green route coverage on the emulator, supply
# an Eliza Cloud token (ELIZA_CLOUD_AUTH_TOKEN secret) so the app onboards to a
# real cloud agent.
#
# iOS onboarding-smoke parity (#13580): mobile-build-smoke.yml runs
# ios-onboarding-smoke.mjs on every path-gated PR, but the Android
# onboarding->home smoke (test:e2e:android:onboarding) is intentionally NOT
# per-PR. The iOS smoke boots a simulator on a macOS runner cheaply; the
# Android smoke needs a KVM-accelerated x86_64 emulator on a Linux runner,
# which is too heavy to boot on every PR (issue #9943). Android
# onboarding->home instead runs on three cadences: the label-gated
# (ci:device) pr-device-smoke job below, the weekly host-backed schedule,
# and workflow_dispatch -- so an onboarding regression is caught on a PR
# opt-in or within one weekly cycle rather than never. This is the
# documented divergence the parity checkbox in #13580 asks for.
on:
schedule:
# Weekly hosted x86_64 emulator run, backend=host by default below. The
# self-hosted arm64 LOCAL route remains separate because bun+llama SIGSEGVs
# on the stock hosted x86_64 emulator (see coverage note above).
- cron: "17 9 * * 1"
workflow_dispatch:
inputs:
backend:
description: "WebView backend for route coverage (cloud|host|local)"
type: choice
options: [cloud, host, local]
default: cloud
api-level:
description: "Android emulator API level"
default: "34"
# Reachable on normal develop PRs and release-promotion PRs, but only when
# explicitly opted in with the `ci:device` label (issue #9943) — booting an
# emulator is slow/heavy, so we never burn a runner on every PR. The
# pr-device-smoke job below is gated on that label and runs a MINIMAL slice
# (build + install + WebView attach + onboarding→home + one host-backed chat
# turn); the full dispatch matrix above is unchanged.
pull_request:
branches: [develop, main]
types: [opened, synchronize, reopened, labeled]
concurrency:
group: android-device-e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
android-e2e:
# Full lane runs on manual dispatch and on the weekly scheduled host-backed
# cadence. Never on PR; PRs use the label-gated minimal smoke below.
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule'
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
timeout-minutes: 90
permissions:
contents: read
env:
ELIZA_MOBILE_REPO_ROOT: ${{ github.workspace }}
ELIZA_WEBVIEW_DEBUG: "1"
ELIZA_BUN_RISCV64_OPTIONAL: "1"
ELIZA_ANDROID_SKIP_FORK_LLAMA_LIB: "1"
ELIZA_ANDROID_BACKEND: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.backend || 'host' }}
ELIZA_CLOUD_AUTH_TOKEN: ${{ secrets.ELIZA_CLOUD_AUTH_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14" # pinned: repo packageManager is bun@1.4.0, an unpublished version (GH+npm 404) that makes bare setup-bun fail here; match .github/ci-bun-version.json (#15071)
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: android-actions/setup-android@v4
- name: Install workspace
run: bun install
- name: Build WebView-debuggable APK
run: bun run --cwd packages/app build:android
# KVM enables a usable emulator on GitHub ubuntu runners.
- 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: Route coverage on emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.api-level || '34' }}
arch: x86_64
ram-size: 6144M
# avx2/fma passthrough so the (x86) on-device agent doesn't SIGILL.
emulator-options: -no-window -no-snapshot -no-boot-anim -gpu swiftshader_indirect -qemu -cpu qemu64,+avx,+avx2,+f16c,+fma
script: |
set -euo pipefail
adb root || true
adb shell setenforce 0 || true
bun run --cwd packages/app build # ensure web dist
mkdir -p packages/app/test-results/android-onboarding-to-home
ELIZA_API_PORT=31337 \
ELIZA_PAIRING_DISABLED=1 \
node packages/app-core/scripts/run-node-tsx.mjs \
packages/app-core/scripts/serve-real-local-agent.ts \
> packages/app/test-results/android-onboarding-to-home/host-agent.log 2>&1 &
HOST_AGENT_PID=$!
trap 'kill "$HOST_AGENT_PID" 2>/dev/null || true' EXIT
for i in $(seq 1 90); do
if curl -fsS \
-H 'X-ElizaOS-Client-Id: android-onboarding-ci' \
http://127.0.0.1:31337/api/health >/tmp/android-host-agent-health.json; then
cat /tmp/android-host-agent-health.json
break
fi
if ! kill -0 "$HOST_AGENT_PID" 2>/dev/null; then
echo "Host agent exited before becoming healthy"
cat packages/app/test-results/android-onboarding-to-home/host-agent.log
exit 1
fi
sleep 2
done
curl -fsS \
-H 'X-ElizaOS-Client-Id: android-onboarding-ci' \
http://127.0.0.1:31337/api/health >/dev/null
ELIZA_ANDROID_BACKEND=host \
ELIZA_ANDROID_REQUIRE_AGENT=1 \
bun run --cwd packages/app test:e2e:android:onboarding
if [ "${ELIZA_ANDROID_BACKEND}" = "local" ]; then
# LOCAL route is still signal-only on hosted x86_64: the embedded
# bun+llama agent is known to require a self-hosted arm64 device
# runner. Keep surfacing the artifact, but do not make the hosted
# emulator falsely red for that known platform gap.
ELIZA_ANDROID_REQUIRE_AGENT=1 \
bun run --cwd packages/app test:e2e:android:routes || true
else
# Host/cloud route coverage is the scheduled unattended health
# signal. Fail red here instead of hiding regressions behind a
# permanent signal-only `|| true` leg.
ELIZA_ANDROID_REQUIRE_AGENT=0 \
bun run --cwd packages/app test:e2e:android:routes
fi
ELIZA_ANDROID_BACKEND=host \
ELIZA_ANDROID_REQUIRE_AGENT=1 \
bun run --cwd packages/app test:e2e:android:native-plugin-view
# Local on-device agent smoke (surfaces failure loudly; expected to
# fail on stock x86 emulator — see coverage note above).
bun run --cwd packages/app test:sim:local-chat:android:live || true
# Seeded launcher gesture loop (#12377): ≥200 real device swipes/taps
# with per-action rail invariants (data-page + AX probe + inertness +
# focus), chunked screenrecord + logcat. Signal-only on the x86
# emulator until proven emulator-green; the seed prints for replay.
ELIZA_ANDROID_BACKEND=host \
ELIZA_ANDROID_REQUIRE_AGENT=1 \
bun run --cwd packages/app test:e2e:android:launcher-loop || true
- name: Cloud provisioning probe
run: |
if [ -z "${ELIZA_CLOUD_AUTH_TOKEN:-}" ]; then
{
echo "### Cloud provisioning probe"
echo ""
echo "skipped: missing ELIZA_CLOUD_AUTH_TOKEN"
} >> "$GITHUB_STEP_SUMMARY"
echo "Cloud provisioning probe skipped: missing ELIZA_CLOUD_AUTH_TOKEN"
exit 0
fi
bun run --cwd packages/app test:e2e:android:cloud
- uses: actions/upload-artifact@v7
if: always()
with:
name: android-e2e-artifacts
path: |
packages/app/test-results/**
packages/app/playwright-report/**
if-no-files-found: ignore
notify-scheduled-failure:
needs: android-e2e
if: ${{ always() && github.event_name == 'schedule' && needs.android-e2e.result != 'success' }}
runs-on: ubuntu-24.04
permissions:
contents: read
issues: write
env:
ANDROID_E2E_RESULT: ${{ needs.android-e2e.result }}
ELIZA_ANDROID_BACKEND: host
steps:
- name: Open / update scheduled failure issue
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
with:
script: |
const title = "Scheduled Android device e2e is failing (#13580)";
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const artifactsUrl = `${runUrl}#artifacts`;
const runDate = new Date().toISOString();
const result = process.env.ANDROID_E2E_RESULT || "unknown";
const body = [
"The weekly host-backed Android device e2e cadence failed.",
"",
`Run: ${runUrl}`,
`Artifacts: ${artifactsUrl}`,
`Workflow: \`${context.workflow}\``,
`Android job result: \`${result}\``,
`Backend: \`${process.env.ELIZA_ANDROID_BACKEND || "host"}\``,
`Observed: ${runDate}`,
"",
"This issue is auto-maintained by `.github/workflows/android-device-e2e.yml` so scheduled failures are visible outside Actions history.",
"",
"Known #13580 residuals still tracked separately:",
"- LOCAL arm64 on-device route needs a self-hosted arm64 runner.",
"- Signal-only PR legs should be promoted only after emulator-green evidence exists.",
].join("\n");
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
per_page: 100,
});
const existing = openIssues.find((issue) => !issue.pull_request && issue.title === title);
if (existing) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.number,
body,
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.number,
body: `Scheduled Android device e2e failed again (${result}): ${runUrl}`,
});
core.info(`Updated scheduled failure issue #${existing.number}`);
} else {
const { data: created } = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
});
core.info(`Opened scheduled failure issue #${created.number}`);
}
# Minimal, label-gated PR device smoke (issue #9943). Reachable on a PR only
# when the `ci:device` label is applied, so it never burns an emulator runner
# on every PR. Runs the proven-green x86 slice: build the WebView-debuggable
# APK, boot the emulator, attach, and drive onboarding→home plus a chat turn
# against the host-run agent (the host backend runs the agent on the runner, so
# it works on a stock x86 emulator where the embedded on-device agent SIGSEGVs).
pr-device-smoke:
if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci:device') }}
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
timeout-minutes: 60
env:
ELIZA_MOBILE_REPO_ROOT: ${{ github.workspace }}
ELIZA_WEBVIEW_DEBUG: "1"
ELIZA_BUN_RISCV64_OPTIONAL: "1"
ELIZA_ANDROID_SKIP_FORK_LLAMA_LIB: "1"
ELIZA_ANDROID_BACKEND: "host"
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14" # pinned: repo packageManager is bun@1.4.0, an unpublished version (GH+npm 404) that makes bare setup-bun fail here; match .github/ci-bun-version.json (#15071)
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: android-actions/setup-android@v4
- name: Install workspace
run: bun install
- name: Build WebView-debuggable APK
run: bun run --cwd packages/app build:android
- 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: Onboarding→home + chat turn on emulator (host agent)
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: "34"
arch: x86_64
ram-size: 6144M
emulator-options: -no-window -no-snapshot -no-boot-anim -gpu swiftshader_indirect -qemu -cpu qemu64,+avx,+avx2,+f16c,+fma
script: |
set -euo pipefail
adb root || true
adb shell setenforce 0 || true
bun run --cwd packages/app build # ensure web dist
mkdir -p packages/app/test-results/android-onboarding-to-home
ELIZA_API_PORT=31337 \
ELIZA_PAIRING_DISABLED=1 \
node packages/app-core/scripts/run-node-tsx.mjs \
packages/app-core/scripts/serve-real-local-agent.ts \
> packages/app/test-results/android-onboarding-to-home/host-agent.log 2>&1 &
HOST_AGENT_PID=$!
trap 'kill "$HOST_AGENT_PID" 2>/dev/null || true' EXIT
for i in $(seq 1 90); do
if curl -fsS \
-H 'X-ElizaOS-Client-Id: android-pr-device-smoke' \
http://127.0.0.1:31337/api/health >/tmp/android-host-agent-health.json; then
cat /tmp/android-host-agent-health.json
break
fi
if ! kill -0 "$HOST_AGENT_PID" 2>/dev/null; then
echo "Host agent exited before becoming healthy"
cat packages/app/test-results/android-onboarding-to-home/host-agent.log
exit 1
fi
sleep 2
done
curl -fsS \
-H 'X-ElizaOS-Client-Id: android-pr-device-smoke' \
http://127.0.0.1:31337/api/health >/dev/null
# Onboarding→home + first chat turn, asserted against the host agent.
ELIZA_ANDROID_BACKEND=host \
ELIZA_ANDROID_REQUIRE_AGENT=1 \
bun run --cwd packages/app test:e2e:android:onboarding
# Native plugin x WebView smoke: proves Capacitor JS calls cross
# into Android Kotlin rather than the desktop Chromium bridge shim.
# Signal-only (|| true) on the PR lane until it's proven green on the
# x86_64 API-34 emulator this job uses — the hard assertion runs in
# the workflow_dispatch `android-e2e` job (validated on a real arm64
# device). Do not make it a hard PR gate on unproven-on-emulator
# evidence; flip to a hard gate once an emulator-green run is attached.
ELIZA_ANDROID_BACKEND=host \
ELIZA_ANDROID_REQUIRE_AGENT=1 \
bun run --cwd packages/app test:e2e:android:native-plugin-view || true
# Real OS-level touch swipe on the chat-sheet grabber (#9943): proves
# the home→launcher pager gesture fires with real Android touch input
# (pointerMouseCount===0), not desktop mouse emulation. Signal-only on
# the PR lane for the same reason as above — flip to a hard gate once
# emulator-green evidence is attached.
ELIZA_ANDROID_BACKEND=host \
ELIZA_ANDROID_REQUIRE_AGENT=1 \
ELIZA_ANDROID_CLEAR_APP_DATA=1 \
bun run --cwd packages/app test:e2e:android:touch-gesture || true
# View-runtime telemetry soak (#10196): activates every registered
# view through the real eliza:navigate:view channel and asserts
# bounded render/heap/cache behavior. Signal-only on the PR lane.
ELIZA_ANDROID_BACKEND=host \
ELIZA_ANDROID_REQUIRE_AGENT=1 \
bun run --cwd packages/app test:e2e:android:view-runtime-soak || true
- uses: actions/upload-artifact@v7
if: always()
with:
name: android-pr-device-smoke-artifacts
path: |
packages/app/test-results/**
packages/app/playwright-report/**
if-no-files-found: ignore
# On-device instrumented (androidTest) gate for the native Capacitor plugins
# (issue #9967 "wire into CI" + issue #9943 "on-device never gates a PR"). Until
# this lane, the native-plugin Kotlin ran on no test, on no device in CI — the
# only coverage was a mocked Capacitor bridge in desktop Chromium. This builds
# every native plugin's androidTest, boots a KVM emulator, and runs the real
# on-device reads (RoleManager / AudioManager / WifiManager / TelecomManager /
# CameraManager / ContactsProvider / content://sms / UsageStatsManager /
# FusedLocationProvider) via `connectedDebugAndroidTest` — gradle fails the job
# on any test failure. Label-gated (`ci:device`) or dispatch, like the smokes
# above, because booting an emulator is heavy.
native-plugin-androidtest:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'ci:device'))
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
timeout-minutes: 60
env:
ELIZA_BUN_RISCV64_OPTIONAL: "1"
ELIZA_ANDROID_SKIP_FORK_LLAMA_LIB: "1"
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14" # pinned: repo packageManager is bun@1.4.0, an unpublished version (GH+npm 404) that makes bare setup-bun fail here; match .github/ci-bun-version.json (#15071)
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- uses: android-actions/setup-android@v4
- name: Install workspace
run: bun install
# Sync the Capacitor Android project so capacitor.settings.gradle points at
# this install's @capacitor/android and every plugin module is wired.
- name: Prepare Android platform
run: bun run --cwd packages/app build:android
- 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: Native-plugin androidTest on emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: "34"
arch: x86_64
ram-size: 4096M
emulator-options: -no-window -no-snapshot -no-boot-anim -gpu swiftshader_indirect
script: |
set -euo pipefail
adb wait-for-device
ANDROID_DIR="$GITHUB_WORKSPACE/packages/app-core/platforms/android"
GRADLEW="$ANDROID_DIR/gradlew"
chmod +x "$GRADLEW"
# Device-state orchestration (survives the test-APK install):
# - messages: inject a marker SMS so the SMS read-back asserts positively
# (it Assume-skips without the marker, so this is what makes it green).
# - location: high-accuracy mode (the fused fetch still Assume-skips on a
# headless GNSS HAL; the priority-mapping assertion runs regardless).
adb emu sms send 15558675309 "probe Eliza-9967-SMS-roundtrip ci" || true
adb shell settings put secure location_mode 3 || true
# The gate: build + install + run every native-plugin androidTest on the
# emulator in one invocation. gradle fails the job on any test failure.
# `:app:connectedDebugAndroidTest` adds the app-level assistant/IME/
# assist-surface instrumented tests (#13581) — the RETAIL-path
# ElizaAssistantSurfaceInstrumentedTest runs on the plain debug APK
# (not assumeSystemEliza-gated), so this app run is not vacuously
# green off-AOSP.
"$GRADLEW" -p "$ANDROID_DIR" --no-daemon \
:app:connectedDebugAndroidTest \
:elizaos-capacitor-system:connectedDebugAndroidTest \
:elizaos-capacitor-wifi:connectedDebugAndroidTest \
:elizaos-capacitor-phone:connectedDebugAndroidTest \
:elizaos-capacitor-camera:connectedDebugAndroidTest \
:elizaos-capacitor-contacts:connectedDebugAndroidTest \
:elizaos-capacitor-messages:connectedDebugAndroidTest \
:elizaos-capacitor-mobile-signals:connectedDebugAndroidTest \
:elizaos-capacitor-location:connectedDebugAndroidTest
# mobile-signals PACKAGE_USAGE_STATS is special-access (no runtime dialog)
# and the grant does NOT survive the gate's reinstall, so its usage reads
# Assume-skip above. For a positive on-device read: re-install the test APK
# the gate built (the gate uninstalls it after running), grant
# GET_USAGE_STATS, and re-run the usage tests via am instrument. Require a
# clean `OK (N tests)` completion and fail the job on any error/failure.
MS_APK="$GITHUB_WORKSPACE/plugins/plugin-native-mobile-signals/android/build/outputs/apk/androidTest/debug/elizaos-capacitor-mobile-signals-debug-androidTest.apk"
adb install -r -t "$MS_APK"
adb shell appops set ai.eliza.plugins.mobilesignals.test android:get_usage_stats allow
MS_OUT="$(adb shell am instrument -w -r \
-e class ai.eliza.plugins.mobilesignals.UsageStatsReaderInstrumentedTest \
ai.eliza.plugins.mobilesignals.test/androidx.test.runner.AndroidJUnitRunner 2>&1)"
printf '%s\n' "$MS_OUT"
if printf '%s\n' "$MS_OUT" | grep -qE 'FAILURES!!!|INSTRUMENTATION_FAILED|INSTRUMENTATION_RESULT: shortMsg'; then
echo "mobile-signals UsageStats assertions failed under granted PACKAGE_USAGE_STATS"
exit 1
fi
printf '%s\n' "$MS_OUT" | grep -qE 'OK \([0-9]+ test' || {
echo "mobile-signals UsageStats run did not complete cleanly"
exit 1
}
# Assistant-role / voice-IME / assist-key adb verification lane
# (#13581). The gradle gate above uninstalled its test APKs, so
# install the app APK the Capacitor build produced, then drive the
# RUNTIME surface: re-apply the role + IME that `adb install -r`
# clears, assert the secure settings, fire `cmd voiceinteraction
# show` / `KEYCODE_ASSIST` / the IME deep-link, and assert they land
# in MainActivity. `--require-device` makes a missing device fatal
# (it is present here, so this only guards against a silent skip).
# `--require-engine` is intentionally NOT passed: the emulator carries
# no on-device ASR engine, so the IME ASR round-trip legitimately
# resolves to the designed ENGINE_OFF state — the lane asserts that
# state rather than requiring a transcript. All other assertions
# (registration, role, IME selection, deep-link landing) still gate
# the job.
APP_APK="$GITHUB_WORKSPACE/packages/app-core/platforms/android/app/build/outputs/apk/debug/app-debug.apk"
if [ -f "$APP_APK" ]; then
adb install -r -t "$APP_APK"
else
echo "app-debug.apk not found at $APP_APK — build:android should have produced it"
exit 1
fi
ASSISTANT_ARTIFACT_DIR="$GITHUB_WORKSPACE/packages/app/test-results/android-assistant-verify"
mkdir -p "$ASSISTANT_ARTIFACT_DIR"
VERIFY_STATUS=0
node "$GITHUB_WORKSPACE/packages/app/scripts/android-assistant-verify.mjs" \
--require-device --json \
| tee "$ASSISTANT_ARTIFACT_DIR/verdict.json" || VERIFY_STATUS=$?
adb shell settings get secure voice_interaction_service \
> "$ASSISTANT_ARTIFACT_DIR/voice_interaction_service.txt" || true
adb shell settings get secure default_input_method \
> "$ASSISTANT_ARTIFACT_DIR/default_input_method.txt" || true
adb shell ime list -s \
> "$ASSISTANT_ARTIFACT_DIR/enabled_imes.txt" || true
adb shell cmd role holders android.app.role.ASSISTANT \
> "$ASSISTANT_ARTIFACT_DIR/assistant_role_holders.txt" || true
adb shell dumpsys activity activities \
> "$ASSISTANT_ARTIFACT_DIR/dumpsys-activity.txt" || true
adb logcat -d -v brief \
> "$ASSISTANT_ARTIFACT_DIR/logcat.txt" || true
exit "$VERIFY_STATUS"
- uses: actions/upload-artifact@v7
if: always()
with:
name: native-plugin-androidtest-reports
path: |
plugins/plugin-native-*/android/build/reports/androidTests/**
plugins/plugin-native-*/android/build/outputs/androidTest-results/**
packages/app/test-results/android-assistant-verify/**
if-no-files-found: ignore