Repository: microsoft/testfx
Pull request #5297: Allow async test methods for UITestMethod on UWP and WinUI
Author: login: Youssef1313
Labels: name: Area: WinUI
name: Area: UWP
Created: 2025-03-22T08:00:04Z | Merged: 2025-03-31T12:28:47Z

Fixes #5306

Linked issue #5306: Support async test methods for UITestMethodAttribute (for UWP/WinUI)
This is currently not supported due to the "sync" nature of test execution in MSTest, which would normally lead to deadlock in this case. This issue is a split of https://github.com/microsoft/testfx/issues/740

Diff:
diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs
index 36f79ed78e..be55750f38 100644
--- a/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs
+++ b/src/Adapter/MSTest.TestAdapter/Execution/TestClassInfo.cs
@@ -466,7 +466,7 @@ TestResult DoRun()
                 {
                     if (logListener is not null)
                     {
-                        FixtureMethodRunner.RunOnContext(ExecutionContext, () =>
+                        ExecutionContextHelpers.RunOnContext(ExecutionContext, () =>
                         {
                             initializationLogs += logListener.GetAndClearStandardOutput();
                             initializationTrace += logListener.GetAndClearDebugTrace();
@@ -801,7 +801,7 @@ void DoRun()
                 {
                     if (logListener is not null)
                     {
-                        FixtureMethodRunner.RunOnContext(ExecutionContext, () =>
+                        ExecutionContextHelpers.RunOnContext(ExecutionContext, () =>
                         {
                             initializationLogs = logListener.GetAndClearStandardOutput();
                             initializationErrorLogs = logListener.GetAndClearStandardError();
diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs
index adc0b5f93b..86c8f9da17 100644
--- a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs
+++ b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs
@@ -133,6 +133,10 @@ public TAttributeType[] GetAttributes<TAttributeType>(bool inherit)
         where TAttributeType : Attribute
         => ReflectHelper.Instance.GetDerivedAttributes<TAttributeType>(TestMethod, inherit).ToArray();
 
+    /// <inheritdoc cref="InvokeAsync(object[])" />
+    public virtual TestResult Invoke(object?[]? arguments)
+        => InvokeAsync(arguments).GetAwaiter().GetResult();
+
     /// <summary>
     /// Execute test method. Capture failures, handle async and return result.
     /// </summary>
@@ -140,7 +144,7 @@ public TAttributeType[] GetAttributes<TAttributeType>(bool inherit)
     ///  Arguments to pass to test method. (E.g. For data driven).
     /// </param>
     /// <returns>Result of test method invocation.</returns>
-    public virtual TestResult Invoke(object?[]? arguments)
+    public virtual async Task<TestResult> InvokeAsync(object?[]? arguments)
     {
         Stopwatch watch = new();
         TestResult? result = null;
@@ -155,13 +159,16 @@ public virtual TestResult Invoke(object?[]? arguments)
 
         try
         {
-            FixtureMethodRunner.RunOnContext(executionContext, () =>
+            ExecutionContextHelpers.RunOnContext(executionContext, () =>
             {
                 ThreadSafeStringWriter.CleanState();
                 listener = new LogMessageListener(MSTestSettings.CurrentSettings.CaptureDebugTraces);
                 executionContext = ExecutionContext.Capture();
             });
-            result = IsTimeoutSet ? ExecuteInternalWithTimeout(arguments, executionContext) : ExecuteInternal(arguments, executionContext, null);
+
+            result = IsTimeoutSet
+                ? await ExecuteInternalWithTimeoutAsync(arguments, executionContext)
+                : await ExecuteInternalAsync(arguments, executionContext, null);
         }
         finally
         {
@@ -173,7 +180,7 @@ public virtual TestResult Invoke(object?[]? arguments)
                 result.Duration = watch.Elapsed;
                 if (listener is not null)
                 {
-                    FixtureMethodRunner.RunOnContext(executionContext, () =>
+                    ExecutionContextHelpers.RunOnContext(executionContext, () =>
                     {
                         result.DebugTrace = listener.GetAndClearDebugTrace();
                         result.LogOutput = listener.GetAndClearStandardOutput();
@@ -391,14 +398,14 @@ private void ThrowMultipleAttributesException(string attributeName)
     /// <param name="timeoutTokenSource">The timeout token source.</param>
     /// <returns>The result of the execution.</returns>
     [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
-    private TestResult ExecuteInternal(object?[]? arguments, ExecutionContext? executionContext, CancellationTokenSource? timeoutTokenSource)
+    private async Task<TestResult> ExecuteInternalAsync(object?[]? arguments, ExecutionContext? executionContext, CancellationTokenSource? timeoutTokenSource)
     {
         DebugEx.Assert(TestMethod != null, "UnitTestExecuter.DefaultTestMethodInvoke: testMethod = null.");
 
         var result = new TestResult();
 
         // TODO remove dry violation with TestMethodRunner
-        FixtureMethodRunner.RunOnContext(executionContext, () =>
+        ExecutionContextHelpers.RunOnContext(executionContext, () =>
         {
             _classInstance = CreateTestClassInstance(result);
             executionContext = ExecutionContext.Capture();
@@ -420,11 +427,33 @@ private TestResult ExecuteInternal(object?[]? arguments, ExecutionContext? execu
                     if (RunTestInitializeMethod(_classInstance, result, ref executionContext, timeoutTokenSource))
                     {
                         hasTestInitializePassed = true;
-                        FixtureMethodRunner.RunOnContext(executionContext, () =>
+                        var tcs = new TaskCompletionSource<object?>();
+#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
+                        ExecutionContextHelpers.RunOnContext(executionContext, async () =>
                         {
-                            TestMethod.InvokeAsSynchronousTask(_classInstance, arguments);
-                            executionContext = ExecutionContext.Capture();
+                            try
+                            {
+                                object? invokeResult = TestMethod.GetInvokeResult(_classInstance, arguments);
+                                if (invokeResult is Task task)
+                                {
+                                    await task;
+                                }
+                                else if (invokeResult is ValueTask valueTask)
+                                {
+                                    await valueTask;
+                                }
+
+                                executionContext = ExecutionContext.Capture();
+                                tcs.SetResult(null);
+                            }
+                            catch (Exception ex)
+                            {
+                                tcs.SetException(ex);
+                            }
                         });
+#pragma warning restore VSTHRD101 // Avoid unsupported async delegates
+
+                        await tcs.Task;
 
                         result.Outcome = UTF.UnitTestOutcome.Passed;
                     }
@@ -694,7 +723,7 @@ private void RunTestCleanupMethod(TestResult result, ExecutionContext? execution
                 if (_classInstance is IAsyncDisposable classInstanceAsAsyncDisposable)
                 {
                     // If you implement IAsyncDisposable without calling the DisposeAsync this would result a resource leak.
-                    FixtureMethodRunner.RunOnContext(executionContext, () =>
+                    ExecutionContextHelpers.RunOnContext(executionContext, () =>
                     {
                         classInstanceAsAsyncDisposable.DisposeAsync().AsTask().Wait();
                         executionContext = ExecutionContext.Capture();
@@ -703,7 +732,7 @@ private void RunTestCleanupMethod(TestResult result, ExecutionContext? execution
 #endif
                 if (_classInstance is IDisposable classInstanceAsDisposable)
                 {
-                    FixtureMethodRunner.RunOnContext(executionContext, () =>
+                    ExecutionContextHelpers.RunOnContext(executionContext, () =>
                     {
                         classInstanceAsDisposable.Dispose();
                         executionContext = ExecutionContext.Capture();
@@ -1058,7 +1087,7 @@ private bool SetTestContext(object classInstance, TestResult result)
     /// <param name="executionContext">The execution context to execute the test method on.</param>
     /// <returns>The result of execution.</returns>
     [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and message appropriately.")]
-    private TestResult ExecuteInternalWithTimeout(object?[]? arguments, ExecutionContext? executionContext)
+    private async Task<TestResult> ExecuteInternalWithTimeoutAsync(object?[]? arguments, ExecutionContext? executionContext)
     {
         DebugEx.Assert(IsTimeoutSet, "Timeout should be set");
 
@@ -1082,7 +1111,7 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments, ExecutionCon
 
                 try
                 {
-                    return ExecuteInternal(arguments, executionContext, timeoutTokenSource);
+                    return await ExecuteInternalAsync(arguments, executionContext, timeoutTokenSource);
                 }
                 catch (OperationCanceledException)
                 {
@@ -1133,7 +1162,7 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments, ExecutionCon
         else
         {
             // Cancel the token source as test has timed out
-            TestContext.Context.CancellationTokenSource.Cancel();
+            await TestContext.Context.CancellationTokenSource.CancelAsync();
         }
 
         TestResult timeoutResult = new() { Outcome = UTF.UnitTestOutcome.Timeout, TestFailureException = new TestFailedException(UTFUnitTestOutcome.Timeout, errorMessage) };
@@ -1152,7 +1181,14 @@ void ExecuteAsyncAction()
         {
             try
             {
-                result = ExecuteInternal(arguments, executionContext, null);
+                // TODO: Avoid blocking.
+                // This used to always happen, but now is moved to the code path where there is a Timeout on the test method.
+                // The GetAwaiter().GetResult() call here can be a source of deadlocks, especially for UWP/WinUI.
+                // When the test method has `await`s with ConfigureAwait(true) (which is the default), the continuation is
+                // dispatched back to the SynchronizationContext which offloads the work to the UI thread.
+                // However, the GetAwaiter().GetResult() here will block the current thread which is also the UI thread.
+                // So, the continuations will not be able, thus this task never completes.
+                result = ExecuteInternalAsync(arguments, executionContext, null).GetAwaiter().GetResult();
             }
             catch (Exception ex)
             {
diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs
index 962eb3ab59..e74949f6d3 100644
--- a/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs
+++ b/src/Adapter/MSTest.TestAdapter/Execution/TestMethodRunner.cs
@@ -61,7 +61,7 @@ public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, IT
     /// Executes a test.
     /// </summary>
     /// <returns>The test results.</returns>
-    internal TestResult[] Execute(string initializationLogs, string initializationErrorLogs, string initializationTrace, string initializationTestContextMessages)
+    internal async Task<TestResult[]> ExecuteAsync(string initializationLogs, string initializationErrorLogs, string initializationTrace, string initializationTestContextMessages)
     {
         bool isSTATestClass = AttributeComparer.IsDerived<STATestClassAttribute>(_testMethodInfo.Parent.ClassAttribute);
         bool isSTATestMethod = AttributeComparer.IsDerived<STATestMethodAttribute>(_testMethodInfo.Executor);
@@ -70,7 +70,7 @@ internal TestResult[] Execute(string initializationLogs, string initializationEr
         if (isSTARequested && isWindowsOS && Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
         {
             TestResult[]? results = null;
-            Thread entryPointThread = new(() => results = SafeRunTestMethod(initializationLogs, initializationErrorLogs, initializationTrace, initializationTestContextMessages))
+            Thread entryPointThread = new(() => results = SafeRunTestMethodAsync(initializationLogs, initializationErrorLogs, initializationTrace, initializationTestContextMessages).GetAwaiter().GetResult())
             {
                 Name = (isSTATestClass, isSTATestMethod) switch
                 {
@@ -102,17 +102,17 @@ internal TestResult[] Execute(string initializationLogs, string initializationEr
                 PlatformServiceProvider.Instance.AdapterTraceLogger.LogWarning(Resource.STAIsOnlySupportedOnWindowsWarning);
             }
 
-            return SafeRunTestMethod(initializationLogs, initializationErrorLogs, initializationTrace, initializationTestContextMessages);
+            return await SafeRunTestMethodAsync(initializationLogs, initializationErrorLogs, initializationTrace, initializationTestContextMessages);
         }
 
         // Local functions
-        TestResult[] SafeRunTestMethod(string initializationLogs, string initializationErrorLogs, string initializationTrace, string initializationTestContextMessages)
+        async Task<TestResult[]> SafeRunTestMethodAsync(string initializationLogs, string initializationErrorLogs, string initializationTrace, string initializationTestContextMessages)
         {
             TestResult[]? result = null;
 
             try
             {
-                result = RunTestMethod();
+                result = await RunTestMethodAsync();
             }
             catch (TestFailedException ex)
             {
@@ -155,7 +155,7 @@ TestResult[] SafeRunTestMethod(string initializationLogs, string initializationE
     /// Runs the test method.
     /// </summary>
     /// <returns>The test results.</returns>
-    internal TestResult[] RunTestMethod()
+    internal async Task<TestResult[]> RunTestMethodAsync()
     {
         DebugEx.Assert(_test != null, "Test should not be null.");
         DebugEx.Assert(_testMethodInfo.TestMethod != null, "Test method should not be null.");
@@ -177,21 +177,21 @@ internal TestResult[] RunTestMethod()
             }
 
             object?[]? data = DataSerializationHelper.Deserialize(_test.SerializedData);
-            TestResult[] testResults = ExecuteTestWithDataSource(null, data);
+            TestResult[] testResults = await ExecuteTestWithDataSourceAsync(null, data);
             results.AddRange(testResults);
         }
-        else if (TryExecuteDataSourceBasedTests(results))
+        else if (await TryExecuteDataSourceBasedTestsAsync(results))
         {
             isDataDriven = true;
         }
-        else if (TryExecuteFoldedDataDrivenTests(results))
+        else if (await TryExecuteFoldedDataDrivenTestsAsync(results))
         {
             isDataDriven = true;
         }
         else
         {
             _testContext.SetDisplayName(_test.DisplayName);
-            TestResult[] testResults = ExecuteTest(_testMethodInfo);
+            TestResult[] testResults = await ExecuteTestAsync(_testMethodInfo);
 
             foreach (TestResult testResult in testResults)
             {
@@ -247,19 +247,19 @@ internal TestResult[] RunTestMethod()
         return results.ToArray();
     }
 
-    private bool TryExecuteDataSourceBasedTests(List<TestResult> results)
+    private async Task<bool> TryExecuteDataSourceBasedTestsAsync(List<TestResult> results)
     {
         DataSourceAttribute[] dataSourceAttribute = _testMethodInfo.GetAttributes<DataSourceAttribute>(false);
         if (dataSourceAttribute is { Length: 1 })
         {
-            ExecuteTestFromDataSourceAttribute(results);
+            await ExecuteTestFromDataSourceAttributeAsync(results);
             return true;
         }
 
         return false;
     }
 
-    private bool TryExecuteFoldedDataDrivenTests(List<TestResult> results)
+    private async Task<bool> TryExecuteFoldedDataDrivenTestsAsync(List<TestResult> results)
     {
         IEnumerable<UTF.ITestDataSource>? testDataSources = _testMethodInfo.GetAttributes<Attribute>(false)?.OfType<UTF.ITestDataSource>();
         if (testDataSources?.Any() != true)
@@ -300,7 +300,7 @@ private bool TryExecuteFoldedDataDrivenTests(List<TestResult> results)
             {
                 try
                 {
-                    TestResult[] testResults = ExecuteTestWithDataSource(testDataSource, data);
+                    TestResult[] testResults = await ExecuteTestWithDataSourceAsync(testDataSource, data);
 
                     results.AddRange(testResults);
                 }
@@ -314,7 +314,7 @@ private bool TryExecuteFoldedDataDrivenTests(List<TestResult> results)
         return true;
     }
 
-    private void ExecuteTestFromDataSourceAttribute(List<TestResult> results)
+    private async Task ExecuteTestFromDataSourceAttributeAsync(List<TestResult> results)
     {
         Stopwatch watch = new();
         watch.Start();
@@ -339,7 +339,7 @@ private void ExecuteTestFromDataSourceAttribute(List<TestResult> results)
 
                 foreach (object dataRow in dataRows)
                 {
-                    TestResult[] testResults = ExecuteTestWithDataRow(dataRow, rowIndex++);
+                    TestResult[] testResults = await ExecuteTestWithDataRowAsync(dataRow, rowIndex++);
                     results.AddRange(testResults);
                 }
             }
@@ -361,7 +361,7 @@ private void ExecuteTestFromDataSourceAttribute(List<TestResult> results)
         }
     }
 
-    private TestResult[] ExecuteTestWithDataSource(UTF.ITestDataSource? testDataSource, object?[]? data)
+    private async Task<TestResult[]> ExecuteTestWithDataSourceAsync(UTF.ITestDataSource? testDataSource, object?[]? data)
     {
         string? displayName = StringEx.IsNullOrWhiteSpace(_test.DisplayName)
             ? _test.Name
@@ -408,7 +408,7 @@ private TestResult[] ExecuteTestWithDataSource(UTF.ITestDataSource? testDataSour
 
         TestResult[] testResults = ignoreFromTestDataRow is not null
             ? [TestResult.CreateIgnoredResult(ignoreFromTestDataRow)]
-            : ExecuteTest(_testMethodInfo);
+            : await ExecuteTestAsync(_testMethodInfo);
 
         stopwatch.Stop();
 
@@ -425,7 +425,7 @@ private TestResult[] ExecuteTestWithDataSource(UTF.ITestDataSource? testDataSour
         return testResults;
     }
 
-    private TestResult[] ExecuteTestWithDataRow(object dataRow, int rowIndex)
+    private async Task<TestResult[]> ExecuteTestWithDataRowAsync(object dataRow, int rowIndex)
     {
         string displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, _test.DisplayName, rowIndex);
         Stopwatch? stopwatch = null;
@@ -435,7 +435,7 @@ private TestResult[] ExecuteTestWithDataRow(object dataRow, int rowIndex)
         {
             stopwatch = Stopwatch.StartNew();
             _testContext.SetDataRow(dataRow);
-            testResults = ExecuteTest(_testMethodInfo);
+            testResults = await ExecuteTestAsync(_testMethodInfo);
         }
         finally
         {
@@ -453,11 +453,11 @@ private TestResult[] ExecuteTestWithDataRow(object dataRow, int rowIndex)
         return testResults;
     }
 
-    private TestResult[] ExecuteTest(TestMethodInfo testMethodInfo)
+    private async Task<TestResult[]> ExecuteTestAsync(TestMethodInfo testMethodInfo)
     {
         try
         {
-            return _testMethodInfo.Executor.Execute(testMethodInfo);
+            return await _testMethodInfo.Executor.ExecuteAsync(testMethodInfo);
         }
         catch (Exception ex)
         {
diff --git a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs
index 26847230df..54921d3be7 100644
--- a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs
+++ b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs
@@ -192,18 +192,16 @@ internal async Task<TestResult[]> RunSingleTestAsync(TestMethod testMethod, IDic
                         testContextForTestExecution.SetOutcome(testContextForClassInit.Context.CurrentTestOutcome);
                         RetryBaseAttribute? retryAttribute = testMethodInfo.RetryAttribute;
                         var testMethodRunner = new TestMethodRunner(testMethodInfo, testMethod, testContextForTestExecution);
-                        result = testMethodRunner.Execute(classInitializeResult.LogOutput!, classInitializeResult.LogError!, classInitializeResult.DebugTrace!, classInitializeResult.TestContextMessages!);
+                        result = await testMethodRunner.ExecuteAsync(classInitializeResult.LogOutput!, classInitializeResult.LogError!, classInitializeResult.DebugTrace!, classInitializeResult.TestContextMessages!);
                         if (retryAttribute is not null && !RetryBaseAttribute.IsAcceptableResultForRetry(result))
                         {
                             RetryResult retryResult = await retryAttribute.ExecuteAsync(
                                 new RetryContext(
-                                    () => Task.FromResult(
-                                        testMethodRunner.Execute(
-                                            classInitializeResult.LogOutput!,
-                                            classInitializeResult.LogError!,
-                                            classInitializeResult.DebugTrace!,
-                                            classInitializeResult.TestContextMessages!)),
-                                    result));
+                                    async () => await testMethodRunner.ExecuteAsync(
+                                        classInitializeResult.LogOutput!,
+                                        classInitializeResult.LogError!,
+                                        classInitializeResult.DebugTrace!,
+                                        classInitializeResult.TestContextMessages!), result));
 
                             result = retryResult.TryGetLast() ?? throw ApplicationStateGuard.Unreachable();
                         }
@@ -255,7 +253,7 @@ private static TestResult RunAssemblyInitializeIfNeeded(TestMethodInfo testMetho
             {
                 if (logListener is not null)
                 {
-                    FixtureMethodRunner.RunOnContext(testMethodInfo.Parent.Parent.ExecutionContext, () =>
+                    ExecutionContextHelpers.RunOnContext(testMethodInfo.Parent.Parent.ExecutionContext, () =>
                     {
                         initializationLogs = logListener.GetAndClearStandardOutput();
                         initializationErrorLogs = logListener.GetAndClearStandardError();
@@ -308,7 +306,7 @@ private static void RunAssemblyCleanupIfNeeded(ITestContext testContext, ClassCl
                 TestFailedException? ex = classInfo.ExecuteClassCleanup(testContext.Context, out logListener);
                 if (logListener is not null)
                 {
-                    FixtureMethodRunner.RunOnContext(classInfo.ExecutionContext, () =>
+                    ExecutionContextHelpers.RunOnContext(classInfo.ExecutionContext, () =>
                     {
                         initializationLogs += logListener.GetAndClearStandardOutput();
                         initializationErrorLogs += logListener.GetAndClearStandardError();
@@ -336,7 +334,7 @@ private static void RunAssemblyCleanupIfNeeded(ITestContext testContext, ClassCl
                 TestFailedException? ex = assemblyInfo.ExecuteAssemblyCleanup(testContext.Context, ref logListener);
                 if (logListener is not null)
                 {
-                    FixtureMethodRunner.RunOnContext(assemblyInfo.ExecutionContext, () =>
+                    ExecutionContextHelpers.RunOnContext(assemblyInfo.ExecutionContext, () =>
                     {
                         initializationLogs += logListener.GetAndClearStandardOutput();
                         initializationErrorLogs += logListener.GetAndClearStandardError();
diff --git a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs
index e383c74cd7..073a48b610 100644
--- a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs
+++ b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs
@@ -107,19 +107,7 @@ internal static bool IsValidReturnType(this MethodInfo method, ReflectHelper? re
         return asyncStateMachineAttribute?.StateMachineType?.FullName;
     }
 
-    /// <summary>
-    /// Invoke a <see cref="MethodInfo"/> as a synchronous <see cref="Task"/>.
-    /// </summary>
-    /// <param name="methodInfo">
-    /// <see cref="MethodInfo"/> instance.
-    /// </param>
-    /// <param name="classInstance">
-    /// Instance of the on which methodInfo is invoked.
-    /// </param>
-    /// <param name="arguments">
-    /// Arguments for the methodInfo invoke.
-    /// </param>
-    internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
+    internal static object? GetInvokeResult(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
     {
         ParameterInfo[]? methodParameters = methodInfo.GetParameters();
 
@@ -184,6 +172,25 @@ internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object?
             }
         }
 
+        return invokeResult;
+    }
+
+    /// <summary>
+    /// Invoke a <see cref="MethodInfo"/> as a synchronous <see cref="Task"/>.
+    /// </summary>
+    /// <param name="methodInfo">
+    /// <see cref="MethodInfo"/> instance.
+    /// </param>
+    /// <param name="classInstance">
+    /// Instance of the on which methodInfo is invoked.
+    /// </param>
+    /// <param name="arguments">
+    /// Arguments for the methodInfo invoke.
+    /// </param>
+    internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object? classInstance, params object?[]? arguments)
+    {
+        object? invokeResult = methodInfo.GetInvokeResult(classInstance, arguments);
+
         // If methodInfo is an async method, wait for returned task
         if (invokeResult is Task task)
         {
diff --git a/src/Adapter/MSTest.TestAdapter/Helpers/ExecutionContextHelpers.cs b/src/Adapter/MSTest.TestAdapter/Helpers/ExecutionContextHelpers.cs
new file mode 100644
index 0000000000..bc45585b18
--- /dev/null
+++ b/src/Adapter/MSTest.TestAdapter/Helpers/ExecutionContextHelpers.cs
@@ -0,0 +1,25 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
+
+internal static class ExecutionContextHelpers
+{
+    internal static void RunOnContext(ExecutionContext? executionContext, Action action)
+    {
+        if (executionContext is null)
+        {
+            action();
+        }
+        else
+        {
+            // CreateCopy doesn't do anything on .NET Core as ExecutionContexts are immutable.
+            // But it's important on .NET Framework.
+            // On .NET Framework, ExecutionContext.Run cannot be called twice with the same ExecutionContext.
+            // Otherwise, it will throw InvalidOperationException with message:
+            // Cannot apply a context that has been marshaled across AppDomains, that was not acquired through a Capture operation or that has already been the argument to a Set call.
+            executionContext = executionContext.CreateCopy();
+            ExecutionContext.Run(executionContext, static action => ((Action)action!).Invoke(), action);
+        }
+    }
+}
diff --git a/src/Adapter/MSTest.TestAdapter/Helpers/FixtureMethodRunner.cs b/src/Adapter/MSTest.TestAdapter/Helpers/FixtureMethodRunner.cs
index c887f103a2..90f66d6189 100644
--- a/src/Adapter/MSTest.TestAdapter/Helpers/FixtureMethodRunner.cs
+++ b/src/Adapter/MSTest.TestAdapter/Helpers/FixtureMethodRunner.cs
@@ -12,24 +12,6 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
 
 internal static class FixtureMethodRunner
 {
-    internal static void RunOnContext(ExecutionContext? executionContext, Action action)
-    {
-        if (executionContext is null)
-        {
-            action();
-        }
-        else
-        {
-            // CreateCopy doesn't do anything on .NET Core as ExecutionContexts are immutable.
-            // But it's important on .NET Framework.
-            // On .NET Framework, ExecutionContext.Run cannot be called twice with the same ExecutionContext.
-            // Otherwise, it will throw InvalidOperationException with message:
-            // Cannot apply a context that has been marshaled across AppDomains, that was not acquired through a Capture operation or that has already been the argument to a Set call.
-            executionContext = executionContext.CreateCopy();
-            ExecutionContext.Run(executionContext, static action => ((Action)action!).Invoke(), action);
-        }
-    }
-
     internal static TestFailedException? RunWithTimeoutAndCancellation(
         Action action, CancellationTokenSource cancellationTokenSource, TimeoutInfo? timeoutInfo, MethodInfo methodInfo,
         ExecutionContext? executionContext, string methodCanceledMessageFormat, string methodTimedOutMessageFormat,
@@ -50,7 +32,7 @@ internal static void RunOnContext(ExecutionContext? executionContext, Action act
         {
             try
             {
-                RunOnContext(executionContext, action);
+                ExecutionContextHelpers.RunOnContext(executionContext, action);
                 return null;
             }
             catch (Exception ex)
@@ -109,7 +91,7 @@ internal static void RunOnContext(ExecutionContext? executionContext, Action act
 
             try
             {
-                RunOnContext(executionContext, action);
+                ExecutionContextHelpers.RunOnContext(executionContext, action);
                 return null;
             }
             catch (Exception ex) when (ex.IsOperationCanceledExceptionFromToken(cancellationTokenSource.Token))
@@ -151,7 +133,7 @@ internal static void RunOnContext(ExecutionContext? executionContext, Action act
                 {
                     try
                     {
-                        RunOnContext(executionContext, action);
+                        ExecutionContextHelpers.RunOnContext(executionContext, action);
                     }
                     catch (Exception ex)
                     {
@@ -213,7 +195,7 @@ internal static void RunOnContext(ExecutionContext? executionContext, Action act
         {
             try
             {
-                RunOnContext(executionContext, action);
+                ExecutionContextHelpers.RunOnContext(executionContext, action);
                 tcs.SetResult(0);
             }
             catch (Exception ex)
diff --git a/src/Adapter/MSTest.TestAdapter/PublicAPI/PublicAPI.Unshipped.txt b/src/Adapter/MSTest.TestAdapter/PublicAPI/PublicAPI.Unshipped.txt
index 7dc5c58110..22b19265a6 100644
--- a/src/Adapter/MSTest.TestAdapter/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/Adapter/MSTest.TestAdapter/PublicAPI/PublicAPI.Unshipped.txt
@@ -1 +1,2 @@
 #nullable enable
+virtual Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution.TestMethodInfo.InvokeAsync(object?[]? arguments) -> System.Threading.Tasks.Task<Microsoft.VisualStudio.TestTools.UnitTesting.TestResult!>!
diff --git a/src/Adapter/MSTest.TestAdapter/PublicAPI/uwp/PublicAPI.Unshipped.txt b/src/Adapter/MSTest.TestAdapter/PublicAPI/uwp/PublicAPI.Unshipped.txt
index 7dc5c58110..22b19265a6 100644
--- a/src/Adapter/MSTest.TestAdapter/PublicAPI/uwp/PublicAPI.Unshipped.txt
+++ b/src/Adapter/MSTest.TestAdapter/PublicAPI/uwp/PublicAPI.Unshipped.txt
@@ -1 +1,2 @@
 #nullable enable
+virtual Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution.TestMethodInfo.InvokeAsync(object?[]? arguments) -> System.Threading.Tasks.Task<Microsoft.VisualStudio.TestTools.UnitTesting.TestResult!>!
diff --git a/src/TestFramework/TestFramework.Extensions/Attributes/UWP_UITestMethodAttribute.cs b/src/TestFramework/TestFramework.Extensions/Attributes/UWP_UITestMethodAttribute.cs
index 9aa48c7e3a..6be0c1acea 100644
--- a/src/TestFramework/TestFramework.Extensions/Attributes/UWP_UITestMethodAttribute.cs
+++ b/src/TestFramework/TestFramework.Extensions/Attributes/UWP_UITestMethodAttribute.cs
@@ -10,6 +10,11 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
 /// </summary>
 public class UITestMethodAttribute : TestMethodAttribute
 {
+    private protected override bool UseAsync => true;
+
+    /// <inheritdoc cref="ExecuteAsync(ITestMethod)" />
+    public override TestResult[] Execute(ITestMethod testMethod) => base.Execute(testMethod);
+
     /// <summary>
     /// Executes the test method on the UI Thread.
     /// </summary>
@@ -19,22 +24,26 @@ public class UITestMethodAttribute : TestMethodAttribute
     /// <returns>
     /// An array of <see cref="TestResult"/> instances.
     /// </returns>
-    /// Throws <exception cref="NotSupportedException"> when run on an async test method.
-    /// </exception>
-    public override TestResult[] Execute(ITestMethod testMethod)
+    internal override async Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
     {
-        AsyncStateMachineAttribute[] attribute = testMethod.GetAttributes<AsyncStateMachineAttribute>(false);
-        if (attribute.Length > 0)
-        {
-            throw new NotSupportedException(FrameworkMessages.AsyncUITestMethodNotSupported);
-        }
-
-        TestResult? result = null;
-        Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
+        var tcs = new TaskCompletionSource<TestResult>();
+#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
+        await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
             Windows.UI.Core.CoreDispatcherPriority.Normal,
-            () => result = testMethod.Invoke(null)).AsTask().GetAwaiter().GetResult();
+            async () =>
+            {
+                try
+                {
+                    tcs.SetResult(await testMethod.InvokeAsync(null));
+                }
+                catch (Exception ex)
+                {
+                    tcs.SetException(ex);
+                }
+            });
+#pragma warning restore VSTHRD101 // Avoid unsupported async delegates
 
-        return [result!];
+        return [await tcs.Task];
     }
 }
 #endif
diff --git a/src/TestFramework/TestFramework.Extensions/Attributes/WinUI_UITestMethodAttribute.cs b/src/TestFramework/TestFramework.Extensions/Attributes/WinUI_UITestMethodAttribute.cs
index 836195f175..37f81c56dc 100644
--- a/src/TestFramework/TestFramework.Extensions/Attributes/WinUI_UITestMethodAttribute.cs
+++ b/src/TestFramework/TestFramework.Extensions/Attributes/WinUI_UITestMethodAttribute.cs
@@ -34,6 +34,8 @@ public UITestMethodAttribute(string displayName)
     {
     }
 
+    private protected override bool UseAsync => true;
+
     /// <summary>
     /// Gets or sets the <see cref="UI.Dispatching.DispatcherQueue"/> that should be used to invoke the UITestMethodAttribute.
     /// If none is provided <see cref="UITestMethodAttribute"/> will check for <see cref="WinUITestTargetAttribute" />, if the attribute is defined it will start the App and use its <see cref="UI.Dispatching.DispatcherQueue"/>.
@@ -41,6 +43,10 @@ public UITestMethodAttribute(string displayName)
     /// </summary>
     public static DispatcherQueue? DispatcherQueue { get; set; }
 
+    /// <inheritdoc />
+    public override TestResult[] Execute(ITestMethod testMethod)
+        => base.Execute(testMethod);
+
     /// <summary>
     /// Executes the test method on the UI Thread.
     /// </summary>
@@ -52,16 +58,8 @@ public UITestMethodAttribute(string displayName)
     /// </returns>
     /// Throws <exception cref="NotSupportedException"> when run on an async test method.
     /// </exception>
-    public override TestResult[] Execute(ITestMethod testMethod)
+    internal override async Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
     {
-        AsyncStateMachineAttribute[] attribute = testMethod.GetAttributes<AsyncStateMachineAttribute>(false);
-        if (attribute.Length > 0)
-        {
-            throw new NotSupportedException(FrameworkMessages.AsyncUITestMethodNotSupported);
-        }
-
-        TestResult? result = null;
-
         // TODO: Code seems to be assuming DeclaringType is never null, but it can be null.
         // Using 'bang' notation for now to ensure same behavior.
         DispatcherQueue dispatcher = GetDispatcherQueue(testMethod.MethodInfo.DeclaringType!.Assembly) ?? throw new InvalidOperationException(FrameworkMessages.AsyncUITestMethodWithNoDispatcherQueue);
@@ -69,38 +67,34 @@ public override TestResult[] Execute(ITestMethod testMethod)
         {
             try
             {
-                result = testMethod.Invoke(null);
+                return [await testMethod.InvokeAsync(null)];
             }
             catch (Exception e)
             {
                 return [new() { TestFailureException = e }];
             }
         }
-        else
+
+        var tcs = new TaskCompletionSource<TestResult>();
+
+#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
+        if (!dispatcher.TryEnqueue(DispatcherQueuePriority.Normal, async () =>
         {
-            var taskCompletionSource = new TaskCompletionSource<object?>();
-
-            if (!dispatcher.TryEnqueue(DispatcherQueuePriority.Normal, () =>
-                {
-                    try
-                    {
-                        result = testMethod.Invoke(null);
-                        taskCompletionSource.SetResult(null);
-                    }
-                    catch (Exception e)
-                    {
-                        result = new TestResult { TestFailureException = e };
-                        taskCompletionSource.SetException(e);
-                    }
-                }))
+            try
             {
-                taskCompletionSource.SetResult(null);
+                tcs.SetResult(await testMethod.InvokeAsync(null));
             }
-
-            taskCompletionSource.Task.GetAwaiter().GetResult();
+            catch (Exception e)
+            {
+                tcs.SetResult(new TestResult { TestFailureException = e });
+            }
+        }))
+        {
+            tcs.SetResult(null!);
         }
+#pragma warning restore VSTHRD101 // Avoid unsupported async delegates
 
-        return [result!];
+        return [await tcs.Task];
     }
 
     private static Type? GetApplicationType(Assembly assembly)
diff --git a/src/TestFramework/TestFramework/Attributes/DataSource/DataTestMethodAttribute.cs b/src/TestFramework/TestFramework/Attributes/DataSource/DataTestMethodAttribute.cs
index 9232c36f59..ed2e10f86f 100644
--- a/src/TestFramework/TestFramework/Attributes/DataSource/DataTestMethodAttribute.cs
+++ b/src/TestFramework/TestFramework/Attributes/DataSource/DataTestMethodAttribute.cs
@@ -13,6 +13,8 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
 [EditorBrowsable(EditorBrowsableState.Never)]
 public class DataTestMethodAttribute : TestMethodAttribute
 {
+    private protected override bool UseAsync => true;
+
     /// <summary>
     /// Initializes a new instance of the <see cref="DataTestMethodAttribute"/> class.
     /// </summary>
diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/STATestMethodAttribute.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/STATestMethodAttribute.cs
index 9e015d39ee..a0cecc24aa 100644
--- a/src/TestFramework/TestFramework/Attributes/TestMethod/STATestMethodAttribute.cs
+++ b/src/TestFramework/TestFramework/Attributes/TestMethod/STATestMethodAttribute.cs
@@ -9,6 +9,8 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting;
 [AttributeUsage(AttributeTargets.Method)]
 public class STATestMethodAttribute : TestMethodAttribute
 {
+    private protected override bool UseAsync => true;
+
     /// <summary>
     /// Initializes a new instance of the <see cref="STATestMethodAttribute"/> class.
     /// </summary>
diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/TestMethodAttribute.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/TestMethodAttribute.cs
index 739cde1827..0c8124cc47 100644
--- a/src/TestFramework/TestFramework/Attributes/TestMethod/TestMethodAttribute.cs
+++ b/src/TestFramework/TestFramework/Attributes/TestMethod/TestMethodAttribute.cs
@@ -41,18 +41,31 @@ public TestMethodAttribute()
     /// Initializes a new instance of the <see cref="TestMethodAttribute"/> class.
     /// </summary>
     /// <param name="displayName">Display name for the test.</param>
-    public TestMethodAttribute(string? displayName) => DisplayName = displayName;
+    public TestMethodAttribute(string? displayName)
+    {
+        DisplayName = displayName;
+        UseAsync = GetType() == typeof(TestMethodAttribute);
+    }
 
     /// <summary>
     /// Gets display name for the test.
     /// </summary>
     public string? DisplayName { get; }
 
+    /// <inheritdoc cref="ExecuteAsync(ITestMethod)" />
+    public virtual TestResult[] Execute(ITestMethod testMethod) => [testMethod.Invoke(null)];
+
+    private protected virtual bool UseAsync { get; }
+
     /// <summary>
     /// Executes a test method.
     /// </summary>
     /// <param name="testMethod">The test method to execute.</param>
     /// <returns>An array of TestResult objects that represent the outcome(s) of the test.</returns>
     /// <remarks>Extensions can override this method to customize running a TestMethod.</remarks>
-    public virtual TestResult[] Execute(ITestMethod testMethod) => [testMethod.Invoke(null)];
+    // TODO: Double check whether this breaks async local propagation between test init, test, test cleanup
+    internal virtual async Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
+        => UseAsync
+        ? [await testMethod.InvokeAsync(null)]
+        : Execute(testMethod);
 }
diff --git a/src/TestFramework/TestFramework/Interfaces/ITestMethod.cs b/src/TestFramework/TestFramework/Interfaces/ITestMethod.cs
index 3f406901e8..9367729a3b 100644
--- a/src/TestFramework/TestFramework/Interfaces/ITestMethod.cs
+++ b/src/TestFramework/TestFramework/Interfaces/ITestMethod.cs
@@ -52,10 +52,24 @@ public interface ITestMethod
     /// Result of test method invocation.
     /// </returns>
     /// <remarks>
-    /// This call handles asynchronous test methods as well.
+    /// This call handles asynchronous test methods in a blocking way.
     /// </remarks>
     TestResult Invoke(object[]? arguments);
 
+    /// <summary>
+    /// Invokes the test method.
+    /// </summary>
+    /// <param name="arguments">
+    /// Arguments to pass to test method. (E.g. For data driven).
+    /// </param>
+    /// <returns>
+    /// Result of test method invocation.
+    /// </returns>
+    /// <remarks>
+    /// This call handles asynchronous test methods as well.
+    /// </remarks>
+    Task<TestResult> InvokeAsync(object[]? arguments);
+
     /// <summary>
     /// Get all attributes of the test method.
     /// </summary>
diff --git a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt
index 768fc73917..284951033c 100644
--- a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt
@@ -2,6 +2,7 @@
 Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException.AssertFailedException(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void
 Microsoft.VisualStudio.TestTools.UnitTesting.AssertInconclusiveException.AssertInconclusiveException(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void
 Microsoft.VisualStudio.TestTools.UnitTesting.InternalTestFailureException.InternalTestFailureException(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void
+Microsoft.VisualStudio.TestTools.UnitTesting.ITestMethod.InvokeAsync(object![]? arguments) -> System.Threading.Tasks.Task<Microsoft.VisualStudio.TestTools.UnitTesting.TestResult!>!
 Microsoft.VisualStudio.TestTools.UnitTesting.UnitTestAssertException.UnitTestAssertException(System.Runtime.Serialization.SerializationInfo! info, System.Runtime.Serialization.StreamingContext context) -> void
 static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Throws<TException>(System.Func<object?>! action, string! message = "", params object![]! messageArgs) -> TException!
 static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Throws<TException>(System.Func<object?>! action, System.Func<System.Exception?, string!>! messageBuilder) -> TException!
diff --git a/src/TestFramework/TestFramework/TestFramework.csproj b/src/TestFramework/TestFramework/TestFramework.csproj
index 5a7bd54160..a48363f7b4 100644
--- a/src/TestFramework/TestFramework/TestFramework.csproj
+++ b/src/TestFramework/TestFramework/TestFramework.csproj
@@ -35,6 +35,7 @@
   <ItemGroup>
     <InternalsVisibleTo Include="MSTest.TestAdapter" Key="$(VsPublicKey)" />
     <InternalsVisibleTo Include="Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests" Key="$(VsPublicKey)" />
+    <InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="$(MoqPublicKey)" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/GenericTestMethodTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/GenericTestMethodTests.cs
index 93e37d209b..c298349c7a 100644
--- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/GenericTestMethodTests.cs
+++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/GenericTestMethodTests.cs
@@ -22,59 +22,59 @@ public async Task TestDifferentGenericMethodTestCases()
             """
             failed AMethodWithBadConstraints \(0\) \(\d+ms\)
               GenericArguments\[0], 'System\.Int32', on 'Void AMethodWithBadConstraints\[T]\(T\)' violates the constraint of type 'T'\.
-                at .+?
+                .+?
             failed NonParameterizedTestMethod \(\d+ms\)
               The generic test method 'NonParameterizedTestMethod' doesn't have arguments, so the generic parameter cannot be inferred\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimple \(1\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodSimple' did run with parameter '1' and type 'System\.Byte'\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimple \(2\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodSimple' did run with parameter '2' and type 'System\.Int32'\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimple \("Hello world"\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodSimple' did run with parameter 'Hello world' and type 'System\.String'\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimple \(null\) \(\d+ms\)
               Test method TestClass\.ParameterizedMethodSimple threw exception: 
               System\.InvalidOperationException: The type of the generic parameter 'T' could not be inferred\.
-                at .+?
+                .+?
             failed ParameterizedMethodTwoGenericParametersAndFourMethodParameters \(1,"Hello world",2,3\) \(\d+ms\)
               Test method TestClass\.ParameterizedMethodTwoGenericParametersAndFourMethodParameters threw exception: 
               System\.InvalidOperationException: Found two conflicting types for generic parameter 'T2'\. The conflicting types are 'System\.Byte' and 'System\.Int32'\.
-                at .+?
+                .+?
             failed ParameterizedMethodTwoGenericParametersAndFourMethodParameters \(null,"Hello world","Hello again",3\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodTwoGenericParametersAndFourMethodParameters' did run with parameters '<null>', 'Hello world', 'Hello again', '3' and generic types 'System\.Int32', 'System\.String'\.
-                at .+?
+                .+?
             failed ParameterizedMethodTwoGenericParametersAndFourMethodParameters \("Hello hello","Hello world",null,null\) \(\d+ms\)
               Test method TestClass\.ParameterizedMethodTwoGenericParametersAndFourMethodParameters threw exception: 
               System\.InvalidOperationException: The type of the generic parameter 'T1' could not be inferred\.
-                at .+?
+                .+?
             failed ParameterizedMethodTwoGenericParametersAndFourMethodParameters \(null,null,null,null\) \(\d+ms\)
               Test method TestClass\.ParameterizedMethodTwoGenericParametersAndFourMethodParameters threw exception: 
               System\.InvalidOperationException: The type of the generic parameter 'T1' could not be inferred\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimpleParams \(1\) \(\d+ms\)
               Cannot create an instance of T\[] because Type\.ContainsGenericParameters is true\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimpleParams \(1,2\) \(\d+ms\)
               Cannot create an instance of T\[] because Type\.ContainsGenericParameters is true\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimpleParams \("Hello world"\) \(\d+ms\)
               Cannot create an instance of T\[] because Type\.ContainsGenericParameters is true\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimpleParams \(null\) \(\d+ms\)
               Cannot create an instance of T\[] because Type\.ContainsGenericParameters is true\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimpleParams \(null,"Hello world"\) \(\d+ms\)
               Cannot create an instance of T\[] because Type\.ContainsGenericParameters is true\.
-                at .+?
+                .+?
             failed ParameterizedMethodWithNestedGeneric \(System\.Collections\.Generic\.List`1\[System.String],System\.Collections\.Generic\.List`1\[System.String]\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodWithNestedGeneric' did run with first list \[Hello, World] and second list \[Unit, Testing]
-                at .+?
+                .+?
             failed ParameterizedMethodWithNestedGeneric \(System\.Collections\.Generic\.List`1\[System.Int32],System\.Collections\.Generic\.List`1\[System.Int32]\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodWithNestedGeneric' did run with first list \[0, 1] and second list \[2, 3]
-                at .+?
+                .+?
             """, RegexOptions.Singleline);
     }
 
diff --git a/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs
index bfae01f18b..fb7b5a8f95 100644
--- a/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs
+++ b/test/UnitTests/MSTestAdapter.UnitTests/Execution/TestMethodRunnerTests.cs
@@ -76,12 +76,12 @@ protected override void Dispose(bool disposing)
         }
     }
 
-    public void ExecuteForTestThrowingExceptionShouldReturnTestResultWithFailedOutcome()
+    public async Task ExecuteForTestThrowingExceptionShouldReturnTestResultWithFailedOutcome()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => throw new Exception("DummyException"));
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.Execute(string.Empty, string.Empty, string.Empty, string.Empty);
+        TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty);
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Failed);
         Verify(results[0].ExceptionMessage!.StartsWith(
             """            
@@ -91,16 +91,16 @@ An unhandled exception was thrown by the 'Execute' method. Please report this er
             StringComparison.Ordinal));
     }
 
-    public void ExecuteForPassingTestShouldReturnTestResultWithPassedOutcome()
+    public async Task ExecuteForPassingTestShouldReturnTestResultWithPassedOutcome()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult() { Outcome = UTF.UnitTestOutcome.Passed });
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.Execute(string.Empty, string.Empty, string.Empty, string.Empty);
+        TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty);
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Passed);
     }
 
-    public void ExecuteShouldNotFillInDebugAndTraceLogsIfDebugTraceDisabled()
+    public async Task ExecuteShouldNotFillInDebugAndTraceLogsIfDebugTraceDisabled()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult() { Outcome = UTF.UnitTestOutcome.Passed });
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
@@ -108,11 +108,11 @@ public void ExecuteShouldNotFillInDebugAndTraceLogsIfDebugTraceDisabled()
         StringWriter writer = new(new StringBuilder("DummyTrace"));
         _testablePlatformServiceProvider.MockTraceListener.Setup(tl => tl.GetWriter()).Returns(writer);
 
-        TestResult[] results = testMethodRunner.Execute(string.Empty, string.Empty, string.Empty, string.Empty);
+        TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty);
         Verify(results[0].DebugTrace == string.Empty);
     }
 
-    public void ExecuteShouldNotFillInDebugAndTraceLogsFromRunningTestMethod()
+    public async Task ExecuteShouldNotFillInDebugAndTraceLogsFromRunningTestMethod()
     {
         StringWriter writer = new(new StringBuilder());
         var testMethodInfo = new TestableTestMethodInfo(
@@ -130,17 +130,17 @@ public void ExecuteShouldNotFillInDebugAndTraceLogsFromRunningTestMethod()
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
         _testablePlatformServiceProvider.MockTraceListener.Setup(tl => tl.GetWriter()).Returns(writer);
 
-        TestResult[] results = testMethodRunner.Execute(string.Empty, string.Empty, string.Empty, string.Empty);
+        TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty);
 
         Verify(results[0].DebugTrace == string.Empty);
     }
 
-    public void RunTestMethodForTestThrowingExceptionShouldReturnTestResultWithFailedOutcome()
+    public async Task RunTestMethodForTestThrowingExceptionShouldReturnTestResultWithFailedOutcome()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => throw new Exception("Dummy Exception"));
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Failed);
         Verify(results[0].ExceptionMessage!.StartsWith(
             """            
@@ -150,68 +150,68 @@ An unhandled exception was thrown by the 'Execute' method. Please report this er
             StringComparison.Ordinal));
     }
 
-    public void RunTestMethodForMultipleResultsReturnMultipleResults()
+    public async Task RunTestMethodForMultipleResultsReturnMultipleResults()
     {
         var testMethodAttributeMock = new Mock<TestMethodAttribute>();
-        testMethodAttributeMock.Setup(_ => _.Execute(It.IsAny<ITestMethod>())).Returns(
+        testMethodAttributeMock.Setup(_ => _.ExecuteAsync(It.IsAny<ITestMethod>())).Returns(Task.FromResult<TestResult[]>(
         [
             new TestResult { Outcome = UTF.UnitTestOutcome.Passed },
             new TestResult { Outcome = UTF.UnitTestOutcome.Failed },
-        ]);
+        ]));
 
         var localTestMethodOptions = new TestMethodOptions(TimeoutInfo.FromTimeout(200), _testContextImplementation, testMethodAttributeMock.Object);
 
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, localTestMethodOptions, null!);
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.Execute(string.Empty, string.Empty, string.Empty, string.Empty);
+        TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty);
         Verify(results.Length == 2);
 
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Passed);
         Verify(results[1].Outcome == UTF.UnitTestOutcome.Failed);
     }
 
-    public void RunTestMethodForPassingTestThrowingExceptionShouldReturnTestResultWithPassedOutcome()
+    public async Task RunTestMethodForPassingTestThrowingExceptionShouldReturnTestResultWithPassedOutcome()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult() { Outcome = UTF.UnitTestOutcome.Passed });
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.Execute(string.Empty, string.Empty, string.Empty, string.Empty);
+        TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty);
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Passed);
     }
 
-    public void RunTestMethodForFailingTestThrowingExceptionShouldReturnTestResultWithFailedOutcome()
+    public async Task RunTestMethodForFailingTestThrowingExceptionShouldReturnTestResultWithFailedOutcome()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult() { Outcome = UTF.UnitTestOutcome.Failed });
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.Execute(string.Empty, string.Empty, string.Empty, string.Empty);
+        TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty);
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Failed);
     }
 
-    public void RunTestMethodShouldGiveTestResultAsPassedWhenTestMethodPasses()
+    public async Task RunTestMethodShouldGiveTestResultAsPassedWhenTestMethodPasses()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult() { Outcome = UTF.UnitTestOutcome.Passed });
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
 
         // Since data is not provided, tests run normally giving passed as outcome.
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Passed);
     }
 
-    public void RunTestMethodShouldGiveTestResultAsFailedWhenTestMethodFails()
+    public async Task RunTestMethodShouldGiveTestResultAsFailedWhenTestMethodFails()
     {
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => new TestResult() { Outcome = UTF.UnitTestOutcome.Failed });
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
 
         // Since data is not provided, tests run normally giving passed as outcome.
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Failed);
     }
 
-    public void RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataSourceAttribute()
+    public async Task RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataSourceAttribute()
     {
         DataSourceAttribute dataSourceAttribute = new("DummyConnectionString", "DummyTableName");
 
@@ -225,7 +225,7 @@ public void RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataSour
 
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
 
         // check for outcome
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Passed);
@@ -233,7 +233,7 @@ public void RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataSour
         Verify(results[2].Outcome == UTF.UnitTestOutcome.Passed);
     }
 
-    public void RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataRowAttribute()
+    public async Task RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataRowAttribute()
     {
         TestResult testResult = new()
         {
@@ -254,11 +254,11 @@ public void RunTestMethodShouldRunDataDrivenTestsWhenDataIsProvidedUsingDataRowA
         // Setup mocks
         _testablePlatformServiceProvider.MockReflectionOperations.Setup(ro => ro.GetCustomAttributes(_methodInfo, It.IsAny<Type>(), It.IsAny<bool>())).Returns(attributes);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
         Verify(results[0].Outcome == UTF.UnitTestOutcome.Inconclusive);
     }
 
-    public void RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvidedUsingDataSourceAttribute()
+    public async Task RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvidedUsingDataSourceAttribute()
     {
         MethodInfo methodInfo = typeof(DummyTestClass).GetMethods().Single(m => m.Name.Equals(nameof(DummyTestClass.DummyDataSourceTestMethod), StringComparison.Ordinal));
         object[] attributes = methodInfo.GetCustomAttributes(inherit: false);
@@ -271,7 +271,7 @@ public void RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvid
 
         _testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, _testContextImplementation)).Returns([1, 2, 3]);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
 
         // check for datarowIndex
         Verify(results[0].DatarowIndex == 0);
@@ -279,7 +279,7 @@ public void RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvid
         Verify(results[2].DatarowIndex == 2);
     }
 
-    public void RunTestMethodShouldRunOnlyDataSourceTestsWhenBothDataSourceAndDataRowAreProvided()
+    public async Task RunTestMethodShouldRunOnlyDataSourceTestsWhenBothDataSourceAndDataRowAreProvided()
     {
         DataSourceAttribute dataSourceAttribute = new("DummyConnectionString", "DummyTableName");
         int dummyIntData = 2;
@@ -298,7 +298,7 @@ public void RunTestMethodShouldRunOnlyDataSourceTestsWhenBothDataSourceAndDataRo
 
         _testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, _testContextImplementation)).Returns([1, 2, 3]);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
 
         // check for datarowIndex as only DataSource Tests are Run
         Verify(results[0].DatarowIndex == 0);
@@ -306,7 +306,7 @@ public void RunTestMethodShouldRunOnlyDataSourceTestsWhenBothDataSourceAndDataRo
         Verify(results[2].DatarowIndex == 2);
     }
 
-    public void RunTestMethodShouldFillInDisplayNameWithDataRowDisplayNameIfProvidedForDataDrivenTests()
+    public async Task RunTestMethodShouldFillInDisplayNameWithDataRowDisplayNameIfProvidedForDataDrivenTests()
     {
         TestResult testResult = new();
 
@@ -325,13 +325,13 @@ public void RunTestMethodShouldFillInDisplayNameWithDataRowDisplayNameIfProvided
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => testResult);
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
 
         Verify(results.Length == 1);
         Verify(results[0].DisplayName == "DataRowTestDisplayName");
     }
 
-    public void RunTestMethodShouldFillInDisplayNameWithDataRowArgumentsIfNoDisplayNameIsProvidedForDataDrivenTests()
+    public async Task RunTestMethodShouldFillInDisplayNameWithDataRowArgumentsIfNoDisplayNameIsProvidedForDataDrivenTests()
     {
         TestResult testResult = new();
 
@@ -349,13 +349,13 @@ public void RunTestMethodShouldFillInDisplayNameWithDataRowArgumentsIfNoDisplayN
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => testResult);
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
 
         Verify(results.Length == 1);
         Verify(results[0].DisplayName is "dummyTestName (2,\"DummyString\")" or "DummyTestMethod (2,DummyString)", $"Display name: {results[0].DisplayName}");
     }
 
-    public void RunTestMethodShouldSetResultFilesIfPresentForDataDrivenTests()
+    public async Task RunTestMethodShouldSetResultFilesIfPresentForDataDrivenTests()
     {
         TestResult testResult = new()
         {
@@ -375,18 +375,18 @@ public void RunTestMethodShouldSetResultFilesIfPresentForDataDrivenTests()
         var testMethodInfo = new TestableTestMethodInfo(_methodInfo, _testClassInfo, _testMethodOptions, () => testResult);
         var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation);
 
-        TestResult[] results = testMethodRunner.RunTestMethod();
+        TestResult[] results = await testMethodRunner.RunTestMethodAsync();
         Verify(results[0].ResultFiles!.Contains("C:\\temp.txt"));
         Verify(results[1].ResultFiles!.Contains("C:\\temp.txt"));
     }
 
-    public void RunTestMethodWithEmptyDataSourceShouldFailBecauseConsiderEmptyDataSourceAsInconclusiveIsFalse()
-        => RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyDataSourceAsInconclusiveIsNotTrueHelper(false);
+    public async Task RunTestMethodWithEmptyDataSourceShouldFailBecauseConsiderEmptyDataSourceAsInconclusiveIsFalse()
+        => await RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyDataSourceAsInconclusiveIsNotTrueHelper(false);
 
-    public void RunTestMethodWithEmptyDataSourceShouldNotFailBecauseConsiderEmptyDataSourceAsInconclusiveIsTrue()
-        => RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyDataSourceAsInconclusiveIsNotTrueHelper(true);
+    public async Task RunTestMethodWithEmptyDataSourceShouldNotFailBecauseConsiderEmptyDataSourceAsInconclusiveIsTrue()
+        => await RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyDataSourceAsInconclusiveIsNotTrueHelper(true);
 
-    private void RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyDataSourceAsInconclusiveIsNotTrueHelper(bool considerEmptyAsInconclusive)
+    private async Task RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyDataSourceAsInconclusiveIsNotTrueHelper(bool considerEmptyAsInconclusive)
     {
         Mock<PlatformServices.Interface.IReflectionOperations2>? existingMock = _testablePlatformServiceProvider.MockReflectionOperations;
         try
@@ -415,12 +415,12 @@ private void RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyDataSource
 
             if (considerEmptyAsInconclusive)
             {
-                TestResult[] results = testMethodRunner.RunTestMethod();
+                TestResult[] results = await testMethodRunner.RunTestMethodAsync();
                 Verify(results[0].Outcome == UTF.UnitTestOutcome.Inconclusive);
             }
             else
             {
-                ArgumentException thrownException = VerifyThrows<ArgumentException>(() => testMethodRunner.RunTestMethod());
+                ArgumentException thrownException = await VerifyThrowsAsync<ArgumentException>(testMethodRunner.RunTestMethodAsync);
                 Verify(thrownException.Message == string.Format(CultureInfo.InvariantCulture, FrameworkMessages.DynamicDataIEnumerableEmpty, nameof(DummyTestClassEmptyDataSource.EmptyProperty), typeof(DummyTestClassEmptyDataSource).FullName));
             }
         }
@@ -467,9 +467,9 @@ internal TestableTestMethodInfo(MethodInfo testMethod, TestClassInfo parent, Tes
             _invokeTest = invoke;
         }
 
-        public override TestResult Invoke(object?[]? arguments) =>
+        public override Task<TestResult> InvokeAsync(object?[]? arguments) =>
             // Ignore args for now
-            _invokeTest();
+            Task.FromResult(_invokeTest());
     }
 
     public class DummyTestClassBase


Reviews:
user: login: Youssef1313
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T08:02:30Z
user: login: Youssef1313
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-22T08:02:42Z
user: login: Youssef1313
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-23T13:45:50Z
user: login: Youssef1313
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T11:06:59Z
user: login: Youssef1313
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T07:43:50Z
user: login: nohwnd
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-31T12:24:01Z

Review comments:
diff_hunk: @@ -422,11 +431,33 @@ private TestResult ExecuteInternal(object?[]? arguments, ExecutionContext? execu
                     if (RunTestInitializeMethod(_classInstance, result, ref executionContext, timeoutTokenSource))
                     {
                         hasTestInitializePassed = true;
-                        FixtureMethodRunner.RunOnContext(executionContext, () =>
+                        var tcs = new TaskCompletionSource<object?>();
+#pragma warning disable VSTHRD101 // Avoid unsupported async delegates
+                        FixtureMethodRunner.RunOnContext(executionContext, async () =>
path: src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs
user: login: Youssef1313
body: TODO: Refactor to `RunOnContextAsync` and move these helper out of `FixtureMethodRunner` to a new helper `ExecutionContextHelpers`
created_at: 2025-03-22T08:02:29Z
author_association: MEMBER
diff_hunk: @@ -1084,7 +1115,8 @@ private TestResult ExecuteInternalWithTimeout(object?[]? arguments, ExecutionCon
 
                 try
                 {
-                    return ExecuteInternal(arguments, executionContext, timeoutTokenSource);
+                    // TODO: Same comment as in ExecuteAsyncAction below.
path: src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs
user: login: Youssef1313
body: Outdated. I'll remove
created_at: 2025-03-22T08:02:42Z
author_association: MEMBER
path: samples/Playground/Tests.cs
user: login: Youssef1313
body: TODO: Revert
created_at: 2025-03-23T13:45:50Z
author_association: MEMBER
diff_hunk: @@ -22,59 +22,59 @@ public async Task TestDifferentGenericMethodTestCases()
             """
             failed AMethodWithBadConstraints \(0\) \(\d+ms\)
               GenericArguments\[0], 'System\.Int32', on 'Void AMethodWithBadConstraints\[T]\(T\)' violates the constraint of type 'T'\.
-                at .+?
+                .+?
             failed NonParameterizedTestMethod \(\d+ms\)
               The generic test method 'NonParameterizedTestMethod' doesn't have arguments, so the generic parameter cannot be inferred\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimple \(1\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodSimple' did run with parameter '1' and type 'System\.Byte'\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimple \(2\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodSimple' did run with parameter '2' and type 'System\.Int32'\.
-                at .+?
+                .+?
             failed ParameterizedMethodSimple \("Hello world"\) \(\d+ms\)
               Assert\.Fail failed\. Test method 'ParameterizedMethodSimple' did run with parameter 'Hello world' and type 'System\.String'\.
-                at .+?
+                .+?
path: test/IntegrationTests/MSTest.Acceptance.IntegrationTests/GenericTestMethodTests.cs
user: login: Youssef1313
body: Some of these are becoming `--- End of stack trace from previous location ---`

The stack trace was previously not so helpful anyways, so I think the change is fine.
created_at: 2025-03-24T11:06:58Z
author_association: MEMBER
diff_hunk: @@ -420,11 +427,33 @@ private TestResult ExecuteInternal(object?[]? arguments, ExecutionContext? execu
                     if (RunTestInitializeMethod(_classInstance, result, ref executionContext, timeoutTokenSource))
                     {
                         hasTestInitializePassed = true;
-                        FixtureMethodRunner.RunOnContext(executionContext, () =>
+                        var tcs = new TaskCompletionSource<object?>();
path: src/Adapter/MSTest.TestAdapter/Execution/TestMethodInfo.cs
user: login: Youssef1313
body: The core of the fix is here.
`InvokeAsSynchronousTask` would have previously blocked the UI thread, preventing the continuation from running.
created_at: 2025-03-25T07:43:50Z
author_association: MEMBER

PR comments:
user: login: marcelwgn
created_at: 2025-03-24T08:12:56Z
body: Uhm, how does this address https://github.com/microsoft/testfx/issues/740? The former is about being able to use datarows with UITests, this PR on the other hand allows using async on tests right?
author_association: NONE
user: login: Youssef1313
created_at: 2025-03-24T08:47:42Z
body: @marcelwgn The original issue description is inaccurate. Based on the comments/discussions there, the real issue is really just UITestMethodAttribute deadlocking. Using parameterized tests does **not** require DataTestMethodAttribute, and so we should not introduce UIDataTestMethodAttribute as it doesn't serve any purpose.
author_association: MEMBER
user: login: marcelwgn
created_at: 2025-03-24T09:33:56Z
body: So the following test should work then?
```
[UITestMethod]
[DataRow(0)]
[DataRow(1)]
[DataRow(2)]
public void TestMethod2(int index)
{
    var grid = new Grid
    {
        MinWidth = index
    };

    grid.UpdateLayout();

    Assert.AreEqual(index, grid.ActualWidth);
}
```

Because when I run it in Visual Studio, I get the following issue:
```
    Cannot run test method 'App8.UnitTest1.TestMethod2': Test data doesn't match method parameters. Either the count or types are different.
Test expected 1 parameter(s), with types 'Int32',
but received 0 argument(s), with types ''.
```

The thread in that issue evolved to providing a workaround to that missing capability using async tests that would essentially implement the behaviour that UITestMethod should support, but that requires async which is not supported. I disagree that the original issue description is misleading, the above sample does not work so there still is no support for this. ALlowing async would just allow for a workaround to work.
author_association: NONE
user: login: Youssef1313
created_at: 2025-03-24T09:44:24Z
body: It's supposed to work. I'll check why it doesn't work, but that wasn't what I understood from the follow-up comments.
author_association: MEMBER
user: login: Youssef1313
created_at: 2025-03-24T09:49:47Z
body: @marcelwgn This should be working if you are using the latest MSTest. I could repro only with really old versions of MSTest that are not supported.
author_association: MEMBER