From 86a1cf7e4e9c063cb4d4c03a5b1e104f82999128 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 19 May 2026 15:20:40 -0500 Subject: [PATCH 1/8] [Microsoft.Android.Run] Add TRX report support Add Microsoft.Testing.Extensions.TrxReport to the Android test adapter, enabling `--report-trx` support when running tests via `dotnet test`. Changes: - Add TrxReport package reference to the project - Register TRX report provider in the MTP builder - Add TRX report properties (TrxFullyQualifiedTypeNameProperty, TrxExceptionProperty) to test nodes - Parse ClassName and StackTrace as separate fields from TRX XML - Add AndroidTrxReportCapability implementing ITrxReportCapability - Ship TrxReport DLLs in the SDK workload pack Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../create-packs/Microsoft.Android.Sdk.proj | 2 + .../AndroidTestAdapter.cs | 41 +++++++++++++------ .../Microsoft.Android.Run.csproj | 1 + src/Microsoft.Android.Run/Program.cs | 3 ++ 4 files changed, 35 insertions(+), 12 deletions(-) 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..ac250288dac 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; @@ -83,10 +84,18 @@ async Task RunAndReportAsync (ExecuteRequestContext context, SessionUid sessionU _ => 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) + 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 +244,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 +261,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 +289,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..139ea8ce980 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; @@ -330,6 +331,8 @@ async Task RunDotnetTestAsync (List mtpArgs) _ => new AndroidTestCapabilities (), (_, _) => adapter); + testApplicationBuilder.AddTrxReportProvider (); + using var testApplication = await testApplicationBuilder.BuildAsync (); return await testApplication.RunAsync (); } From 7690d39374d5db22e257b5f102e696cc621a8597 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 19 May 2026 15:25:30 -0500 Subject: [PATCH 2/8] [tests] Add TRX report test cases to DotNetNewAndroidTest Add "test-trx" test cases for MonoVM and CoreCLR that pass --report-trx to dotnet test and verify a valid TRX file is produced with a ResultSummary element. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/InstallAndRunTests.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index af4af861bbb..6392357bb4b 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -2405,6 +2405,8 @@ public void StartAndroidActivityRespectsAndroidDeviceUserId () [TestCase ("run", AndroidRuntime.CoreCLR)] [TestCase ("test", AndroidRuntime.MonoVM)] [TestCase ("test", AndroidRuntime.CoreCLR)] + [TestCase ("test-trx", AndroidRuntime.MonoVM)] + [TestCase ("test-trx", AndroidRuntime.CoreCLR)] public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) { var templateName = $"DotNetNewAndroidTest_{mode}_{runtime}"; @@ -2437,16 +2439,21 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) Assert.IsTrue (dotnet.Build (parameters: buildParameters.ToArray ()), "`dotnet build` should succeed"); dotnet.AssertHasNoWarnings (); + bool isTestMode = mode.StartsWith ("test", StringComparison.Ordinal); + // `dotnet test` doesn't go through the MSBuild Run target, so Install // must be invoked explicitly to deploy the APK to the device. - if (mode == "test") + if (isTestMode) 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-trx") + 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 +2506,19 @@ 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."); + + if (mode == "test-trx") { + // Verify that a TRX file was produced + 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."); + + 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]}"); + + TestContext.AddTestAttachment (trxFiles [0]); + } } } From ed20076d712dce029c003f267fb733513dc80be9 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 19 May 2026 15:29:30 -0500 Subject: [PATCH 3/8] [tests] Always pass --report-trx and verify TRX output in dotnet test Instead of a separate test-trx mode, always pass --report-trx when running dotnet test and assert the TRX file is produced. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/InstallAndRunTests.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 6392357bb4b..01b1d68abea 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -2405,8 +2405,6 @@ public void StartAndroidActivityRespectsAndroidDeviceUserId () [TestCase ("run", AndroidRuntime.CoreCLR)] [TestCase ("test", AndroidRuntime.MonoVM)] [TestCase ("test", AndroidRuntime.CoreCLR)] - [TestCase ("test-trx", AndroidRuntime.MonoVM)] - [TestCase ("test-trx", AndroidRuntime.CoreCLR)] public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) { var templateName = $"DotNetNewAndroidTest_{mode}_{runtime}"; @@ -2439,7 +2437,7 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) Assert.IsTrue (dotnet.Build (parameters: buildParameters.ToArray ()), "`dotnet build` should succeed"); dotnet.AssertHasNoWarnings (); - bool isTestMode = mode.StartsWith ("test", StringComparison.Ordinal); + bool isTestMode = mode == "test"; // `dotnet test` doesn't go through the MSBuild Run target, so Install // must be invoked explicitly to deploy the APK to the device. @@ -2448,7 +2446,7 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) // Run based on mode var runParameters = buildParameters.Select (p => $"/p:{p}").ToList (); - if (mode == "test-trx") + if (isTestMode) runParameters.Add ("--report-trx"); using var process = mode == "run" @@ -2507,18 +2505,16 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) 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."); - if (mode == "test-trx") { - // Verify that a TRX file was produced - 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."); + // 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."); - 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]}"); + 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]}"); - TestContext.AddTestAttachment (trxFiles [0]); - } + TestContext.AddTestAttachment (trxFiles [0]); } } From f8363849ba4998a995ef2712226965518e11f574 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 19 May 2026 15:30:19 -0500 Subject: [PATCH 4/8] [tests] Remove unnecessary isTestMode variable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 01b1d68abea..dca527f5e7f 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -2437,16 +2437,14 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) Assert.IsTrue (dotnet.Build (parameters: buildParameters.ToArray ()), "`dotnet build` should succeed"); dotnet.AssertHasNoWarnings (); - bool isTestMode = mode == "test"; - // `dotnet test` doesn't go through the MSBuild Run target, so Install // must be invoked explicitly to deploy the APK to the device. - if (isTestMode) + if (mode == "test") 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}").ToList (); - if (isTestMode) + if (mode == "test") runParameters.Add ("--report-trx"); using var process = mode == "run" From 3a994238ec7b33c35a1d73270a0f02db2a401868 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 19 May 2026 15:32:15 -0500 Subject: [PATCH 5/8] [tests] Attach TRX file before assertions for debuggability Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index dca527f5e7f..0b046c3d3c5 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -2507,12 +2507,12 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) 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]}"); - - TestContext.AddTestAttachment (trxFiles [0]); } } From 5da1c6b80f975d9205a1dcd5ab3fc200af68fdea Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 20 May 2026 08:16:49 -0500 Subject: [PATCH 6/8] [tests] Parse TRX path from dotnet test output The TRX file is written relative to the Microsoft.Android.Run process working directory (the SDK tools folder), not the project directory. Parse the path from the "In process file artifacts produced" output instead of searching the project directory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/InstallAndRunTests.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 0b046c3d3c5..d404ef10c9c 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -2504,15 +2504,21 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) 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."); + // The TRX path is reported in stdout, e.g.: + // In process file artifacts produced: + // - /path/to/TestResults/file.trx + var trxPathMatch = System.Text.RegularExpressions.Regex.Match (outputText, @"^\s+-\s+(.+\.trx)\s*$", System.Text.RegularExpressions.RegexOptions.Multiline); + Assert.IsTrue (trxPathMatch.Success, $"Expected TRX file path in output. See {logPath} for details."); - TestContext.AddTestAttachment (trxFiles [0]); + var trxPath = trxPathMatch.Groups [1].Value.Trim (); + Assert.IsTrue (File.Exists (trxPath), $"TRX file should exist at: {trxPath}. See {logPath} for details."); - var trxDoc = XDocument.Load (trxFiles [0]); + TestContext.AddTestAttachment (trxPath); + + var trxDoc = XDocument.Load (trxPath); 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]}"); + Assert.IsNotNull (resultSummary, $"TRX file should contain a ResultSummary element. File: {trxPath}"); } } From eab03c602cf3e56bf2909ef77429c9dc5a31ee3b Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 20 May 2026 08:55:06 -0500 Subject: [PATCH 7/8] [Microsoft.Android.Run] Fix TRX results directory MTP defaults its working directory to the DLL location (SDK tools directory) rather than Environment.CurrentDirectory. This caused TRX reports from --report-trx to be written to /tools/TestResults/ instead of /TestResults/. Fix by passing --results-directory explicitly to MTP, using Environment.CurrentDirectory which dotnet test sets to the project directory via RunWorkingDirectory. Also revert the regex-based TRX path parsing workaround in the test, restoring the simpler approach of searching the project directory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Microsoft.Android.Run/Program.cs | 7 +++++++ .../Tests/InstallAndRunTests.cs | 16 +++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Android.Run/Program.cs b/src/Microsoft.Android.Run/Program.cs index 139ea8ce980..7b95b9288b5 100644 --- a/src/Microsoft.Android.Run/Program.cs +++ b/src/Microsoft.Android.Run/Program.cs @@ -318,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 ( diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index d404ef10c9c..0b046c3d3c5 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -2504,21 +2504,15 @@ public void DotNetNewAndroidTest (string mode, AndroidRuntime runtime) 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 - // The TRX path is reported in stdout, e.g.: - // In process file artifacts produced: - // - /path/to/TestResults/file.trx - var trxPathMatch = System.Text.RegularExpressions.Regex.Match (outputText, @"^\s+-\s+(.+\.trx)\s*$", System.Text.RegularExpressions.RegexOptions.Multiline); - Assert.IsTrue (trxPathMatch.Success, $"Expected TRX file path in output. See {logPath} for details."); + 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."); - var trxPath = trxPathMatch.Groups [1].Value.Trim (); - Assert.IsTrue (File.Exists (trxPath), $"TRX file should exist at: {trxPath}. See {logPath} for details."); + TestContext.AddTestAttachment (trxFiles [0]); - TestContext.AddTestAttachment (trxPath); - - var trxDoc = XDocument.Load (trxPath); + 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: {trxPath}"); + Assert.IsNotNull (resultSummary, $"TRX file should contain a ResultSummary element. File: {trxFiles [0]}"); } } From b07341c13dc0d833e33484a7e46c66a46b0ef0ec Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 20 May 2026 11:49:21 -0500 Subject: [PATCH 8/8] [Microsoft.Android.Run] Address review comments for TRX properties - Include stack trace in FailedTestNodeStateProperty message so non-TRX consumers (console output, test node messages) still see the full failure details. - Only add TrxExceptionProperty when at least one of ErrorMessage or StackTrace is present, avoiding empty exception entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Microsoft.Android.Run/AndroidTestAdapter.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Android.Run/AndroidTestAdapter.cs b/src/Microsoft.Android.Run/AndroidTestAdapter.cs index ac250288dac..bd63505f0ab 100644 --- a/src/Microsoft.Android.Run/AndroidTestAdapter.cs +++ b/src/Microsoft.Android.Run/AndroidTestAdapter.cs @@ -77,9 +77,14 @@ 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 (), }; @@ -89,7 +94,7 @@ async Task RunAndReportAsync (ExecuteRequestContext context, SessionUid sessionU // Add TRX report properties required by ITrxReportCapability if (!string.IsNullOrEmpty (result.ClassName)) properties.Add (new TrxFullyQualifiedTypeNameProperty (result.ClassName)); - if (result.Outcome == TrxOutcome.Failed) + 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 {