diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/AppThemeShouldChangeDarkTheme.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/AppThemeShouldChangeDarkTheme.png
new file mode 100644
index 000000000000..66c4af527fd2
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/AppThemeShouldChangeDarkTheme.png differ
diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/AppThemeShouldChangeLightTheme.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/AppThemeShouldChangeLightTheme.png
new file mode 100644
index 000000000000..c91623cc6593
Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/AppThemeShouldChangeLightTheme.png differ
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/ThemeChange.xaml b/src/Controls/tests/TestCases.HostApp/Issues/ThemeChange.xaml
new file mode 100644
index 000000000000..5721a4e8e688
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/ThemeChange.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.HostApp/Issues/ThemeChange.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/ThemeChange.xaml.cs
new file mode 100644
index 000000000000..867f458ac0d5
--- /dev/null
+++ b/src/Controls/tests/TestCases.HostApp/Issues/ThemeChange.xaml.cs
@@ -0,0 +1,15 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Controls.Xaml;
+
+namespace Maui.Controls.Sample.Issues
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ [Issue(IssueTracker.None, 2, "UI theme change during the runtime", PlatformAffected.Android | PlatformAffected.iOS)]
+ public partial class ThemeChange : ContentPage
+ {
+ public ThemeChange()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/ThemeChange.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/ThemeChange.cs
new file mode 100644
index 000000000000..be0ba5dc1137
--- /dev/null
+++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/ThemeChange.cs
@@ -0,0 +1,40 @@
+#if ANDROID || IOS
+using NUnit.Framework;
+using UITest.Appium;
+using UITest.Core;
+
+namespace Microsoft.Maui.TestCases.Tests.Issues
+{
+ public class ThemeChange : _IssuesUITest
+ {
+ public override string Issue => "UI theme change during the runtime";
+
+ public ThemeChange(TestDevice device) : base(device)
+ {
+ }
+
+ [Test]
+ [Category(UITestCategories.LifeCycle)]
+ public void AppThemeShouldChange()
+ {
+ try
+ {
+ App.SetLightTheme();
+ _ = App.WaitForElement("labelVisibleOnlyInLightMode");
+
+ App.SetDarkTheme();
+ _ = App.WaitForElement("labelVisibleOnlyInDarkMode");
+ VerifyScreenshot("AppThemeShouldChangeDarkTheme");
+
+ App.SetLightTheme();
+ _ = App.WaitForElement("labelVisibleOnlyInLightMode");
+ VerifyScreenshot("AppThemeShouldChangeLightTheme");
+ }
+ finally
+ {
+ App.SetLightTheme();
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/AppThemeShouldChangeDarkTheme.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/AppThemeShouldChangeDarkTheme.png
new file mode 100644
index 000000000000..02f9c00d798c
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/AppThemeShouldChangeDarkTheme.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/AppThemeShouldChangeLightTheme.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/AppThemeShouldChangeLightTheme.png
new file mode 100644
index 000000000000..e15dc869a173
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/AppThemeShouldChangeLightTheme.png differ
diff --git a/src/TestUtils/src/UITest.Appium/Actions/AppiumAndroidThemeChangeAction.cs b/src/TestUtils/src/UITest.Appium/Actions/AppiumAndroidThemeChangeAction.cs
new file mode 100644
index 000000000000..ecb720e359e7
--- /dev/null
+++ b/src/TestUtils/src/UITest.Appium/Actions/AppiumAndroidThemeChangeAction.cs
@@ -0,0 +1,73 @@
+using System.Diagnostics;
+using UITest.Core;
+
+namespace UITest.Appium
+{
+ public class AppiumAndroidThemeChangeAction : ICommandExecutionGroup
+ {
+ const string SetLightTheme = "setLightTheme";
+ const string SetDarkTheme = "setDarkTheme";
+
+ readonly List _commands = new()
+ {
+ SetLightTheme,
+ SetDarkTheme
+ };
+
+ public CommandResponse Execute(string commandName, IDictionary parameters)
+ {
+ if (commandName == SetLightTheme)
+ {
+ ExecuteAdbCommand($"adb shell cmd uimode night no");
+ return CommandResponse.SuccessEmptyResponse;
+ }
+ else if (commandName == SetDarkTheme)
+ {
+ ExecuteAdbCommand($"adb shell cmd uimode night yes");
+ return CommandResponse.SuccessEmptyResponse;
+ }
+
+ return CommandResponse.FailedEmptyResponse;
+ }
+
+ public bool IsCommandSupported(string commandName)
+ {
+ return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);
+ }
+
+ private static void ExecuteAdbCommand(string command)
+ {
+ var shell = GetShell();
+ var shellArgument = GetShellArgument(shell, command);
+
+ var processInfo = new ProcessStartInfo(shell, shellArgument)
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+
+ var process = new Process { StartInfo = processInfo };
+
+ process.Start();
+ process.WaitForExit();
+ }
+
+ private static string GetShell()
+ {
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ return "cmd.exe";
+ else
+ return "/bin/bash";
+ }
+
+ private static string GetShellArgument(string shell, string command)
+ {
+ if (shell == "cmd.exe")
+ return $"/C {command}";
+ else
+ return $"-c \"{command}\"";
+ }
+ }
+}
diff --git a/src/TestUtils/src/UITest.Appium/Actions/AppiumIOSThemeChangeAction.cs b/src/TestUtils/src/UITest.Appium/Actions/AppiumIOSThemeChangeAction.cs
new file mode 100644
index 000000000000..73c5b57f6ad1
--- /dev/null
+++ b/src/TestUtils/src/UITest.Appium/Actions/AppiumIOSThemeChangeAction.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Diagnostics;
+using UITest.Core;
+
+namespace UITest.Appium
+{
+ public class AppiumIOSThemeChangeAction : ICommandExecutionGroup
+ {
+ const string SetLightTheme = "setLightTheme";
+ const string SetDarkTheme = "setDarkTheme";
+
+ protected readonly AppiumApp _app;
+
+ public AppiumIOSThemeChangeAction(AppiumApp app)
+ {
+ _app = app;
+ }
+
+ readonly List _commands = new()
+ {
+ SetLightTheme,
+ SetDarkTheme
+ };
+
+ public CommandResponse Execute(string commandName, IDictionary parameters)
+ {
+ if (commandName == SetLightTheme)
+ {
+ var args = new Dictionary { { "style", "light" } };
+ _app.Driver.ExecuteScript("mobile: setAppearance", args);
+ return CommandResponse.SuccessEmptyResponse;
+ }
+ else if(commandName == SetDarkTheme)
+ {
+ var args = new Dictionary { { "style", "dark" } };
+ _app.Driver.ExecuteScript("mobile: setAppearance", args);
+ return CommandResponse.SuccessEmptyResponse;
+ }
+
+ return CommandResponse.FailedEmptyResponse;
+ }
+
+ public bool IsCommandSupported(string commandName)
+ {
+ return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase);
+ }
+ }
+}
+
diff --git a/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs b/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs
index 6200d100fbe1..b1b4def20a45 100644
--- a/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs
+++ b/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs
@@ -10,6 +10,7 @@ public class AppiumAndroidApp : AppiumApp, IAndroidApp
private AppiumAndroidApp(Uri remoteAddress, IConfig config)
: base(new AndroidDriver(remoteAddress, GetOptions(config)), config)
{
+ _commandExecutor.AddCommandGroup(new AppiumAndroidThemeChangeAction());
_commandExecutor.AddCommandGroup(new AppiumAndroidVirtualKeyboardActions(this));
_commandExecutor.AddCommandGroup(new AppiumAndroidAlertActions(this));
}
diff --git a/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs b/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs
index 29ffba9ad99c..56c707dc6c63 100644
--- a/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs
+++ b/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs
@@ -14,7 +14,9 @@ public AppiumIOSApp(Uri remoteAddress, IConfig config)
_commandExecutor.AddCommandGroup(new AppiumIOSMouseActions(this));
_commandExecutor.AddCommandGroup(new AppiumIOSTouchActions(this));
_commandExecutor.AddCommandGroup(new AppiumIOSVirtualKeyboardActions(this));
+ _commandExecutor.AddCommandGroup(new AppiumIOSThemeChangeAction(this));
_commandExecutor.AddCommandGroup(new AppiumIOSAlertActions(this));
+ _commandExecutor.AddCommandGroup(new AppiumIOSThemeChangeAction(this));
}
public override ApplicationState AppState
diff --git a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs
index 8822c3cebf9d..5bd1060d55d3 100644
--- a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs
+++ b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs
@@ -831,6 +831,34 @@ public static TestDevice GetTestDevice(this IApp app)
return aaa.Config.GetProperty("TestDevice");
}
+ ///
+ /// Sets light device's theme
+ ///
+ /// Represents the main gateway to interact with an app.
+ public static void SetLightTheme(this IApp app)
+ {
+ if (app is not AppiumAndroidApp && app is not AppiumIOSApp)
+ {
+ throw new InvalidOperationException($"SetLightTheme is not supported");
+ }
+
+ app.CommandExecutor.Execute("setLightTheme", ImmutableDictionary.Empty);
+ }
+
+ ///
+ /// Sets dark device's theme
+ ///
+ /// Represents the main gateway to interact with an app.
+ public static void SetDarkTheme(this IApp app)
+ {
+ if (app is not AppiumAndroidApp && app is not AppiumIOSApp)
+ {
+ throw new InvalidOperationException($"SetDarkTheme is not supported");
+ }
+
+ app.CommandExecutor.Execute("setDarkTheme", ImmutableDictionary.Empty);
+ }
+
///
/// Check if element has focused
///