diff --git a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs index 81b17e6eb38a..01444ecee66e 100644 --- a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs +++ b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs @@ -242,42 +242,6 @@ await InvokeOnMainThreadAsync(async () => await AssertionExtensions.WaitForGC(viewReference, handlerReference, platformViewReference); } - [Fact("WebView Does Not Leak")] - public async Task WebViewDoesNotLeak() - { - SetupBuilder(); - - var references = new List(); - var navPage = new NavigationPage(new ContentPage { Title = "Page 1" }); - - await CreateHandlerAndAddToWindow(new Window(navPage), async () => - { - { - var webView = new WebView - { - HeightRequest = 500, // NOTE: non-zero size required for Windows - Source = new HtmlWebViewSource { Html = "

hi

" }, - }; - var page = new ContentPage - { - Content = new VerticalStackLayout { webView } - }; - await navPage.Navigation.PushAsync(page); - await OnLoadedAsync(page); - await Task.Delay(1000); // give the WebView time to load - - references.Add(new(webView)); - references.Add(new(webView.Handler)); - references.Add(new(webView.Handler.PlatformView)); - - await navPage.Navigation.PopAsync(); - } - - // Assert *before* the Window is closed - await AssertionExtensions.WaitForGC(references.ToArray()); - }); - } - [Theory("Gesture Does Not Leak")] [InlineData(typeof(DragGestureRecognizer))] [InlineData(typeof(DropGestureRecognizer))] diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue22972.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue22972.cs new file mode 100644 index 000000000000..d8af187a9006 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue22972.cs @@ -0,0 +1,20 @@ +using Microsoft.Maui.Controls; + +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 22972, "Win platform WebView cannot be release after its parent window get close")] + public class Issue22972 : NavigationPage + { + public Issue22972() + { + this.RunMemoryTest(() => + { + return new WebView + { + HeightRequest = 500, // NOTE: non-zero size required for Windows + Source = new HtmlWebViewSource { Html = "

hi

" }, + }; + }); + } + } +} diff --git a/src/Controls/tests/TestCases.HostApp/Utils/GarbageCollectionHelper.cs b/src/Controls/tests/TestCases.HostApp/Utils/GarbageCollectionHelper.cs index d366cc4b37f3..a5e22c447dbe 100644 --- a/src/Controls/tests/TestCases.HostApp/Utils/GarbageCollectionHelper.cs +++ b/src/Controls/tests/TestCases.HostApp/Utils/GarbageCollectionHelper.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Text; -using System.Threading.Tasks; - namespace Maui.Controls.Sample { public static class GarbageCollectionHelper @@ -50,5 +48,78 @@ public static async Task AssertEventually(this Func assertion, int timeout throw new Exception(message); } } + + public static void RunMemoryTest(this NavigationPage navigationPage, Func elementToTest) + { + ContentPage rootPage = new ContentPage { Title = "Page 1" }; + navigationPage.PushAsync(rootPage); + rootPage.Content = new VerticalStackLayout() + { + new Label + { + Text = "If you don't see a success label this test has failed" + } + }; + + rootPage.Loaded += OnPageLoaded; + + async void OnPageLoaded(object sender, EventArgs e) + { + var references = new List(); + rootPage.Loaded -= OnPageLoaded; + + { + var element = elementToTest(); + var page = new ContentPage + { + Content = new VerticalStackLayout { element } + }; + + await navigationPage.PushAsync(page); + await Task.Delay(500); // give the View time to load + + references.Add(new(element)); + references.Add(new(element.Handler)); + references.Add(new(element.Handler.PlatformView)); + + await navigationPage.PopAsync(); + } + + try + { + rootPage.Content = new VerticalStackLayout() + { + new Label + { + Text = "Waiting for resources to cleanup", + AutomationId = "Waiting" + + } + }; + + await WaitForGC(references.ToArray()); + rootPage.Content = new VerticalStackLayout() + { + new Label + { + Text = "Success, everything has been cleaned up", + AutomationId = "Success" + } + }; + } + catch + { + var stillAlive = references.Where(x=> x.IsAlive).Select(x=> x.Target).ToList(); + rootPage.Content = new VerticalStackLayout() + { + new Label + { + Text = "Failed to cleanup: " + string.Join(", ", stillAlive), + AutomationId = "Failed" + } + }; + } + } + } } } diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue22972.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue22972.cs new file mode 100644 index 000000000000..5508b3f0fff1 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue22972.cs @@ -0,0 +1,22 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues +{ + public class Issue22972 : _IssuesUITest + { + public Issue22972(TestDevice device) : base(device) + { + } + + public override string Issue => "Win platform WebView cannot be release after its parent window get close"; + + [Test] + [Category(UITestCategories.WebView)] + public void WebViewDoesNotLeak() + { + App.AssertMemoryTest(); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs b/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs index 801862b6f34f..2c63caa036c7 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs @@ -1,4 +1,5 @@ using System.Drawing; +using NUnit.Framework; using UITest.Appium; using UITest.Appium.NUnit; using UITest.Core; @@ -56,5 +57,23 @@ public static int CenterY(this Rectangle rect) { return rect.Y + rect.Height / 2; } + + public static void AssertMemoryTest(this IApp app) + { + try + { + app.WaitForElement("Success", timeout: TimeSpan.FromSeconds(10)); + } + catch + { + var failure = app.FindElement("Failed")?.GetText(); + if(failure is not null) + { + Assert.Fail(failure); + } + + throw; + } + } } } \ No newline at end of file