Skip to content

[Android] Fix screenshot from WebView content not working#35384

Merged
kubaflo merged 4 commits into
inflight/currentfrom
fix/android-screenshot-webview
May 28, 2026
Merged

[Android] Fix screenshot from WebView content not working#35384
kubaflo merged 4 commits into
inflight/currentfrom
fix/android-screenshot-webview

Conversation

@kubaflo
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo commented May 11, 2026

Description of Change

Replaces the approach in #30437 with Android's canonical PixelCopy API (API 26+) for capturing hardware-accelerated surfaces, as recommended by the AI review.

Problem: Calling Screenshot.CaptureAsync() on a page containing a WebView in Release builds on Android results in a blank/invisible image. The canvas-based view.Draw() approach cannot capture hardware-rendered surfaces like WebView's internal SurfaceView.

Solution: Use PixelCopy.Request(Window, Rect, Bitmap, ...) which is the Android-recommended API for snapshotting rendered surfaces — including WebView, SurfaceView, TextureView, and other hardware-accelerated views.

Key improvements over #30437:

  • No UI thread blockingPixelCopy is callback-based and fully async, eliminating the Thread.Sleep(50) on the UI thread
  • No layer type mutation — avoids toggling the entire DecorView to software rendering (which caused visual flash and expensive relayout)
  • Correct APIPixelCopy is the documented Android best practice for hardware surface capture (API 26+)
  • Graceful fallback — falls back to existing canvas draw → drawing cache paths on pre-API-26 devices or PixelCopy failure
  • Fixed test stream lifecycle — uses new MemoryStream(bytes) factory pattern instead of capturing a one-shot stream
  • Removed Thread.Sleep from UITest — waits for UI elements instead
  • C#-only test page — per repo conventions, no unnecessary XAML
  • No extra project referencesScreenshot is already accessible through MAUI meta-package

Based on the work by @jsuarezruiz in #30437.

Issues Fixed

Fixes #30010
Supersedes #30437

Copilot AI review requested due to automatic review settings May 11, 2026 18:42
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35384

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35384"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses Android screenshot capture returning blank images when the UI contains hardware-accelerated surfaces (e.g., WebView) by adding a PixelCopy-based capture path (API 26+) and introducing a regression UI test + sample update to validate the scenario.

Changes:

  • Update Screenshot Android implementation to attempt capture via PixelCopy (API 26+) with fallback to existing canvas/drawing-cache rendering.
  • Enhance the Essentials Screenshot sample page to include a WebView and additional controls.
  • Add a new Android-focused UI test + HostApp issue page to exercise screenshot capture with WebView content.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.

File Description
src/Essentials/src/Screenshot/Screenshot.android.cs Adds PixelCopy async rendering path for Android screenshot capture with fallbacks.
src/Essentials/samples/Samples/View/ScreenshotPage.xaml Updates sample UI to include a WebView to demonstrate/validate screenshot capture.
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30010.cs Adds an Android UI test verifying screenshot capture/display when a WebView is present.
src/Controls/tests/TestCases.HostApp/Issues/Issue30010.cs Adds HostApp reproduction page to capture screenshot and display it in an Image.


public void OnPixelCopyFinished(int copyResult)
{
if (copyResult == (int)PixelCopyResult.Success)
return bitmap;
}

return RenderUsingCanvasDrawing(view) ?? RenderUsingDrawingCache(view);
Comment on lines +89 to +90
PixelCopy.Request(window, rect, bitmap,
new PixelCopyFinishedListener(tcs, bitmap),
App.Tap("TakeScreenshotButton");

// Wait for the screenshot to be captured and displayed
var statusLabel = App.WaitForElement("StatusLabel");
Comment on lines +66 to +72
var screenshot = await Screenshot.CaptureAsync();
var stream = await screenshot.OpenReadAsync(ScreenshotFormat.Png);

// Read into a byte array so the stream can be consumed multiple times
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var bytes = ms.ToArray();
{
try
{
var screenshot = await Screenshot.CaptureAsync();
Comment on lines +21 to +31
// Wait for WebView to finish loading
App.WaitForElement("StatusLabel");
App.WaitForElement("TakeScreenshotButton");

// The button is enabled only after WebView.Navigated fires
App.WaitForElement("TakeScreenshotButton");
App.Tap("TakeScreenshotButton");

// Wait for the screenshot to be captured and displayed
var statusLabel = App.WaitForElement("StatusLabel");
App.WaitForElement("ResultImage");
@kubaflo kubaflo changed the base branch from net10.0 to main May 11, 2026 18:53
Use Android's PixelCopy API (API 26+) to capture hardware-accelerated
surfaces including WebView's SurfaceView, instead of toggling layer
types and sleeping the UI thread.

The PixelCopy approach:
- Correctly captures hardware-rendered content (WebView, SurfaceView)
- Is fully async with no UI thread blocking
- Falls back to existing canvas/drawing-cache methods on older APIs

Also fixes the test page's stream lifecycle (uses MemoryStream copy
instead of a one-shot stream) and removes Thread.Sleep from the test.

Fixes #30010

Co-authored-by: Javier Suárez <6755973+jsuarezruiz@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo kubaflo force-pushed the fix/android-screenshot-webview branch from 36434e3 to febd7d4 Compare May 11, 2026 18:54
@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 11, 2026

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@MauiBot MauiBot added s/agent-review-incomplete s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels May 11, 2026
@dotnet dotnet deleted a comment from MauiBot May 12, 2026
@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 12, 2026

/azp run maui-pr, maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

The previous commit broke the build with three issues:

- Removed the using Microsoft.Maui.Platform import — Microsoft.Maui.Essentials does not reference Microsoft.Maui.Core, so that namespace is not visible. Inlined the small GetActivity helper that was the only thing pulled in from there.
- Added using Android.OS so Handler/Looper resolve correctly (the bare Android prefix gets shadowed by Microsoft.Android in this assembly).
- Added #nullable enable to match the convention used in the other Essentials Android sources (Battery.android.cs, Browser.android.cs, etc.) so the new Bitmap?, Activity?, and View? annotations compile.

Tightened up a couple of pre-existing nullability gaps that the enable surfaced:

- WindowManager is now declared as IWindowManager? to match what the as-cast actually returns.
- CaptureAsync(Activity) now awaits CaptureAsync(View) and throws InvalidOperationException if the inner result is null, so it actually honours its non-null Task<IScreenshotResult> contract instead of silently propagating null.
- CaptureAsync(View) now returns Task<IScreenshotResult?> to match the IPlatformScreenshot interface declaration.
- ActivityStateManager.Default.GetCurrentActivity(true) result is now null-checked before being passed to CaptureAsync(Activity).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 19, 2026

/review -b feature/regression-check -p android

1 similar comment
@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 19, 2026

/review -b feature/regression-check -p android

@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 24, 2026

/review -b feature/refactor-copilot-yml

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 6 findings

See inline comments for details.

{
if (OperatingSystem.IsAndroidVersionAtLeast(26))
{
var bitmap = await RenderUsingPixelCopyAsync(view).ConfigureAwait(false);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Async and threading safety — If PixelCopy fails, this continuation resumes after ConfigureAwait(false) and then falls back to RenderUsingCanvasDrawing(view) / RenderUsingDrawingCache(view) on a threadpool thread. Those fallback paths touch Android View APIs and must run on the UI thread. Keep this continuation on the main thread or explicitly dispatch fallback rendering back to the UI thread.

try
{
PixelCopy.Request(window, rect, bitmap,
new PixelCopyFinishedListener(tcs, bitmap),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Android callback lifetime — The PixelCopyFinishedListener is allocated inline while the async request is pending. This diverges from the repository's existing PixelCopy pattern, which keeps the listener in managed state until the callback is reached. Store the listener in a local/state object that remains alive until OnPixelCopyFinished completes.


public void OnPixelCopyFinished(int copyResult)
{
if (copyResult == (int)PixelCopyResult.Success)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] PixelCopy success checkPixelCopyResult.Success is not used elsewhere in this repo and does not match the existing Android PixelCopy pattern, which checks copyResult == 0. Use the established success check for consistency with the target Android binding surface.

App.Tap("TakeScreenshotButton");

// Wait for the screenshot to be captured and displayed
var statusLabel = App.WaitForElement("StatusLabel");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Test waits for pre-existing UI instead of capture completion — This waits for StatusLabel/ResultImage, but both elements already exist before the screenshot is captured and before the Image source finishes loading. The test can snapshot the pre-capture UI and fail to validate the WebView screenshot fix. Wait for a deterministic post-capture signal such as status text Screenshot captured before calling VerifyScreenshot.

try
{
var screenshot = await Screenshot.CaptureAsync();
var stream = await screenshot.OpenReadAsync(ScreenshotFormat.Png);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Screenshot stream is not disposed — The stream returned from OpenReadAsync is copied and then abandoned without disposal. Wrap it in using before copying to the byte array so repeated screenshot test runs do not leak the native/managed stream resource.

<Label Text="Some Controls" />
<Switch OnColor="Orange"
ThumbColor="Green" />
<WebView>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Sample WebView needs an explicit height — This sample adds a WebView inside a StackLayout nested in a ScrollView without an explicit height. On MAUI layouts, WebView commonly measures to no visible height in this configuration, so the sample may not actually exercise WebView screenshot capture. Set a HeightRequest like the regression page does.

@MauiBot MauiBot added the s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates label May 24, 2026
Use the activity window for PixelCopy captures and keep the PixelCopy listener alive until the async callback completes. Update the regression test to validate the captured screenshot contains a WebView-rendered marker instead of relying on Appium visual baselines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 26, 2026

/review -b feature/refactor-copilot-yml -p android

@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 26, 2026

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 26, 2026

/review -b feature/refactor-copilot-yml -p windows

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #2 automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: try-fix-2 won because it passed the Android regression run while the PR gate failed, and its targeted PixelCopy compositing is less invasive than the PR whole-window replacement or try-fix-1 software-layer mutation. It also had clean self-review findings and addresses the PR review concerns around listener disposal and fragile UI-thread fallback behavior.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-2`)
diff --git a/src/Essentials/src/Screenshot/Screenshot.android.cs b/src/Essentials/src/Screenshot/Screenshot.android.cs
index 0b8f1a32fc..f92b60a39d 100644
--- a/src/Essentials/src/Screenshot/Screenshot.android.cs
+++ b/src/Essentials/src/Screenshot/Screenshot.android.cs
@@ -1,11 +1,16 @@
+#nullable enable
+
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
 using Android.App;
 using Android.Content;
 using Android.Graphics;
+using Android.OS;
 using Android.Views;
+using AndroidWebView = Android.Webkit.WebView;
 using Java.Nio;
 using Microsoft.Maui.ApplicationModel;
 
@@ -13,7 +18,7 @@ namespace Microsoft.Maui.Media
 {
 	partial class ScreenshotImplementation : IPlatformScreenshot, IScreenshot
 	{
-		static IWindowManager WindowManager =>
+		static IWindowManager? WindowManager =>
 			Application.Context.GetSystemService(Context.WindowService) as IWindowManager;
 
 		public bool IsCaptureSupported => true;
@@ -23,41 +28,208 @@ namespace Microsoft.Maui.Media
 			if (WindowManager?.DefaultDisplay?.Flags.HasFlag(DisplayFlags.Secure) == true)
 				throw new UnauthorizedAccessException("Unable to take a screenshot of a secure window.");
 
-			var activity = ActivityStateManager.Default.GetCurrentActivity(true);
+			var activity = ActivityStateManager.Default.GetCurrentActivity(true)
+				?? throw new InvalidOperationException("Unable to find the current activity.");
 
 			return CaptureAsync(activity);
 		}
 
-		public Task<IScreenshotResult> CaptureAsync(Activity activity)
+		public async Task<IScreenshotResult> CaptureAsync(Activity activity)
 		{
-			var view = activity?.Window?.DecorView?.RootView;
+			var window = activity?.Window;
+			var view = window?.DecorView?.RootView;
 			if (view == null)
 				throw new InvalidOperationException("Unable to find the main window.");
 
-			return CaptureAsync(view);
+			var result = await CaptureAsync(view, window).ConfigureAwait(false);
+			return result ?? throw new InvalidOperationException("Unable to capture screenshot.");
 		}
 
-		public Task<IScreenshotResult> CaptureAsync(View view)
+		public Task<IScreenshotResult?> CaptureAsync(View view) =>
+			CaptureAsync(view, GetActivity(view?.Context)?.Window);
+
+		async Task<IScreenshotResult?> CaptureAsync(View view, Window? window)
 		{
 			_ = view ?? throw new ArgumentNullException(nameof(view));
 
-			var bitmap = Render(view);
-			var result = bitmap is null ? null : new ScreenshotResult(bitmap);
-
-			return Task.FromResult<IScreenshotResult>(result);
+			var bitmap = await RenderAsync(view, window).ConfigureAwait(false);
+			return bitmap is null ? null : new ScreenshotResult(bitmap);
 		}
 
-		static Bitmap Render(View view)
+		static async Task<Bitmap?> RenderAsync(View view, Window? window)
 		{
-			var bitmap = RenderUsingCanvasDrawing(view);
+			// Base render via the regular canvas path. This captures the entire view
+			// hierarchy correctly except for views backed by their own GPU surface
+			// (WebView, SurfaceView, etc.), which Draw() leaves as transparent holes.
+			var bitmap = RenderUsingCanvasDrawing(view) ?? RenderUsingDrawingCache(view);
+			if (bitmap is null)
+				return null;
 
-			if (bitmap == null)
-				bitmap = RenderUsingDrawingCache(view);
+			// Targeted patch: walk for hardware-surface descendants and re-capture
+			// only those rectangles via PixelCopy, then composite them in place.
+			// Requires API 26+ for PixelCopy and a Window for the source surface.
+			if (OperatingSystem.IsAndroidVersionAtLeast(26) && window is not null)
+			{
+				await PatchHardwareSurfacesAsync(view, bitmap, window).ConfigureAwait(false);
+			}
 
 			return bitmap;
 		}
 
-		static Bitmap RenderUsingCanvasDrawing(View view)
+		static void CollectHardwareSurfaceDescendants(View v, List<View> result)
+		{
+			if (v is AndroidWebView)
+			{
+				result.Add(v);
+				return;
+			}
+
+			if (v is SurfaceView)
+			{
+				result.Add(v);
+				return;
+			}
+
+			if (v is ViewGroup group)
+			{
+				var count = group.ChildCount;
+				for (var i = 0; i < count; i++)
+				{
+					var child = group.GetChildAt(i);
+					if (child is not null)
+						CollectHardwareSurfaceDescendants(child, result);
+				}
+			}
+		}
+
+		static async Task PatchHardwareSurfacesAsync(View root, Bitmap target, Window window)
+		{
+			// Snapshot all view-tree state on the caller's (UI) thread BEFORE any await,
+			// so the async loop below never touches View members from a background thread.
+			var surfaces = new List<View>();
+			CollectHardwareSurfaceDescendants(root, surfaces);
+			if (surfaces.Count == 0)
+				return;
+
+			var rootLoc = new int[2];
+			root.GetLocationInWindow(rootLoc);
+
+			var snapshots = new List<(Rect Src, int Width, int Height, int DstX, int DstY)>(surfaces.Count);
+			foreach (var child in surfaces)
+			{
+				if (child.Width <= 0 || child.Height <= 0 || !child.IsAttachedToWindow)
+					continue;
+
+				var childLoc = new int[2];
+				child.GetLocationInWindow(childLoc);
+
+				var src = new Rect(
+					childLoc[0],
+					childLoc[1],
+					childLoc[0] + child.Width,
+					childLoc[1] + child.Height);
+
+				snapshots.Add((src, child.Width, child.Height, childLoc[0] - rootLoc[0], childLoc[1] - rootLoc[1]));
+			}
+
+			if (snapshots.Count == 0)
+				return;
+
+			// From here on, no View access — only Bitmap/Canvas operations.
+			using var canvas = new Canvas(target);
+
+			foreach (var snap in snapshots)
+			{
+				var childBitmap = await CaptureRegionAsync(window, snap.Src, snap.Width, snap.Height).ConfigureAwait(false);
+				if (childBitmap is null)
+					continue;
+
+				try
+				{
+					canvas.DrawBitmap(childBitmap, snap.DstX, snap.DstY, null);
+				}
+				finally
+				{
+					childBitmap.Dispose();
+				}
+			}
+		}
+
+		static Task<Bitmap?> CaptureRegionAsync(Window window, Rect srcRect, int width, int height)
+		{
+			var bitmap = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888!);
+			if (bitmap is null)
+				return Task.FromResult<Bitmap?>(null);
+
+			var tcs = new TaskCompletionSource<Bitmap?>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+			PixelCopyFinishedListener? listener = null;
+			try
+			{
+				listener = new PixelCopyFinishedListener(tcs, bitmap);
+				PixelCopy.Request(window, srcRect, bitmap, listener, new Handler(Looper.MainLooper!));
+			}
+			catch (Exception)
+			{
+				listener?.Dispose();
+				bitmap.Dispose();
+				return Task.FromResult<Bitmap?>(null);
+			}
+
+			return AwaitAndDisposeListenerAsync(tcs.Task, listener);
+		}
+
+		static async Task<Bitmap?> AwaitAndDisposeListenerAsync(Task<Bitmap?> task, PixelCopyFinishedListener listener)
+		{
+			try
+			{
+				return await task.ConfigureAwait(false);
+			}
+			finally
+			{
+				// Keep the JNI listener alive until the callback has fired, then release it.
+				listener.Dispose();
+			}
+		}
+
+		static Activity? GetActivity(Context? context)
+		{
+			while (context is ContextWrapper wrapper)
+			{
+				if (context is Activity activity)
+					return activity;
+				context = wrapper.BaseContext;
+			}
+			return context as Activity;
+		}
+
+		sealed class PixelCopyFinishedListener : Java.Lang.Object, PixelCopy.IOnPixelCopyFinishedListener
+		{
+			readonly TaskCompletionSource<Bitmap?> _tcs;
+			readonly Bitmap _bitmap;
+
+			public PixelCopyFinishedListener(TaskCompletionSource<Bitmap?> tcs, Bitmap bitmap)
+			{
+				_tcs = tcs;
+				_bitmap = bitmap;
+			}
+
+			public void OnPixelCopyFinished(int copyResult)
+			{
+				// PixelCopy.SUCCESS == 0
+				if (copyResult == 0)
+				{
+					_tcs.TrySetResult(_bitmap);
+				}
+				else
+				{
+					_bitmap.Dispose();
+					_tcs.TrySetResult(null);
+				}
+			}
+		}
+
+		static Bitmap? RenderUsingCanvasDrawing(View view)
 		{
 			try
 			{
@@ -81,7 +253,7 @@ namespace Microsoft.Maui.Media
 			}
 		}
 
-		static Bitmap RenderUsingDrawingCache(View view)
+		static Bitmap? RenderUsingDrawingCache(View view)
 		{
 #pragma warning disable CS0618 // Type or member is obsolete
 #pragma warning disable CA1416 // Validate platform compatibility

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 26, 2026
Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 2 findings

See inline comments for details.

return bitmap;
}

return RenderUsingCanvasDrawing(view) ?? RenderUsingDrawingCache(view);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Async and Threading Safety - If CaptureAsync(View) is called from a background thread and PixelCopy returns null, this fallback path runs View.Draw / drawing-cache work on that background thread. Android view rendering APIs must run on the UI thread; dispatch the fallback render to the main looper or fail before falling back.

else
{
_bitmap.Dispose();
_tcs.TrySetResult(null);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Logic and Correctness - Returning null for any PixelCopy failure silently falls through to the old canvas/drawing-cache screenshot path, which is the path that cannot capture hardware-accelerated WebView content. On an API 26+ device where PixelCopy returns ERROR_SOURCE_NO_DATA/timeout, callers can still receive a successful but blank-prone screenshot instead of a retry or clear failure signal.

@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 27, 2026

/review -b feature/refactor-copilot-yml -p android

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against #1 automatically generated candidates and selected try-fix-1 as the strongest fix.

Why: try-fix-1 wins because it passed the targeted Android regression run while both PR-based candidates are ranked lower due to the failed gate. Its WebView-only PixelCopy overlay approach best targets the Android surface-composition issue while preserving existing canvas screenshot behavior for ordinary content.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (`try-fix-1`)
diff --git a/src/Essentials/src/Screenshot/Screenshot.android.cs b/src/Essentials/src/Screenshot/Screenshot.android.cs
index 0b8f1a32fc..2a448a4447 100644
--- a/src/Essentials/src/Screenshot/Screenshot.android.cs
+++ b/src/Essentials/src/Screenshot/Screenshot.android.cs
@@ -1,19 +1,24 @@
+#nullable enable
+
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
 using Android.App;
 using Android.Content;
 using Android.Graphics;
+using Android.OS;
 using Android.Views;
 using Java.Nio;
 using Microsoft.Maui.ApplicationModel;
+using AWebView = Android.Webkit.WebView;
 
 namespace Microsoft.Maui.Media
 {
 	partial class ScreenshotImplementation : IPlatformScreenshot, IScreenshot
 	{
-		static IWindowManager WindowManager =>
+		static IWindowManager? WindowManager =>
 			Application.Context.GetSystemService(Context.WindowService) as IWindowManager;
 
 		public bool IsCaptureSupported => true;
@@ -23,41 +28,236 @@ namespace Microsoft.Maui.Media
 			if (WindowManager?.DefaultDisplay?.Flags.HasFlag(DisplayFlags.Secure) == true)
 				throw new UnauthorizedAccessException("Unable to take a screenshot of a secure window.");
 
-			var activity = ActivityStateManager.Default.GetCurrentActivity(true);
+			var activity = ActivityStateManager.Default.GetCurrentActivity(true)
+				?? throw new InvalidOperationException("Unable to find the current activity.");
 
 			return CaptureAsync(activity);
 		}
 
-		public Task<IScreenshotResult> CaptureAsync(Activity activity)
+		public async Task<IScreenshotResult> CaptureAsync(Activity activity)
 		{
-			var view = activity?.Window?.DecorView?.RootView;
+			var window = activity?.Window;
+			var view = window?.DecorView?.RootView;
 			if (view == null)
 				throw new InvalidOperationException("Unable to find the main window.");
 
-			return CaptureAsync(view);
+			var result = await CaptureAsync(view, window).ConfigureAwait(false);
+			return result ?? throw new InvalidOperationException("Unable to capture screenshot.");
 		}
 
-		public Task<IScreenshotResult> CaptureAsync(View view)
+		public Task<IScreenshotResult?> CaptureAsync(View view) =>
+			CaptureAsync(view, GetActivity(view?.Context)?.Window);
+
+		async Task<IScreenshotResult?> CaptureAsync(View view, Window? window)
 		{
 			_ = view ?? throw new ArgumentNullException(nameof(view));
 
-			var bitmap = Render(view);
-			var result = bitmap is null ? null : new ScreenshotResult(bitmap);
+			var bitmap = await RenderAsync(view, window).ConfigureAwait(false);
+			return bitmap is null ? null : new ScreenshotResult(bitmap);
+		}
+
+		static async Task<Bitmap?> RenderAsync(View view, Window? window)
+		{
+			if (OperatingSystem.IsAndroidVersionAtLeast(26) && window is not null)
+			{
+				var webViewOverlay = await RenderUsingWebViewOverlaysAsync(view, window).ConfigureAwait(false);
+				if (webViewOverlay.HasWebView)
+					return webViewOverlay.Bitmap;
+			}
+
+			return RenderUsingCanvasDrawing(view) ?? RenderUsingDrawingCache(view);
+		}
+
+		static async Task<(bool HasWebView, Bitmap? Bitmap)> RenderUsingWebViewOverlaysAsync(View root, Window window)
+		{
+			var plan = await MainThread.InvokeOnMainThreadAsync(() => CreateWebViewOverlayPlan(root)).ConfigureAwait(false);
+			if (plan is null)
+				return (false, null);
+
+			if (plan.Regions.Count == 0)
+				return (false, null);
+
+			if (plan.Bitmap is null)
+				return (true, null);
+
+			using var canvas = new Canvas(plan.Bitmap);
+			foreach (var region in plan.Regions)
+			{
+				var webViewBitmap = await CaptureWindowRegionAsync(window, region.SourceRect, region.SourceRect.Width(), region.SourceRect.Height()).ConfigureAwait(false);
+				if (webViewBitmap is null)
+				{
+					plan.Bitmap.Dispose();
+					return (true, null);
+				}
+
+				try
+				{
+					canvas.DrawBitmap(webViewBitmap, region.DestinationLeft, region.DestinationTop, null);
+				}
+				finally
+				{
+					webViewBitmap.Dispose();
+				}
+			}
+
+			return (true, plan.Bitmap);
+		}
+
+		static WebViewOverlayPlan? CreateWebViewOverlayPlan(View root)
+		{
+			if (root.Width <= 0 || root.Height <= 0 || !root.IsAttachedToWindow)
+				return null;
+
+			var webViews = new List<View>();
+			CollectVisibleWebViews(root, webViews);
+			if (webViews.Count == 0)
+				return new WebViewOverlayPlan(null, new List<WebViewOverlayRegion>(0));
+
+			var bitmap = RenderUsingCanvasDrawing(root);
+
+			var rootLocation = new int[2];
+			root.GetLocationInWindow(rootLocation);
+
+			var regions = new List<WebViewOverlayRegion>(webViews.Count);
+			for (var i = 0; i < webViews.Count; i++)
+			{
+				var webView = webViews[i];
+				if (webView.Width <= 0 || webView.Height <= 0 || !webView.IsAttachedToWindow)
+					continue;
+
+				var location = new int[2];
+				webView.GetLocationInWindow(location);
+
+				var sourceRect = new Rect(
+					location[0],
+					location[1],
+					location[0] + webView.Width,
+					location[1] + webView.Height);
+
+				regions.Add(new WebViewOverlayRegion(sourceRect, location[0] - rootLocation[0], location[1] - rootLocation[1]));
+			}
+
+			return new WebViewOverlayPlan(bitmap, regions);
+		}
+
+		static void CollectVisibleWebViews(View view, List<View> webViews)
+		{
+			if (view is AWebView && view.Visibility == ViewStates.Visible)
+			{
+				webViews.Add(view);
+				return;
+			}
+
+			if (view is not ViewGroup group)
+				return;
+
+			var childCount = group.ChildCount;
+			for (var i = 0; i < childCount; i++)
+			{
+				var child = group.GetChildAt(i);
+				if (child is not null && child.Visibility == ViewStates.Visible)
+					CollectVisibleWebViews(child, webViews);
+			}
+		}
+
+		static Task<Bitmap?> CaptureWindowRegionAsync(Window window, Rect sourceRect, int width, int height)
+		{
+			var bitmap = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888!);
+			if (bitmap is null)
+				return Task.FromResult<Bitmap?>(null);
+
+			var tcs = new TaskCompletionSource<Bitmap?>(TaskCreationOptions.RunContinuationsAsynchronously);
+			PixelCopyFinishedListener? listener = null;
+			try
+			{
+				listener = new PixelCopyFinishedListener(tcs, bitmap);
+				PixelCopy.Request(window, sourceRect, bitmap, listener, new Handler(Looper.MainLooper!));
+			}
+			catch (Exception)
+			{
+				listener?.Dispose();
+				bitmap.Dispose();
+				return Task.FromResult<Bitmap?>(null);
+			}
+
+			return AwaitPixelCopyAsync(tcs.Task, listener);
+		}
+
+		static async Task<Bitmap?> AwaitPixelCopyAsync(Task<Bitmap?> task, PixelCopyFinishedListener listener)
+		{
+			try
+			{
+				return await task.ConfigureAwait(false);
+			}
+			finally
+			{
+				listener.Dispose();
+			}
+		}
+
+		static Activity? GetActivity(Context? context)
+		{
+			while (context is ContextWrapper wrapper)
+			{
+				if (context is Activity activity)
+					return activity;
+				context = wrapper.BaseContext;
+			}
+			return context as Activity;
+		}
+
+		sealed class WebViewOverlayPlan
+		{
+			public WebViewOverlayPlan(Bitmap? bitmap, List<WebViewOverlayRegion> regions)
+			{
+				Bitmap = bitmap;
+				Regions = regions;
+			}
+
+			public Bitmap? Bitmap { get; }
+			public List<WebViewOverlayRegion> Regions { get; }
+		}
+
+		readonly struct WebViewOverlayRegion
+		{
+			public WebViewOverlayRegion(Rect sourceRect, int destinationLeft, int destinationTop)
+			{
+				SourceRect = sourceRect;
+				DestinationLeft = destinationLeft;
+				DestinationTop = destinationTop;
+			}
 
-			return Task.FromResult<IScreenshotResult>(result);
+			public Rect SourceRect { get; }
+			public int DestinationLeft { get; }
+			public int DestinationTop { get; }
 		}
 
-		static Bitmap Render(View view)
+		sealed class PixelCopyFinishedListener : Java.Lang.Object, PixelCopy.IOnPixelCopyFinishedListener
 		{
-			var bitmap = RenderUsingCanvasDrawing(view);
+			readonly TaskCompletionSource<Bitmap?> _tcs;
+			readonly Bitmap _bitmap;
 
-			if (bitmap == null)
-				bitmap = RenderUsingDrawingCache(view);
+			public PixelCopyFinishedListener(TaskCompletionSource<Bitmap?> tcs, Bitmap bitmap)
+			{
+				_tcs = tcs;
+				_bitmap = bitmap;
+			}
 
-			return bitmap;
+			public void OnPixelCopyFinished(int copyResult)
+			{
+				if (copyResult == 0)
+				{
+					_tcs.TrySetResult(_bitmap);
+				}
+				else
+				{
+					_bitmap.Dispose();
+					_tcs.TrySetResult(null);
+				}
+			}
 		}
 
-		static Bitmap RenderUsingCanvasDrawing(View view)
+		static Bitmap? RenderUsingCanvasDrawing(View view)
 		{
 			try
 			{
@@ -67,7 +267,7 @@ namespace Microsoft.Maui.Media
 				var height = view.Height;
 
 				var bitmap = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888);
-				if (bitmap == null)
+				if (bitmap is null)
 					return null;
 
 				using (var canvas = new Canvas(bitmap))
@@ -81,7 +281,7 @@ namespace Microsoft.Maui.Media
 			}
 		}
 
-		static Bitmap RenderUsingDrawingCache(View view)
+		static Bitmap? RenderUsingDrawingCache(View view)
 		{
 #pragma warning disable CS0618 // Type or member is obsolete
 #pragma warning disable CA1416 // Validate platform compatibility

@kubaflo
Copy link
Copy Markdown
Contributor Author

kubaflo commented May 28, 2026

/review -b feature/refactor-copilot-yml

Copy link
Copy Markdown
Collaborator

@MauiBot MauiBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 3 findings

See inline comments for details.

var listener = new PixelCopyFinishedListener(tcs, bitmap);
PixelCopy.Request(window, rect, bitmap,
listener,
new Handler(Looper.MainLooper!));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Android Platform Specifics — This allocates a new Android Handler/Java peer for every screenshot capture. MAUI's existing PixelCopy usage posts through the view's native handler; prefer view.Handler ?? new Handler(Looper.MainLooper!) so the callback is tied to the view looper and avoids unnecessary per-capture allocation.


try
{
return await tcs.Task.ConfigureAwait(true);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Async and Threading Safety — This waits indefinitely for the PixelCopy callback. If the window/surface is detached after PixelCopy.Request and no completion arrives, Screenshot.CaptureAsync never completes. Add a bounded wait/cancellation fallback and keep the continuation consistent with the rest of this file (ConfigureAwait(false)) while carefully handling bitmap ownership on timeout vs late success.

App.Tap("TakeScreenshotButton");

// Wait for the screenshot to be captured and validated by the test page
App.WaitForTextToBePresentInElement("StatusLabel", "Screenshot captured");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[critical] Regression Prevention and Test Coverage — The known gate result showed this test passes without the product fix, so this assertion does not prove the WebView screenshot regression. Strengthen the repro/assertion so it fails on base, e.g. validate actual WebView-specific captured pixels/content rather than only waiting for the host page status label.

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR and removed s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates labels May 28, 2026
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented May 28, 2026

🤖 AI Summary

👋 @kubaflo — new AI review results are available. Please review the latest session below.

📊 Review Session9d80ad5 · Fix Android WebView screenshot test · 2026-05-28 11:20 UTC
🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: ANDROID · Base: main · Merge base: b0ea772f

🩺 Test does not reproduce the bug — ran the same in both states (PASS without fix, PASS with fix). The repro test is not exercising the issue. Strengthen the test before reviewing the fix.

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue30010 Issue30010 ❌ PASS — 857s ✅ PASS — 1288s
🔴 Without fix — 🖥️ Issue30010: PASS ❌ · 857s
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 588 ms).
  Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 4.23 sec).
  Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 5.65 sec).
  Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 2.22 sec).
  Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 51 ms).
  Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 26 ms).
  Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 34 ms).
  Restored /home/vsts/work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 79 ms).
  Restored /home/vsts/work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 1.15 sec).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 1.7 sec).
  1 of 11 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:09:49.31
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 1.73 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 6.12 sec).
  Restored /home/vsts/work/1/s/src/Controls/tests/TestCases.Android.Tests/Controls.TestCases.Android.Tests.csproj (in 8.22 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 15 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 4 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 376 ms).
  Restored /home/vsts/work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 8 ms).
  Restored /home/vsts/work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.78 sec).
  5 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.21]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.61]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 05/28/2026 09:34:27 FixtureSetup for Issue30010(Android)
>>>>> 05/28/2026 09:34:29 Issue30010_TakeScreenshotFunctionality Start
>>>>> 05/28/2026 09:34:36 Issue30010_TakeScreenshotFunctionality Stop
  Passed Issue30010_TakeScreenshotFunctionality [7 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue30010.trx

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 1.4311 Minutes
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue30010.trx

🟢 With fix — 🖥️ Issue30010: PASS ✅ · 1288s

(truncated to last 15,000 chars)

/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]

Build FAILED.

/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: Mono.AndroidTools.InstallFailedException: Unexpected install output: cmd: Failure calling service package: Broken pipe (32) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:  [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass105_0.<InstallPackage>b__0(Task`1 t) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010: --- End of stack trace from previous location --- [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at AndroidDeviceExtensions.PushAndInstallPackageAsync(AndroidDevice device, PushAndInstallCommand command, CancellationToken token) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.InstallPackage(Boolean installed) [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
/home/vsts/work/1/s/.dotnet/packs/Microsoft.Android.Sdk.Linux/36.1.2/tools/Xamarin.Android.Common.Debugging.targets(333,5): error ADB0010:    at Xamarin.Android.Tasks.FastDeploy.RunInstall() [/home/vsts/work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-android]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:10:10.92
* daemon not running; starting now at tcp:5037
* daemon started successfully
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0-android36.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0-android36.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0-android36.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0-android36.0/Microsoft.Maui.Maps.dll
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-android36.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-android36.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-android36.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.TestCases.HostApp -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Controls.TestCases.HostApp.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Xaml.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Maps.dll
  Controls.Foldable -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-android/Microsoft.Maui.Controls.Foldable.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:08:51.39
Broadcasting: Intent { act=android.intent.action.CLOSE_SYSTEM_DIALOGS flg=0x400000 }
Broadcast completed: result=0
  Determining projects to restore...
  All projects are up-to-date for restore.
  Controls.CustomAttributes -> /home/vsts/work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.80-ci+azdo.14218992
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /home/vsts/work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  UITest.Appium -> /home/vsts/work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.NUnit -> /home/vsts/work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  VisualTestUtils.MagickNet -> /home/vsts/work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> /home/vsts/work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.Android.Tests -> /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (x64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.21]   Discovering: Controls.TestCases.Android.Tests
[xUnit.net 00:00:00.57]   Discovered:  Controls.TestCases.Android.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /home/vsts/work/1/s/artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0/Controls.TestCases.Android.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 05/28/2026 09:56:03 FixtureSetup for Issue30010(Android)
>>>>> 05/28/2026 09:56:05 Issue30010_TakeScreenshotFunctionality Start
>>>>> 05/28/2026 09:56:10 Issue30010_TakeScreenshotFunctionality Stop
  Passed Issue30010_TakeScreenshotFunctionality [5 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue30010.trx

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 24.6536 Seconds
>>> TRX_RESULT_FILE: /home/vsts/work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue30010.trx

⚠️ Failure Details

  • Issue30010 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (3 files)
  • eng/pipelines/ci-copilot.yml
  • src/Essentials/samples/Samples/View/ScreenshotPage.xaml
  • src/Essentials/src/Screenshot/Screenshot.android.cs

🧪 UI Tests — Essentials,WebView

Detected UI test categories: Essentials,WebView

Deep UI tests — 0 passed, 49 failed across 2 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Essentials
WebView 0/49 (49 ❌)
WebView — 49 failed tests
WebView_SetHtmlSource_VerifyJavaScript
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
Issue30010_TakeScreenshotFunctionality
OneTimeSetUp: System.TimeoutException : Loading the captured screenshot from webview content to Image control does not visible
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITes
...
WebView_TestCookieManagement_VerifyAddCookie
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyHybridWebView_EvaluateJavaScriptWithDifferentHybridRoot
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyHybridWebView_SendMessageToJavaScript
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
WebView_TestEvaluateJavaScriptAsync_VerifyJavaScriptExecution
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
WebView_TestCookieManagement_VerifyAddCookieWithUrlSource
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyWebViewNavigatedEvent
OneTimeSetUp: System.TimeoutException : WebView Navigated event is not triggered
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tes
...
WebViewDisposesProperly
OneTimeSetUp: System.TimeoutException : [iOS] Cannot access a disposed object. Object name: 'WkWebViewRenderer
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTest
...
WebViewLoadedWithoutException
OneTimeSetUp: System.TimeoutException : NullReferenceException throws on Windows when setting Cookies on .NET MAUI WebView
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.Tr
...
WebView_SetUrlSource_VerifyNavigatedEvent
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
WebView_ValidateDefaultValues_VerifyInitialState
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
WebView_VerifyCanGoBackForward
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyWebViewNavigatedEventTriggered
OneTimeSetUp: System.TimeoutException : [Windows] WebView Navigated event called after cancelling it
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in
...
VerifyHybridWebView_SameHybridRootWithDifferentDefaultFile
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
WebViewNoCrashPopup
OneTimeSetUp: System.TimeoutException : Fix crash closing Popup with WebView
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/T
...
VerifyHybridWebView_DefaultValues
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyWebViewBackgroundColor
OneTimeSetUp: System.TimeoutException : [iOS] WebView BackgroundColor is not setting correctly
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/sr
...
WebViewShouldNotMirrored
OneTimeSetUp: System.TimeoutException : FlowDirection RightToLeft causes mirrored content in WebView
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in
...
WebViewEvaluateJavaScriptReturnsCorrectResults
OneTimeSetUp: System.TimeoutException : Evaluating javascript in MAUI WebView on IOS returns NULL
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_
...
VerifyWebViewDynamicBackgroundColor
OneTimeSetUp: System.TimeoutException : [iOS] WebView BackgroundColor is not setting correctly
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/sr
...
WebView_TestCookieManagement_VerifyAddCookieAndEvaluateJavaScript
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
WebView_SetHtmlSource_VerifyNavigatedEvent
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyHybridWebViewWithFlowDirection
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyWebViewWithShadow
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
Bugzilla35733Test
OneTimeSetUp: System.TimeoutException : iOS WebView crashes when loading an URL with encoded parameters
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState()
...
WebViewEvalCrashesOnAndroidWithLongString
OneTimeSetUp: System.TimeoutException : [Android] WebView.Eval crashes on Android with long string
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /
...
WebView_TestEvaluateJavaScriptAsync_VerifyJavaScriptExecutionWithMultiplePages
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
WebView_TestClearCookies_VerifyCookiesCleared
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...
VerifyHybridWebView_EvaluateJavaScriptWithDifferentDefaultFile
OneTimeSetUp: System.TimeoutException : Timed out waiting for Go To Test button to appear
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests.UtilExtensions.NavigateToGallery(IApp app, String page) in /_/src/Controls/tests/TestCases.Shared.Tests/UtilExtensions.cs:line 37
   at Microsoft.Maui.TestCases.Tests._GalleryUITest.FixtureSetup() in /_/src/Controls/
...

(+19 more — see TRX in artifact)

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)


🔍 Regression Cross-Reference

🔍 Regression Cross-Reference

🟢 No regression risks detected. No labeled bug-fix PRs in the last 6 months touched the modified files.


🔍 Pre-Flight — Context & Validation

Issue: #30010 - Loading the captured screenshot from webview content to Image control does not visible
PR: #35384 - Android Screenshot/WebView capture fix
Platforms Affected: Android
Files Changed: 4 implementation/build/sample, 2 test, plus Copilot pipeline support files

Key Findings

  • GitHub CLI authentication was unavailable, so pre-flight used the checked-out pr-review-35384 branch, local diffs, gate artifacts, and existing PR state files.
  • Effective product fix changes src/Essentials/src/Screenshot/Screenshot.android.cs to add an Android PixelCopy capture path for compositor-backed WebView pixels.
  • Gate result was already failed: Issue30010 passed both without and with fix, so the current repro test does not prove the bug is caught.
  • Changed test files are UI tests: src/Controls/tests/TestCases.HostApp/Issues/Issue30010.cs and src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30010.cs; detected category is WebView.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: medium
Errors: 3 | Warnings: 5 | Suggestions: 7

Key code review findings:

  • ❌ PixelCopy await has no timeout/cancellation path; a missing callback can hang screenshot capture indefinitely.
  • ConfigureAwait(true) on PixelCopy result is inconsistent with surrounding async code and can increase main-thread deadlock risk.
  • CaptureAsync(View) can silently fall back to Canvas when no Activity/Window is found, leaving WebView capture broken for that path.
  • ⚠️ Current PR allocates a new Handler(Looper.MainLooper!) per capture; using view.Handler matches existing MAUI PixelCopy sample code and avoids extra Java peer allocation.
  • ⚠️ UI test is weak because the gate showed it passes without the product fix.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35384 Add Android PixelCopy path before Canvas/DrawingCache fallback ❌ Gate failed (test passes without fix) Screenshot.android.cs, Issue30010 test files Original PR; root cause direction is sound but validation is weak

🔬 Code Review — Deep Analysis

I now have all the information needed. Here is the complete, structured review.


Code Review: PR #35384 — Android Screenshot WebView Fix

Context Established from Local Diff

The actual local diff (pr-review-35384 branch, single squashed commit) modifies:

File Nature
src/Essentials/src/Screenshot/Screenshot.android.cs Core fix: introduces PixelCopy API path
src/Controls/tests/TestCases.HostApp/Issues/Issue30010.cs New test host-app page
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue30010.cs New UI test
src/Essentials/samples/Samples/View/ScreenshotPage.xaml Sample page update

Root cause (Issue 30010): Canvas.draw(view) cannot capture hardware-accelerated layers. Android renders WebView content through a dedicated GPU compositor surface that is entirely invisible to a Canvas-based software capture. The fix adds a PixelCopy.Request() code path (API 26+) which reads directly from the GPU frame buffer.

Secondary context — HybridWebView.js: The current HybridWebView.js in src/Core/src/Handlers/HybridWebView/ is the __awaiter/__generator downlevel (ES5-target) TypeScript compilation output. The Core.csproj compiles HybridWebView.ts via Microsoft.TypeScript.MSBuild 5.7.1 and embeds the result as a logical resource _framework/hybridwebview.js. The git history shows repeated cycles of "add downlevel polyfill → revert to ES6" — that churn is the "downlevel-generated JavaScript fix" described in the PR context.


Part 1 — Code Review Findings

🔴 CRITICAL

[C-1] Screenshot.android.cs:~105tcs.Task has no timeout or cancellation path

RenderUsingPixelCopyAsync awaits tcs.Task without any timeout or CancellationToken. If PixelCopy.Request() succeeds (no exception) but OnPixelCopyFinished is never invoked — e.g. the Window surface is destroyed between Request() and the callback, or the underlying SurfaceView is detached mid-capture — the Task hangs forever. There is no CancellationToken on any CaptureAsync overload to propagate cancellation.

// PROBLEM: no deadline here
return await tcs.Task.ConfigureAwait(true);  // line ~105 in RenderUsingPixelCopyAsync

Required fix: wrap with Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromSeconds(5), ct)), or accept an optional CancellationToken and wire it to tcs.TrySetCanceled().


[C-2] Screenshot.android.cs:~105ConfigureAwait(true) is the only non-false await in the file

All other await calls use ConfigureAwait(false). The one await tcs.Task.ConfigureAwait(true) forces resumption on the captured SynchronizationContext (the main thread). OnPixelCopyFinished is already posted to Looper.MainLooper (main thread) and calls TrySetResult. Because TaskCreationOptions.RunContinuationsAsynchronously is set, this schedules the continuation as a queued work item on the main-thread message loop. That is fine in an interactive app. But any caller that blocks on .Result or .GetAwaiter().GetResult() while already on the main thread will deadlock. The inconsistency is confusing and should be ConfigureAwait(false) for consistency unless there is a documented reason to return to the UI thread.


[C-3] Screenshot.android.cs:~63-74RenderUsingPixelCopyAsync falls back silently when window is null, leaving WebView screenshots uncaptured

CaptureAsync(View view) resolves a Window via GetActivity(view.Context)?.Window. If the View's Context is not an Activity (e.g. an ApplicationContext, service-level context, or any ContextWrapper that doesn't wrap an Activity), GetActivity returns null, window is null, and RenderUsingPixelCopyAsync returns null early. The code then falls through to RenderUsingCanvasDrawing(view) — which also cannot capture WebView content. The net result is that the entire fix is inert in this code path. Users calling Screenshot.CaptureAsync(view) on a WebView embedded with a non-Activity context get the same broken behavior as before.


🟠 MAJOR

[M-1] Screenshot.android.cs:~88new Handler(Looper.MainLooper!) allocates a new Handler per capture; existing DeviceTests uses view.Handler

The existing PixelCopy reference implementation in src/Core/tests/DeviceTests.Shared/ImageAnalysis/RawBitmap.cs:152 passes view.Handler to PixelCopy.Request(...). This is the view's own native handler and requires no allocation. The PR creates new Handler(Looper.MainLooper!) on every call. On Android, Handler creation has JNI overhead and allocates a Java peer. view.Handler should be used when available and non-null; fall back to new Handler(Looper.MainLooper!) only when it is null. Additionally, Looper.MainLooper! null-suppression will throw a NullPointerException in test harness environments where no main looper is prepared.


[M-2] Screenshot.android.cs:~108-116 — Magic constant 0 instead of PixelCopy.Success

if (copyResult == 0)  // PixelCopy.SUCCESS == 0

The Android binding exposes PixelCopy.Success as a typed constant. Using the magic 0 requires the comment to explain it, and the comment could become stale (if the binding is updated to use an enum). Use (int)PixelCopy.Success or name the literal.


[M-3] Screenshot.android.cs:~43-46CaptureAsync(View) public API return type changed from effectively-non-null to explicitly nullable

The old code:

public Task<IScreenshotResult> CaptureAsync(View view) { ... return Task.FromResult<IScreenshotResult>(result); }

The new code:

public async Task<IScreenshotResult?> CaptureAsync(View view) { ... }

The IPlatformScreenshot interface already declares Task<IScreenshotResult?> CaptureAsync(View view), so the implementation change is conformant to the interface. But external consumers that held a reference typed as ScreenshotImplementation directly and expected a non-null Task<IScreenshotResult> from the concrete class now see a nullable return. The behavioral change (now may return null where before it would have returned null but typed as non-nullable) should be noted in the PR description, and the shared interface contract should be reviewed to ensure callers downstream handle null.


[M-4] Issue30010.cs:~72-84 — Test validation runs inside host app; UI test cannot detect host-app validation failure

The ContainsWebViewMarker logic runs in the host app C# code and only sets _statusLabel.Text. The UI test:

App.WaitForTextToBePresentInElement("StatusLabel", "Screenshot captured");

will only pass if ContainsWebViewMarker returns true. But there is no explicit test assertion on "Error:" absence. If the test infrastructure reports "Screenshot captured" due to a timing quirk (race between setting the label and waiting for it), the test passes vacuously. The UI test should additionally assert that _resultImage.Source is non-null, or check that the label text does not contain "Error". More importantly, the ContainsWebViewMarker only samples every 4th pixel in both axes — this 16× subsampling may miss narrow WebView renders on small emulator screen resolutions.


[M-5] Screenshot.android.cs:~112-123GetActivity context-unwrap loop: infinite loop risk

while (context is ContextWrapper wrapper)
{
    if (context is Activity activity)
        return activity;
    context = wrapper.BaseContext;
}

While extremely rare, a pathological ContextWrapper subclass could return this from BaseContext, creating a cycle. This would spin the loop indefinitely. Add a depth guard (int depth = 0; if (++depth > 10) break;) consistent with AOSP's own context-unwrapping pattern.


🟡 MODERATE

[Mo-1] Screenshot.android.cs:~60 — No documentation of the three-tier fallback strategy

RenderAsync silently tries PixelCopy → Canvas → DrawingCache with no logging at any tier boundary. When PixelCopy returns null (e.g. window is null, view not attached), callers have no signal that they fell through to a tier that cannot capture WebView content. At minimum, a Debug.WriteLine or a structured return type distinguishing "captured but empty" from "fallback used" would aid diagnostics.


[Mo-2] ScreenshotPage.xaml:~18-36 — Indentation inconsistency in sample

The new <StackLayout> block uses 2-space indentation while surrounding <StackLayout> elements use 4-space. MAUI XAML convention is 4-space. This is minor but CI dotnet format does not check XAML indentation.


[Mo-3] HybridWebView.js:18__generator references Iterator global — ES2025-era fragility

The downlevel TypeScript compilation emits:

var g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);

Iterator (with capital I) is the TC39 Iterator Helpers proposal, shipping in V8 12.2+ (Chrome 122+, Android WebView ≥ March 2024). On older Android WebView versions that don't define Iterator, the ternary falls back to Object.prototype, which is correct. On newer WebViews that do define it, the generator inherits from Iterator.prototype, gaining .map(), .filter(), etc. This is functionally inert for the current code but creates a subtle semantic shift depending on WebView version — the generator object is not the same shape on Android 13 vs. Android 15 devices. This version-dependent prototype chain is a sign that the downlevel compilation target is wrong for the deployment environment.


[Mo-4] HybridWebView.js:~44-271 — Downlevel compilation target mismatch with deployed Android WebView capability

Android WebView has shipped Chromium ≥ 55 since Android 7.0 (API 24, 2016), which natively supports async/await, const/let, arrow functions, and template literals. Devices running API 21–23 receive WebView updates via Google Play independently of the OS version; as of Play Store policy, a WebView supporting ES2017 is installed. The downlevel __awaiter/__generator polyfills add ~40 lines of non-obvious state-machine code for zero gain on any real device. The git history shows 15e99bb "Revert HybridWebView.js to clean ES6 version" and acec196 "Actually revert HybridWebView.js to match origin/main" — the project has already established intent to maintain a clean ES6 output.


[Mo-5] Screenshot.android.cs:~PlatformOpenReadAsyncMemoryStream position not reset before return (pre-existing, exposed by fix)

Task PlatformCopyToAsync(Stream destination, ScreenshotFormat format, int quality)
{
    bmp.Compress(f, quality, destination);
    return Task.CompletedTask;
}

PlatformOpenReadAsync creates a MemoryStream, calls PlatformCopyToAsync, then sets result.Position = 0. This is fine. But PlatformCopyToAsync takes a generic Stream destination and doesn't reset position. If the destination is not seekable, callers piping to a non-seekable stream get no error. Pre-existing, but worth noting since the PR adds a new using var ms = new MemoryStream(); await stream.CopyToAsync(ms) pattern in Issue30010.cs that re-reads after OpenReadAsync — the two-stream approach is correct but unnecessarily round-trips through the Bitmap encoder when a direct PlatformCopyToAsync would suffice.


🔵 MINOR

[Mi-1] Issue30010.cs:~50 — WebView Navigated event subscription never unsubscribed

webView.Navigated += (s, e) => { _statusLabel.Text = "WebView loaded"; ... };

The lambda captures _statusLabel and _screenshotButton. The ContentPage holds the webView, which holds the event subscription, which holds a closure back to page fields. This creates a cycle that prevents collection until the page is finalized. In a test page this is acceptable but should use += with a named method or be unsubscribed in OnDisappearing.


[Mi-2] Issue30010.cs:~56async void event handler without timeout guard

OnTakeScreenshotClicked is async void — correct for event handlers — and has a try/catch, also correct. But there is no timeout on Screenshot.CaptureAsync(). If the new PixelCopy path hangs (see C-1), the button tap will silently freeze the page with the status label stuck on "WebView loaded".


Part 2 — Root-Cause Hypotheses

Root Cause A — Hardware-Compositor Surface Isolation (Primary)

Android draws all accelerated content — including WebView, SurfaceView, TextureView in surface mode — through a dedicated hardware layer that is composited by SurfaceFlinger. The CPU-side Canvas.draw(view) and DrawingCache approaches only capture what the view renders onto a Canvas. WebView, which manages its own SurfaceView/TextureView child, simply calls drawChild → View.draw(canvas) on that child, which produces nothing (or black) because the actual pixel data lives on the GPU surface, not in the view hierarchy. PixelCopy bypasses this by requesting a copy of a screen rect from the compositor's frame buffer, which includes all layers.

Root Cause B — getDrawingCache() and draw(canvas) API Deprecation

DrawingCache was deprecated in API 28 and was always unreliable for hardware-accelerated views. It worked in API < 11 when all rendering was software. For API 11+, setDrawingCacheEnabled(true) on a hardware-layer view produces a blank bitmap unless the view explicitly supports software rendering fallback. WebView explicitly opts out of software fallback, so DrawingCache returns a black or empty bitmap.

Root Cause C — TypeScript Compilation Target Drift (HybridWebView.js secondary)

The HybridWebView.ts is compiled to HybridWebView.js at build time via Microsoft.TypeScript.MSBuild. The TypeScript configuration (no tsconfig.json committed to src/Core/) means the TypeScript compiler uses its default settings or project-level props. When target is not explicitly set to ES2015+, the TypeScript MSBuild task defaults to ES3 or ES5 and emits __awaiter/__generator. The git churn (15e99bb, acec196, 135e8e8, 9d0aabcb30) shows that contributors repeatedly tried to commit downlevel-compiled JS and then had to revert it, implying the tsconfig.json / TypeScript MSBuild settings are not pinned to an ES6+ target — leaving the generated output sensitive to local TypeScript tooling versions.


Part 3 — Alternative Fix Candidates for the Downlevel JavaScript Issue

Alternative 1: Commit a tsconfig.json pinning "target": "ES2017" in the handler directory

Approach: Add src/Core/src/Handlers/HybridWebView/tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2017",
    "module": "none",
    "strict": true,
    "noImplicitAny": true,
    "outFile": "HybridWebView.js",
    "sourceMap": true
  },
  "files": ["HybridWebView.ts"]
}

ES2017 is the minimum that includes native async/await. The module: "none" matches the current IIFE output structure. The generated HybridWebView.js would use native async/await, const/let, arrow functions, and template literals with no polyfill helpers. Android WebView has supported all ES2017 features since Chrome 55 (Android 7.0+ native; API 21–23 via Play Store WebView update). The Core.csproj already has Microsoft.TypeScript.MSBuild 5.7.1 — it would honour the tsconfig.json. This resolves the compilation drift by making the target explicit and reproducible.

Testability: The existing HybridWebView integration tests validate the JS↔.NET messaging contract regardless of the compilation target. No new test infrastructure needed; the compiled JS can be diffed in CI to confirm no polyfills appear.

Tradeoff: Very old devices (API 21–23) without an updated WebView APK could fail. In practice, Google Play has required WebView updates since 2014 and MAUI's minimum API is 21, but adding an explicit note in SUPPORTED_VERSIONS.md is warranted.


Alternative 2: Rewrite invokeDotNet and invokeJavaScript as explicit Promise chains (zero polyfill, zero transpilation concern)

Approach: Remove async/await from HybridWebView.ts and use .then()/.catch() chaining:

function invokeDotNet(methodName: string, paramValues?: any): Promise<any> {
    // ... build body / message ...
    return fetch(requestUrl, { method: 'POST', headers, body: message })
        .then(rawResponse => rawResponse.json())
        .then((response: DotNetInvokeResult) => {
            if (!response) return null;
            if (response.IsError) {
                const error = new Error(response.ErrorMessage ?? 'Unknown error');
                throw error;
            }
            return response.IsJson ? JSON.parse(response.Result) : response.Result;
        });
}

function invokeJavaScript(taskId: string, methodName: Function, args: any[]): Promise<void> {
    return Promise.resolve()
        .then(() => methodName(...args))
        .then(result => invokeJavaScriptCallbackInDotNet(taskId, result))
        .catch(ex => { console.error(ex); invokeJavaScriptFailedInDotNet(taskId, ex); });
}

Why this is meaningfully different: This approach requires no TypeScript async/await at all. The TypeScript compiler emits pure ES5-compatible code even without __awaiter/__generator because Promise is used directly. The .ts file becomes compilable at any TypeScript target without producing polyfills. The __generator state machine — which has the Iterator global fragility — disappears entirely.

Testability: Functionally identical to the current implementation; all existing HybridWebView device tests validate messaging correctness. The .then() chain is unit-testable with a mock fetch in a Node.js test runner, which the current __awaiter-based code is not easily isolated from.

Tradeoff: Less readable for TypeScript maintainers accustomed to async/await. The current TypeScript source expresses intent clearly with await — converting to chains sacrifices ergonomics for portability.


Alternative 3: Replace fetch with the existing hybridWebViewHost.sendMessage channel for invokeDotNet on Android (eliminating async/await entirely for Android)

Approach: The current design uses an HTTP fetch to /__hwvInvokeDotNet for .NET method invocation. On Android, the Window.hybridWebViewHost JavaScript interface (a @JavascriptInterface Java object) is already available for JS→.NET communication via sendMessage. The response can be returned through the same message-passing channel (HybridWebViewMessageReceived event) rather than through an HTTP response body.

This would change Android invokeDotNet to:

function invokeDotNet(methodName, paramValues) {
    return new Promise(function(resolve, reject) {
        var taskId = Math.random().toString(36).slice(2);
        // register a one-shot response listener
        function onResponse(e) {
            var msg = e.detail.message;
            if (msg.startsWith('__DotNetResult|' + taskId + '|')) {
                window.removeEventListener('HybridWebViewMessageReceived', onResponse);
                var payload = JSON.parse(msg.slice(('__DotNetResult|' + taskId + '|').length));
                if (payload.IsError) reject(new Error(payload.ErrorMessage));
                else resolve(payload.IsJson ? JSON.parse(payload.Result) : payload.Result);
            }
        }
        window.addEventListener('HybridWebViewMessageReceived', onResponse);
        window.hybridWebViewHost.sendMessage('__InvokeDotNet|' + taskId + '|' + JSON.stringify({MethodName: methodName, ParamValues: paramValues}));
    });
}

The C# handler would add a __InvokeDotNet message type and route the response back via EvaluateJavaScript or the existing SendMessage path.

Why this is meaningfully different from both current fix and Alt 1/2: This eliminates the fetch HTTP call entirely on Android, removing the cross-origin fake-URL hack (/__hwvInvokeDotNet) from the Android code path. It also eliminates async/await from the Android code path since the Promise constructor callback is synchronous. Android WebView's JavascriptInterface path (hybridWebViewHost.sendMessage) is guaranteed to work regardless of WebView version or network stack state. The HTTP fetch requires a functioning WebViewClient.shouldInterceptRequest handler — which can fail if the handler is incorrectly registered or if the URL scheme isn't intercepted.

Testability: Requires a new C# handler message type (__InvokeDotNet). Unit-testable: the JavaScript side can be tested with a mock window.hybridWebViewHost in Node.js. Device tests for HybridWebView.InvokeDotNetAsync would exercise the new path.

Tradeoff: A non-trivial architectural change affecting both the JS and the Android C# handler. All three platforms share HybridWebView.ts; the Android-only branch (detected via window.hybridWebViewHost) creates a conditional code path that doesn't apply to iOS/Windows, increasing maintenance surface. This is best suited for a platform-specific .android.ts compilation unit, but the MAUI build infrastructure would need to support per-platform TypeScript compilation.


Alternative 4 (Bonus): Add Babel as a post-compilation polyfill injection step with feature-detection guard

Approach: Add a Babel transform step after tsc compilation that wraps the generated async code in a feature-detection check:

// Injected at top of HybridWebView.js by Babel
if (typeof Promise === 'undefined' || typeof Symbol === 'undefined') {
    // load __awaiter / __generator polyfill only here
}

Using @babel/preset-env with targets: { chrome: "55" } (matching Android WebView minimum) produces polyfill injection only for the features not available in Chrome 55+. On modern WebViews, the polyfill block is dead code eliminated by the JS engine. On very old WebViews (pre-API 21 with ancient Chromium), the polyfill activates.

Why meaningfully different: This is a build-infrastructure solution rather than a source-code solution. It doesn't require changing the TypeScript source or the .NET handler, only the MSBuild pipeline. The Core.csproj TypeScript compilation target becomes Target AfterTargets="CompileTypeScript" with a Babel invocation.

Testability: The generated output can be snapshot-tested against a reference. The Microsoft.TypeScript.MSBuild NuGet would remain for type-checking; Babel handles the output transform. Adds a node dependency to the MAUI build, which may conflict with the project's dotnet-first build philosophy (see [Build & MSBuild dimension]: "Use dotnet-public feed" / "Build tasks cannot depend on optional NuGet packages").


Summary Table

ID Severity Area One-line description
C-1 🔴 Critical Android/Async tcs.Task has no timeout; PixelCopy callback may never fire → infinite hang
C-2 🔴 Critical Android/Threading ConfigureAwait(true) inconsistency + potential main-thread deadlock
C-3 🔴 Critical Android/WebView CaptureAsync(View) with non-Activity context falls back to Canvas → WebView still blank
M-1 🟠 Major Android/Performance new Handler(Looper.MainLooper!) allocation per call; use view.Handler
M-2 🟠 Major Android Magic constant 0 for PixelCopy.SUCCESS
M-3 🟠 Major Public API CaptureAsync(View) return type behavioral change needs documentation
M-4 🟠 Major Testability UI test cannot detect host-app validation failure; ContainsWebViewMarker too coarse
M-5 🟠 Major Android GetActivity context-walk loop has no depth guard
Mo-1 🟡 Moderate Diagnostics No logging at fallback tier boundaries
Mo-2 🟡 Moderate Sample XAML indentation inconsistency
Mo-3 🟡 Moderate JS/Android Iterator global in __generator is ES2025 — version-dependent prototype chain
Mo-4 🟡 Moderate JS/Build Downlevel __awaiter/__generator mismatch with deployed Android WebView capability
Mo-5 🟡 Moderate Android Pre-existing: PlatformCopyToAsync doesn't reset stream position
Mi-1 🔵 Minor Memory webView.Navigated lambda never unsubscribed in test page
Mi-2 🔵 Minor Async OnTakeScreenshotClicked has no timeout guard against PixelCopy hang

Blocking issues before merge: C-1, C-2, C-3, M-1, M-4 (test must reliably catch regressions), Mo-3 (JS Iterator fragility if the downlevel JS is retained).


🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix Temporary software-layer Canvas capture ✅ PASS 1 file Satisfies weak UI test but mutates the view tree and is less robust for compositor-backed WebView content than PixelCopy
2 try-fix PixelCopy using view.Handler ✅ PASS 1 file Strongest candidate: same root-cause fix as PR, lower allocation, aligns with existing MAUI PixelCopy sample; still lacks timeout
3 try-fix PixelCopy using view.Handler plus 5-second timeout/fallback ✅ PASS 1 file More resilient to missing callbacks, but self-review found bitmap ownership race on late callback
PR PR #35384 PixelCopy using new main-looper Handler ❌ Gate failed 3 product/test files Original PR; correct root-cause direction, but gate did not prove regression coverage

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Generated distinct approaches: software-layer Canvas, view-handler PixelCopy, bounded PixelCopy, and broader JS/build alternatives that were determined unrelated to the effective branch diff
local test loop 1 Yes Candidate 1 passing showed the Issue30010 test is too weak to distinguish root-cause fixes from potentially fragile Canvas workarounds
local test loop 2 Yes Candidate 2 passing plus expert warning led to candidate 3 with timeout/fallback
local test loop 3 No Candidate 3 exposed a race requiring ownership design; no further meaningfully different low-risk candidate remained without expanding API/cancellation surface

Exhausted: Yes
Selected Fix: Candidate #2 as the best alternative candidate — it keeps the PixelCopy root-cause fix, reduces per-capture Handler allocation, and follows existing MAUI PixelCopy usage. Candidate #3 may become better after fixing bitmap ownership around timeout/late-callback races. None can be called demonstrably better than the PR from tests alone because the gate test passes without the fix.


📋 Report — Final Recommendation

Comparative Report — PR #35384 Android Screenshot/WebView Fix

Ranking

Rank Candidate Regression status Assessment
1 pr-plus-reviewer Not rerun per instruction Best technical candidate. Preserves the PR's PixelCopy root-cause fix, adopts view.Handler, adds a bounded wait, and guards bitmap ownership so timeout and late PixelCopy callbacks cannot race into double-dispose/returning a disposed bitmap. Also strengthens the test validation by checking the WebView rectangle.
2 try-fix-2 Passed Strongest completed try-fix. It uses PixelCopy and view.Handler, matching the root cause and existing MAUI PixelCopy patterns. It still has the unbounded callback wait.
3 try-fix-3 Passed Adds a timeout/fallback and uses view.Handler, but its self-review found a bitmap ownership race between timeout disposal and a late success callback.
4 try-fix-1 Passed The software-layer Canvas workaround passed the weak test, but it mutates the view tree and is less reliable for compositor-backed WebView pixels than PixelCopy.
5 pr Failed gate Correct PixelCopy direction, but ranked below candidates that passed regression checks because the gate failed: the test passed without the fix. It also allocates a new Handler per capture and can hang indefinitely if PixelCopy never calls back.

Decision

Winner: pr-plus-reviewer. It is the only candidate that combines the correct Android PixelCopy root-cause fix with the expert review's actionable improvements and fixes the ownership race identified in the bounded try-fix candidate. The raw PR is not acceptable as submitted because the regression gate failed and the implementation can hang indefinitely; the passing try-fix candidates are useful evidence but either omit timeout handling or implement it with a race.

Notes on Regression Evidence

The known gate result means the current Issue30010 UI test is weak: it passed without the product fix. The try-fix pass results therefore prove the candidates did not obviously break the scenario, but they do not conclusively prove the original regression is covered. This is why the winning candidate includes test-strengthening work in addition to the product fix.


@kubaflo kubaflo changed the base branch from main to inflight/current May 28, 2026 15:32
@kubaflo kubaflo merged commit c48946e into inflight/current May 28, 2026
155 of 167 checks passed
@kubaflo kubaflo deleted the fix/android-screenshot-webview branch May 28, 2026 15:33
@github-actions github-actions Bot added this to the .NET 10.0 SR8 milestone May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Loading the captured screenshot from webview content to Image control does not visible

4 participants