Skip to content

Add UseInstrumentation support to dotnet run for device testing #10683

@rmarinho

Description

@rmarinho

Summary

Add support for launching Android apps via adb shell am instrument instead of adb shell am start when running device tests. This enables proper wait-for-completion semantics for test execution scenarios.

Background

The dotnet test for mobile platforms spec (dotnet/maui#33117) defines how dotnet test will work with device test projects. A working POC (rmarinho/testfx#2) demonstrates two execution modes for Android:

  1. Activity Mode (current): Uses adb shell am start to launch MainActivity
  2. Instrumentation Mode (proposed): Uses adb shell am instrument -w for reliable test execution

The Instrumentation Mode provides:

  • Proper wait-for-completion via -w flag
  • Reliable exit code propagation via Instrumentation.Finish()
  • Standard Android test execution pattern

Proposed Changes

Modify _AndroidComputeRunArguments target in [Microsoft.Android.Sdk.Application.targets](https://github.com/dotnet/android/blob/main/src/Xamarin.Android.Build. Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android. Sdk.Application.targets#L52-L79) to support a new UseInstrumentation property.

Current Implementation

<!-- When WaitForExit is false, use direct adb command (no logcat streaming) -->
<PropertyGroup Condition=" '$(WaitForExit)' == 'false' ">
  <RunCommand>$(_AdbToolPath)</RunCommand>
  <RunArguments>$(AdbTarget) shell am start -S -n "$(_AndroidPackage)/$(AndroidLaunchActivity)"</RunArguments>
</PropertyGroup>

Proposed Enhancement

<!-- New: When UseInstrumentation is true, launch via adb instrument -->
<PropertyGroup Condition=" '$(UseInstrumentation)' == 'true' ">
  <_AndroidRunPath Condition=" '$(_AndroidRunPath)' == '' ">$(MSBuildThisFileDirectory)..\tools\Microsoft.Android.Run.dll</_AndroidRunPath>
  <RunCommand>dotnet</RunCommand>
  <RunArguments>exec "$(_AndroidRunPath)" --adb "$(_AdbToolPath)" --package "$(_AndroidPackage)" --instrument "$(AndroidInstrumentationName)" --logcat-args "$(_AndroidRunLogcatArgs)" $(_AndroidRunExtraArgs)</RunArguments>
</PropertyGroup>

<!-- Alternative: Direct adb instrument without Microsoft. Android.Run wrapper -->
<PropertyGroup Condition=" '$(UseInstrumentation)' == 'true' AND '$(WaitForExit)' == 'false' ">
  <RunCommand>$(_AdbToolPath)</RunCommand>
  <RunArguments>$(AdbTarget) shell am instrument -w "$(_AndroidPackage)/$(AndroidInstrumentationName)"</RunArguments>
</PropertyGroup>

New MSBuild Properties

Property Description Default
$(UseInstrumentation) When true, use adb instrument instead of adb am start false
$(AndroidInstrumentationName) Full instrumentation class name (e.g., myapp.TestInstrumentation) (none, required when UseInstrumentation=true, if not provided try to find the first on the manifest or using the Instrumentation attribute )

Usage

# Current (Activity Mode)
dotnet run --project MyTests.csproj -f net10.0-android --device emulator-5554

# Proposed (Instrumentation Mode)
dotnet run --project MyTests.csproj -f net10.0-android --device emulator-5554 -p:UseInstrumentation=true -p:AndroidInstrumentationName=myapp.TestInstrumentation

Test Project Requirements

Projects using Instrumentation Mode need:

1. TestInstrumentation class:

[Instrumentation(Name = "myapp.TestInstrumentation")]
public class TestInstrumentation : Instrumentation
{
    public override void OnCreate(Bundle? arguments)
    {
        base.OnCreate(arguments);
        Start();
    }

    public override async void OnStart()
    {
        base.OnStart();
        int exitCode = 1;
        Bundle results = new Bundle();
        
        try
        {
            // Run tests via Microsoft. Testing.Platform
            exitCode = await MicrosoftTestingPlatformEntryPoint.Main(args);
            results. PutString("status", exitCode == 0 ? "SUCCESS" : "FAILURE");
        }
        finally
        {
            // Signal completion - adb instrument -w waits for this
            Finish(exitCode == 0 ? Result.Ok : Result. Canceled, results);
        }
    }
}

2. AndroidManifest.xml registration:

<instrumentation
  android:name="myapp.TestInstrumentation"
  android:targetPackage="com.mycompany.mytestapp"
  android: label="Test Instrumentation" />

Why Instrumentation Mode?

Aspect Activity Mode Instrumentation Mode
Completion detection App termination (unreliable) Finish() call (reliable)
Exit code Java.Lang.JavaSystem.Exit() Finish(Result, Bundle)
Wait semantics Polling/timeout -w flag blocks until done
Long-running tests May timeout Waits indefinitely
Standard pattern Custom Android test convention

Related

/cc @jonathanpeppers @rmarinho

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssues that need to be assigned.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions