From b584afded66f4daa3138e3a3f07240dcabdebce6 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 12 Aug 2024 16:52:10 -0500 Subject: [PATCH 1/2] Move EnsureSupportForCustomWebViewClients to Appium --- .../TestCases.HostApp/Issues/Issue16032.cs | 174 ++++++++++++++++++ .../Resources/Raw/extracontent.html | 0 .../Tests/Issues/Issue16032.cs | 24 +++ .../WebView/WebViewHandlerTests.Android.cs | 140 -------------- 4 files changed, 198 insertions(+), 140 deletions(-) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue16032.cs rename src/{Core/tests/DeviceTests => Controls/tests/TestCases.HostApp}/Resources/Raw/extracontent.html (100%) create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue16032.cs diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue16032.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue16032.cs new file mode 100644 index 000000000000..19338c1490cc --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue16032.cs @@ -0,0 +1,174 @@ +#if ANDROID +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Handlers; +using AWebView = Android.Webkit.WebView; +using IWebResourceRequest = Android.Webkit.IWebResourceRequest; +using Microsoft.Maui.Platform; +using WebResourceResponse = Android.Webkit.WebResourceResponse; + +namespace Maui.Controls.Sample.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 16032, "Improve the customization of WebView on Android", PlatformAffected.Android)] + public class Issue16032 : ContentPage + { + public Issue16032() + { + Content = new VerticalStackLayout() + { + new Issue16032WebView + { + Background = new SolidPaint(Colors.Red), + WidthRequest = 300, + HeightRequest = 300 + } + }; + } + + class Issue16032WebView : WebView, IPropertyMapperView + { + PropertyMapper TestMapper = + new PropertyMapper(WebViewHandler.Mapper); + + public Issue16032WebView() + { + TestMapper.ModifyMapping( + "WebViewClient", + (handler, view, setter) => + { + WebClient ??= new CustomWebClient((WebViewHandler)handler); + handler.PlatformView.SetWebViewClient(WebClient); + }); + + } + + protected async override void OnHandlerChanged() + { + if (Handler is not WebViewHandler webViewHandler) + { + return; + } + + var platformWebView = webViewHandler.PlatformView; + platformWebView.Settings.AllowFileAccess = true; + + Source = new UrlWebViewSource { Url = "extracontent.html" }; + + var tcsLoaded = new TaskCompletionSource(); + var tcsNavigating = new TaskCompletionSource(); + var tcsRequested = new TaskCompletionSource(); + + // if the timeout happens, cancel everything + var pageLoadTimeout = TimeSpan.FromSeconds(30); + var ctsTimeout = new CancellationTokenSource(pageLoadTimeout); + ctsTimeout.Token.Register(() => + { + tcsRequested.TrySetException(new TimeoutException($"Failed to request the image")); + tcsNavigating.TrySetException(new TimeoutException($"Failed to navigate to the loaded page")); + tcsLoaded.TrySetException(new TimeoutException($"Failed to load HTML")); + }); + + // attach some event handlers to track things + var navigatingCount = 0; + Navigating += ((sender, args) => + { + navigatingCount++; + + if (args.Url == "file:///android_asset/extracontent.html") + tcsNavigating.TrySetResult(); + }); + var shouldRequestCount = 0; + ShouldInterceptRequestDelegate = new((view, request) => + { + shouldRequestCount++; + + if (request.Url.ToString().StartsWith("https://raw.githubusercontent.com/dotnet/maui/4c096c1f17e9a23bf3961ba5778d3936039ad881/Assets/icon.png")) + tcsRequested.TrySetResult(); + }); + + // set up a task to wait for the page to load + Navigated += (sender, args) => + { + // Set success when we have a successful nav result + if (args.Result == WebNavigationResult.Success && args.Url == "file:///android_asset/extracontent.html") + tcsLoaded.TrySetResult(args.Result == WebNavigationResult.Success); + }; + + string failureMessage = String.Empty; + + try + { + + if (WebClient is not CustomWebClient) + { + throw new Exception("CustomWebClient was not set"); + } + + // wait for the navigation to complete + await tcsNavigating.Task; + if (navigatingCount < 1) + { + throw new Exception("Navigating event did not fire"); + } + + if ((!await tcsLoaded.Task)) + { + throw new Exception("HTML Source Failed to Load"); + } + + // wait for the image to be requested + await tcsRequested.Task; + + if (shouldRequestCount != 1) + { + throw new Exception("only 1 request for the image to load"); + } + } + catch(Exception ex) + { + failureMessage = ex.Message; + } + + if (String.IsNullOrEmpty(failureMessage)) + { + ((VerticalStackLayout)Parent).Insert(0, new Label { Text = "All Expectations Have Been Met", AutomationId = "Success" }); + } + else + { + ((VerticalStackLayout)Parent).Insert(0, new Label { Text = failureMessage }); + } + } + + PropertyMapper IPropertyMapperView.GetPropertyMapperOverrides() => TestMapper; + + CustomWebClient WebClient { get; set; } + + public Action ShouldInterceptRequestDelegate { get; set; } + + class CustomWebClient : MauiWebViewClient + { + WebViewHandler _handler; + + public CustomWebClient(WebViewHandler handler) + : base(handler) + { + _handler = handler; + } + + public override WebResourceResponse ShouldInterceptRequest(AWebView view, IWebResourceRequest request) + { + if (_handler.VirtualView is Issue16032WebView customWebView) + customWebView.ShouldInterceptRequestDelegate(view, request); + + return base.ShouldInterceptRequest(view, request); + } + } + } + } +} + + +#endif diff --git a/src/Core/tests/DeviceTests/Resources/Raw/extracontent.html b/src/Controls/tests/TestCases.HostApp/Resources/Raw/extracontent.html similarity index 100% rename from src/Core/tests/DeviceTests/Resources/Raw/extracontent.html rename to src/Controls/tests/TestCases.HostApp/Resources/Raw/extracontent.html diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue16032.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue16032.cs new file mode 100644 index 000000000000..16757caeb2df --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue16032.cs @@ -0,0 +1,24 @@ +#if ANDROID +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues +{ + public class Issue16032 : _IssuesUITest + { + public Issue16032(TestDevice device) + : base(device) + { } + + public override string Issue => "Improve the customization of WebView on Android"; + + [Test] + [Category(UITestCategories.WebView)] + public void EnsureSupportForCustomWebViewClients() + { + App.WaitForElement("Success"); + } + } +} +#endif \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs index 616a78b40bcc..1d6356204452 100644 --- a/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs @@ -10,145 +10,5 @@ namespace Microsoft.Maui.DeviceTests { public partial class WebViewHandlerTests { - [Fact] - public Task EnsureSupportForCustomWebViewClients() => - InvokeOnMainThreadAsync(async () => - { - // create the cross-platform view - var webView = new WebViewStub - { - Width = 300, - Height = 200, - Background = new SolidPaint(Colors.Red), - }; - - // create the platform view - await AttachAndRun(webView, async webViewHandler => - { - var platformWebView = webViewHandler.PlatformView; - - var tcsLoaded = new TaskCompletionSource(); - var tcsNavigating = new TaskCompletionSource(); - var tcsRequested = new TaskCompletionSource(); - - // if the timeout happens, cancel everything - var pageLoadTimeout = TimeSpan.FromSeconds(30); - var ctsTimeout = new CancellationTokenSource(pageLoadTimeout); - ctsTimeout.Token.Register(() => - { - tcsLoaded.TrySetException(new TimeoutException($"Failed to load HTML")); - tcsNavigating.TrySetException(new TimeoutException($"Failed to navigate to the loaded page")); - tcsRequested.TrySetException(new TimeoutException($"Failed to request the image")); - }); - - // attach some event handlers to track things - var navigatingCount = 0; - webView.NavigatingDelegate = new((evnt, url) => - { - navigatingCount++; - - if (url == "file:///android_asset/extracontent.html") - tcsNavigating.TrySetResult(); - - return false; // do not cancel the navigation - }); - var shouldRequestCount = 0; - webViewHandler.ShouldInterceptRequestDelegate = new((view, request) => - { - shouldRequestCount++; - - if (request.Url.ToString().StartsWith("https://raw.githubusercontent.com/dotnet/maui/4c096c1f17e9a23bf3961ba5778d3936039ad881/Assets/icon.png")) - tcsRequested.TrySetResult(); - }); - - // set up a task to wait for the page to load - webView.NavigatedDelegate = (evnt, url, result) => - { - // Set success when we have a successful nav result - if (result == WebNavigationResult.Success && url == "file:///android_asset/extracontent.html") - tcsLoaded.TrySetResult(result == WebNavigationResult.Success); - }; - - // load the page - webView.Source = new UrlWebViewSourceStub { Url = "extracontent.html" }; - webViewHandler.UpdateValue(nameof(IWebView.Source)); - - // wait for the loaded event - Assert.True(await tcsLoaded.Task, "HTML Source Failed to Load"); - - // make sure the mapper override fired at least once - Assert.IsType(webViewHandler.CustomWebClient); - - // wait for the navigation to complete - await tcsNavigating.Task; - Assert.True(navigatingCount > 1); // at least 1 navigation, Android seems to do a few - - // wait for the image to be requested - await tcsRequested.Task; - Assert.Equal(1, shouldRequestCount); // only 1 request for the image to load - }); - }); - - AWebView GetNativeWebView(WebViewHandler webViewHandler) => - webViewHandler.PlatformView; - - string GetNativeSource(WebViewHandler webViewHandler) => - GetNativeWebView(webViewHandler).Url; - - class CustomWebViewHandler : WebViewHandler - { - // make a copy of the Core mappers because we don't want any Controls changes or to override us - static IPropertyMapper TestMapper = - new PropertyMapper(WebViewHandler.Mapper); - static CommandMapper TestCommandMapper = - new(WebViewHandler.CommandMapper); - - static CustomWebViewHandler() - { - // this is part of the test: testing the modify/replace the existing mapper - TestMapper.ModifyMapping( - nameof(WebViewClient), - (handler, view, setter) => - { - if (handler is not CustomWebViewHandler custom) - throw new Exception("The CustomWebViewHandler.TestMapper is only meant to be used with the CustomWebViewHandler tests."); - - if (custom.CustomWebClient is not null) - throw new Exception("The [WebViewClient] mapper method is only supposed to be called once."); - - custom.CustomWebClient = new CustomWebClient((CustomWebViewHandler)handler); - - handler.PlatformView.SetWebViewClient(custom.CustomWebClient); - }); - } - - // make sure to use the Core mappers - public CustomWebViewHandler() - : base(TestMapper, TestCommandMapper) - { - } - - public CustomWebClient CustomWebClient { get; private set; } - - public Action ShouldInterceptRequestDelegate { get; set; } - } - - class CustomWebClient : MauiWebViewClient - { - CustomWebViewHandler _handler; - - public CustomWebClient(CustomWebViewHandler handler) - : base(handler) - { - _handler = handler; - } - - public override WebResourceResponse ShouldInterceptRequest(AWebView view, IWebResourceRequest request) - { - _handler.ShouldInterceptRequestDelegate(view, request); - - return base.ShouldInterceptRequest(view, request); - } - } } } \ No newline at end of file From 0b2c0bf6038c7b4e3c9e610b4b53f8d845700819 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 12 Aug 2024 17:13:35 -0500 Subject: [PATCH 2/2] - fix --- .../Handlers/WebView/WebViewHandlerTests.Android.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs index 1d6356204452..47c352fdd6d3 100644 --- a/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs @@ -10,5 +10,10 @@ namespace Microsoft.Maui.DeviceTests { public partial class WebViewHandlerTests { + AWebView GetNativeWebView(WebViewHandler webViewHandler) => + webViewHandler.PlatformView; + + string GetNativeSource(WebViewHandler webViewHandler) => + GetNativeWebView(webViewHandler).Url; } } \ No newline at end of file