diff --git a/build-tools/create-packs/Microsoft.Android.Sdk.proj b/build-tools/create-packs/Microsoft.Android.Sdk.proj
index e9e8af48982..ea9becb0dce 100644
--- a/build-tools/create-packs/Microsoft.Android.Sdk.proj
+++ b/build-tools/create-packs/Microsoft.Android.Sdk.proj
@@ -75,6 +75,8 @@ core workload SDK packs imported by WorkloadManifest.targets.
+
+
diff --git a/src/Microsoft.Android.Run/AndroidTestAdapter.cs b/src/Microsoft.Android.Run/AndroidTestAdapter.cs
index bd0983ec671..bd63505f0ab 100644
--- a/src/Microsoft.Android.Run/AndroidTestAdapter.cs
+++ b/src/Microsoft.Android.Run/AndroidTestAdapter.cs
@@ -1,4 +1,5 @@
using System.Xml.Linq;
+using Microsoft.Testing.Extensions.TrxReport.Abstractions;
using Microsoft.Testing.Platform.Capabilities.TestFramework;
using Microsoft.Testing.Platform.Extensions;
using Microsoft.Testing.Platform.Extensions.Messages;
@@ -76,17 +77,30 @@ async Task RunAndReportAsync (ExecuteRequestContext context, SessionUid sessionU
var testResults = ParseTrxFile (localTrxPath);
foreach (var result in testResults) {
+ // Build the failure message including stack trace for non-TRX consumers
+ var failureMessage = result.ErrorMessage ?? "Test failed";
+ if (!string.IsNullOrEmpty (result.StackTrace))
+ failureMessage += "\n" + result.StackTrace;
+
var stateProperty = result.Outcome switch {
TrxOutcome.Passed => (IProperty) new PassedTestNodeStateProperty (),
- TrxOutcome.Failed => new FailedTestNodeStateProperty (result.ErrorMessage ?? "Test failed"),
+ TrxOutcome.Failed => new FailedTestNodeStateProperty (failureMessage),
TrxOutcome.NotExecuted => new SkippedTestNodeStateProperty (result.ErrorMessage),
_ => new PassedTestNodeStateProperty (),
};
+ var properties = new List { stateProperty };
+
+ // Add TRX report properties required by ITrxReportCapability
+ if (!string.IsNullOrEmpty (result.ClassName))
+ properties.Add (new TrxFullyQualifiedTypeNameProperty (result.ClassName));
+ if (result.Outcome == TrxOutcome.Failed && (!string.IsNullOrEmpty (result.ErrorMessage) || !string.IsNullOrEmpty (result.StackTrace)))
+ properties.Add (new TrxExceptionProperty (result.ErrorMessage, result.StackTrace));
+
var testNode = new TestNode {
Uid = new TestNodeUid (result.FullyQualifiedName),
DisplayName = result.TestName,
- Properties = new PropertyBag (stateProperty),
+ Properties = new PropertyBag (properties.ToArray ()),
};
await context.MessageBus.PublishAsync (this, new TestNodeUpdateMessage (sessionUid, testNode));
@@ -235,18 +249,14 @@ static List ParseTrxFile (string trxPath)
? $"{className}.{testName}"
: testName;
- // Extract error message if present
+ // Extract error message and stack trace if present
string? errorMessage = null;
+ string? stackTrace = null;
var outputElement = unitTestResult.Element (ns + "Output");
var errorInfo = outputElement?.Element (ns + "ErrorInfo");
if (errorInfo != null) {
- var message = errorInfo.Element (ns + "Message")?.Value;
- var stackTrace = errorInfo.Element (ns + "StackTrace")?.Value;
- errorMessage = message;
- if (!string.IsNullOrEmpty (stackTrace))
- errorMessage = string.IsNullOrEmpty (errorMessage)
- ? stackTrace
- : $"{errorMessage}\n{stackTrace}";
+ errorMessage = errorInfo.Element (ns + "Message")?.Value;
+ stackTrace = errorInfo.Element (ns + "StackTrace")?.Value;
}
var trxOutcome = outcome switch {
@@ -256,7 +266,7 @@ static List ParseTrxFile (string trxPath)
_ => TrxOutcome.Passed,
};
- results.Add (new TrxTestResult (fullyQualifiedName, testName, trxOutcome, errorMessage));
+ results.Add (new TrxTestResult (fullyQualifiedName, testName, className, trxOutcome, errorMessage, stackTrace));
}
}
@@ -284,10 +294,22 @@ enum TrxOutcome
record TrxTestResult (
string FullyQualifiedName,
string TestName,
+ string? ClassName,
TrxOutcome Outcome,
- string? ErrorMessage);
+ string? ErrorMessage,
+ string? StackTrace);
class AndroidTestCapabilities : ITestFrameworkCapabilities
{
- public IReadOnlyCollection Capabilities { get; } = [];
+ public IReadOnlyCollection Capabilities { get; } = [new AndroidTrxReportCapability ()];
+}
+
+class AndroidTrxReportCapability : ITrxReportCapability
+{
+ public bool IsSupported => true;
+
+ public void Enable ()
+ {
+ // No-op: TRX properties are always added to test nodes.
+ }
}
diff --git a/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj b/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj
index b33678f9fcf..1f2466451e5 100644
--- a/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj
+++ b/src/Microsoft.Android.Run/Microsoft.Android.Run.csproj
@@ -17,6 +17,7 @@
+
diff --git a/src/Microsoft.Android.Run/Program.cs b/src/Microsoft.Android.Run/Program.cs
index 2da984571d2..7b95b9288b5 100644
--- a/src/Microsoft.Android.Run/Program.cs
+++ b/src/Microsoft.Android.Run/Program.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using Microsoft.Testing.Extensions;
using Mono.Options;
using Xamarin.Android.Tools;
@@ -317,6 +318,13 @@ async Task RunDotnetTestAsync (List mtpArgs)
// since MTP needs them to set up the test communication channel.
mtpArgs.AddRange (["--server", "dotnettestcli", "--dotnet-test-pipe", validatedDotnetTestPipe]);
+ // MTP defaults its working directory to the DLL location (SDK tools directory),
+ // not Environment.CurrentDirectory. Pass --results-directory explicitly so TRX
+ // reports are written to the project directory, matching dotnet test conventions.
+ if (!mtpArgs.Contains ("--results-directory")) {
+ mtpArgs.AddRange (["--results-directory", Path.Combine (Environment.CurrentDirectory, "TestResults")]);
+ }
+
var testApplicationBuilder = await Microsoft.Testing.Platform.Builder.TestApplication.CreateBuilderAsync (mtpArgs.ToArray ());
var adapter = new AndroidTestAdapter (
@@ -330,6 +338,8 @@ async Task RunDotnetTestAsync (List mtpArgs)
_ => new AndroidTestCapabilities (),
(_, _) => adapter);
+ testApplicationBuilder.AddTrxReportProvider ();
+
using var testApplication = await testApplicationBuilder.BuildAsync ();
return await testApplication.RunAsync ();
}
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index af4af861bbb..0b046c3d3c5 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -2443,10 +2443,13 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime)
Assert.IsTrue (dotnet.Build (target: "Install", parameters: buildParameters.ToArray ()), "`dotnet build -t:Install` should succeed");
// Run based on mode
- var runParameters = buildParameters.Select (p => $"/p:{p}").ToArray ();
+ var runParameters = buildParameters.Select (p => $"/p:{p}").ToList ();
+ if (mode == "test")
+ runParameters.Add ("--report-trx");
+
using var process = mode == "run"
- ? dotnet.StartRun (waitForExit: true, parameters: runParameters)
- : dotnet.StartTest (parameters: runParameters);
+ ? dotnet.StartRun (waitForExit: true, parameters: runParameters.ToArray ())
+ : dotnet.StartTest (parameters: runParameters.ToArray ());
var locker = new Lock ();
var output = new StringBuilder ();
@@ -2499,6 +2502,17 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime)
StringAssert.Contains ("succeeded: 1", outputText, $"Output should report 1 passed test. See {logPath} for details.");
StringAssert.Contains ("failed: 1", outputText, $"Output should report 1 failed test. See {logPath} for details.");
StringAssert.Contains ("skipped: 1", outputText, $"Output should report 1 skipped test. See {logPath} for details.");
+
+ // Verify that a TRX file was produced by --report-trx
+ var trxFiles = Directory.GetFiles (projectDirectory, "*.trx", SearchOption.AllDirectories);
+ Assert.IsTrue (trxFiles.Length > 0, $"Expected at least one .trx file in {projectDirectory}. See {logPath} for details.");
+
+ TestContext.AddTestAttachment (trxFiles [0]);
+
+ var trxDoc = XDocument.Load (trxFiles [0]);
+ var trxNs = trxDoc.Root?.Name.Namespace ?? XNamespace.None;
+ var resultSummary = trxDoc.Root?.Element (trxNs + "ResultSummary");
+ Assert.IsNotNull (resultSummary, $"TRX file should contain a ResultSummary element. File: {trxFiles [0]}");
}
}