Skip to content

Conversation

@tustanivsky
Copy link
Collaborator

@tustanivsky tustanivsky commented Nov 26, 2025

Summary

Adds Android platform support to app-runner enabling application execution on both local Android devices (via ADB) and cloud-based devices (via SauceLabs Real Device Cloud).

Architecture

The implementation adds two new providers to the device provider hierarchy allowing to run apps on Android devices:

  DeviceProvider (existing base)
  ├── AdbProvider (local devices/emulators)
  └── SauceLabsProvider (SauceLabs cloud devices)

Key design decisions:

  • Separate providers for ADB and SauceLabs rather than unified-different connection models and constraints
  • Shared AndroidHelpers.ps1 module to reduce duplication across both providers
  • Skip mutex coordination for Android platforms (ADB handles concurrency, SauceLabs sessions are isolated)

Implementation Details

Core Functionality:

  • Device discovery and connection (auto-discovery for ADB, config-based for SauceLabs)
  • APK installation with automatic cleanup of previous versions
  • Application execution with logcat monitoring and process tracking
  • Device status and diagnostics collection

Android-Specific Helpers (AndroidHelpers.ps1):

  • ConvertFrom-AndroidActivityPath - Parses package.name/activity.name format
  • Test-IntentExtrasFormat - Validates Intent extras (-e, -es, -ez, etc.)
  • Get-ApkPackageName - Extracts real package names using aapt with filename fallback
  • Format-LogcatOutput - Formats logcat for consistent output across providers

Note: The iOS-specific implementation in the SauceLabs provider is mostly placeholder code at this point and hasn’t been tested.

Testing

  • AndroidHelpers.Tests.ps1: 33 unit tests covering helper functions
  • AdbTests.ps1: 6 integration tests covering ADB device connection, app management (install/run), diagnostics (logs), and screenshot capture
  • SauceLabs.Tests.ps1: 1 integration test per platform (Android/iOS) covering the full provider contract (connect, install, run, disconnect)
  • Added simple Android test app (sources + pre-built .apk) in app-runner/Tests/Fixtures

CI/CD

  • Added SauceLabs Real Device Cloud integration tests to CI pipeline
  • Configured environment variables for SauceLabs authentication:
    • SAUCE_USERNAME (from repository secrets)
    • SAUCE_ACCESS_KEY (from repository secrets)
    • SAUCE_REGION (hardcoded to us-west-1)

Documentation

  • Updated main README with desktop platform support
  • Added platform-specific examples to Connect-Device

Usage examples

# Connect to local Android device
Connect-Device -Platform Adb

# Connect to SauceLabs device
Connect-Device -Platform SauceLabs

# Install and run app
Install-DeviceApp -PackagePath "path/to/app.apk"
Invoke-DeviceApp -ExecutablePath "com.example.app/.MainActivity" -Arguments "-e test true"

Requirements

  • AndroidAdb: ADB in PATH, USB debugging enabled, device visible via adb devices
  • AndroidSauceLabs: Environment variables (SAUCE_USERNAME, SAUCE_ACCESS_KEY, SAUCE_REGION, SAUCE_DEVICE_NAME, SAUCE_SESSION_NAME - optional)

Related PRs

Comment on lines 77 to 81
# Intent extras must start with flags: -e, -es, -ez, -ei, -el, -ef, -eu, etc.
# Followed by at least one whitespace and additional content
if ($Arguments -notmatch '^-[a-z]{1,2}\s+') {
throw "Invalid Intent extras format: '$Arguments'. Must start with flags like -e, -es, -ez, -ei, -el, etc. followed by key-value pairs."
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure we really need to moderate this. Other flags should be allowed too. Plus, the canonical way to pass these flags is to use a single - for one-letter options, and -- for things like es, ez, and so on.

Copy link
Collaborator

@limbonaut limbonaut Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a fix for -- arguments in d9e7839, but maybe we could remove this whole validation? Wdyt?

Write-Debug "DeviceProviderFactory: Creating AndroidAdbProvider"
return [AndroidAdbProvider]::new()
}
"AndroidSauceLabs" {
Copy link
Collaborator

@vaind vaind Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't actually reviewed yet but one question - do we need android-specific saucelabs provider? I thought the API is the same regardless of iOS/Android.

Suggested change
"AndroidSauceLabs" {
"SauceLabs" {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it will likely be the same for both Android and iOS. I’ve renamed the provider class to SauceLabsProvider but kept the platform-specific names AndroidSauceLabs and iOSSauceLabs since there are still some implementation differences (e.g. app launch, passing input args, etc.). Does that make sense?

@limbonaut
Copy link
Collaborator

I'm having this bizarre issue with Adb tests:

image

The test is very simple:

        It "Exits with code zero" {
            $runResult.ExitCode | Should -Be 0
        }

Somehow, exit code 0 becomes @(0, 0). This doesn't happen on desktop platforms. I'm perplexed.

@tustanivsky tustanivsky marked this pull request as ready for review November 28, 2025 14:08
@limbonaut
Copy link
Collaborator

The problem in the screenshot above seems to have been resolved.

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.

4 participants