From 019af516c64b1a571c83451e2f36792390493e33 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 14 Oct 2026 22:27:05 +0200 Subject: [PATCH 1/4] Fix misleading WebView network capture capability Return the standard unsupported browser-network envelope instead of native traffic under the WebView route. Preserve native request endpoints and align capability examples, docs, and focused tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b2bd9105-f47b-4c5b-b5b9-11740d943d70 --- docs/DevFlow/spec/README.md | 8 +- .../examples/capabilities-maui-response.json | 2 +- docs/DevFlow/spec/openapi.yaml | 45 +------ .../DevFlowAgentService.Handlers.cs | 4 +- .../DevFlowAgentService.cs | 2 +- .../ProtocolSpecTests.cs | 15 +++ .../WebViewNetworkCapabilityTests.cs | 121 ++++++++++++++++++ src/DevFlow/README.md | 8 ++ 8 files changed, 160 insertions(+), 45 deletions(-) create mode 100644 src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs diff --git a/docs/DevFlow/spec/README.md b/docs/DevFlow/spec/README.md index 2056abe2f..abbf4b83b 100644 --- a/docs/DevFlow/spec/README.md +++ b/docs/DevFlow/spec/README.md @@ -45,9 +45,11 @@ WebView endpoints also accept `contextId`; query values override the JSON body's JSON object. `/webview/screenshot` returns PNG, trying the registered native element before the CDP screenshot fallback. -`/api/v1/webview/network` is an existing HTTP 200 alias of -`/api/v1/network/requests`: it reads the native .NET HTTP capture store, not browser -fetch/XHR traffic. It does not associate requests with a WebView context. +Browser fetch/XHR capture is explicitly unsupported: `/api/v1/webview/network` +returns the standard 501 `webview.network` envelope instead of its former HTTP 200 +alias of the native capture store. Native .NET traffic remains available at +`/api/v1/network/requests`; the WebView capability no longer advertises network +capture. ## Streaming payloads diff --git a/docs/DevFlow/spec/examples/capabilities-maui-response.json b/docs/DevFlow/spec/examples/capabilities-maui-response.json index beb3490db..f4a234700 100644 --- a/docs/DevFlow/spec/examples/capabilities-maui-response.json +++ b/docs/DevFlow/spec/examples/capabilities-maui-response.json @@ -36,7 +36,7 @@ }, "webview": { "version": 1, - "features": ["evaluate", "contexts", "source", "dom", "dom-query", "network", "console", "screenshot"] + "features": ["evaluate", "contexts", "source", "dom", "dom-query", "console", "screenshot"] }, "network": { "version": 1, diff --git a/docs/DevFlow/spec/openapi.yaml b/docs/DevFlow/spec/openapi.yaml index 9ce4e5700..3fad4a0be 100644 --- a/docs/DevFlow/spec/openapi.yaml +++ b/docs/DevFlow/spec/openapi.yaml @@ -1473,47 +1473,14 @@ paths: get: operationId: getWebViewNetworkRequests tags: [webview] - summary: List captured native .NET network requests + summary: Discover unsupported browser-network capture description: > - Convenience alias for `/api/v1/network/requests`. Returns captured HTTP - requests from the same native .NET network store, optionally filtered - by host, method, or status code. It does not capture browser fetch/XHR - traffic or filter requests by WebView context. - parameters: - - name: limit - in: query - description: Maximum number of requests to return. - schema: - type: integer - minimum: 1 - - name: host - in: query - description: Filter by host name. - schema: - type: string - - name: method - in: query - description: Filter by HTTP method. - schema: - type: string - - name: status - in: query - description: Filter by status code. - schema: - type: integer - minimum: 100 - maximum: 599 + Browser fetch/XHR capture is not implemented. Returns the standard + not_supported envelope for webview.network. Native .NET HTTP traffic + remains available through /api/v1/network/requests. responses: - "200": - description: Array of network request summaries. - content: - application/json: - schema: - type: array - items: - $ref: "./schemas/network.json#/$defs/NetworkRequestSummary" - "500": - $ref: "#/components/responses/InternalError" + "501": + $ref: "#/components/responses/BackendNotSupported" /api/v1/webview/console: get: diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs index b3b8c2a7e..183b65574 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs @@ -2218,7 +2218,9 @@ protected async Task HandleWebViewDomQuery(HttpRequest request) } protected Task HandleWebViewNetwork(HttpRequest request) - => HandleNetworkList(request); + => Task.FromResult(NotSupported( + "webview.network", + "Browser-network capture is not implemented. Use /api/v1/network/requests for native .NET HTTP traffic.")); protected Task HandleWebViewConsole(HttpRequest request) { diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.cs index 5303d871b..272c38f2d 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.cs @@ -359,7 +359,7 @@ private Task HandleCapabilities(HttpRequest request) // Listed unconditionally so clients can discover the group and see *why* it is unavailable, // per the capability protocol: unsupported groups report supported:false rather than vanish. capabilities["webview"] = Capability(1, cdpWebViews.Length > 0, - ["evaluate", "contexts", "source", "dom", "dom-query", "network", "console", "screenshot"], + ["evaluate", "contexts", "source", "dom", "dom-query", "console", "screenshot"], "No WebView has registered a CDP endpoint with this agent."); capabilities["profiler"] = Capability(1, IsProfilerFeatureAvailable, BuildProfilerFeatureList(), "Profiler collector is unavailable on this platform."); diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs index a29c6400b..6b93db6ec 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs @@ -63,6 +63,21 @@ public class ProtocolSpecTests private static readonly Lazy SpecRoot = new(FindSpecRoot); + [Fact] + public void WebViewNetwork_DocumentsUnsupportedBrowserCaptureWithoutChangingNativeNetwork() + { + var openApi = LoadDocument(Path.Combine(SpecRoot.Value, "openapi.yaml")); + var responses = openApi["paths"]!["/api/v1/webview/network"]!["get"]!["responses"]!; + Assert.Null(responses["200"]); + Assert.Equal("#/components/responses/BackendNotSupported", responses["501"]!["$ref"]!.GetValue()); + Assert.NotNull(openApi["paths"]!["/api/v1/network/requests"]!["get"]!["responses"]!["200"]); + + var example = LoadDocument(Path.Combine(SpecRoot.Value, "examples", "capabilities-maui-response.json")); + Assert.DoesNotContain("network", example["capabilities"]!["webview"]!["features"]!.AsArray() + .Select(feature => feature!.GetValue())); + Assert.NotNull(example["capabilities"]!["network"]); + } + [Fact] public async Task OpenApiYaml_CanBeParsedByOpenApiTooling() { diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs new file mode 100644 index 000000000..c4220c4d2 --- /dev/null +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs @@ -0,0 +1,121 @@ +using System.Net; +using System.Net.Sockets; +using System.Text.Json; +using Microsoft.Maui.DevFlow.Agent.Core; +using Microsoft.Maui.DevFlow.Agent.Core.Network; + +namespace Microsoft.Maui.DevFlow.Tests; + +public class WebViewNetworkCapabilityTests +{ + private const string UnsupportedReason = + "Browser-network capture is not implemented. Use /api/v1/network/requests for native .NET HTTP traffic."; + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task WebViewNetwork_ReturnsUnsupportedWithoutReadingNativeTraffic(bool registerWebView) + { + using var service = CreateService(out var port); + if (registerWebView) + service.RegisterCdpWebView(_ => throw new InvalidOperationException("Network capture must not invoke CDP."), () => true); + var entry = AddNativeRequest(service); + service.StartServerOnly(dispatcher: null); + using var http = CreateClient(port); + + using var response = await http.GetAsync("/api/v1/webview/network?host=native.test&contextId=0"); + + Assert.Equal(HttpStatusCode.NotImplemented, response.StatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType); + using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + var body = document.RootElement; + Assert.Equal(3, body.EnumerateObject().Count()); + Assert.Equal("not_supported", body.GetProperty("error").GetString()); + Assert.Equal("webview.network", body.GetProperty("capability").GetString()); + Assert.Equal(UnsupportedReason, body.GetProperty("reason").GetString()); + Assert.Same(entry, service.NetworkStore.GetById(entry.Id)); + Assert.Equal(1, service.NetworkStore.Count); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task Capabilities_NeverAdvertiseBrowserNetworkAndPreserveNativeNetwork(bool registerWebView) + { + using var service = CreateService(out var port); + if (registerWebView) + service.RegisterCdpWebView(_ => Task.FromResult("{}"), () => true); + service.StartServerOnly(dispatcher: null); + using var http = CreateClient(port); + using var document = JsonDocument.Parse(await http.GetStringAsync("/api/v1/agent/capabilities")); + var capabilities = document.RootElement.GetProperty("capabilities"); + var webView = capabilities.GetProperty("webview"); + + Assert.Equal(registerWebView, webView.GetProperty("supported").GetBoolean()); + var features = webView.GetProperty("features").EnumerateArray().Select(feature => feature.GetString()).ToArray(); + Assert.DoesNotContain("network", features); + if (registerWebView) + Assert.Equal(new[] { "evaluate", "contexts", "source", "dom", "dom-query", "console", "screenshot" }, features); + else + Assert.Empty(features); + var network = capabilities.GetProperty("network"); + Assert.True(network.GetProperty("supported").GetBoolean()); + Assert.Equal(new[] { "list", "detail", "clear", "stream" }, + network.GetProperty("features").EnumerateArray().Select(feature => feature.GetString())); + } + + [Fact] + public async Task NativeNetwork_StillReturnsFilteredSummariesAndRequestDetails() + { + using var service = CreateService(out var port); + var entry = AddNativeRequest(service); + service.NetworkStore.Add(new NetworkRequestEntry + { + Id = "other-request", Host = "other.test", Method = "POST", StatusCode = 201 + }); + service.StartServerOnly(dispatcher: null); + using var http = CreateClient(port); + + using var response = await http.GetAsync("/api/v1/network/requests?host=native.test&method=GET&status=200&limit=1"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + using var summaries = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + var summary = Assert.Single(summaries.RootElement.EnumerateArray()); + Assert.Equal(entry.Id, summary.GetProperty("id").GetString()); + Assert.Equal(entry.Url, summary.GetProperty("url").GetString()); + Assert.Equal(200, summary.GetProperty("statusCode").GetInt32()); + Assert.False(summary.TryGetProperty("responseBody", out _)); + + using var detailResponse = await http.GetAsync($"/api/v1/network/requests/{entry.Id}"); + Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode); + using var detail = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync()); + Assert.Equal(entry.ResponseBody, detail.RootElement.GetProperty("responseBody").GetString()); + Assert.Equal(2, service.NetworkStore.Count); + } + + private static NetworkRequestEntry AddNativeRequest(DevFlowAgentService service) + { + var entry = new NetworkRequestEntry + { + Id = "native-request", + Timestamp = DateTimeOffset.UtcNow, + Host = "native.test", + Url = "https://native.test/data", + Method = "GET", + StatusCode = 200, + ResponseBody = "native response" + }; + service.NetworkStore.Add(entry); + return entry; + } + + private static DevFlowAgentService CreateService(out int port) + { + using var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + port = ((IPEndPoint)listener.LocalEndpoint).Port; + return new DevFlowAgentService(new AgentOptions { Port = port, EnableNetworkMonitoring = false }); + } + + private static HttpClient CreateClient(int port) + => new() { BaseAddress = new Uri($"http://localhost:{port}") }; +} diff --git a/src/DevFlow/README.md b/src/DevFlow/README.md index bb774f8fb..376ad4721 100644 --- a/src/DevFlow/README.md +++ b/src/DevFlow/README.md @@ -157,6 +157,14 @@ builder.AddMauiDevFlowAgent(options => }); ``` +### WebView network capture + +Browser fetch/XHR capture is not implemented. **Breaking behavior:** +`/api/v1/webview/network` now returns the standard 501 `not_supported` envelope +with capability `webview.network`, rather than an HTTP 200 alias of the native +capture store. `network` is no longer advertised as a WebView feature. Native +.NET HTTP capture remains available, unchanged, at `/api/v1/network/requests`. + ### GTK/Linux apps Use `Microsoft.Maui.DevFlow.Agent.Gtk` instead of the standard agent package with From 5e0bc01e0f8a7ced024fc0500b5f2a08def21044 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 14 Oct 2026 23:09:09 +0200 Subject: [PATCH 2/4] Remove misleading WebView network alias Remove the unused browser-network route and handler instead of retaining an unsupported placeholder. Keep native HTTP capture unchanged and align the protocol, regression tests, and CLI mock with the default 404 response. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b2bd9105-f47b-4c5b-b5b9-11740d943d70 --- docs/DevFlow/spec/README.md | 8 ++++---- docs/DevFlow/spec/openapi.yaml | 13 ------------- .../Fixtures/MockAgentServer.cs | 1 - .../DevFlowAgentService.Handlers.cs | 6 ------ .../ProtocolSpecTests.cs | 6 ++---- .../WebViewNetworkCapabilityTests.cs | 14 +++++--------- src/DevFlow/README.md | 6 +++--- 7 files changed, 14 insertions(+), 40 deletions(-) diff --git a/docs/DevFlow/spec/README.md b/docs/DevFlow/spec/README.md index abbf4b83b..6f964c218 100644 --- a/docs/DevFlow/spec/README.md +++ b/docs/DevFlow/spec/README.md @@ -45,10 +45,10 @@ WebView endpoints also accept `contextId`; query values override the JSON body's JSON object. `/webview/screenshot` returns PNG, trying the registered native element before the CDP screenshot fallback. -Browser fetch/XHR capture is explicitly unsupported: `/api/v1/webview/network` -returns the standard 501 `webview.network` envelope instead of its former HTTP 200 -alias of the native capture store. Native .NET traffic remains available at -`/api/v1/network/requests`; the WebView capability no longer advertises network +Browser fetch/XHR capture is not implemented. The misleading +`/api/v1/webview/network` alias has been removed and returns the normal 404 +route-not-found response. Native .NET traffic remains available at +`/api/v1/network/requests`; the WebView capability does not advertise network capture. ## Streaming payloads diff --git a/docs/DevFlow/spec/openapi.yaml b/docs/DevFlow/spec/openapi.yaml index 3fad4a0be..e221990cb 100644 --- a/docs/DevFlow/spec/openapi.yaml +++ b/docs/DevFlow/spec/openapi.yaml @@ -1469,19 +1469,6 @@ paths: "500": $ref: "#/components/responses/InternalError" - /api/v1/webview/network: - get: - operationId: getWebViewNetworkRequests - tags: [webview] - summary: Discover unsupported browser-network capture - description: > - Browser fetch/XHR capture is not implemented. Returns the standard - not_supported envelope for webview.network. Native .NET HTTP traffic - remains available through /api/v1/network/requests. - responses: - "501": - $ref: "#/components/responses/BackendNotSupported" - /api/v1/webview/console: get: operationId: getWebViewConsoleLogs diff --git a/src/Cli/Microsoft.Maui.Cli.UnitTests/Fixtures/MockAgentServer.cs b/src/Cli/Microsoft.Maui.Cli.UnitTests/Fixtures/MockAgentServer.cs index 17a8121c0..2a31d5b29 100644 --- a/src/Cli/Microsoft.Maui.Cli.UnitTests/Fixtures/MockAgentServer.cs +++ b/src/Cli/Microsoft.Maui.Cli.UnitTests/Fixtures/MockAgentServer.cs @@ -558,7 +558,6 @@ private static void RegisterWebViewEndpoints(WebApplication app) app.MapGet("/api/v1/webview/source", () => Results.Content(MockAgentResponses.WebViewSource, "text/html")); app.MapGet("/api/v1/webview/dom", () => Results.Content("""{"root":{"tag":"html"}}""", "application/json")); app.MapGet("/api/v1/webview/dom/query", () => Results.Content("""{"matches":[{"tag":"div","id":"app"}]}""", "application/json")); - app.MapGet("/api/v1/webview/network", () => Results.Content("""{"entries":[]}""", "application/json")); app.MapGet("/api/v1/webview/console", () => Results.Content("""{"entries":[]}""", "application/json")); app.MapGet("/api/v1/webview/screenshot", () => Results.File(MockAgentResponses.ScreenshotPng, "image/png")); diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs index 183b65574..042565023 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Agent.Abstractions/DevFlowAgentService.Handlers.cs @@ -398,7 +398,6 @@ protected void RegisterRoutes() _server.MapPost("/api/v1/webview/input/click", request => ExecuteUiMutationAsync(request, HandleWebViewInputClick)); _server.MapPost("/api/v1/webview/input/fill", request => ExecuteUiMutationAsync(request, HandleWebViewInputFill)); _server.MapPost("/api/v1/webview/input/text", request => ExecuteUiMutationAsync(request, HandleWebViewInputText)); - _server.MapGet("/api/v1/webview/network", HandleWebViewNetwork); _server.MapGet("/api/v1/webview/console", HandleWebViewConsole); _server.MapGet("/api/v1/webview/screenshot", HandleWebViewScreenshot); @@ -2217,11 +2216,6 @@ protected async Task HandleWebViewDomQuery(HttpRequest request) } } - protected Task HandleWebViewNetwork(HttpRequest request) - => Task.FromResult(NotSupported( - "webview.network", - "Browser-network capture is not implemented. Use /api/v1/network/requests for native .NET HTTP traffic.")); - protected Task HandleWebViewConsole(HttpRequest request) { request.QueryParams["source"] = "webview"; diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs index 6b93db6ec..2fd5a7b0f 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs @@ -64,12 +64,10 @@ public class ProtocolSpecTests private static readonly Lazy SpecRoot = new(FindSpecRoot); [Fact] - public void WebViewNetwork_DocumentsUnsupportedBrowserCaptureWithoutChangingNativeNetwork() + public void WebViewNetwork_RemovedFromSpecWithoutChangingNativeNetwork() { var openApi = LoadDocument(Path.Combine(SpecRoot.Value, "openapi.yaml")); - var responses = openApi["paths"]!["/api/v1/webview/network"]!["get"]!["responses"]!; - Assert.Null(responses["200"]); - Assert.Equal("#/components/responses/BackendNotSupported", responses["501"]!["$ref"]!.GetValue()); + Assert.Null(openApi["paths"]!["/api/v1/webview/network"]); Assert.NotNull(openApi["paths"]!["/api/v1/network/requests"]!["get"]!["responses"]!["200"]); var example = LoadDocument(Path.Combine(SpecRoot.Value, "examples", "capabilities-maui-response.json")); diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs index c4220c4d2..691c37baf 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs @@ -8,13 +8,10 @@ namespace Microsoft.Maui.DevFlow.Tests; public class WebViewNetworkCapabilityTests { - private const string UnsupportedReason = - "Browser-network capture is not implemented. Use /api/v1/network/requests for native .NET HTTP traffic."; - [Theory] [InlineData(false)] [InlineData(true)] - public async Task WebViewNetwork_ReturnsUnsupportedWithoutReadingNativeTraffic(bool registerWebView) + public async Task WebViewNetwork_ReturnsRouteNotFoundWithoutReadingNativeTraffic(bool registerWebView) { using var service = CreateService(out var port); if (registerWebView) @@ -25,14 +22,13 @@ public async Task WebViewNetwork_ReturnsUnsupportedWithoutReadingNativeTraffic(b using var response = await http.GetAsync("/api/v1/webview/network?host=native.test&contextId=0"); - Assert.Equal(HttpStatusCode.NotImplemented, response.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType); using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); var body = document.RootElement; - Assert.Equal(3, body.EnumerateObject().Count()); - Assert.Equal("not_supported", body.GetProperty("error").GetString()); - Assert.Equal("webview.network", body.GetProperty("capability").GetString()); - Assert.Equal(UnsupportedReason, body.GetProperty("reason").GetString()); + Assert.Equal(2, body.EnumerateObject().Count()); + Assert.False(body.GetProperty("success").GetBoolean()); + Assert.Equal("Route not found", body.GetProperty("error").GetString()); Assert.Same(entry, service.NetworkStore.GetById(entry.Id)); Assert.Equal(1, service.NetworkStore.Count); } diff --git a/src/DevFlow/README.md b/src/DevFlow/README.md index 376ad4721..a4e35e58d 100644 --- a/src/DevFlow/README.md +++ b/src/DevFlow/README.md @@ -160,9 +160,9 @@ builder.AddMauiDevFlowAgent(options => ### WebView network capture Browser fetch/XHR capture is not implemented. **Breaking behavior:** -`/api/v1/webview/network` now returns the standard 501 `not_supported` envelope -with capability `webview.network`, rather than an HTTP 200 alias of the native -capture store. `network` is no longer advertised as a WebView feature. Native +The `/api/v1/webview/network` alias has been removed and now returns the normal +404 route-not-found response instead of native traffic. +`network` is no longer advertised as a WebView feature. Native .NET HTTP capture remains available, unchanged, at `/api/v1/network/requests`. ### GTK/Linux apps From 91afbfdedc0e9aa489a5cd67e0dc07aef2ea04a7 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 14 Oct 2026 23:13:47 +0200 Subject: [PATCH 3/4] Remove obsolete WebView network README text Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b2bd9105-f47b-4c5b-b5b9-11740d943d70 --- docs/DevFlow/spec/README.md | 6 ------ src/DevFlow/README.md | 8 -------- 2 files changed, 14 deletions(-) diff --git a/docs/DevFlow/spec/README.md b/docs/DevFlow/spec/README.md index 6f964c218..5f9356da7 100644 --- a/docs/DevFlow/spec/README.md +++ b/docs/DevFlow/spec/README.md @@ -45,12 +45,6 @@ WebView endpoints also accept `contextId`; query values override the JSON body's JSON object. `/webview/screenshot` returns PNG, trying the registered native element before the CDP screenshot fallback. -Browser fetch/XHR capture is not implemented. The misleading -`/api/v1/webview/network` alias has been removed and returns the normal 404 -route-not-found response. Native .NET traffic remains available at -`/api/v1/network/requests`; the WebView capability does not advertise network -capture. - ## Streaming payloads There is no universal `{ type, timestamp, data }` wire envelope. Network and log diff --git a/src/DevFlow/README.md b/src/DevFlow/README.md index a4e35e58d..bb774f8fb 100644 --- a/src/DevFlow/README.md +++ b/src/DevFlow/README.md @@ -157,14 +157,6 @@ builder.AddMauiDevFlowAgent(options => }); ``` -### WebView network capture - -Browser fetch/XHR capture is not implemented. **Breaking behavior:** -The `/api/v1/webview/network` alias has been removed and now returns the normal -404 route-not-found response instead of native traffic. -`network` is no longer advertised as a WebView feature. Native -.NET HTTP capture remains available, unchanged, at `/api/v1/network/requests`. - ### GTK/Linux apps Use `Microsoft.Maui.DevFlow.Agent.Gtk` instead of the standard agent package with From cd776d888df22726bcbfc0094b88fa79cabeff83 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 14 Oct 2026 23:17:44 +0200 Subject: [PATCH 4/4] Drop tests for removed WebView network endpoint Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b2bd9105-f47b-4c5b-b5b9-11740d943d70 --- .../ProtocolSpecTests.cs | 13 -- .../WebViewNetworkCapabilityTests.cs | 117 ------------------ 2 files changed, 130 deletions(-) delete mode 100644 src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs index 2fd5a7b0f..a29c6400b 100644 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs +++ b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/ProtocolSpecTests.cs @@ -63,19 +63,6 @@ public class ProtocolSpecTests private static readonly Lazy SpecRoot = new(FindSpecRoot); - [Fact] - public void WebViewNetwork_RemovedFromSpecWithoutChangingNativeNetwork() - { - var openApi = LoadDocument(Path.Combine(SpecRoot.Value, "openapi.yaml")); - Assert.Null(openApi["paths"]!["/api/v1/webview/network"]); - Assert.NotNull(openApi["paths"]!["/api/v1/network/requests"]!["get"]!["responses"]!["200"]); - - var example = LoadDocument(Path.Combine(SpecRoot.Value, "examples", "capabilities-maui-response.json")); - Assert.DoesNotContain("network", example["capabilities"]!["webview"]!["features"]!.AsArray() - .Select(feature => feature!.GetValue())); - Assert.NotNull(example["capabilities"]!["network"]); - } - [Fact] public async Task OpenApiYaml_CanBeParsedByOpenApiTooling() { diff --git a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs b/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs deleted file mode 100644 index 691c37baf..000000000 --- a/src/DevFlow/Microsoft.Maui.DevFlow.Tests/WebViewNetworkCapabilityTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Net; -using System.Net.Sockets; -using System.Text.Json; -using Microsoft.Maui.DevFlow.Agent.Core; -using Microsoft.Maui.DevFlow.Agent.Core.Network; - -namespace Microsoft.Maui.DevFlow.Tests; - -public class WebViewNetworkCapabilityTests -{ - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task WebViewNetwork_ReturnsRouteNotFoundWithoutReadingNativeTraffic(bool registerWebView) - { - using var service = CreateService(out var port); - if (registerWebView) - service.RegisterCdpWebView(_ => throw new InvalidOperationException("Network capture must not invoke CDP."), () => true); - var entry = AddNativeRequest(service); - service.StartServerOnly(dispatcher: null); - using var http = CreateClient(port); - - using var response = await http.GetAsync("/api/v1/webview/network?host=native.test&contextId=0"); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType); - using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); - var body = document.RootElement; - Assert.Equal(2, body.EnumerateObject().Count()); - Assert.False(body.GetProperty("success").GetBoolean()); - Assert.Equal("Route not found", body.GetProperty("error").GetString()); - Assert.Same(entry, service.NetworkStore.GetById(entry.Id)); - Assert.Equal(1, service.NetworkStore.Count); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task Capabilities_NeverAdvertiseBrowserNetworkAndPreserveNativeNetwork(bool registerWebView) - { - using var service = CreateService(out var port); - if (registerWebView) - service.RegisterCdpWebView(_ => Task.FromResult("{}"), () => true); - service.StartServerOnly(dispatcher: null); - using var http = CreateClient(port); - using var document = JsonDocument.Parse(await http.GetStringAsync("/api/v1/agent/capabilities")); - var capabilities = document.RootElement.GetProperty("capabilities"); - var webView = capabilities.GetProperty("webview"); - - Assert.Equal(registerWebView, webView.GetProperty("supported").GetBoolean()); - var features = webView.GetProperty("features").EnumerateArray().Select(feature => feature.GetString()).ToArray(); - Assert.DoesNotContain("network", features); - if (registerWebView) - Assert.Equal(new[] { "evaluate", "contexts", "source", "dom", "dom-query", "console", "screenshot" }, features); - else - Assert.Empty(features); - var network = capabilities.GetProperty("network"); - Assert.True(network.GetProperty("supported").GetBoolean()); - Assert.Equal(new[] { "list", "detail", "clear", "stream" }, - network.GetProperty("features").EnumerateArray().Select(feature => feature.GetString())); - } - - [Fact] - public async Task NativeNetwork_StillReturnsFilteredSummariesAndRequestDetails() - { - using var service = CreateService(out var port); - var entry = AddNativeRequest(service); - service.NetworkStore.Add(new NetworkRequestEntry - { - Id = "other-request", Host = "other.test", Method = "POST", StatusCode = 201 - }); - service.StartServerOnly(dispatcher: null); - using var http = CreateClient(port); - - using var response = await http.GetAsync("/api/v1/network/requests?host=native.test&method=GET&status=200&limit=1"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - using var summaries = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); - var summary = Assert.Single(summaries.RootElement.EnumerateArray()); - Assert.Equal(entry.Id, summary.GetProperty("id").GetString()); - Assert.Equal(entry.Url, summary.GetProperty("url").GetString()); - Assert.Equal(200, summary.GetProperty("statusCode").GetInt32()); - Assert.False(summary.TryGetProperty("responseBody", out _)); - - using var detailResponse = await http.GetAsync($"/api/v1/network/requests/{entry.Id}"); - Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode); - using var detail = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync()); - Assert.Equal(entry.ResponseBody, detail.RootElement.GetProperty("responseBody").GetString()); - Assert.Equal(2, service.NetworkStore.Count); - } - - private static NetworkRequestEntry AddNativeRequest(DevFlowAgentService service) - { - var entry = new NetworkRequestEntry - { - Id = "native-request", - Timestamp = DateTimeOffset.UtcNow, - Host = "native.test", - Url = "https://native.test/data", - Method = "GET", - StatusCode = 200, - ResponseBody = "native response" - }; - service.NetworkStore.Add(entry); - return entry; - } - - private static DevFlowAgentService CreateService(out int port) - { - using var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - port = ((IPEndPoint)listener.LocalEndpoint).Port; - return new DevFlowAgentService(new AgentOptions { Port = port, EnableNetworkMonitoring = false }); - } - - private static HttpClient CreateClient(int port) - => new() { BaseAddress = new Uri($"http://localhost:{port}") }; -}