Skip to content

Add native Android test runtime support#21

Merged
joyzoursky merged 95 commits intomainfrom
android
Feb 23, 2026
Merged

Add native Android test runtime support#21
joyzoursky merged 95 commits intomainfrom
android

Conversation

@joyzoursky
Copy link
Copy Markdown
Collaborator

  • Add end-to-end Android test execution support:
    • emulator pool + reliable ADB utilities
    • AVD profile/project APK APIs
    • Android target configuration and run UI updates
  • Switch Android runtime flow toward native AVD management (with runtime capability checks / feature gating) and tighten server-side enforcement for
    Android runs.
  • Harden queue and test-runner lifecycle handling:
    • better cancel/cleanup behavior
    • terminal status persistence consistency
    • startup sync / queue advancement guards
    • reduced duplicate/missing run events
  • Improve run visibility and results UX (emulator status panel, Android artifacts/log handling, result viewer/status updates).
  • Extend import/export and config handling for Android entry points, plus secret masking in copied logs.
  • Add maintainer/operator docs for Android runtime setup, maintenance, deployment constraints, and Excel format updates.

joyzoursky and others added 30 commits February 20, 2026 15:37
- Add emulator pool config (maxInstances, timeouts, adb, apk, ports)
- Add features.androidEmulator feature flag
- Add TargetType, BrowserTargetConfig, AndroidTargetConfig, TargetConfig
- Add AndroidDevice and AndroidAgent interfaces (placeholder until @midscene/android)
- Re-export android types from src/types/index.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- shell() with per-command timeout and configurable retries
- Classifies ADB errors (DEVICE_OFFLINE, CONNECTION_RESET, COMMAND_TIMEOUT, etc.)
- Retries on transient errors, fails fast on unrecoverable ones
- Doubles timeout on COMMAND_TIMEOUT then gives up after one retry
- installApk() with configurable install timeout
- healthCheck() checks device online, boot_completed, screen responsive
- reconnect() via adb disconnect/connect cycle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
State machine: BOOTING → IDLE → ACQUIRED → CLEANING → IDLE/DEAD
- acquire() finds idle emulator, boots new one if under limit, or queues
- release() cleans device (APK uninstall, home screen, kill-all), returns to IDLE
- Idle timeout: auto-stops emulators idle beyond idleTimeoutMs
- Force-reclaim: kills emulators stuck in ACQUIRED beyond maxDuration + 60s grace
- Periodic health checks on IDLE emulators; reconnect or stop on failure
- AbortSignal support in acquire() wait queue for run cancellation
- cleanupOrphans() on initialize() kills stale emulator processes from prior server runs
- setEmulatorDevice/Agent() hooks for Phase 2 midscene integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- setupExecutionTargets() replaces setupBrowserInstances(); handles browser
  and android targets in a single pass
- executeSteps() dispatches to AndroidAgent for android targets; guards
  playwright-code steps on android with a clear error
- Browser-specific logic (URL wait, page.screenshot) is gated on isAndroid
- cleanupTargets() closes the browser AND releases emulators back to the pool
- URL substitution in resolvedBrowserConfig skips android configs (no url field)
- BrowserConfig | TargetConfig union used throughout RunTestOptions + TestData

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AvdProfile: stores server-managed AVD configs (name, displayName, apiLevel, enabled)
- ProjectApk: stores uploaded APK files per project with metadata
  (packageName, activityName, versionName); cascade-deletes with project
- Add apks relation to Project model
- Add getApkUploadPath/getApkFilePath helpers to file-security

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /api/avd-profiles: list enabled AVD profiles (authenticated)
- GET /api/projects/[id]/apks: list project APKs (ownership checked)
- POST /api/projects/[id]/apks: upload APK with validation (size, extension,
  per-project count limit); extracts packageName/activityName/versionName
  via aapt2 if available, otherwise stores empty metadata gracefully
- DELETE /api/projects/[id]/apks/[apkId]: delete APK and remove file from disk

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BrowserEntry.config now accepts BrowserConfig | TargetConfig across all components
- ConfigurationsSection: renders android entries with AVD/APK dropdowns, fetches avd-profiles and project APKs, adds "Add Android Target" button when AVD profiles available
- SortableStepItem: shows 🌐/📱 icon per target in step selector, disables Code mode for android targets
- BuilderForm/TestForm: updated BrowserEntry type + buildBrowsers/buildCurrentData to preserve android configs
- testCaseExcel: filters out android targets from Excel export (browser-only format)
- i18n: android target keys added to all 3 locales (en, zh-Hant, zh-Hans)
- TimelineEvent: android screenshots (browserId starting with android_) use portrait 1080x2400 ratio and max-w-[280px] frame with 📱 badge
- GET /api/admin/emulators: returns EmulatorPoolStatus (gated by FEATURE_ANDROID_EMULATOR)
- POST /api/admin/emulators: stop action for idle/booting emulators
- /admin/emulators page: real-time pool dashboard with 5s polling, state badges, uptime, memory, stop button
- Mark stale RUNNING/QUEUED test runs as FAIL on server restart via queue.startup()
- Initialize emulator pool on server start via Next.js instrumentation hook
- Gate android target execution behind FEATURE_ANDROID_EMULATOR flag in test runner
- Gate APK upload/list APIs behind feature flag (return 404 when disabled)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useApkUpload hook for file input + upload flow
- ApkManager component: list, upload, delete with confirmation modal
- EmulatorStatusPanel: pool status, boot/stop controls, 15s visibility-aware polling
- Project page: androidEnabled feature flag, apks/emulators tabs, replace inline active-run checks with isActiveRunStatus()
- i18n: apk.*, emulator.panel.*, feature.android.disabled, project.tab.apks/emulators (en/zh-Hant/zh-Hans)
Reuses useApkUpload hook; on success auto-selects new APK and closes dropdown.
i18n: configs.android.apk.upload (en/zh-Hant/zh-Hans)
- queue.ts: include PREPARING in startup stale-run cleanup and orphan cancel guard
- events route: forward PREPARING status changes via SSE
- projects routes: include PREPARING in active-run guards (delete project, active badge)
- run/page.tsx: PREPARING in TestResult type, SSE connect check, isRunInProgress, stop button
- history/page.tsx: PREPARING in delete modal and row active-run checks
- history/[runId]/page.tsx: expand TestRun.status type; allow TargetConfig in browserConfig
- ResultStatus.tsx: add PREPARING spinner panel
- i18n: status.preparing, status.preparing.detail (en/zh-Hant/zh-Hans)
- ResultViewer: derive targetTypeMap from config.browserConfig; pass targetType to TimelineEvent
- TimelineEvent: add targetType prop; replace all startsWith('android_') checks; add 📱/🌐 icon on log events
- RunTestOptions: add optional onPreparing callback
- test-runner.ts: call onPreparing before setupExecutionTargets when Android target present
- queue.ts: pass onPreparing to runTest; sets run + test case to PREPARING and publishes project event
Android support is now gated solely by the user-level androidEnabled
field. The FEATURE_ANDROID_EMULATOR env var, config.features block,
and server-side guard in test-runner are no longer needed.
EmulatorPool now always initializes at startup.
- TimelineEvent: remove 📱/🌐 from log events; replace 📱 Android badge with plain text
- SortableStepItem: remove 📱/🌐 from target selector button and dropdown
- ConfigurationsSection: remove 📱 from Android target header, 🌐 from browser target header, 📱 from add-Android button
- EmulatorStatusPanel, admin emulators page: remove 📱 from emulator list rows
- i18n: rename Configurations tab to Variables (en/zh-Hant/zh-Hans)
AVD profiles are now scoped to projects instead of being global. Adds
CRUD API endpoints, an AvdProfileManager UI component on the project
Emulators tab, and updates all consumers (test-runner, admin emulators,
ConfigurationsSection) to use the project-scoped lookup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace native <select> with custom button-dropdown and move the boot
controls from the header into a centered footer row inside the emulator
list card. Includes click-outside-to-close behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move /api/admin/emulators to /api/emulators since there is no admin
role — the route only checks androidEnabled. Delete the unused
standalone /admin/emulators page that duplicated EmulatorStatusPanel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 3 oversized stat cards with inline summary text next to the
title. Move AVD dropdown and Boot button to the header row (right side)
to match the AVD Profiles section pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Accept browserConfig-only payloads when saving a test case so Android targets no longer require a URL.
- add server Android runtime capability detection and effective feature gating\n- block Android-only API paths and runs when the host lacks Android tooling\n- hide Android UI entry points on non-Android servers\n- document optional Android server mode for operators and maintainers
@joyzoursky joyzoursky merged commit e10dbfa into main Feb 23, 2026
@joyzoursky joyzoursky deleted the android branch February 23, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant