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?

@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.

Exclude adb tests by path

Test

Test

Remove secrets for adb workflow

Fix exclude path

Test adb ci

test

test

Revert "test"

This reverts commit 85d90f2.

Fix PR suggestions

fix excluding adb test (require abs path)

test

Potential fix for code scanning alert no. 135: Workflow does not contain permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

Fix Adb test path

Add Android build-tools dir to PATH so aapt will be available

test
@tustanivsky tustanivsky force-pushed the feat/android-providers branch 2 times, most recently from 95c591f to e1a9413 Compare December 2, 2025 14:50
@@ -0,0 +1,64 @@
name: Test ADB Provider
Copy link
Contributor

Choose a reason for hiding this comment

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

Why a separate file? the other one (test-powershell-module.yml) exists just because it's used for multiple modules.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Adb.Tests.ps1 requires setup that’s specific to these integration tests (Android built tools installation, KVM config, emulator-runner action) which would unnecessarily clutter the generic workflow.

Comment on lines +61 to +64
adb wait-for-device
echo "Android emulator is ready"
adb devices
cd ${{ inputs.module-path }} && pwsh -Command "Invoke-Pester Tests/Adb.Tests.ps1 -CI"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
adb wait-for-device
echo "Android emulator is ready"
adb devices
cd ${{ inputs.module-path }} && pwsh -Command "Invoke-Pester Tests/Adb.Tests.ps1 -CI"
adb wait-for-device
echo "Android emulator is ready"
adb devices
cd ${{ inputs.module-path }}
pwsh -Command "Invoke-Pester Tests/Adb.Tests.ps1 -CI"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The issue is that each line in the script: block is executed as a separate shell command, so we need to use && to chain them and run everything in a single session.

Copy link
Contributor

@vaind vaind left a comment

Choose a reason for hiding this comment

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

LGTM with some minor suggestions. Thanks!

@tustanivsky tustanivsky merged commit 2f5268c into main Dec 2, 2025
14 checks passed
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