From b5c4aa821f28acc772f3f2fb8503474231c3a95f Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 22:45:02 -0500 Subject: [PATCH 1/4] feat(blazor): Add navigation breadcrumbs for Blazor WebAssembly Subscribe to NavigationManager.LocationChanged to automatically create navigation breadcrumbs with type/category "navigation" and from/to relative paths, matching the JS SDK behavior. Also keeps scope.Request.Url updated with the current route. Closes #4906 Co-Authored-By: Claude Opus 4.6 --- .../Internal/BlazorWasmOptionsSetup.cs | 56 ++++++ ...entry.AspNetCore.Blazor.WebAssembly.csproj | 4 + .../WebAssemblyHostBuilderExtensions.cs | 5 + .../BlazorWasmOptionsSetupTests.cs | 163 ++++++++++++++++++ .../FakeNavigationManager.cs | 26 +++ ...AspNetCore.Blazor.WebAssembly.Tests.csproj | 12 ++ test/Sentry.Testing/Sentry.Testing.csproj | 1 + 7 files changed, 267 insertions(+) create mode 100644 src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/FakeNavigationManager.cs create mode 100644 test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs b/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs new file mode 100644 index 0000000000..ff14913182 --- /dev/null +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.Options; + +namespace Sentry.AspNetCore.Blazor.WebAssembly.Internal; + +internal sealed class BlazorWasmOptionsSetup : IConfigureOptions +{ + private readonly NavigationManager _navigationManager; + + public BlazorWasmOptionsSetup(NavigationManager navigationManager) + { + _navigationManager = navigationManager; + } + + public void Configure(SentryBlazorOptions options) + { + var previousUrl = _navigationManager.Uri; + + // Set the initial scope request URL + SentrySdk.ConfigureScope(scope => + { + scope.Request.Url = ToRelativePath(previousUrl); + }); + + _navigationManager.LocationChanged += (_, args) => + { + var from = ToRelativePath(previousUrl); + var to = ToRelativePath(args.Location); + + SentrySdk.AddBreadcrumb( + message: "", + category: "navigation", + type: "navigation", + data: new Dictionary + { + { "from", from }, + { "to", to } + }); + + SentrySdk.ConfigureScope(scope => + { + scope.Request.Url = to; + }); + + previousUrl = args.Location; + }; + } + + private string ToRelativePath(string url) + { + var relative = _navigationManager.ToBaseRelativePath(url); + return "/" + relative; + } +} diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj b/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj index b0635091c6..c02680154d 100644 --- a/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/Sentry.AspNetCore.Blazor.WebAssembly.csproj @@ -10,6 +10,10 @@ + + + + diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs b/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs index ca208e0280..8985e9cc5e 100644 --- a/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs @@ -1,5 +1,8 @@ +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Sentry; +using Sentry.AspNetCore.Blazor.WebAssembly.Internal; using Sentry.Extensions.Logging; // ReSharper disable once CheckNamespace - Discoverability @@ -30,6 +33,8 @@ public static WebAssemblyHostBuilder UseSentry(this WebAssemblyHostBuilder build blazorOptions.IsGlobalModeEnabled = true; }); + builder.Services.AddSingleton, BlazorWasmOptionsSetup>(); + return builder; } } diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs new file mode 100644 index 0000000000..dcf7567624 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs @@ -0,0 +1,163 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Sentry.AspNetCore.Blazor.WebAssembly.Internal; + +namespace Sentry.AspNetCore.Blazor.WebAssembly.Tests; + +public class BlazorWasmOptionsSetupTests : IDisposable +{ + private readonly FakeNavigationManager _navigationManager; + private readonly BlazorWasmOptionsSetup _sut; + private readonly IDisposable _sentryInit; + + public BlazorWasmOptionsSetupTests() + { + _navigationManager = new FakeNavigationManager( + baseUri: "https://localhost:5001/", + initialUri: "https://localhost:5001/"); + + _sut = new BlazorWasmOptionsSetup(_navigationManager); + + _sentryInit = SentrySdk.Init(o => + { + o.Dsn = ValidDsn; + o.IsGlobalModeEnabled = true; + }); + } + + public void Dispose() + { + _sentryInit.Dispose(); + } + + [Fact] + public void Configure_SetsInitialRequestUrl() + { + // Act + _sut.Configure(new SentryBlazorOptions()); + + // Assert + SentrySdk.ConfigureScope(scope => + { + scope.Request.Url.Should().Be("/"); + }); + } + + [Fact] + public void Configure_SetsInitialRequestUrl_WithPath() + { + // Arrange + var nav = new FakeNavigationManager( + baseUri: "https://localhost:5001/", + initialUri: "https://localhost:5001/counter"); + var sut = new BlazorWasmOptionsSetup(nav); + + // Act + sut.Configure(new SentryBlazorOptions()); + + // Assert + SentrySdk.ConfigureScope(scope => + { + scope.Request.Url.Should().Be("/counter"); + }); + } + + [Fact] + public void Navigation_CreatesBreadcrumbWithCorrectTypeAndCategory() + { + // Arrange + _sut.Configure(new SentryBlazorOptions()); + + // Act + _navigationManager.NavigateTo("/dashboard"); + + // Assert + SentrySdk.ConfigureScope(scope => + { + var crumb = scope.Breadcrumbs.Should().ContainSingle().Subject; + crumb.Type.Should().Be("navigation"); + crumb.Category.Should().Be("navigation"); + }); + } + + [Fact] + public void Navigation_CreatesBreadcrumbWithRelativePaths() + { + // Arrange + _sut.Configure(new SentryBlazorOptions()); + + // Act + _navigationManager.NavigateTo("/dashboard"); + + // Assert + SentrySdk.ConfigureScope(scope => + { + var crumb = scope.Breadcrumbs.Should().ContainSingle().Subject; + crumb.Data.Should().ContainKey("from").WhoseValue.Should().Be("/"); + crumb.Data.Should().ContainKey("to").WhoseValue.Should().Be("/dashboard"); + }); + } + + [Fact] + public void Navigation_UpdatesRequestUrl() + { + // Arrange + _sut.Configure(new SentryBlazorOptions()); + + // Act + _navigationManager.NavigateTo("/dashboard"); + + // Assert + SentrySdk.ConfigureScope(scope => + { + scope.Request.Url.Should().Be("/dashboard"); + }); + } + + [Fact] + public void MultipleNavigations_TrackFromCorrectly() + { + // Arrange + _sut.Configure(new SentryBlazorOptions()); + + // Act + _navigationManager.NavigateTo("/page1"); + _navigationManager.NavigateTo("/page2"); + + // Assert + SentrySdk.ConfigureScope(scope => + { + var breadcrumbs = scope.Breadcrumbs.ToList(); + breadcrumbs.Should().HaveCount(2); + + var first = breadcrumbs[0]; + first.Data.Should().ContainKey("from").WhoseValue.Should().Be("/"); + first.Data.Should().ContainKey("to").WhoseValue.Should().Be("/page1"); + + var second = breadcrumbs[1]; + second.Data.Should().ContainKey("from").WhoseValue.Should().Be("/page1"); + second.Data.Should().ContainKey("to").WhoseValue.Should().Be("/page2"); + }); + } + + [Fact] + public void Navigation_FromInitialPath_TracksCorrectFrom() + { + // Arrange - start on /login + var nav = new FakeNavigationManager( + baseUri: "https://localhost:5001/", + initialUri: "https://localhost:5001/login"); + var sut = new BlazorWasmOptionsSetup(nav); + sut.Configure(new SentryBlazorOptions()); + + // Act + nav.NavigateTo("/home"); + + // Assert + SentrySdk.ConfigureScope(scope => + { + var crumb = scope.Breadcrumbs.Should().ContainSingle().Subject; + crumb.Data.Should().ContainKey("from").WhoseValue.Should().Be("/login"); + crumb.Data.Should().ContainKey("to").WhoseValue.Should().Be("/home"); + }); + } +} diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/FakeNavigationManager.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/FakeNavigationManager.cs new file mode 100644 index 0000000000..2f888201c8 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/FakeNavigationManager.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; + +namespace Sentry.AspNetCore.Blazor.WebAssembly.Tests; + +internal sealed class FakeNavigationManager : NavigationManager +{ + public FakeNavigationManager(string baseUri = "https://localhost/", string initialUri = "https://localhost/") + { + Initialize(baseUri, initialUri); + } + + public void NavigateTo(string uri) + { + var absoluteUri = ToAbsoluteUri(uri).ToString(); + Uri = absoluteUri; + NotifyLocationChanged(isInterceptedLink: false); + } + + protected override void NavigateToCore(string uri, bool forceLoad) + { + var absoluteUri = ToAbsoluteUri(uri).ToString(); + Uri = absoluteUri; + NotifyLocationChanged(isInterceptedLink: false); + } +} diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj new file mode 100644 index 0000000000..d65aec285a --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/Sentry.AspNetCore.Blazor.WebAssembly.Tests.csproj @@ -0,0 +1,12 @@ + + + + $(CurrentTfms) + + + + + + + + diff --git a/test/Sentry.Testing/Sentry.Testing.csproj b/test/Sentry.Testing/Sentry.Testing.csproj index 4a5c86e2bd..66a5feab07 100644 --- a/test/Sentry.Testing/Sentry.Testing.csproj +++ b/test/Sentry.Testing/Sentry.Testing.csproj @@ -18,6 +18,7 @@ + From 06ba684b05903ca38ec38117b8a50ce592683f68 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 23:11:30 -0500 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20Address=20PR=20review=20=E2=80=94=20?= =?UTF-8?q?use=20IHub=20instead=20of=20SentrySdk=20statics,=20drop=20empty?= =?UTF-8?q?=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BlazorWasmOptionsSetup now takes IHub via an internal constructor (defaults to HubAdapter.Instance in production). This allows tests to inject a mock hub instead of relying on SentrySdk global state. - Use the internal Breadcrumb constructor with message: null instead of sending message: "" (matching JS SDK which omits the message property). - Add InternalsVisibleTo from Sentry to the Blazor WASM project. - Rewrite tests to use NSubstitute IHub + real Scope (following MAUI test patterns) instead of SentrySdk.Init(). - Add test verifying breadcrumb message is null. Co-Authored-By: Claude Opus 4.6 --- .../Internal/BlazorWasmOptionsSetup.cs | 33 +++--- src/Sentry/Sentry.csproj | 1 + .../BlazorWasmOptionsSetupTests.cs | 103 ++++++++---------- 3 files changed, 66 insertions(+), 71 deletions(-) diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs b/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs index ff14913182..2223968385 100644 --- a/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs @@ -1,17 +1,24 @@ using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.Options; +using Sentry.Extensibility; namespace Sentry.AspNetCore.Blazor.WebAssembly.Internal; internal sealed class BlazorWasmOptionsSetup : IConfigureOptions { private readonly NavigationManager _navigationManager; + private readonly IHub _hub; public BlazorWasmOptionsSetup(NavigationManager navigationManager) + : this(navigationManager, HubAdapter.Instance) + { + } + + internal BlazorWasmOptionsSetup(NavigationManager navigationManager, IHub hub) { _navigationManager = navigationManager; + _hub = hub; } public void Configure(SentryBlazorOptions options) @@ -19,7 +26,7 @@ public void Configure(SentryBlazorOptions options) var previousUrl = _navigationManager.Uri; // Set the initial scope request URL - SentrySdk.ConfigureScope(scope => + _hub.ConfigureScope(scope => { scope.Request.Url = ToRelativePath(previousUrl); }); @@ -29,17 +36,17 @@ public void Configure(SentryBlazorOptions options) var from = ToRelativePath(previousUrl); var to = ToRelativePath(args.Location); - SentrySdk.AddBreadcrumb( - message: "", - category: "navigation", - type: "navigation", - data: new Dictionary - { - { "from", from }, - { "to", to } - }); - - SentrySdk.ConfigureScope(scope => + _hub.AddBreadcrumb( + new Breadcrumb( + type: "navigation", + category: "navigation", + data: new Dictionary + { + { "from", from }, + { "to", to } + })); + + _hub.ConfigureScope(scope => { scope.Request.Url = to; }); diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index a2b75db0ae..2fce203ef5 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -137,6 +137,7 @@ + diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs index dcf7567624..d9960f3087 100644 --- a/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/BlazorWasmOptionsSetupTests.cs @@ -3,11 +3,12 @@ namespace Sentry.AspNetCore.Blazor.WebAssembly.Tests; -public class BlazorWasmOptionsSetupTests : IDisposable +public class BlazorWasmOptionsSetupTests { private readonly FakeNavigationManager _navigationManager; + private readonly IHub _hub; + private readonly Scope _scope; private readonly BlazorWasmOptionsSetup _sut; - private readonly IDisposable _sentryInit; public BlazorWasmOptionsSetupTests() { @@ -15,18 +16,11 @@ public BlazorWasmOptionsSetupTests() baseUri: "https://localhost:5001/", initialUri: "https://localhost:5001/"); - _sut = new BlazorWasmOptionsSetup(_navigationManager); + _hub = Substitute.For(); + _scope = new Scope(new SentryOptions()); + _hub.SubstituteConfigureScope(_scope); - _sentryInit = SentrySdk.Init(o => - { - o.Dsn = ValidDsn; - o.IsGlobalModeEnabled = true; - }); - } - - public void Dispose() - { - _sentryInit.Dispose(); + _sut = new BlazorWasmOptionsSetup(_navigationManager, _hub); } [Fact] @@ -36,10 +30,7 @@ public void Configure_SetsInitialRequestUrl() _sut.Configure(new SentryBlazorOptions()); // Assert - SentrySdk.ConfigureScope(scope => - { - scope.Request.Url.Should().Be("/"); - }); + _scope.Request.Url.Should().Be("/"); } [Fact] @@ -49,16 +40,13 @@ public void Configure_SetsInitialRequestUrl_WithPath() var nav = new FakeNavigationManager( baseUri: "https://localhost:5001/", initialUri: "https://localhost:5001/counter"); - var sut = new BlazorWasmOptionsSetup(nav); + var sut = new BlazorWasmOptionsSetup(nav, _hub); // Act sut.Configure(new SentryBlazorOptions()); // Assert - SentrySdk.ConfigureScope(scope => - { - scope.Request.Url.Should().Be("/counter"); - }); + _scope.Request.Url.Should().Be("/counter"); } [Fact] @@ -71,12 +59,23 @@ public void Navigation_CreatesBreadcrumbWithCorrectTypeAndCategory() _navigationManager.NavigateTo("/dashboard"); // Assert - SentrySdk.ConfigureScope(scope => - { - var crumb = scope.Breadcrumbs.Should().ContainSingle().Subject; - crumb.Type.Should().Be("navigation"); - crumb.Category.Should().Be("navigation"); - }); + var crumb = _scope.Breadcrumbs.Should().ContainSingle().Subject; + crumb.Type.Should().Be("navigation"); + crumb.Category.Should().Be("navigation"); + } + + [Fact] + public void Navigation_BreadcrumbHasNoMessage() + { + // Arrange + _sut.Configure(new SentryBlazorOptions()); + + // Act + _navigationManager.NavigateTo("/dashboard"); + + // Assert + var crumb = _scope.Breadcrumbs.Should().ContainSingle().Subject; + crumb.Message.Should().BeNull(); } [Fact] @@ -89,12 +88,9 @@ public void Navigation_CreatesBreadcrumbWithRelativePaths() _navigationManager.NavigateTo("/dashboard"); // Assert - SentrySdk.ConfigureScope(scope => - { - var crumb = scope.Breadcrumbs.Should().ContainSingle().Subject; - crumb.Data.Should().ContainKey("from").WhoseValue.Should().Be("/"); - crumb.Data.Should().ContainKey("to").WhoseValue.Should().Be("/dashboard"); - }); + var crumb = _scope.Breadcrumbs.Should().ContainSingle().Subject; + crumb.Data.Should().ContainKey("from").WhoseValue.Should().Be("/"); + crumb.Data.Should().ContainKey("to").WhoseValue.Should().Be("/dashboard"); } [Fact] @@ -107,10 +103,7 @@ public void Navigation_UpdatesRequestUrl() _navigationManager.NavigateTo("/dashboard"); // Assert - SentrySdk.ConfigureScope(scope => - { - scope.Request.Url.Should().Be("/dashboard"); - }); + _scope.Request.Url.Should().Be("/dashboard"); } [Fact] @@ -124,19 +117,16 @@ public void MultipleNavigations_TrackFromCorrectly() _navigationManager.NavigateTo("/page2"); // Assert - SentrySdk.ConfigureScope(scope => - { - var breadcrumbs = scope.Breadcrumbs.ToList(); - breadcrumbs.Should().HaveCount(2); - - var first = breadcrumbs[0]; - first.Data.Should().ContainKey("from").WhoseValue.Should().Be("/"); - first.Data.Should().ContainKey("to").WhoseValue.Should().Be("/page1"); - - var second = breadcrumbs[1]; - second.Data.Should().ContainKey("from").WhoseValue.Should().Be("/page1"); - second.Data.Should().ContainKey("to").WhoseValue.Should().Be("/page2"); - }); + var breadcrumbs = _scope.Breadcrumbs.ToList(); + breadcrumbs.Should().HaveCount(2); + + var first = breadcrumbs[0]; + first.Data.Should().ContainKey("from").WhoseValue.Should().Be("/"); + first.Data.Should().ContainKey("to").WhoseValue.Should().Be("/page1"); + + var second = breadcrumbs[1]; + second.Data.Should().ContainKey("from").WhoseValue.Should().Be("/page1"); + second.Data.Should().ContainKey("to").WhoseValue.Should().Be("/page2"); } [Fact] @@ -146,18 +136,15 @@ public void Navigation_FromInitialPath_TracksCorrectFrom() var nav = new FakeNavigationManager( baseUri: "https://localhost:5001/", initialUri: "https://localhost:5001/login"); - var sut = new BlazorWasmOptionsSetup(nav); + var sut = new BlazorWasmOptionsSetup(nav, _hub); sut.Configure(new SentryBlazorOptions()); // Act nav.NavigateTo("/home"); // Assert - SentrySdk.ConfigureScope(scope => - { - var crumb = scope.Breadcrumbs.Should().ContainSingle().Subject; - crumb.Data.Should().ContainKey("from").WhoseValue.Should().Be("/login"); - crumb.Data.Should().ContainKey("to").WhoseValue.Should().Be("/home"); - }); + var crumb = _scope.Breadcrumbs.Should().ContainSingle().Subject; + crumb.Data.Should().ContainKey("from").WhoseValue.Should().Be("/login"); + crumb.Data.Should().ContainKey("to").WhoseValue.Should().Be("/home"); } } From fdcf4559b4368060262658eb8fa433edd99b92a7 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 9 Feb 2026 23:17:40 -0500 Subject: [PATCH 3/4] Add changelog entry and regenerate solution filters Add the test project to the solution and regenerate all .slnf files so CI test-solution-filters passes. Add changelog entry for the navigation breadcrumbs feature. Co-Authored-By: Claude Opus 4.6 --- .generated.NoMobile.sln | 16 ++++++++++++++++ CHANGELOG.md | 1 + Sentry-CI-Build-Linux-NoMobile.slnf | 1 + Sentry-CI-Build-Linux.slnf | 1 + Sentry-CI-Build-Windows-arm64.slnf | 1 + Sentry-CI-Build-Windows.slnf | 1 + Sentry-CI-Build-macOS.slnf | 1 + Sentry.sln | 15 +++++++++++++++ SentryAspNetCore.slnf | 1 + SentryNoMobile.slnf | 1 + SentryNoSamples.slnf | 1 + 11 files changed, 40 insertions(+) diff --git a/.generated.NoMobile.sln b/.generated.NoMobile.sln index 1301310a3e..df9a68c619 100644 --- a/.generated.NoMobile.sln +++ b/.generated.NoMobile.sln @@ -278,6 +278,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration-test", "integra integration-test\pester.ps1 = integration-test\pester.ps1 integration-test\ios.Tests.ps1 = integration-test\ios.Tests.ps1 integration-test\msbuild.Tests.ps1 = integration-test\msbuild.Tests.ps1 + integration-test\android.Tests.ps1 = integration-test\android.Tests.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net4-console", "net4-console", "{33793113-C7B5-434D-B5C1-6CA1A9587842}" @@ -290,6 +291,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI.Tests", "test\Sentry.Extensions.AI.Tests\Sentry.Extensions.AI.Tests.csproj", "{28D6E004-DC64-464F-91BE-BC0B3A8E543F}" 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1357,6 +1360,18 @@ Global {28D6E004-DC64-464F-91BE-BC0B3A8E543F}.Release|x64.Build.0 = Release|Any CPU {28D6E004-DC64-464F-91BE-BC0B3A8E543F}.Release|x86.ActiveCfg = Release|Any CPU {28D6E004-DC64-464F-91BE-BC0B3A8E543F}.Release|x86.Build.0 = Release|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x64.Build.0 = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x86.ActiveCfg = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x86.Build.0 = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|Any CPU.Build.0 = Release|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x64.ActiveCfg = Release|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1457,5 +1472,6 @@ Global {3D91128A-5695-4BAE-B939-5F5F7C56A679} = {21B42F60-5802-404E-90F0-AEBCC56760C0} {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} EndGlobalSection EndGlobal diff --git a/CHANGELOG.md b/CHANGELOG.md index 396ee4a7a8..f7c0db7d39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Add navigation breadcrumbs for Blazor WebAssembly ([#4907](https://github.com/getsentry/sentry-dotnet/pull/4907)) - Add _experimental_ support for [Sentry trace-connected Metrics](https://docs.sentry.io/product/explore/metrics/) ([#4834](https://github.com/getsentry/sentry-dotnet/pull/4834)) - Extended `SentryThread` by `Main` to allow indication whether the thread is considered the current main thread ([#4807](https://github.com/getsentry/sentry-dotnet/pull/4807)) diff --git a/Sentry-CI-Build-Linux-NoMobile.slnf b/Sentry-CI-Build-Linux-NoMobile.slnf index a0bba5a7b5..5aff2b9e87 100644 --- a/Sentry-CI-Build-Linux-NoMobile.slnf +++ b/Sentry-CI-Build-Linux-NoMobile.slnf @@ -49,6 +49,7 @@ "src\\Sentry.Serilog\\Sentry.Serilog.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", diff --git a/Sentry-CI-Build-Linux.slnf b/Sentry-CI-Build-Linux.slnf index c3cb98016e..dd182881d2 100644 --- a/Sentry-CI-Build-Linux.slnf +++ b/Sentry-CI-Build-Linux.slnf @@ -56,6 +56,7 @@ "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.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", diff --git a/Sentry-CI-Build-Windows-arm64.slnf b/Sentry-CI-Build-Windows-arm64.slnf index 9ee5420055..5878f20b2f 100644 --- a/Sentry-CI-Build-Windows-arm64.slnf +++ b/Sentry-CI-Build-Windows-arm64.slnf @@ -58,6 +58,7 @@ "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.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", diff --git a/Sentry-CI-Build-Windows.slnf b/Sentry-CI-Build-Windows.slnf index 46f5fd978f..6bc68aeaad 100644 --- a/Sentry-CI-Build-Windows.slnf +++ b/Sentry-CI-Build-Windows.slnf @@ -58,6 +58,7 @@ "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.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", diff --git a/Sentry-CI-Build-macOS.slnf b/Sentry-CI-Build-macOS.slnf index ab4d5302ab..7a024d3b5e 100644 --- a/Sentry-CI-Build-macOS.slnf +++ b/Sentry-CI-Build-macOS.slnf @@ -63,6 +63,7 @@ "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.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", diff --git a/Sentry.sln b/Sentry.sln index f6db5afa73..df9a68c619 100644 --- a/Sentry.sln +++ b/Sentry.sln @@ -291,6 +291,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Extensions.AI.Tests", "test\Sentry.Extensions.AI.Tests\Sentry.Extensions.AI.Tests.csproj", "{28D6E004-DC64-464F-91BE-BC0B3A8E543F}" 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1358,6 +1360,18 @@ Global {28D6E004-DC64-464F-91BE-BC0B3A8E543F}.Release|x64.Build.0 = Release|Any CPU {28D6E004-DC64-464F-91BE-BC0B3A8E543F}.Release|x86.ActiveCfg = Release|Any CPU {28D6E004-DC64-464F-91BE-BC0B3A8E543F}.Release|x86.Build.0 = Release|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x64.Build.0 = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x86.ActiveCfg = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Debug|x86.Build.0 = Debug|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|Any CPU.Build.0 = Release|Any CPU + {705223D7-FEE3-485B-8DFB-3F9B29DAC6CA}.Release|x64.ActiveCfg = Release|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1458,5 +1472,6 @@ Global {3D91128A-5695-4BAE-B939-5F5F7C56A679} = {21B42F60-5802-404E-90F0-AEBCC56760C0} {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} EndGlobalSection EndGlobal diff --git a/SentryAspNetCore.slnf b/SentryAspNetCore.slnf index 7b89d9e91c..c5eea865ea 100644 --- a/SentryAspNetCore.slnf +++ b/SentryAspNetCore.slnf @@ -24,6 +24,7 @@ "src\\Sentry.Serilog\\Sentry.Serilog.csproj", "src\\Sentry\\Sentry.csproj", "test\\Sentry.Analyzers.Tests\\Sentry.Analyzers.Tests.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", diff --git a/SentryNoMobile.slnf b/SentryNoMobile.slnf index e4754f322d..e4e8cd9771 100644 --- a/SentryNoMobile.slnf +++ b/SentryNoMobile.slnf @@ -52,6 +52,7 @@ "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.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", diff --git a/SentryNoSamples.slnf b/SentryNoSamples.slnf index d6231a7e06..2fa3d0bf7a 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.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", "test\\Sentry.AspNetCore.TestUtils\\Sentry.AspNetCore.TestUtils.csproj", From 11ada43f13b6dfcfa246c22638525ec8d05a43a4 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 10 Feb 2026 00:56:49 -0500 Subject: [PATCH 4/4] Address PR review comments - Remove unused using and hidden NavigateTo method in FakeNavigationManager - Add ArgumentNullException.ThrowIfNull(options) for consistency - Add _initialized guard to prevent duplicate event handlers Co-Authored-By: Claude Opus 4.6 --- .../Internal/BlazorWasmOptionsSetup.cs | 9 +++++++++ .../FakeNavigationManager.cs | 8 -------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs b/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs index 2223968385..ad1f5bbcc9 100644 --- a/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs @@ -9,6 +9,7 @@ internal sealed class BlazorWasmOptionsSetup : IConfigureOptions