From a8fb94707556cf13cac7d895d93c77ec47242b4d Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 23:04:41 -0500 Subject: [PATCH 1/8] test(blazor): Add Playwright E2E tests for navigation breadcrumbs Add browser-level tests using Playwright to verify that navigation breadcrumbs are created end-to-end in a real Blazor WebAssembly app. The test navigates between pages, triggers SentrySdk.CaptureMessage, intercepts the Sentry envelope via Playwright's route API, and verifies the event contains navigation breadcrumbs with correct from/to paths. Includes a minimal Blazor WASM test host app with a fake DSN. Co-Authored-By: Claude Opus 4.6 --- .../App.razor | 8 ++ .../Pages/Index.razor | 4 + .../Pages/Second.razor | 4 + .../Pages/TriggerCapture.razor | 11 ++ .../Program.cs | 16 +++ ...WebAssembly.PlaywrightTests.TestApp.csproj | 38 +++++++ .../Shared/MainLayout.razor | 5 + .../_Imports.razor | 4 + .../wwwroot/index.html | 16 +++ .../BlazorWasmTestApp.cs | 81 ++++++++++++++ .../NavigationBreadcrumbTests.cs | 100 ++++++++++++++++++ ....Blazor.WebAssembly.PlaywrightTests.csproj | 33 ++++++ .../SentryEnvelopeParser.cs | 32 ++++++ 13 files changed, 352 insertions(+) create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/App.razor create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Index.razor create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Second.razor create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/TriggerCapture.razor create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Program.cs create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Shared/MainLayout.razor create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/_Imports.razor create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/wwwroot/index.html create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/App.razor b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/App.razor new file mode 100644 index 0000000000..93b6831e5e --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/App.razor @@ -0,0 +1,8 @@ + + + + + +

Not found

+
+
diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Index.razor b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Index.razor new file mode 100644 index 0000000000..a5c06a63be --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Index.razor @@ -0,0 +1,4 @@ +@page "/" + +

Home

+Go to Second diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Second.razor b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Second.razor new file mode 100644 index 0000000000..2ad425c0a3 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/Second.razor @@ -0,0 +1,4 @@ +@page "/second" + +

Second Page

+Go to Trigger diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/TriggerCapture.razor b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/TriggerCapture.razor new file mode 100644 index 0000000000..e504e9f017 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Pages/TriggerCapture.razor @@ -0,0 +1,11 @@ +@page "/trigger-capture" + +

Trigger Capture

+ + +@code { + private void Capture() + { + SentrySdk.CaptureMessage("playwright-test"); + } +} diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Program.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Program.cs new file mode 100644 index 0000000000..2f88219148 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Program.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.UseSentry(options => +{ + // Fake DSN — Playwright intercepts requests before they reach the network + options.Dsn = "https://key@o0.ingest.sentry.io/0"; + options.AutoSessionTracking = false; +}); + +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +await builder.Build().RunAsync(); diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj new file mode 100644 index 0000000000..530f804349 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj @@ -0,0 +1,38 @@ + + + + net10.0 + enable + enable + false + false + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Shared/MainLayout.razor b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Shared/MainLayout.razor new file mode 100644 index 0000000000..724fc91b60 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/Shared/MainLayout.razor @@ -0,0 +1,5 @@ +@inherits LayoutComponentBase + +
+ @Body +
diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/_Imports.razor b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/_Imports.razor new file mode 100644 index 0000000000..263f7a0fd9 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp +@using Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.Shared diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/wwwroot/index.html b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/wwwroot/index.html new file mode 100644 index 0000000000..5eafdd6ae8 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp/wwwroot/index.html @@ -0,0 +1,16 @@ + + + + + + + Playwright Test App + + + + +
Loading...
+ + + + diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs new file mode 100644 index 0000000000..817bebfd77 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs @@ -0,0 +1,81 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +namespace Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests; + +internal sealed class BlazorWasmTestApp : IAsyncDisposable +{ + private Process? _process; + + public string BaseUrl { get; private set; } = null!; + + public async Task StartAsync() + { + var port = GetFreePort(); + BaseUrl = $"http://localhost:{port}"; + + var projectPath = Path.GetFullPath( + Path.Combine(AppContext.BaseDirectory, + "..", "..", "..", "..", + "Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp")); + + _process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"run --no-build --project \"{projectPath}\" --urls {BaseUrl}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + } + }; + _process.Start(); + + // Discard stdout/stderr to prevent buffer deadlock + _process.BeginOutputReadLine(); + _process.BeginErrorReadLine(); + + using var http = new HttpClient(); + var timeout = TimeSpan.FromSeconds(60); + var sw = Stopwatch.StartNew(); + while (sw.Elapsed < timeout) + { + try + { + var response = await http.GetAsync(BaseUrl); + if (response.IsSuccessStatusCode) + { + return; + } + } + catch + { + // Server not ready yet + } + await Task.Delay(500); + } + + throw new TimeoutException($"Blazor WASM test app did not start within {timeout.TotalSeconds}s at {BaseUrl}"); + } + + private static int GetFreePort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + + public async ValueTask DisposeAsync() + { + if (_process is { HasExited: false }) + { + _process.Kill(entireProcessTree: true); + await _process.WaitForExitAsync(); + } + _process?.Dispose(); + } +} diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs new file mode 100644 index 0000000000..785945bcbd --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs @@ -0,0 +1,100 @@ +using System.Text.Json; +using Microsoft.Playwright; + +namespace Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests; + +public class NavigationBreadcrumbTests : IAsyncLifetime +{ + private readonly BlazorWasmTestApp _app = new(); + private IPlaywright _playwright = null!; + private IBrowser _browser = null!; + + public async Task InitializeAsync() + { + await _app.StartAsync(); + + _playwright = await Playwright.CreateAsync(); + _browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions + { + Headless = true + }); + } + + public async Task DisposeAsync() + { + await _browser.DisposeAsync(); + _playwright.Dispose(); + await _app.DisposeAsync(); + } + + [Fact] + public async Task Navigation_CreatesBreadcrumbs_WithCorrectFromAndTo() + { + var page = await _browser.NewPageAsync(); + + // Collect all intercepted envelopes + var envelopes = new List(); + var envelopeReceived = new TaskCompletionSource(); + + await page.RouteAsync("**/api/0/envelope/**", async route => + { + var body = route.Request.PostData; + if (body != null) + { + envelopes.Add(body); + if (body.Contains("\"breadcrumbs\"")) + { + envelopeReceived.TrySetResult(body); + } + } + await route.FulfillAsync(new RouteFulfillOptions + { + Status = 200, + ContentType = "application/json", + Body = "{}" + }); + }); + + // 1. Navigate to app root + await page.GotoAsync(_app.BaseUrl); + await page.WaitForSelectorAsync("#page-title"); + + // 2. Navigate to /second (creates first navigation breadcrumb: / -> /second) + await page.ClickAsync("#nav-second"); + await page.WaitForSelectorAsync("h1:has-text('Second Page')"); + + // 3. Navigate to /trigger-capture (creates second breadcrumb: /second -> /trigger-capture) + await page.ClickAsync("#nav-trigger"); + await page.WaitForSelectorAsync("h1:has-text('Trigger Capture')"); + + // 4. Click button to trigger SentrySdk.CaptureMessage — sends event with breadcrumbs + await page.ClickAsync("#btn-capture"); + + // 5. Wait for the envelope containing breadcrumbs + var envelopeBody = await envelopeReceived.Task.WaitAsync(TimeSpan.FromSeconds(10)); + + // 6. Parse and verify + var eventPayload = SentryEnvelopeParser.ExtractEventFromEnvelope(envelopeBody); + eventPayload.Should().NotBeNull("expected an event payload in the Sentry envelope"); + + var breadcrumbs = eventPayload!.Value.GetProperty("breadcrumbs").EnumerateArray().ToList(); + + var navBreadcrumbs = breadcrumbs + .Where(b => + b.TryGetProperty("type", out var t) && t.GetString() == "navigation" && + b.TryGetProperty("category", out var c) && c.GetString() == "navigation") + .ToList(); + + navBreadcrumbs.Should().HaveCount(2, "expected two navigation breadcrumbs (/ -> /second -> /trigger-capture)"); + + // First navigation: / -> /second + var first = navBreadcrumbs[0]; + first.GetProperty("data").GetProperty("from").GetString().Should().Be("/"); + first.GetProperty("data").GetProperty("to").GetString().Should().Be("/second"); + + // Second navigation: /second -> /trigger-capture + var second = navBreadcrumbs[1]; + second.GetProperty("data").GetProperty("from").GetString().Should().Be("/second"); + second.GetProperty("data").GetProperty("to").GetString().Should().Be("/trigger-capture"); + } +} diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj new file mode 100644 index 0000000000..87bb45f2a2 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj @@ -0,0 +1,33 @@ + + + + net10.0 + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs new file mode 100644 index 0000000000..e4bb56d20f --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs @@ -0,0 +1,32 @@ +using System.Text.Json; + +namespace Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests; + +internal static class SentryEnvelopeParser +{ + /// + /// Extracts the first event payload from a Sentry envelope body. + /// Envelope format: newline-delimited JSON lines. + /// Line 0 = envelope header, then pairs of (item header, item payload). + /// + public static JsonElement? ExtractEventFromEnvelope(string envelopeBody) + { + var lines = envelopeBody.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + // lines[0] = envelope header + // lines[1..] = pairs of (item header, item payload) + for (var i = 1; i < lines.Length - 1; i += 2) + { + using var itemHeaderDoc = JsonDocument.Parse(lines[i]); + var itemHeader = itemHeaderDoc.RootElement; + + if (itemHeader.TryGetProperty("type", out var typeEl) && + typeEl.GetString() == "event") + { + return JsonDocument.Parse(lines[i + 1]).RootElement; + } + } + + return null; + } +} From be0139e0913b8115013d70e2fb887e7ba00f6434 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 23:18:27 -0500 Subject: [PATCH 2/8] Add Playwright test projects to solution and regenerate filters Co-Authored-By: Claude Opus 4.6 --- .generated.NoMobile.sln | 30 +++++++++++++++++++++++++++++ Sentry-CI-Build-Linux-NoMobile.slnf | 2 ++ Sentry-CI-Build-Linux.slnf | 2 ++ Sentry-CI-Build-Windows-arm64.slnf | 2 ++ Sentry-CI-Build-Windows.slnf | 2 ++ Sentry-CI-Build-macOS.slnf | 2 ++ Sentry.sln | 30 +++++++++++++++++++++++++++++ SentryAspNetCore.slnf | 2 ++ SentryNoMobile.slnf | 2 ++ SentryNoSamples.slnf | 1 + 10 files changed, 75 insertions(+) diff --git a/.generated.NoMobile.sln b/.generated.NoMobile.sln index df9a68c619..50761fe993 100644 --- a/.generated.NoMobile.sln +++ b/.generated.NoMobile.sln @@ -293,6 +293,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI.Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.Blazor.WebAssembly.Tests", "test\Sentry.AspNetCore.Blazor.WebAssembly.Tests\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "{705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp", "test\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", "{010F29C9-EF49-436B-811E-832195330364}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests", "test\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "{CC357377-2429-4ED0-95E2-A8604B722514}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1372,6 +1376,30 @@ Global {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x64.Build.0 = Release|Any CPU {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x86.ActiveCfg = Release|Any CPU {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x86.Build.0 = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|Any CPU.Build.0 = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x64.ActiveCfg = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x64.Build.0 = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x86.ActiveCfg = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x86.Build.0 = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|Any CPU.ActiveCfg = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|Any CPU.Build.0 = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x64.ActiveCfg = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x64.Build.0 = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x86.ActiveCfg = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x86.Build.0 = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x64.ActiveCfg = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x64.Build.0 = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x86.Build.0 = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|Any CPU.Build.0 = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x64.ActiveCfg = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x64.Build.0 = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x86.ActiveCfg = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1473,5 +1501,7 @@ Global {AE461926-00B8-4FDD-A41D-3C83C2D9A045} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} {28D6E004-DC64-464F-91BE-BC0B3A8E543F} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {010F29C9-EF49-436B-811E-832195330364} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {CC357377-2429-4ED0-95E2-A8604B722514} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} EndGlobalSection EndGlobal diff --git a/Sentry-CI-Build-Linux-NoMobile.slnf b/Sentry-CI-Build-Linux-NoMobile.slnf index 5aff2b9e87..cc8f922992 100644 --- a/Sentry-CI-Build-Linux-NoMobile.slnf +++ b/Sentry-CI-Build-Linux-NoMobile.slnf @@ -49,6 +49,8 @@ "src\\Sentry.Serilog\\Sentry.Serilog.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-Linux.slnf b/Sentry-CI-Build-Linux.slnf index dd182881d2..12cb9a0893 100644 --- a/Sentry-CI-Build-Linux.slnf +++ b/Sentry-CI-Build-Linux.slnf @@ -56,6 +56,8 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-Windows-arm64.slnf b/Sentry-CI-Build-Windows-arm64.slnf index 5878f20b2f..68e4d4d960 100644 --- a/Sentry-CI-Build-Windows-arm64.slnf +++ b/Sentry-CI-Build-Windows-arm64.slnf @@ -58,6 +58,8 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-Windows.slnf b/Sentry-CI-Build-Windows.slnf index 6bc68aeaad..43d5d9f6c6 100644 --- a/Sentry-CI-Build-Windows.slnf +++ b/Sentry-CI-Build-Windows.slnf @@ -58,6 +58,8 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-macOS.slnf b/Sentry-CI-Build-macOS.slnf index 7a024d3b5e..aa1d615b7e 100644 --- a/Sentry-CI-Build-macOS.slnf +++ b/Sentry-CI-Build-macOS.slnf @@ -63,6 +63,8 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry.sln b/Sentry.sln index df9a68c619..50761fe993 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -293,6 +293,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI.Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.Blazor.WebAssembly.Tests", "test\Sentry.AspNetCore.Blazor.WebAssembly.Tests\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "{705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp", "test\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", "{010F29C9-EF49-436B-811E-832195330364}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests", "test\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "{CC357377-2429-4ED0-95E2-A8604B722514}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1372,6 +1376,30 @@ Global {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x64.Build.0 = Release|Any CPU {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x86.ActiveCfg = Release|Any CPU {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x86.Build.0 = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|Any CPU.Build.0 = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x64.ActiveCfg = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x64.Build.0 = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x86.ActiveCfg = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Debug|x86.Build.0 = Debug|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|Any CPU.ActiveCfg = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|Any CPU.Build.0 = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x64.ActiveCfg = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x64.Build.0 = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x86.ActiveCfg = Release|Any CPU + {010F29C9-EF49-436B-811E-832195330364}.Release|x86.Build.0 = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x64.ActiveCfg = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x64.Build.0 = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Debug|x86.Build.0 = Debug|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|Any CPU.Build.0 = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x64.ActiveCfg = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x64.Build.0 = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x86.ActiveCfg = Release|Any CPU + {CC357377-2429-4ED0-95E2-A8604B722514}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1473,5 +1501,7 @@ Global {AE461926-00B8-4FDD-A41D-3C83C2D9A045} = {230B9384-90FD-4551-A5DE-1A5C197F25B6} {28D6E004-DC64-464F-91BE-BC0B3A8E543F} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {010F29C9-EF49-436B-811E-832195330364} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} + {CC357377-2429-4ED0-95E2-A8604B722514} = {6987A1CC-608E-4868-A02C-09D30C8B7B2D} EndGlobalSection EndGlobal diff --git a/SentryAspNetCore.slnf b/SentryAspNetCore.slnf index c5eea865ea..c5b441a6e3 100644 --- a/SentryAspNetCore.slnf +++ b/SentryAspNetCore.slnf @@ -24,6 +24,8 @@ "src\\Sentry.Serilog\\Sentry.Serilog.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/SentryNoMobile.slnf b/SentryNoMobile.slnf index e4e8cd9771..bf85e64c46 100644 --- a/SentryNoMobile.slnf +++ b/SentryNoMobile.slnf @@ -52,6 +52,8 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/SentryNoSamples.slnf b/SentryNoSamples.slnf index 2fa3d0bf7a..39e5c9dfc7 100644 --- a/SentryNoSamples.slnf +++ b/SentryNoSamples.slnf @@ -27,6 +27,7 @@ "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", "test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj", + "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", From ae6ee061bbe7fecbda9cc7912c688ea263044961 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 23:34:44 -0500 Subject: [PATCH 3/8] Increase Blazor WASM test app startup timeout to 120s CI environments can be slow; 60s was not enough. Co-Authored-By: Claude Opus 4.6 --- .../BlazorWasmTestApp.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs index 817bebfd77..e1448f51bf 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs @@ -38,7 +38,7 @@ public async Task StartAsync() _process.BeginErrorReadLine(); using var http = new HttpClient(); - var timeout = TimeSpan.FromSeconds(60); + var timeout = TimeSpan.FromSeconds(120); var sw = Stopwatch.StartNew(); while (sw.Elapsed < timeout) { @@ -57,7 +57,7 @@ public async Task StartAsync() await Task.Delay(500); } - throw new TimeoutException($"Blazor WASM test app did not start within {timeout.TotalSeconds}s at {BaseUrl}"); + throw new TimeoutException($"Blazor WASM test app did not start within {(int)timeout.TotalSeconds}s at {BaseUrl}"); } private static int GetFreePort() From 8573ca9de26a6c4cbe04c4fcffc6b9f895c99909 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 23:37:10 -0500 Subject: [PATCH 4/8] Improve test app startup: capture output, detect early exit, 180s timeout - Capture stdout/stderr into a queue for diagnostics - Detect early process exit and fail immediately with logs - Increase timeout to 180s for slow CI environments - Include process output in both error and timeout messages Co-Authored-By: Claude Opus 4.6 --- .../BlazorWasmTestApp.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs index e1448f51bf..63618cdfc4 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Diagnostics; using System.Net; using System.Net.Sockets; @@ -7,6 +8,7 @@ namespace Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests; internal sealed class BlazorWasmTestApp : IAsyncDisposable { private Process? _process; + private readonly ConcurrentQueue _output = new(); public string BaseUrl { get; private set; } = null!; @@ -31,17 +33,24 @@ public async Task StartAsync() UseShellExecute = false, } }; + _process.OutputDataReceived += (_, e) => { if (e.Data != null) _output.Enqueue($"[stdout] {e.Data}"); }; + _process.ErrorDataReceived += (_, e) => { if (e.Data != null) _output.Enqueue($"[stderr] {e.Data}"); }; _process.Start(); - - // Discard stdout/stderr to prevent buffer deadlock _process.BeginOutputReadLine(); _process.BeginErrorReadLine(); using var http = new HttpClient(); - var timeout = TimeSpan.FromSeconds(120); + var timeout = TimeSpan.FromSeconds(180); var sw = Stopwatch.StartNew(); while (sw.Elapsed < timeout) { + if (_process.HasExited) + { + var logs = string.Join(Environment.NewLine, _output); + throw new InvalidOperationException( + $"Blazor WASM test app exited with code {_process.ExitCode} before becoming ready. Output:{Environment.NewLine}{logs}"); + } + try { var response = await http.GetAsync(BaseUrl); @@ -57,7 +66,9 @@ public async Task StartAsync() await Task.Delay(500); } - throw new TimeoutException($"Blazor WASM test app did not start within {(int)timeout.TotalSeconds}s at {BaseUrl}"); + var timeoutLogs = string.Join(Environment.NewLine, _output); + throw new TimeoutException( + $"Blazor WASM test app did not start within {(int)timeout.TotalSeconds}s at {BaseUrl}. Output:{Environment.NewLine}{timeoutLogs}"); } private static int GetFreePort() From 09d007cdc0676c7bfa342741b063da14339b768f Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 23:52:43 -0500 Subject: [PATCH 5/8] Remove --no-build from dotnet run for test app The Blazor WASM DevServer needs the staticwebassets.endpoints.json manifest which is only generated during build of the project itself, not when built from the solution level. Letting dotnet run build the project ensures this manifest is generated. Co-Authored-By: Claude Opus 4.6 --- .../BlazorWasmTestApp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs index 63618cdfc4..10e9b3c1bb 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs @@ -27,7 +27,7 @@ public async Task StartAsync() StartInfo = new ProcessStartInfo { FileName = "dotnet", - Arguments = $"run --no-build --project \"{projectPath}\" --urls {BaseUrl}", + Arguments = $"run --project \"{projectPath}\" --urls {BaseUrl}", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, From ae89086d8b5e3b33c4b742c4f89536c11cac834c Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 10 Feb 2026 00:09:38 -0500 Subject: [PATCH 6/8] Install Playwright Chromium in test setup CI doesn't have Playwright browsers pre-installed. Call Program.Main(["install", "chromium"]) in InitializeAsync so the test is self-contained. The download is cached, so subsequent runs are a no-op. Co-Authored-By: Claude Opus 4.6 --- .../NavigationBreadcrumbTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs index 785945bcbd..ec170795b4 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs @@ -11,6 +11,13 @@ public class NavigationBreadcrumbTests : IAsyncLifetime public async Task InitializeAsync() { + // Ensure Chromium is installed (no-op if already cached) + var exitCode = Microsoft.Playwright.Program.Main(["install", "chromium"]); + if (exitCode != 0) + { + throw new InvalidOperationException($"Playwright browser install failed with exit code {exitCode}"); + } + await _app.StartAsync(); _playwright = await Playwright.CreateAsync(); From 6a3029d677bcc0c8873b958f6ce17e70d3627a11 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 10 Feb 2026 00:14:56 -0500 Subject: [PATCH 7/8] Exclude Playwright tests from CI solution filters Playwright requires browser binaries and system dependencies that aren't available across all CI platforms (ARM64, musl). Exclude the PlaywrightTests projects from all CI build filters. They remain in local dev filters (SentryAspNetCore, SentryNoMobile, etc.) and can be run manually or in a dedicated CI job later. Co-Authored-By: Claude Opus 4.6 --- Sentry-CI-Build-Linux-NoMobile.slnf | 2 -- Sentry-CI-Build-Linux.slnf | 2 -- Sentry-CI-Build-Windows-arm64.slnf | 2 -- Sentry-CI-Build-Windows.slnf | 2 -- Sentry-CI-Build-macOS.slnf | 2 -- scripts/generate-solution-filters-config.yaml | 7 +++++++ .../NavigationBreadcrumbTests.cs | 9 ++------- 7 files changed, 9 insertions(+), 17 deletions(-) diff --git a/Sentry-CI-Build-Linux-NoMobile.slnf b/Sentry-CI-Build-Linux-NoMobile.slnf index cc8f922992..5aff2b9e87 100644 --- a/Sentry-CI-Build-Linux-NoMobile.slnf +++ b/Sentry-CI-Build-Linux-NoMobile.slnf @@ -49,8 +49,6 @@ "src\\Sentry.Serilog\\Sentry.Serilog.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-Linux.slnf b/Sentry-CI-Build-Linux.slnf index 12cb9a0893..dd182881d2 100644 --- a/Sentry-CI-Build-Linux.slnf +++ b/Sentry-CI-Build-Linux.slnf @@ -56,8 +56,6 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-Windows-arm64.slnf b/Sentry-CI-Build-Windows-arm64.slnf index 68e4d4d960..5878f20b2f 100644 --- a/Sentry-CI-Build-Windows-arm64.slnf +++ b/Sentry-CI-Build-Windows-arm64.slnf @@ -58,8 +58,6 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-Windows.slnf b/Sentry-CI-Build-Windows.slnf index 43d5d9f6c6..6bc68aeaad 100644 --- a/Sentry-CI-Build-Windows.slnf +++ b/Sentry-CI-Build-Windows.slnf @@ -58,8 +58,6 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.AspNet.Tests\\Sentry.AspNet.Tests.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/Sentry-CI-Build-macOS.slnf b/Sentry-CI-Build-macOS.slnf index aa1d615b7e..7a024d3b5e 100644 --- a/Sentry-CI-Build-macOS.slnf +++ b/Sentry-CI-Build-macOS.slnf @@ -63,8 +63,6 @@ "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.csproj", "test\\Sentry.Android.AssemblyReader.Tests\\Sentry.Android.AssemblyReader.Tests.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.TestApp.csproj", - "test\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests\\Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests.csproj", "test\\Sentry.AspNetCore.Blazor.WebAssembly.Tests\\Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj", "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", diff --git a/scripts/generate-solution-filters-config.yaml b/scripts/generate-solution-filters-config.yaml index 331042afbc..b41c525b61 100644 --- a/scripts/generate-solution-filters-config.yaml +++ b/scripts/generate-solution-filters-config.yaml @@ -32,6 +32,8 @@ groupConfigs: trimTests: - "**/Sentry.TrimTest.csproj" - "**/Sentry.MauiTrimTest.csproj" + playwrightTests: + - "**/*PlaywrightTests*.csproj" mobileOnly: - "**/*Android*.csproj" - "**/*Ios*.csproj" @@ -50,6 +52,7 @@ filterConfigs: - "windowsOnly" - "artefacts" - "trimTests" + - "playwrightTests" patterns: - "**/*AndroidTestApp.csproj" - "**/*DeviceTests*.csproj" @@ -67,6 +70,7 @@ filterConfigs: - "artefacts" - "trimTests" - "mobileOnly" + - "playwrightTests" patterns: - "**/*Android*.csproj" - "**/*DeviceTests*.csproj" @@ -83,6 +87,7 @@ filterConfigs: groups: - "artefacts" - "trimTests" + - "playwrightTests" patterns: - "**/*AndroidTestApp.csproj" - "**/*DeviceTests*.csproj" @@ -100,6 +105,7 @@ filterConfigs: - "macOnly" - "artefacts" - "trimTests" + - "playwrightTests" patterns: - "**/*AndroidTestApp.csproj" # AssemblyReader tests are flaky on Windows: https://github.com/getsentry/sentry-dotnet/issues/4091 @@ -121,6 +127,7 @@ filterConfigs: - "macOnly" - "artefacts" - "trimTests" + - "playwrightTests" patterns: - "**/*AndroidTestApp.csproj" # AssemblyReader tests are flaky on Windows: https://github.com/getsentry/sentry-dotnet/issues/4091 diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs index ec170795b4..c136818e4b 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs @@ -40,19 +40,14 @@ public async Task Navigation_CreatesBreadcrumbs_WithCorrectFromAndTo() var page = await _browser.NewPageAsync(); // Collect all intercepted envelopes - var envelopes = new List(); var envelopeReceived = new TaskCompletionSource(); await page.RouteAsync("**/api/0/envelope/**", async route => { var body = route.Request.PostData; - if (body != null) + if (body != null && body.Contains("\"breadcrumbs\"")) { - envelopes.Add(body); - if (body.Contains("\"breadcrumbs\"")) - { - envelopeReceived.TrySetResult(body); - } + envelopeReceived.TrySetResult(body); } await route.FulfillAsync(new RouteFulfillOptions { From 07c66083002b4a329941a8ef6285bd7c1518344b Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Wed, 11 Feb 2026 18:39:20 -0500 Subject: [PATCH 8/8] Address PR review comments - Fix JsonDocument leak: clone RootElement and dispose document - Make _playwright/_browser nullable, null-check in DisposeAsync - Close page after test to avoid resource leak - Use using on TcpListener Co-Authored-By: Claude Opus 4.6 --- .../BlazorWasmTestApp.cs | 2 +- .../NavigationBreadcrumbTests.cs | 15 ++++++++++----- .../SentryEnvelopeParser.cs | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs index 10e9b3c1bb..9461a7a35d 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/BlazorWasmTestApp.cs @@ -73,7 +73,7 @@ public async Task StartAsync() private static int GetFreePort() { - var listener = new TcpListener(IPAddress.Loopback, 0); + using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var port = ((IPEndPoint)listener.LocalEndpoint).Port; listener.Stop(); diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs index c136818e4b..7c13bdd244 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/NavigationBreadcrumbTests.cs @@ -6,8 +6,8 @@ namespace Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests; public class NavigationBreadcrumbTests : IAsyncLifetime { private readonly BlazorWasmTestApp _app = new(); - private IPlaywright _playwright = null!; - private IBrowser _browser = null!; + private IPlaywright? _playwright; + private IBrowser? _browser; public async Task InitializeAsync() { @@ -29,15 +29,18 @@ public async Task InitializeAsync() public async Task DisposeAsync() { - await _browser.DisposeAsync(); - _playwright.Dispose(); + if (_browser != null) + { + await _browser.DisposeAsync(); + } + _playwright?.Dispose(); await _app.DisposeAsync(); } [Fact] public async Task Navigation_CreatesBreadcrumbs_WithCorrectFromAndTo() { - var page = await _browser.NewPageAsync(); + var page = await _browser!.NewPageAsync(); // Collect all intercepted envelopes var envelopeReceived = new TaskCompletionSource(); @@ -98,5 +101,7 @@ await route.FulfillAsync(new RouteFulfillOptions var second = navBreadcrumbs[1]; second.GetProperty("data").GetProperty("from").GetString().Should().Be("/second"); second.GetProperty("data").GetProperty("to").GetString().Should().Be("/trigger-capture"); + + await page.CloseAsync(); } } diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs index e4bb56d20f..cd2de0c9a1 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTests/SentryEnvelopeParser.cs @@ -23,7 +23,8 @@ internal static class SentryEnvelopeParser if (itemHeader.TryGetProperty("type", out var typeEl) && typeEl.GetString() == "event") { - return JsonDocument.Parse(lines[i + 1]).RootElement; + using var eventDoc = JsonDocument.Parse(lines[i + 1]); + return eventDoc.RootElement.Clone(); } }