Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/devflow-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ jobs:
xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24"
dotnet test $TEST_PROJECT
-c Debug
--filter "Category!=Device&Category!=Sensors&Category!=SecureStorage"
--filter "Category!=Device&Category!=Sensors&Category!=SecureStorage&fixture!=GenericWebView"
--logger "trx;LogFileName=devflow-integration-gtk-maui.trx"
--logger "console;verbosity=normal"
--results-directory ./artifacts/TestResults/devflow-integration/gtk
Expand Down
3 changes: 3 additions & 0 deletions samples/DevFlow.Sample/AppShell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ public partial class AppShell : Shell
public AppShell()
{
InitializeComponent();
AddWebViewTestPages();
#if MACOS
FlyoutBehavior = FlyoutBehavior.Locked;
MacOSShell.SetUseNativeSidebar(this, true);
#endif
}

partial void AddWebViewTestPages();
}
1 change: 1 addition & 0 deletions samples/DevFlow.Sample/DevFlow.Sample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\DevFlow\Microsoft.Maui.DevFlow.Agent\Microsoft.Maui.DevFlow.Agent.csproj" />
<ProjectReference Include="..\..\src\DevFlow\Microsoft.Maui.DevFlow.Blazor\Microsoft.Maui.DevFlow.Blazor.csproj" />
<ProjectReference Include="..\..\src\DevFlow\Microsoft.Maui.DevFlow.WebView\Microsoft.Maui.DevFlow.WebView.csproj" />
<ProjectReference Include="..\..\src\DevFlow\Microsoft.Maui.DevFlow.Analyzers\Microsoft.Maui.DevFlow.Analyzers.csproj"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer" />
Expand Down
2 changes: 2 additions & 0 deletions samples/DevFlow.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.Maui.DevFlow.Agent;
using Microsoft.Maui.DevFlow.Agent.Core;
using Microsoft.Maui.DevFlow.Blazor;
using Microsoft.Maui.DevFlow.WebView;

namespace DevFlow.Sample;

Expand Down Expand Up @@ -118,6 +119,7 @@ public static MauiApp CreateMauiApp()
PreferenceDiagnostics.Register(diagnostics);
});
builder.AddMauiBlazorDevFlowTools();
builder.AddMauiWebViewDevFlowTools();
#endif

return builder.Build();
Expand Down
92 changes: 92 additions & 0 deletions samples/DevFlow.Sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# DevFlow maintained sample

This experimental .NET 10 MAUI app exercises native automation and the shared
DevFlow WebView bridge. Build it with the matching Agent, WebView, Blazor, Client
and CLI versions from this repository. Debug builds register the agent and both
WebView adapters; Blazor registration already includes the generic adapter, so
the explicit generic registration also exercises idempotency.
The optional Shell partial hook adds these pages only where their source is
compiled. Backend samples that link the shared Shell keep their existing routes;
this change does not add backend handlers or claim backend runtime coverage.

| Shell route | Maintained fixture |
|---|---|
| `//native` | Native todo controls and screenshots |
| `//blazor` | Real Blazor todo events and `/counter` routing |
| `//webview` | Standard WebView loading packaged HTML, input events, one-click count, console and controlled DOM defects |
| `//hybrid` | Actual `MauiAsset` resources, original Hybrid raw JSON messages in both directions, relative navigation and reload |
| `//mixedwebviews` | All three hosts together: unnamed standard host and intentionally duplicate Hybrid/Blazor AutomationIds |

Use the native restore/recreate buttons to reset fixtures. The standard fixture
has a pink background; Hybrid has a green background. Both include a 60px
ellipsis and an 80x80 child clipped by a 40x40 parent (75% area loss). The mixed
page also has a native 80x80 label clipped by a 40x40 grid, so disabling DOM layout
inspection can be checked without disabling native diagnostics.

Standard inline HTML is self-contained. Its relative link is exercised only
after the integration test navigates that host to its owned loopback HTTP server:
navigation from `HtmlWebViewSource` to cache files is platform-restricted.
Hybrid relative links use the real packaged resources and MAUI resource/message
infrastructure. Tests restore standard HTML **before** disposing their HTTP
fixture; the sample never needs a test server after a test ends.

## Run and inspect

```bash
dotnet build samples/DevFlow.Sample/DevFlow.Sample.csproj -f net10.0-maccatalyst -c Debug
# Launch the resulting MauiTodo.app, then select its actual agent port:
maui devflow --agent-port <port> agent status
maui devflow --agent-port <port> webview webviews
maui devflow --agent-port <port> webview --context-id webview-<index> source
```

Context IDs come from the live contexts response; do not hard-code indexes or
use AutomationIds as browser selectors. Hidden Shell pages may retain registered
hosts, but only the active page participates in layout diagnostics. Recreation
removes old registrations, including when a new native host reuses the same
AutomationId. A native element ID can remain stable while its browser context ID
changes.

## Cross-host integration matrix

```bash
# Reuse a freshly built/running app, or omit DEVFLOW_TEST_PORT to let the fixture build/launch it.
DEVFLOW_TEST_PLATFORM=maccatalyst DEVFLOW_TEST_PORT=<port> \
dotnet test src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests \
--filter 'FullyQualifiedName~GenericWebViewTests|FullyQualifiedName~WebViewTests'
```

`GenericWebViewTests` verifies canonical ready-only contexts, actual owner
correlation, input events/fill/insertText, exactly one click, original Hybrid
ready/ack messages, real Blazor counter events/routing, decoded native-resolution
host-sized PNGs with host-specific colors, HTTP and packaged relative navigation,
reload/recreation, console delivery, measured layout defects and native-only
scope/revision stability. It returns to the native page after each case.

All 24 generic fixture cases run on the Android, iOS, Mac Catalyst and Windows
MAUI sample heads. They carry `fixture=GenericWebView` separately from the
`Category=WebView` trait. GTK excludes only that fixture because its linked Shell
does not compile these pages; its existing Blazor WebView and native integration
cases remain selected. AppKit/WPF retain their existing WebView exclusions, and
plain .NET samples retain the `framework!=maui` filter.

For the class-order screenshot reproduction in [#407](https://github.com/dotnet/maui-labs/issues/407),
run the **complete existing class**, repeatedly on the same app:

```bash
for run in 1 2 3 4 5; do
sleep 11 # Let the previous process's default 10-second mutation lease expire.
DEVFLOW_TEST_PLATFORM=maccatalyst DEVFLOW_TEST_PORT=<port> \
dotnet test src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests --no-build \
--filter 'FullyQualifiedName~Microsoft.Maui.DevFlow.Agent.IntegrationTests.WebViewTests' \
--logger "trx;LogFileName=webview-$run.trx"
done
```

Use a longer wait if the app configures a longer lease timeout. Separate test
runner processes must not compete for the mutation lease; a lease rejection
does not reproduce the in-suite screenshot failure.

The app targets Android, iOS, Mac Catalyst and Windows. A run on one host is not
runtime evidence for the others; repeated local passes alone do not establish
that an intermittent native WebKit crash or #407 is resolved.
56 changes: 56 additions & 0 deletions samples/DevFlow.Sample/Resources/Raw/webview-fixture/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hybrid WebView fixture</title>
<script src="_framework/hybridwebview.js"></script>
<style>
html { background: #e2f4e7; }
html:has(body[data-host="standard"]) { background: #fbe4e4; }
body { font: 18px system-ui; margin: 16px; }
input, button { font: inherit; padding: 6px; margin: 4px; max-width: 90%; }
#fixture-overflow { width: 60px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#fixture-clip { position: relative; width: 40px; height: 40px; overflow: hidden; }
#fixture-clipped { width: 80px; height: 80px; background: #345678; }
</style>
</head>
<body data-host="hybrid">
<h1>Generic WebView fixture</h1>
<p>Click count: <span id="fixture-count">0</span></p>
<input id="fixture-input" placeholder="Enter text">
<p>Input event value: <span id="fixture-input-value"></span></p>
<button id="fixture-button">Increment once</button>
<button id="fixture-log">Write console marker</button>
<p>Message from .NET: <span id="native-message">none</span></p>
<p id="fixture-overflow">Deliberately overflowing fixture text</p>
<div id="fixture-clip"><div id="fixture-clipped">Clipped</div></div>
<a id="fixture-link" href="second.html">Navigate to second document (packaged Hybrid or HTTP)</a>
<script>
let count = 0;
function send(action, value) {
if (window.HybridWebView) {
window.HybridWebView.SendRawMessage(JSON.stringify({ action, value }));
}
}
document.querySelector("#fixture-button").addEventListener("click", () => {
document.querySelector("#fixture-count").textContent = String(++count);
send("js-click", String(count));
});
document.querySelector("#fixture-input").addEventListener("input", event => {
document.querySelector("#fixture-input-value").textContent = event.target.value;
});
document.querySelector("#fixture-log").addEventListener("click", event => {
const marker = `devflow-generic-webview-console-marker-${Date.now()}-${Math.random()}`;
event.currentTarget.dataset.lastMarker = marker;
console.log(marker);
});
window.addEventListener("HybridWebViewMessageReceived", event => {
const message = JSON.parse(event.detail.message);
document.querySelector("#native-message").textContent = message.value;
send("js-ack", message.value);
});
send("ready", document.title);
</script>
</body>
</html>
22 changes: 22 additions & 0 deletions samples/DevFlow.Sample/Resources/Raw/webview-fixture/second.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Second WebView document</title>
<script src="_framework/hybridwebview.js"></script>
</head>
<body style="background:#e2f4e7">
<h1 id="second-document">Second document loaded</h1>
<p>Message from .NET: <span id="native-message">none</span></p>
<a id="return-link" href="index.html">Return to fixture</a>
<script>
window.addEventListener("HybridWebViewMessageReceived", event => {
const message = JSON.parse(event.detail.message);
document.querySelector("#native-message").textContent = message.value;
window.HybridWebView.SendRawMessage(JSON.stringify({ action: "js-ack", value: message.value }));
});
window.HybridWebView.SendRawMessage(JSON.stringify({ action: "ready", value: document.title }));
</script>
</body>
</html>
Loading