diff --git a/dotnet/Makefile b/dotnet/Makefile
index bd150b2f5e6c..07ed5db2217f 100644
--- a/dotnet/Makefile
+++ b/dotnet/Makefile
@@ -42,6 +42,7 @@ $(1)_NUGET_TARGETS = \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.TargetFrameworkInference.props \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.props \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.Trimming.props \
+ $(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.MSTest.props \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Xamarin.Shared.Sdk.targets \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/dotnet-xcsync.targets \
$(DOTNET_DESTDIR)/$($(1)_NUGET_SDK_NAME)/targets/Microsoft.MaciOS.Sdk.Xcode.targets \
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.cs.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.cs.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.cs.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.de.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.de.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.de.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.en.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.en.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.en.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.es.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.es.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.es.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.fr.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.fr.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.fr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.it.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.it.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.it.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ja.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ja.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ja.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ko.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ko.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ko.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.pl.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.pl.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.pl.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.pt-BR.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.pt-BR.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.pt-BR.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ru.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ru.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.ru.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.tr.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.tr.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.tr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.zh-Hans.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.zh-Hans.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.zh-Hans.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.zh-Hant.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.zh-Hant.json
new file mode 100644
index 000000000000..8d1d96d1fe79
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/localize/templatestrings.zh-Hant.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/template.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/template.json
new file mode 100644
index 000000000000..f5cd12a1c1d8
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/.template.config/template.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Microsoft",
+ "classifications": [ "macOS", "Mac Catalyst", "Test" ],
+ "groupIdentity": "Microsoft.MacCatalyst.MacCatalystTest",
+ "identity": "Microsoft.MacCatalyst.MacCatalystTest.CSharp",
+ "name": "Mac Catalyst Test Project",
+ "description": "A project for creating a .NET Mac Catalyst test project using MSTest",
+ "shortName": "maccatalysttest",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "MacCatalystTest1",
+ "sources": [
+ {
+ "source": "./",
+ "target": "./"
+ }
+ ],
+ "preferNameDirectory": true,
+ "primaryOutputs": [
+ { "path": "MacCatalystTest1.csproj" }
+ ],
+ "symbols": {
+ "bundleId": {
+ "type": "parameter",
+ "description": "Overrides the ApplicationId in the project file",
+ "datatype": "string",
+ "replaces": "com.companyname.MacCatalystTest1"
+ },
+ "minOSVersion": {
+ "type": "parameter",
+ "description": "Overrides SupportedOSPlatformVersion in the project file",
+ "replaces": "minOSVersion",
+ "datatype": "string",
+ "defaultValue": "17.0"
+ }
+ },
+ "defaultName": "MacCatalystTest1"
+}
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Info.plist b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Info.plist
new file mode 100644
index 000000000000..8fb7ffdcfd20
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Info.plist
@@ -0,0 +1,41 @@
+
+
+
+
+ CFBundleDisplayName
+ MacCatalystTest1
+ CFBundleIdentifier
+ com.companyname.MacCatalystTest1
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ UIDeviceFamily
+
+ 2
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneConfigurationName
+ Default Configuration
+ UISceneDelegateClassName
+ SceneDelegate
+
+
+
+
+
+
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/MacCatalystTest1.csproj b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/MacCatalystTest1.csproj
new file mode 100644
index 000000000000..7eefeea6d40d
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/MacCatalystTest1.csproj
@@ -0,0 +1,21 @@
+
+
+ net11.0-maccatalyst
+ MacCatalystTest1
+ Exe
+ enable
+ true
+ true
+ minOSVersion
+ com.companyname.MacCatalystTest1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Main.cs b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Main.cs
new file mode 100644
index 000000000000..c66e487fc13f
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Main.cs
@@ -0,0 +1,133 @@
+using System.Diagnostics;
+using Microsoft.Testing.Extensions;
+using Microsoft.Testing.Platform.Builder;
+using Microsoft.Testing.Platform.Extensions;
+using Microsoft.Testing.Platform.Extensions.Messages;
+using MacCatalystTest1;
+
+[assembly: Parallelize (Scope = ExecutionScope.MethodLevel)]
+
+// UIApplication.Main() provides a proper UIKit run loop,
+// preventing watchdog kills during long test runs.
+UIApplication.Main (args, null, typeof (AppDelegate));
+
+[Register ("AppDelegate")]
+class AppDelegate : UIApplicationDelegate {
+ public override UISceneConfiguration GetConfiguration (UIApplication application,
+ UISceneSession connectingSceneSession, UISceneConnectionOptions options)
+ {
+ return new UISceneConfiguration ("Default Configuration", connectingSceneSession.Role);
+ }
+}
+
+[Register ("SceneDelegate")]
+class SceneDelegate : UIResponder, IUIWindowSceneDelegate {
+ [Export ("window")]
+ public UIWindow? Window { get; set; }
+
+ [Export ("scene:willConnectToSession:options:")]
+ public void WillConnect (UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
+ {
+ if (scene is not UIWindowScene windowScene)
+ return;
+
+ Window = new UIWindow (windowScene);
+ var vc = new UIViewController ();
+ var view = vc.View;
+ Debug.Assert (view is not null, "UIViewController.View should not be null");
+ view.BackgroundColor = UIColor.SystemBackground;
+
+ var label = new UILabel {
+ Text = "Running tests...\n",
+ TextAlignment = UITextAlignment.Left,
+ Lines = 0,
+ Font = UIFont.GetMonospacedSystemFont (12, UIFontWeight.Regular)!,
+ TextColor = UIColor.Label,
+ TranslatesAutoresizingMaskIntoConstraints = false,
+ };
+ view.AddSubview (label);
+ var guide = view.SafeAreaLayoutGuide;
+ label.TopAnchor.ConstraintEqualTo (guide.TopAnchor, 8).Active = true;
+ label.LeadingAnchor.ConstraintEqualTo (guide.LeadingAnchor, 8).Active = true;
+ label.TrailingAnchor.ConstraintLessThanOrEqualTo (guide.TrailingAnchor, -8).Active = true;
+
+ Window.RootViewController = vc;
+ Window.MakeKeyAndVisible ();
+
+ var consumer = new ResultConsumer ();
+ consumer.StatusChanged += line =>
+ vc.InvokeOnMainThread (() => label.Text += line + "\n");
+
+ Task.Run (async () => {
+ try {
+ var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
+ var resultsPath = Path.Combine (documentsPath, "TestResults");
+
+ var builder = await TestApplication.CreateBuilderAsync ([
+ "--results-directory", resultsPath,
+ "--report-trx"
+ ]);
+ builder.AddMSTest (() => [typeof (Test1).Assembly]);
+ builder.AddTrxReportProvider ();
+ builder.TestHost.AddDataConsumer (_ => consumer);
+
+ using ITestApplication app = await builder.BuildAsync ();
+ await app.RunAsync ();
+ // UIApplication.Main() keeps the process alive, so exit explicitly
+ Environment.Exit (consumer.Failed > 0 ? 1 : 0);
+ } catch (Exception ex) {
+ Console.WriteLine ($"Error running tests: {ex}");
+ Environment.Exit (1);
+ }
+ });
+ }
+
+ class ResultConsumer : IDataConsumer {
+ int _passed, _failed, _skipped;
+ public int Passed => _passed;
+ public int Failed => _failed;
+ public int Skipped => _skipped;
+ public string? TrxReportPath;
+ public event Action? StatusChanged;
+
+ public string Uid => nameof (ResultConsumer);
+ public string DisplayName => nameof (ResultConsumer);
+ public string Description => "";
+ public string Version => "1.0";
+ public Task IsEnabledAsync () => Task.FromResult (true);
+
+ public Type [] DataTypesConsumed => [typeof (TestNodeUpdateMessage), typeof (SessionFileArtifact)];
+
+ public Task ConsumeAsync (IDataProducer dataProducer, IData value, CancellationToken cancellationToken)
+ {
+ if (value is SessionFileArtifact artifact) {
+ TrxReportPath = artifact.FileInfo.FullName;
+
+ Console.WriteLine ($"Results: passed={Passed}, failed={Failed}, skipped={Skipped}");
+ Console.WriteLine ($"TRX report: {TrxReportPath}");
+ StatusChanged?.Invoke ($"\n✅ {Passed} passed ❌ {Failed} failed ⏭️ {Skipped} skipped");
+ } else if (value is TestNodeUpdateMessage { TestNode: var node }) {
+ var state = node.Properties.SingleOrDefault ();
+ string? outcome = state switch {
+ PassedTestNodeStateProperty => "passed",
+ FailedTestNodeStateProperty or ErrorTestNodeStateProperty
+ or TimeoutTestNodeStateProperty => "failed",
+ SkippedTestNodeStateProperty => "skipped",
+ _ => null
+ };
+ if (outcome is null)
+ return Task.CompletedTask;
+
+ _ = outcome switch { "passed" => Interlocked.Increment (ref _passed), "failed" => Interlocked.Increment (ref _failed), _ => Interlocked.Increment (ref _skipped) };
+
+ var id = node.Properties.SingleOrDefault ();
+ var testName = id is not null ? $"{id.Namespace}.{id.TypeName}.{id.MethodName}" : node.DisplayName;
+ Console.WriteLine ($"[{outcome.ToUpperInvariant ()}] {testName}");
+
+ var icon = outcome switch { "passed" => "✅", "failed" => "❌", _ => "⏭️" };
+ StatusChanged?.Invoke ($"{icon} {testName}");
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Test1.cs b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Test1.cs
new file mode 100644
index 000000000000..5df74213d439
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/Test1.cs
@@ -0,0 +1,21 @@
+namespace MacCatalystTest1;
+
+[TestClass]
+public sealed class Test1 {
+ [TestMethod]
+ public void TestMethod1 ()
+ {
+ }
+
+ [TestMethod]
+ public void TestMethod2 ()
+ {
+ Assert.Fail ("This test is expected to fail");
+ }
+
+ [TestMethod]
+ public void TestMethod3 ()
+ {
+ Assert.Inconclusive ("This test is expected to be skipped");
+ }
+}
diff --git a/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/global.json b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/global.json
new file mode 100644
index 000000000000..3140116df397
--- /dev/null
+++ b/dotnet/Templates/Microsoft.MacCatalyst.Templates/maccatalysttest/csharp/global.json
@@ -0,0 +1,5 @@
+{
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ }
+}
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.cs.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.cs.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.cs.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.de.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.de.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.de.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.en.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.en.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.en.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.es.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.es.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.es.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.fr.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.fr.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.fr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.it.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.it.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.it.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ja.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ja.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ja.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ko.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ko.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ko.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.pl.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.pl.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.pl.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.pt-BR.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.pt-BR.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.pt-BR.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ru.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ru.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.ru.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.tr.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.tr.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.tr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.zh-Hans.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.zh-Hans.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.zh-Hans.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.zh-Hant.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.zh-Hant.json
new file mode 100644
index 000000000000..46c2d08fe27e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/localize/templatestrings.zh-Hant.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/template.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/template.json
new file mode 100644
index 000000000000..2380694a1a2b
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/.template.config/template.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Microsoft",
+ "classifications": [ "iOS", "Mobile", "Test" ],
+ "groupIdentity": "Microsoft.iOS.iOSTest",
+ "identity": "Microsoft.iOS.iOSTest.CSharp",
+ "name": "iOS Test Project",
+ "description": "A project for creating a .NET iOS test project using MSTest",
+ "shortName": "iostest",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "iOSTest1",
+ "sources": [
+ {
+ "source": "./",
+ "target": "./"
+ }
+ ],
+ "preferNameDirectory": true,
+ "primaryOutputs": [
+ { "path": "iOSTest1.csproj" }
+ ],
+ "symbols": {
+ "bundleId": {
+ "type": "parameter",
+ "description": "Overrides the ApplicationId in the project file",
+ "datatype": "string",
+ "replaces": "com.companyname.iOSTest1"
+ },
+ "minOSVersion": {
+ "type": "parameter",
+ "description": "Overrides SupportedOSPlatformVersion in the project file",
+ "replaces": "minOSVersion",
+ "datatype": "string",
+ "defaultValue": "13.0"
+ }
+ },
+ "defaultName": "iOSTest1"
+}
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Info.plist b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Info.plist
new file mode 100644
index 000000000000..17c9ab1147d6
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Info.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ CFBundleDisplayName
+ iOSTest1
+ CFBundleIdentifier
+ com.companyname.iOSTest1
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneConfigurationName
+ Default Configuration
+ UISceneDelegateClassName
+ SceneDelegate
+
+
+
+
+
+
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Main.cs b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Main.cs
new file mode 100644
index 000000000000..5210f24cfcc0
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Main.cs
@@ -0,0 +1,133 @@
+using System.Diagnostics;
+using Microsoft.Testing.Extensions;
+using Microsoft.Testing.Platform.Builder;
+using Microsoft.Testing.Platform.Extensions;
+using Microsoft.Testing.Platform.Extensions.Messages;
+using iOSTest1;
+
+[assembly: Parallelize (Scope = ExecutionScope.MethodLevel)]
+
+// UIApplication.Main() provides a proper UIKit run loop,
+// preventing iOS watchdog kills during long test runs.
+UIApplication.Main (args, null, typeof (AppDelegate));
+
+[Register ("AppDelegate")]
+class AppDelegate : UIApplicationDelegate {
+ public override UISceneConfiguration GetConfiguration (UIApplication application,
+ UISceneSession connectingSceneSession, UISceneConnectionOptions options)
+ {
+ return new UISceneConfiguration ("Default Configuration", connectingSceneSession.Role);
+ }
+}
+
+[Register ("SceneDelegate")]
+class SceneDelegate : UIResponder, IUIWindowSceneDelegate {
+ [Export ("window")]
+ public UIWindow? Window { get; set; }
+
+ [Export ("scene:willConnectToSession:options:")]
+ public void WillConnect (UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
+ {
+ if (scene is not UIWindowScene windowScene)
+ return;
+
+ Window = new UIWindow (windowScene);
+ var vc = new UIViewController ();
+ var view = vc.View;
+ Debug.Assert (view is not null, "UIViewController.View should not be null");
+ view.BackgroundColor = UIColor.SystemBackground;
+
+ var label = new UILabel {
+ Text = "Running tests...\n",
+ TextAlignment = UITextAlignment.Left,
+ Lines = 0,
+ Font = UIFont.GetMonospacedSystemFont (12, UIFontWeight.Regular)!,
+ TextColor = UIColor.Label,
+ TranslatesAutoresizingMaskIntoConstraints = false,
+ };
+ view.AddSubview (label);
+ var guide = view.SafeAreaLayoutGuide;
+ label.TopAnchor.ConstraintEqualTo (guide.TopAnchor, 8).Active = true;
+ label.LeadingAnchor.ConstraintEqualTo (guide.LeadingAnchor, 8).Active = true;
+ label.TrailingAnchor.ConstraintLessThanOrEqualTo (guide.TrailingAnchor, -8).Active = true;
+
+ Window.RootViewController = vc;
+ Window.MakeKeyAndVisible ();
+
+ var consumer = new ResultConsumer ();
+ consumer.StatusChanged += line =>
+ vc.InvokeOnMainThread (() => label.Text += line + "\n");
+
+ Task.Run (async () => {
+ try {
+ var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
+ var resultsPath = Path.Combine (documentsPath, "TestResults");
+
+ var builder = await TestApplication.CreateBuilderAsync ([
+ "--results-directory", resultsPath,
+ "--report-trx"
+ ]);
+ builder.AddMSTest (() => [typeof (Test1).Assembly]);
+ builder.AddTrxReportProvider ();
+ builder.TestHost.AddDataConsumer (_ => consumer);
+
+ using ITestApplication app = await builder.BuildAsync ();
+ await app.RunAsync ();
+ // UIApplication.Main() keeps the process alive, so exit explicitly
+ Environment.Exit (consumer.Failed > 0 ? 1 : 0);
+ } catch (Exception ex) {
+ Console.WriteLine ($"Error running tests: {ex}");
+ Environment.Exit (1);
+ }
+ });
+ }
+
+ class ResultConsumer : IDataConsumer {
+ int _passed, _failed, _skipped;
+ public int Passed => _passed;
+ public int Failed => _failed;
+ public int Skipped => _skipped;
+ public string? TrxReportPath;
+ public event Action? StatusChanged;
+
+ public string Uid => nameof (ResultConsumer);
+ public string DisplayName => nameof (ResultConsumer);
+ public string Description => "";
+ public string Version => "1.0";
+ public Task IsEnabledAsync () => Task.FromResult (true);
+
+ public Type [] DataTypesConsumed => [typeof (TestNodeUpdateMessage), typeof (SessionFileArtifact)];
+
+ public Task ConsumeAsync (IDataProducer dataProducer, IData value, CancellationToken cancellationToken)
+ {
+ if (value is SessionFileArtifact artifact) {
+ TrxReportPath = artifact.FileInfo.FullName;
+
+ Console.WriteLine ($"Results: passed={Passed}, failed={Failed}, skipped={Skipped}");
+ Console.WriteLine ($"TRX report: {TrxReportPath}");
+ StatusChanged?.Invoke ($"\n✅ {Passed} passed ❌ {Failed} failed ⏭️ {Skipped} skipped");
+ } else if (value is TestNodeUpdateMessage { TestNode: var node }) {
+ var state = node.Properties.SingleOrDefault ();
+ string? outcome = state switch {
+ PassedTestNodeStateProperty => "passed",
+ FailedTestNodeStateProperty or ErrorTestNodeStateProperty
+ or TimeoutTestNodeStateProperty => "failed",
+ SkippedTestNodeStateProperty => "skipped",
+ _ => null
+ };
+ if (outcome is null)
+ return Task.CompletedTask;
+
+ _ = outcome switch { "passed" => Interlocked.Increment (ref _passed), "failed" => Interlocked.Increment (ref _failed), _ => Interlocked.Increment (ref _skipped) };
+
+ var id = node.Properties.SingleOrDefault ();
+ var testName = id is not null ? $"{id.Namespace}.{id.TypeName}.{id.MethodName}" : node.DisplayName;
+ Console.WriteLine ($"[{outcome.ToUpperInvariant ()}] {testName}");
+
+ var icon = outcome switch { "passed" => "✅", "failed" => "❌", _ => "⏭️" };
+ StatusChanged?.Invoke ($"{icon} {testName}");
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Test1.cs b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Test1.cs
new file mode 100644
index 000000000000..bde71968be0b
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/Test1.cs
@@ -0,0 +1,21 @@
+namespace iOSTest1;
+
+[TestClass]
+public sealed class Test1 {
+ [TestMethod]
+ public void TestMethod1 ()
+ {
+ }
+
+ [TestMethod]
+ public void TestMethod2 ()
+ {
+ Assert.Fail ("This test is expected to fail");
+ }
+
+ [TestMethod]
+ public void TestMethod3 ()
+ {
+ Assert.Inconclusive ("This test is expected to be skipped");
+ }
+}
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/global.json b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/global.json
new file mode 100644
index 000000000000..3140116df397
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/global.json
@@ -0,0 +1,5 @@
+{
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ }
+}
diff --git a/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/iOSTest1.csproj b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/iOSTest1.csproj
new file mode 100644
index 000000000000..54fe10a19972
--- /dev/null
+++ b/dotnet/Templates/Microsoft.iOS.Templates/iostest/csharp/iOSTest1.csproj
@@ -0,0 +1,21 @@
+
+
+ net11.0-ios
+ iOSTest1
+ Exe
+ enable
+ true
+ true
+ minOSVersion
+ com.companyname.iOSTest1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.cs.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.cs.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.cs.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.de.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.de.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.de.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.en.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.en.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.en.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.es.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.es.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.es.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.fr.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.fr.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.fr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.it.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.it.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.it.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ja.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ja.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ja.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ko.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ko.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ko.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.pl.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.pl.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.pl.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.pt-BR.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.pt-BR.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.pt-BR.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ru.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ru.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.ru.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.tr.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.tr.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.tr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.zh-Hans.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.zh-Hans.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.zh-Hans.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.zh-Hant.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.zh-Hant.json
new file mode 100644
index 000000000000..2ecc02b6552e
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/localize/templatestrings.zh-Hant.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/template.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/template.json
new file mode 100644
index 000000000000..2b27f0581997
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/.template.config/template.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Microsoft",
+ "classifications": [ "macOS", "Test" ],
+ "groupIdentity": "Microsoft.macOS.macOSTest",
+ "identity": "Microsoft.macOS.macOSTest.CSharp",
+ "name": "macOS Test Project",
+ "description": "A project for creating a .NET macOS test project using MSTest",
+ "shortName": "macostest",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "macOSTest1",
+ "sources": [
+ {
+ "source": "./",
+ "target": "./"
+ }
+ ],
+ "preferNameDirectory": true,
+ "primaryOutputs": [
+ { "path": "macOSTest1.csproj" }
+ ],
+ "symbols": {
+ "bundleId": {
+ "type": "parameter",
+ "description": "Overrides the ApplicationId in the project file",
+ "datatype": "string",
+ "replaces": "com.companyname.macOSTest1"
+ },
+ "minOSVersion": {
+ "type": "parameter",
+ "description": "Overrides SupportedOSPlatformVersion in the project file",
+ "replaces": "minOSVersion",
+ "datatype": "string",
+ "defaultValue": "14.0"
+ }
+ },
+ "defaultName": "macOSTest1"
+}
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Info.plist b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Info.plist
new file mode 100644
index 000000000000..42bd28a3c698
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleName
+ macOSTest1
+ CFBundleIdentifier
+ com.companyname.macOSTest1
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ CFBundleDevelopmentRegion
+ en
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ APPL
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Main.cs b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Main.cs
new file mode 100644
index 000000000000..3c3c16808d33
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Main.cs
@@ -0,0 +1,122 @@
+using Microsoft.Testing.Extensions;
+using Microsoft.Testing.Platform.Builder;
+using Microsoft.Testing.Platform.Extensions;
+using Microsoft.Testing.Platform.Extensions.Messages;
+using macOSTest1;
+
+[assembly: Parallelize (Scope = ExecutionScope.MethodLevel)]
+
+// NSApplication.Main() provides a proper AppKit run loop.
+NSApplication.Init ();
+
+var app = NSApplication.SharedApplication;
+app.Delegate = new AppDelegate ();
+app.Run ();
+
+[Register ("AppDelegate")]
+class AppDelegate : NSApplicationDelegate {
+ NSWindow? _window;
+
+ public override void DidFinishLaunching (NSNotification notification)
+ {
+ _window = new NSWindow (
+ new CoreGraphics.CGRect (0, 0, 600, 400),
+ NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Resizable,
+ NSBackingStore.Buffered,
+ false
+ ) {
+ Title = "Running tests...",
+ };
+
+ var label = new NSTextField {
+ Editable = false,
+ Bordered = false,
+ BackgroundColor = NSColor.WindowBackground,
+ StringValue = "Running tests...\n",
+ TranslatesAutoresizingMaskIntoConstraints = false,
+ };
+ _window.ContentView!.AddSubview (label);
+ label.TopAnchor.ConstraintEqualTo (_window.ContentView.TopAnchor, 8).Active = true;
+ label.LeadingAnchor.ConstraintEqualTo (_window.ContentView.LeadingAnchor, 8).Active = true;
+ label.TrailingAnchor.ConstraintEqualTo (_window.ContentView.TrailingAnchor, -8).Active = true;
+
+ _window.Center ();
+ _window.MakeKeyAndOrderFront (this);
+
+ var consumer = new ResultConsumer ();
+ consumer.StatusChanged += line =>
+ NSRunLoop.Main.InvokeOnMainThread (() => label.StringValue += line + "\n");
+
+ Task.Run (async () => {
+ try {
+ var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
+ var resultsPath = Path.Combine (documentsPath, "TestResults");
+
+ var builder = await TestApplication.CreateBuilderAsync ([
+ "--results-directory", resultsPath,
+ "--report-trx"
+ ]);
+ builder.AddMSTest (() => [typeof (Test1).Assembly]);
+ builder.AddTrxReportProvider ();
+ builder.TestHost.AddDataConsumer (_ => consumer);
+
+ using ITestApplication testApp = await builder.BuildAsync ();
+ await testApp.RunAsync ();
+ // NSApplication.Run() keeps the process alive, so exit explicitly
+ Environment.Exit (consumer.Failed > 0 ? 1 : 0);
+ } catch (Exception ex) {
+ Console.WriteLine ($"Error running tests: {ex}");
+ Environment.Exit (1);
+ }
+ });
+ }
+
+ class ResultConsumer : IDataConsumer {
+ int _passed, _failed, _skipped;
+ public int Passed => _passed;
+ public int Failed => _failed;
+ public int Skipped => _skipped;
+ public string? TrxReportPath;
+ public event Action? StatusChanged;
+
+ public string Uid => nameof (ResultConsumer);
+ public string DisplayName => nameof (ResultConsumer);
+ public string Description => "";
+ public string Version => "1.0";
+ public Task IsEnabledAsync () => Task.FromResult (true);
+
+ public Type [] DataTypesConsumed => [typeof (TestNodeUpdateMessage), typeof (SessionFileArtifact)];
+
+ public Task ConsumeAsync (IDataProducer dataProducer, IData value, CancellationToken cancellationToken)
+ {
+ if (value is SessionFileArtifact artifact) {
+ TrxReportPath = artifact.FileInfo.FullName;
+
+ Console.WriteLine ($"Results: passed={Passed}, failed={Failed}, skipped={Skipped}");
+ Console.WriteLine ($"TRX report: {TrxReportPath}");
+ StatusChanged?.Invoke ($"\n✅ {Passed} passed ❌ {Failed} failed ⏭️ {Skipped} skipped");
+ } else if (value is TestNodeUpdateMessage { TestNode: var node }) {
+ var state = node.Properties.SingleOrDefault ();
+ string? outcome = state switch {
+ PassedTestNodeStateProperty => "passed",
+ FailedTestNodeStateProperty or ErrorTestNodeStateProperty
+ or TimeoutTestNodeStateProperty => "failed",
+ SkippedTestNodeStateProperty => "skipped",
+ _ => null
+ };
+ if (outcome is null)
+ return Task.CompletedTask;
+
+ _ = outcome switch { "passed" => Interlocked.Increment (ref _passed), "failed" => Interlocked.Increment (ref _failed), _ => Interlocked.Increment (ref _skipped) };
+
+ var id = node.Properties.SingleOrDefault ();
+ var testName = id is not null ? $"{id.Namespace}.{id.TypeName}.{id.MethodName}" : node.DisplayName;
+ Console.WriteLine ($"[{outcome.ToUpperInvariant ()}] {testName}");
+
+ var icon = outcome switch { "passed" => "✅", "failed" => "❌", _ => "⏭️" };
+ StatusChanged?.Invoke ($"{icon} {testName}");
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Test1.cs b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Test1.cs
new file mode 100644
index 000000000000..56ae1eaa0d9c
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/Test1.cs
@@ -0,0 +1,21 @@
+namespace macOSTest1;
+
+[TestClass]
+public sealed class Test1 {
+ [TestMethod]
+ public void TestMethod1 ()
+ {
+ }
+
+ [TestMethod]
+ public void TestMethod2 ()
+ {
+ Assert.Fail ("This test is expected to fail");
+ }
+
+ [TestMethod]
+ public void TestMethod3 ()
+ {
+ Assert.Inconclusive ("This test is expected to be skipped");
+ }
+}
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/global.json b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/global.json
new file mode 100644
index 000000000000..3140116df397
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/global.json
@@ -0,0 +1,5 @@
+{
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ }
+}
diff --git a/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/macOSTest1.csproj b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/macOSTest1.csproj
new file mode 100644
index 000000000000..5e4e86c60c20
--- /dev/null
+++ b/dotnet/Templates/Microsoft.macOS.Templates/macostest/csharp/macOSTest1.csproj
@@ -0,0 +1,21 @@
+
+
+ net11.0-macos
+ macOSTest1
+ Exe
+ enable
+ true
+ true
+ minOSVersion
+ com.companyname.macOSTest1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.cs.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.cs.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.cs.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.de.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.de.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.de.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.en.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.en.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.en.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.es.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.es.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.es.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.fr.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.fr.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.fr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.it.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.it.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.it.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ja.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ja.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ja.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ko.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ko.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ko.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.pl.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.pl.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.pl.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.pt-BR.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.pt-BR.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.pt-BR.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ru.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ru.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.ru.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.tr.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.tr.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.tr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.zh-Hans.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.zh-Hans.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.zh-Hans.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.zh-Hant.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.zh-Hant.json
new file mode 100644
index 000000000000..7f4b9444b6d7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/localize/templatestrings.zh-Hant.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "symbols/bundleId/description": "Overrides the ApplicationId in the project file",
+ "symbols/minOSVersion/description": "Overrides SupportedOSPlatformVersion in the project file"
+}
\ No newline at end of file
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/template.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/template.json
new file mode 100644
index 000000000000..cc49221741c7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/.template.config/template.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Microsoft",
+ "classifications": [ "tvOS", "Mobile", "Test" ],
+ "groupIdentity": "Microsoft.tvOS.tvOSTest",
+ "identity": "Microsoft.tvOS.tvOSTest.CSharp",
+ "name": "tvOS Test Project",
+ "description": "A project for creating a .NET tvOS test project using MSTest",
+ "shortName": "tvostest",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "tvOSTest1",
+ "sources": [
+ {
+ "source": "./",
+ "target": "./"
+ }
+ ],
+ "preferNameDirectory": true,
+ "primaryOutputs": [
+ { "path": "tvOSTest1.csproj" }
+ ],
+ "symbols": {
+ "bundleId": {
+ "type": "parameter",
+ "description": "Overrides the ApplicationId in the project file",
+ "datatype": "string",
+ "replaces": "com.companyname.tvOSTest1"
+ },
+ "minOSVersion": {
+ "type": "parameter",
+ "description": "Overrides SupportedOSPlatformVersion in the project file",
+ "replaces": "minOSVersion",
+ "datatype": "string",
+ "defaultValue": "13.0"
+ }
+ },
+ "defaultName": "tvOSTest1"
+}
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Info.plist b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Info.plist
new file mode 100644
index 000000000000..93d8ef644dd0
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDisplayName
+ tvOSTest1
+ CFBundleIdentifier
+ com.companyname.tvOSTest1
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ UIDeviceFamily
+
+ 3
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+
+
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Main.cs b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Main.cs
new file mode 100644
index 000000000000..f6ce6b4b9ec7
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Main.cs
@@ -0,0 +1,93 @@
+using Microsoft.Testing.Extensions;
+using Microsoft.Testing.Platform.Builder;
+using Microsoft.Testing.Platform.Extensions;
+using Microsoft.Testing.Platform.Extensions.Messages;
+using tvOSTest1;
+
+[assembly: Parallelize (Scope = ExecutionScope.MethodLevel)]
+
+// UIApplication.Main() provides a proper UIKit run loop,
+// preventing tvOS watchdog kills during long test runs.
+UIApplication.Main (args, null, typeof (AppDelegate));
+
+[Register ("AppDelegate")]
+class AppDelegate : UIApplicationDelegate {
+ public override UIWindow? Window {
+ get;
+ set;
+ }
+
+ public override bool FinishedLaunching (UIApplication application, NSDictionary? launchOptions)
+ {
+ var consumer = new ResultConsumer ();
+
+ Task.Run (async () => {
+ try {
+ var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
+ var resultsPath = Path.Combine (documentsPath, "TestResults");
+
+ var builder = await TestApplication.CreateBuilderAsync ([
+ "--results-directory", resultsPath,
+ "--report-trx"
+ ]);
+ builder.AddMSTest (() => [typeof (Test1).Assembly]);
+ builder.AddTrxReportProvider ();
+ builder.TestHost.AddDataConsumer (_ => consumer);
+
+ using ITestApplication app = await builder.BuildAsync ();
+ await app.RunAsync ();
+ // UIApplication.Main() keeps the process alive, so exit explicitly
+ Environment.Exit (consumer.Failed > 0 ? 1 : 0);
+ } catch (Exception ex) {
+ Console.WriteLine ($"Error running tests: {ex}");
+ Environment.Exit (1);
+ }
+ });
+
+ return true;
+ }
+
+ class ResultConsumer : IDataConsumer {
+ int _passed, _failed, _skipped;
+ public int Passed => _passed;
+ public int Failed => _failed;
+ public int Skipped => _skipped;
+ public string? TrxReportPath;
+
+ public string Uid => nameof (ResultConsumer);
+ public string DisplayName => nameof (ResultConsumer);
+ public string Description => "";
+ public string Version => "1.0";
+ public Task IsEnabledAsync () => Task.FromResult (true);
+
+ public Type [] DataTypesConsumed => [typeof (TestNodeUpdateMessage), typeof (SessionFileArtifact)];
+
+ public Task ConsumeAsync (IDataProducer dataProducer, IData value, CancellationToken cancellationToken)
+ {
+ if (value is SessionFileArtifact artifact) {
+ TrxReportPath = artifact.FileInfo.FullName;
+
+ Console.WriteLine ($"Results: passed={Passed}, failed={Failed}, skipped={Skipped}");
+ Console.WriteLine ($"TRX report: {TrxReportPath}");
+ } else if (value is TestNodeUpdateMessage { TestNode: var node }) {
+ var state = node.Properties.SingleOrDefault ();
+ string? outcome = state switch {
+ PassedTestNodeStateProperty => "passed",
+ FailedTestNodeStateProperty or ErrorTestNodeStateProperty
+ or TimeoutTestNodeStateProperty => "failed",
+ SkippedTestNodeStateProperty => "skipped",
+ _ => null
+ };
+ if (outcome is null)
+ return Task.CompletedTask;
+
+ _ = outcome switch { "passed" => Interlocked.Increment (ref _passed), "failed" => Interlocked.Increment (ref _failed), _ => Interlocked.Increment (ref _skipped) };
+
+ var id = node.Properties.SingleOrDefault ();
+ var testName = id is not null ? $"{id.Namespace}.{id.TypeName}.{id.MethodName}" : node.DisplayName;
+ Console.WriteLine ($"[{outcome.ToUpperInvariant ()}] {testName}");
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Test1.cs b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Test1.cs
new file mode 100644
index 000000000000..75b8f1aa5848
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/Test1.cs
@@ -0,0 +1,21 @@
+namespace tvOSTest1;
+
+[TestClass]
+public sealed class Test1 {
+ [TestMethod]
+ public void TestMethod1 ()
+ {
+ }
+
+ [TestMethod]
+ public void TestMethod2 ()
+ {
+ Assert.Fail ("This test is expected to fail");
+ }
+
+ [TestMethod]
+ public void TestMethod3 ()
+ {
+ Assert.Inconclusive ("This test is expected to be skipped");
+ }
+}
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/global.json b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/global.json
new file mode 100644
index 000000000000..3140116df397
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/global.json
@@ -0,0 +1,5 @@
+{
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ }
+}
diff --git a/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/tvOSTest1.csproj b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/tvOSTest1.csproj
new file mode 100644
index 000000000000..709aa6e78942
--- /dev/null
+++ b/dotnet/Templates/Microsoft.tvOS.Templates/tvostest/csharp/tvOSTest1.csproj
@@ -0,0 +1,21 @@
+
+
+ net11.0-tvos
+ tvOSTest1
+ Exe
+ enable
+ true
+ true
+ minOSVersion
+ com.companyname.tvOSTest1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.MSTest.props b/dotnet/targets/Xamarin.Shared.Sdk.MSTest.props
new file mode 100644
index 000000000000..83ced6200365
--- /dev/null
+++ b/dotnet/targets/Xamarin.Shared.Sdk.MSTest.props
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ all
+
+ false
+
+ false
+
+ true
+
+
+
+
+
+
+
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.Trimming.props b/dotnet/targets/Xamarin.Shared.Sdk.Trimming.props
index f2734028ef04..77e7138d9b51 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.Trimming.props
+++ b/dotnet/targets/Xamarin.Shared.Sdk.Trimming.props
@@ -46,6 +46,7 @@
<_DefaultLinkMode>TrimMode
+
<_DefaultLinkMode Condition="'$(_DefaultLinkMode)' == '' And '$(_UseNativeAot)' == 'true'">Full
diff --git a/tests/dotnet/UnitTests/TemplateTest.cs b/tests/dotnet/UnitTests/TemplateTest.cs
index 11da53ac3e5a..3696741ca273 100644
--- a/tests/dotnet/UnitTests/TemplateTest.cs
+++ b/tests/dotnet/UnitTests/TemplateTest.cs
@@ -104,24 +104,28 @@ public enum TemplateType {
new TemplateInfo (ApplePlatform.iOS, "iosbinding", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.iOS, "ios-notification-content-extension", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.iOS, "ios-notification-service-extension", TemplateLanguage.CSharp),
+ new TemplateInfo (ApplePlatform.iOS, "iostest", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.TVOS, "tvos", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.TVOS, "tvos", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.TVOS, "tvoslib", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.TVOS, "tvoslib", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.TVOS, "tvosbinding", TemplateLanguage.CSharp),
+ new TemplateInfo (ApplePlatform.TVOS, "tvostest", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst", TemplateLanguage.CSharp, execute: true),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst", TemplateLanguage.VisualBasic, execute: true),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalystlib", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalystlib", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalystbinding", TemplateLanguage.CSharp),
+ new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalysttest", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacOSX, "macos", TemplateLanguage.CSharp, execute: true),
new TemplateInfo (ApplePlatform.MacOSX, "macos", TemplateLanguage.VisualBasic, execute: true),
new TemplateInfo (ApplePlatform.MacOSX, "macoslib", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacOSX, "macoslib", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.MacOSX, "macosbinding", TemplateLanguage.CSharp),
+ new TemplateInfo (ApplePlatform.MacOSX, "macostest", TemplateLanguage.CSharp),
/* item templates */
new TemplateInfo (ApplePlatform.iOS, "ios-controller"),