Skip to content

Commit

Permalink
Move EnsureSupportForCustomWebViewClients to Appium (#24167)
Browse files Browse the repository at this point in the history
* Move EnsureSupportForCustomWebViewClients to Appium

* - fix
  • Loading branch information
PureWeen committed Aug 13, 2024
1 parent f40b6c7 commit e39d4b1
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 135 deletions.
174 changes: 174 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue16032.cs
Original file line number Diff line number Diff line change
@@ -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<Issue16032WebView, WebViewHandler> TestMapper =
new PropertyMapper<Issue16032WebView, WebViewHandler>(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<bool>();
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<AWebView, IWebResourceRequest> 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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -10,145 +10,10 @@ 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<CustomWebViewHandler>(webView, async webViewHandler =>
{
var platformWebView = webViewHandler.PlatformView;
var tcsLoaded = new TaskCompletionSource<bool>();
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<CustomWebClient>(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<IWebView, IWebViewHandler> TestMapper =
new PropertyMapper<IWebView, IWebViewHandler>(WebViewHandler.Mapper);
static CommandMapper<IWebView, IWebViewHandler> 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<AWebView, IWebResourceRequest> 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);
}
}
}
}

0 comments on commit e39d4b1

Please sign in to comment.