From 40d84b1fa595eedbe3f975887df9a097844e7174 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:33:59 -0700 Subject: [PATCH 01/11] [release/10.0] Fix openapi schema type null for UrlAttribute and Base64StringAttribute (#63528) * Fix openapi schema null type for UrlAttribute and Base64StringAttribute * Fix copilot nits --------- Co-authored-by: Sjoerd van der Meer --- .../Extensions/JsonNodeSchemaExtensions.cs | 2 - .../OpenApiSchemaService.PropertySchemas.cs | 297 ++++++++++++++++++ 2 files changed, 297 insertions(+), 2 deletions(-) diff --git a/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs b/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs index 553c87643557..945326b1d5b1 100644 --- a/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs +++ b/src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs @@ -85,7 +85,6 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable { if (attribute is Base64StringAttribute) { - schema[OpenApiSchemaKeywords.TypeKeyword] = JsonSchemaType.String.ToString(); schema[OpenApiSchemaKeywords.FormatKeyword] = "byte"; } else if (attribute is RangeAttribute rangeAttribute) @@ -153,7 +152,6 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable } else if (attribute is UrlAttribute) { - schema[OpenApiSchemaKeywords.TypeKeyword] = JsonSchemaType.String.ToString(); schema[OpenApiSchemaKeywords.FormatKeyword] = "uri"; } else if (attribute is StringLengthAttribute stringLengthAttribute) diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PropertySchemas.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PropertySchemas.cs index 0773249e195d..e3b021b22294 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PropertySchemas.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.PropertySchemas.cs @@ -411,6 +411,236 @@ await VerifyOpenApiDocument(builder, document => }); } + [Fact] + public async Task GetOpenApiSchema_Base64StringAttribute_StringProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["base64StringValue"]; + var nullable = schema.Properties["nullableBase64StringValue"]; + Assert.Equal(JsonSchemaType.String, nonNullable.Type); + Assert.Equal("byte", nonNullable.Format); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type); + Assert.Equal("byte", nullable.Format); + }); + } + + [Fact] + public async Task GetOpenApiSchema_RangeAttribute_IntProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["rangeIntValue"]; + var nullable = schema.Properties["nullableRangeIntValue"]; + Assert.Equal(JsonSchemaType.Integer, nonNullable.Type); + Assert.Equal("int32", nonNullable.Format); + Assert.Equal("1", nonNullable.Minimum); + Assert.Equal("100", nonNullable.Maximum); + Assert.Equal(JsonSchemaType.Integer | JsonSchemaType.Null, nullable.Type); + Assert.Equal("int32", nullable.Format); + Assert.Equal("1", nullable.Minimum); + Assert.Equal("100", nullable.Maximum); + }); + } + + [Fact] + public async Task GetOpenApiSchema_RangeAttribute_DoubleProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["rangeDoubleValue"]; + var nullable = schema.Properties["nullableRangeDoubleValue"]; + Assert.Equal(JsonSchemaType.Number, nonNullable.Type); + Assert.Equal("double", nonNullable.Format); + Assert.Equal("0.1", nonNullable.Minimum as string); + Assert.Equal("99.9", nonNullable.Maximum as string); + Assert.Equal(JsonSchemaType.Number | JsonSchemaType.Null, nullable.Type); + Assert.Equal("double", nullable.Format); + Assert.Equal("0.1", nullable.Minimum as string); + Assert.Equal("99.9", nullable.Maximum as string); + }); + } + + [Fact] + public async Task GetOpenApiSchema_RegularExpressionAttribute_StringProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["regexStringValue"]; + var nullable = schema.Properties["nullableRegexStringValue"]; + Assert.Equal(JsonSchemaType.String, nonNullable.Type); + Assert.Equal("^[A-Z]{3}$", nonNullable.Pattern); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type); + Assert.Equal("^[A-Z]{3}$", nullable.Pattern); + }); + } + + [Fact] + public async Task GetOpenApiSchema_MaxLengthAttribute_StringProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["maxLengthStringValue"]; + var nullable = schema.Properties["nullableMaxLengthStringValue"]; + Assert.Equal(JsonSchemaType.String, nonNullable.Type); + Assert.Equal(10, nonNullable.MaxLength); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type); + Assert.Equal(10, nullable.MaxLength); + }); + } + + [Fact] + public async Task GetOpenApiSchema_MaxLengthAttribute_ArrayProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["maxLengthArrayValue"]; + var nullable = schema.Properties["nullableMaxLengthArrayValue"]; + Assert.Equal(JsonSchemaType.Array, nonNullable.Type); + Assert.Equal(5, nonNullable.MaxItems); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, nullable.Type); + Assert.Equal(5, nullable.MaxItems); + }); + } + + [Fact] + public async Task GetOpenApiSchema_MinLengthAttribute_StringProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["minLengthStringValue"]; + var nullable = schema.Properties["nullableMinLengthStringValue"]; + Assert.Equal(JsonSchemaType.String, nonNullable.Type); + Assert.Equal(3, nonNullable.MinLength); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type); + Assert.Equal(3, nullable.MinLength); + }); + } + + [Fact] + public async Task GetOpenApiSchema_MinLengthAttribute_ArrayProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["minLengthArrayValue"]; + var nullable = schema.Properties["nullableMinLengthArrayValue"]; + Assert.Equal(JsonSchemaType.Array, nonNullable.Type); + Assert.Equal(2, nonNullable.MinItems); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, nullable.Type); + Assert.Equal(2, nullable.MinItems); + }); + } + + [Fact] + public async Task GetOpenApiSchema_LengthAttribute_StringProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["lengthStringValue"]; + var nullable = schema.Properties["nullableLengthStringValue"]; + Assert.Equal(JsonSchemaType.String, nonNullable.Type); + Assert.Equal(2, nonNullable.MinLength); + Assert.Equal(8, nonNullable.MaxLength); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type); + Assert.Equal(2, nullable.MinLength); + Assert.Equal(8, nullable.MaxLength); + }); + } + + [Fact] + public async Task GetOpenApiSchema_LengthAttribute_ArrayProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["lengthArrayValue"]; + var nullable = schema.Properties["nullableLengthArrayValue"]; + Assert.Equal(JsonSchemaType.Array, nonNullable.Type); + Assert.Equal(1, nonNullable.MinItems); + Assert.Equal(4, nonNullable.MaxItems); + Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, nullable.Type); + Assert.Equal(1, nullable.MinItems); + Assert.Equal(4, nullable.MaxItems); + }); + } + + [Fact] + public async Task GetOpenApiSchema_UrlAttribute_StringProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["urlStringValue"]; + var nullable = schema.Properties["nullableUrlStringValue"]; + Assert.Equal(JsonSchemaType.String, nonNullable.Type); + Assert.Equal("uri", nonNullable.Format); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type); + Assert.Equal("uri", nullable.Format); + }); + } + + [Fact] + public async Task GetOpenApiSchema_StringLengthAttribute_StringProperties() + { + var builder = CreateBuilder(); + builder.MapPost("/api", (PropertiesWithDataAnnotations model) => { }); + + await VerifyOpenApiDocument(builder, document => + { + var schema = document.Paths["/api"].Operations[HttpMethod.Post].RequestBody.Content.First().Value.Schema; + var nonNullable = schema.Properties["stringLengthValue"]; + var nullable = schema.Properties["nullableStringLengthValue"]; + Assert.Equal(JsonSchemaType.String, nonNullable.Type); + Assert.Equal(5, nonNullable.MinLength); + Assert.Equal(20, nonNullable.MaxLength); + Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, nullable.Type); + Assert.Equal(5, nullable.MinLength); + Assert.Equal(20, nullable.MaxLength); + }); + } + #nullable enable private class NullablePropertiesTestModel { @@ -452,5 +682,72 @@ private class NullablePropertiesWithValidationModel [Description("A description field")] public string? NullableDescription { get; set; } } + + private class PropertiesWithDataAnnotations + { + // Base64StringAttribute + [Base64String] + public string Base64StringValue { get; set; } = string.Empty; + [Base64String] + public string? NullableBase64StringValue { get; set; } + + // RangeAttribute + [Range(1, 100)] + public int RangeIntValue { get; set; } = 0; + [Range(1, 100)] + public int? NullableRangeIntValue { get; set; } + [Range(0.1, 99.9)] + public double RangeDoubleValue { get; set; } = 0.0; + [Range(0.1, 99.9)] + public double? NullableRangeDoubleValue { get; set; } + + // RegularExpressionAttribute + [RegularExpression(@"^[A-Z]{3}$")] + public string RegexStringValue { get; set; } = string.Empty; + [RegularExpression(@"^[A-Z]{3}$")] + public string? NullableRegexStringValue { get; set; } + + // MaxLengthAttribute + [MaxLength(10)] + public string MaxLengthStringValue { get; set; } = string.Empty; + [MaxLength(10)] + public string? NullableMaxLengthStringValue { get; set; } + [MaxLength(5)] + public int[] MaxLengthArrayValue { get; set; } = []; + [MaxLength(5)] + public int[]? NullableMaxLengthArrayValue { get; set; } + + // MinLengthAttribute + [MinLength(3)] + public string MinLengthStringValue { get; set; } = string.Empty; + [MinLength(3)] + public string? NullableMinLengthStringValue { get; set; } + [MinLength(2)] + public int[] MinLengthArrayValue { get; set; } = []; + [MinLength(2)] + public int[]? NullableMinLengthArrayValue { get; set; } + + // LengthAttribute (custom, if available) + [Length(2, 8)] + public string LengthStringValue { get; set; } = string.Empty; + [Length(2, 8)] + public string? NullableLengthStringValue { get; set; } + [Length(1, 4)] + public int[] LengthArrayValue { get; set; } = []; + [Length(1, 4)] + public int[]? NullableLengthArrayValue { get; set; } + + // UrlAttribute + [Url] + public string UrlStringValue { get; set; } = string.Empty; + [Url] + public string? NullableUrlStringValue { get; set; } + + // StringLengthAttribute + [StringLength(20, MinimumLength = 5)] + public string StringLengthValue { get; set; } = string.Empty; + [StringLength(20, MinimumLength = 5)] + public string? NullableStringLengthValue { get; set; } + } #nullable restore } From 2d3bd05e689b0a6c226eb0c739073e45e21769d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:27:10 -0700 Subject: [PATCH 02/11] Extend Unofficial 1ES template in IdentityModel nightly tests job (#63468) Co-authored-by: William Godbe --- .azure/pipelines/identitymodel-helix-matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/pipelines/identitymodel-helix-matrix.yml b/.azure/pipelines/identitymodel-helix-matrix.yml index 8329ee4d11f9..11e9cf0a37d8 100644 --- a/.azure/pipelines/identitymodel-helix-matrix.yml +++ b/.azure/pipelines/identitymodel-helix-matrix.yml @@ -26,7 +26,7 @@ resources: ref: refs/tags/release extends: - template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines + template: v1/1ES.Unofficial.PipelineTemplate.yml@1esPipelines parameters: sdl: sourceAnalysisPool: From c14eaafcc97c0b3e9a9772ad4993352aab8b7249 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:40:55 -0700 Subject: [PATCH 03/11] Update dependencies from https://github.com/dotnet/extensions build 20250827.1 (#63490) Microsoft.Extensions.Caching.Hybrid , Microsoft.Extensions.Diagnostics.Testing , Microsoft.Extensions.TimeProvider.Testing From Version 9.9.0-preview.1.25419.1 -> To Version 9.9.0-preview.1.25427.1 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 6 +++--- eng/Version.Details.xml | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index c22289794a86..737493fbaf29 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -104,9 +104,9 @@ This file should be imported by eng/Versions.props 4.13.0-3.24613.7 4.13.0-3.24613.7 - 9.9.0-preview.1.25419.1 - 9.9.0-preview.1.25419.1 - 9.9.0-preview.1.25419.1 + 9.9.0-preview.1.25427.1 + 9.9.0-preview.1.25427.1 + 9.9.0-preview.1.25427.1 1.0.0-prerelease.25421.1 1.0.0-prerelease.25421.1 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 1b05b18f5c37..4fc60027ca19 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -390,17 +390,17 @@ https://github.com/dotnet/dotnet 7ac1ca67bb1fb8a381c1c94a9f82a97725f0ccf3 - + https://github.com/dotnet/extensions - 77859cb87c33bcda7187871a683c5779557cb2ef + ec3b31d717a05bfa5ea1ea084ff33d15430a6cdf - + https://github.com/dotnet/extensions - 77859cb87c33bcda7187871a683c5779557cb2ef + ec3b31d717a05bfa5ea1ea084ff33d15430a6cdf - + https://github.com/dotnet/extensions - 77859cb87c33bcda7187871a683c5779557cb2ef + ec3b31d717a05bfa5ea1ea084ff33d15430a6cdf https://dev.azure.com/dnceng/internal/_git/dotnet-optimization From ad184d4d52dee2b1786c9cd7ccb3898d0499ca28 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:23:36 -0700 Subject: [PATCH 04/11] Fix Directory Browser accessibility (#63462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applied contrast recommendations from accessibility insights. Co-authored-by: Sébastien Ros --- src/Middleware/StaticFiles/src/HtmlDirectoryFormatter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Middleware/StaticFiles/src/HtmlDirectoryFormatter.cs b/src/Middleware/StaticFiles/src/HtmlDirectoryFormatter.cs index 2cd66530ec05..83a7c74b89b3 100644 --- a/src/Middleware/StaticFiles/src/HtmlDirectoryFormatter.cs +++ b/src/Middleware/StaticFiles/src/HtmlDirectoryFormatter.cs @@ -79,7 +79,7 @@ header h1 { vertical-align: bottom; padding: 10px 5px 5px 5px; font-weight: 400; - color: #a0a0a0; + color: #737373; text-align: center; } #index td { padding: 3px 10px; } #index th, #index td { @@ -91,7 +91,7 @@ header h1 { #index th:last-child, #index td:last-child { border-right: 1px transparent solid; } #index td.length, td.modified { text-align:right; } - a { color:#1ba1e2;text-decoration:none; } + a { color:#127aac;text-decoration:none; } a:hover { color:#13709e;text-decoration:underline; } From 78b2252a47cde8a4b9990c33ba02616ee5603efe Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:37:35 -0700 Subject: [PATCH 05/11] Update dependencies from https://github.com/dotnet/extensions build 20250906.3 (#63580) Microsoft.Extensions.Caching.Hybrid , Microsoft.Extensions.Diagnostics.Testing , Microsoft.Extensions.TimeProvider.Testing From Version 9.9.0-preview.1.25427.1 -> To Version 9.10.0-preview.1.25456.3 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 6 +++--- eng/Version.Details.xml | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 737493fbaf29..437ccb1107e4 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -104,9 +104,9 @@ This file should be imported by eng/Versions.props 4.13.0-3.24613.7 4.13.0-3.24613.7 - 9.9.0-preview.1.25427.1 - 9.9.0-preview.1.25427.1 - 9.9.0-preview.1.25427.1 + 9.10.0-preview.1.25456.3 + 9.10.0-preview.1.25456.3 + 9.10.0-preview.1.25456.3 1.0.0-prerelease.25421.1 1.0.0-prerelease.25421.1 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 4fc60027ca19..dde396660939 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -390,17 +390,17 @@ https://github.com/dotnet/dotnet 7ac1ca67bb1fb8a381c1c94a9f82a97725f0ccf3 - + https://github.com/dotnet/extensions - ec3b31d717a05bfa5ea1ea084ff33d15430a6cdf + 3645ccc33d29294341654cc37d0c448ed47c836c - + https://github.com/dotnet/extensions - ec3b31d717a05bfa5ea1ea084ff33d15430a6cdf + 3645ccc33d29294341654cc37d0c448ed47c836c - + https://github.com/dotnet/extensions - ec3b31d717a05bfa5ea1ea084ff33d15430a6cdf + 3645ccc33d29294341654cc37d0c448ed47c836c https://dev.azure.com/dnceng/internal/_git/dotnet-optimization From d73e2bbd25533e3feaf98a51da0a058487772467 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:40:47 -0700 Subject: [PATCH 06/11] Fix SuppliesCancellationTokenThatSignalsWhenRevalidationLoopIsBeingDiscarded flake (#63538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Roztočil --- .../RevalidatingServerAuthenticationStateProviderTest.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProviderTest.cs b/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProviderTest.cs index c734415ce0cc..483fc9f667d6 100644 --- a/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProviderTest.cs +++ b/src/Components/Server/test/Circuits/RevalidatingServerAuthenticationStateProviderTest.cs @@ -142,7 +142,6 @@ public async Task StopsRevalidatingAfterDisposal() } [Fact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/60472")] public async Task SuppliesCancellationTokenThatSignalsWhenRevalidationLoopIsBeingDiscarded() { // Arrange @@ -174,9 +173,11 @@ public async Task SuppliesCancellationTokenThatSignalsWhenRevalidationLoopIsBein Assert.Equal("different user", (await provider.GetAuthenticationStateAsync()).User.Identity.Name); // Subsequent revalidation can complete successfully + // We are checking all new logs because the revalidation loop iteration + // may happen multiple times (this made the test flaky in the past) await provider.NextValidateAuthenticationStateAsyncCall; - Assert.Collection(provider.RevalidationCallLog.Skip(1), - call => Assert.Equal("different user", call.AuthenticationState.User.Identity.Name)); + Assert.All(provider.RevalidationCallLog.Skip(1), + call => Assert.Equal("different user", call.AuthenticationState.User.Identity.Name)); } [Fact] From 15392325746550c084dcee5b3b8165fe14d7251a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:41:28 -0700 Subject: [PATCH 07/11] [release/10.0] Run entire E2E test with consistent enhanced navigation suppression setting (#63527) * Supress enhanced nav per test. * Each test should have a testId. * Move granting test id to the base class. * Remove unused namespace. * Not all tests can use session storage. * Improve exception message. * Fix build + fix multiple blank lines. * redirection tests do not ned supression. * `fixture.Navigate` clears the storage, reading testId has to be done first. * NonInteractivityTests contain tests that require supression and tests that don't support session storage at the same time. Allow single tests to request id assignment. * Wait for the base page to be loaded to make sure session storage is available. * Move the id initialization to the enhanced nav supression method. * Cleanup. * Go back to setting test id only when supression happens but try to clean it after each test. * Improve cleanup - supression flag can also get removed. * Fix * Fix `RefreshCanFallBackOnFullPageReload` * Fix tests. * Cleanup - removal of storage items can be done once, on disposal. * Feedback. * BasicTestApp did not have h1 "Hello" that we expect on cleanup. Fix it. * Tests that closed the browser do not have to clean the session storage. * Another test needs a more specific selector. * Fix `DragDrop_CanTrigger` that requires specific layout of elements on the index page. * Add logs in case of "Failed to execute script after 3 retries." error. * Avoid searching for just h1 tag, use specific ids. * Limit relying on JS execution for checking the element position + increase timeouts for landing page assert that can be too short once in every 20 runs. * Grant ID for each test on initialization. Change BinaryHttpClientTest inheritance to ServerTestBase, so that it calls ID granting method as well. * Try on CI if tests run with small or big window (toggle bar has problems locally because it runs in a small window). * Fix failing tests. * Revert "Fix failing tests." This reverts commit 6dcfd16fccfbb593b942c437b3ad101077f19563. * Revert "Try on CI if tests run with small or big window (toggle bar has problems locally because it runs in a small window)." This reverts commit 18ee068be59a6bbe79132da101b49635857ebfb9. * Revert "Grant ID for each test on initialization. Change BinaryHttpClientTest inheritance to ServerTestBase, so that it calls ID granting method as well." This reverts commit 7ac455c5bc07d4e2536ded8b21d2aa64c8a28833. * @javiercn's feedback: selenium gives absolute position. * Remove the "tax" on each test, we can return early from cleaning if supression was not requested. * Rename according to feedback. * Use element id to check if page got loaded. * Fix tests - use specific ID, not TagName that picks the first element of the type. * ID is not necessary if we're cleaning and not creating it for every test. --------- Co-authored-by: Ilona Tomkowicz --- .../StandaloneApp/Pages/Index.razor | 2 +- .../E2ETest/Infrastructure/ServerTestBase.cs | 7 ++ .../WebDriverExtensions.cs | 28 ++++---- .../CircuitContextTest.cs | 2 +- .../CircuitGracefulTerminationTests.cs | 2 +- .../EnhancedNavigationTest.cs | 32 +++++---- .../EnhancedNavigationTestUtil.cs | 70 +++++++++++++++++-- .../SectionsWithCascadingParametersTest.cs | 14 ++-- .../Tests/SectionsWithErrorBoundaryTest.cs | 6 +- .../BasicTestApp/GracefulTermination.razor | 2 +- .../test/testassets/BasicTestApp/Index.razor | 2 + .../SectionsWithErrorBoundary.razor | 2 +- .../TextComponentWithCascadingParameter.razor | 2 +- .../CircuitContextComponent.razor | 2 +- .../RazorComponents/App.razor | 1 - .../RazorComponents/Pages/Index.razor | 2 +- .../RazorComponents/Root.razor | 1 - src/Shared/E2ETesting/BrowserTestBase.cs | 2 +- 18 files changed, 126 insertions(+), 53 deletions(-) diff --git a/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor index 16dac3192520..71201aa021ee 100644 --- a/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor +++ b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor @@ -1,5 +1,5 @@ @page "/" -

Hello, world!

+

Hello, world!

Welcome to your new app. diff --git a/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs b/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs index 841cecb32695..315b80cd2248 100644 --- a/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs +++ b/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; using Microsoft.AspNetCore.E2ETesting; using OpenQA.Selenium; using Xunit.Abstractions; @@ -31,6 +32,12 @@ public void Navigate(string relativeUrl) Browser.Navigate(_serverFixture.RootUri, relativeUrl); } + public override async Task DisposeAsync() + { + EnhancedNavigationTestUtil.CleanEnhancedNavigationSuppression(this); + await base.DisposeAsync(); + } + protected override void InitializeAsyncCore() { // Clear logs - we check these during tests in some cases. diff --git a/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs b/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs index 34276e78613e..fd24da459497 100644 --- a/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs +++ b/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs @@ -3,15 +3,11 @@ using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; -using System; namespace Microsoft.AspNetCore.Components.E2ETest; internal static class WebDriverExtensions { - private static string GetFindPositionScript(string elementId) => - $"return Math.round(document.getElementById('{elementId}').getBoundingClientRect().top + window.scrollY);"; - public static void Navigate(this IWebDriver browser, Uri baseUri, string relativeUrl) { var absoluteUrl = new Uri(baseUri, relativeUrl); @@ -45,27 +41,27 @@ public static void WaitForElementToBeVisible(this IWebDriver browser, By by, int public static long GetElementPositionWithRetry(this IWebDriver browser, string elementId, int retryCount = 3, int delayBetweenRetriesMs = 100) { - var jsExecutor = (IJavaScriptExecutor)browser; - string script = GetFindPositionScript(elementId); - browser.WaitForElementToBeVisible(By.Id(elementId)); + string log = ""; + for (int i = 0; i < retryCount; i++) { try { - var result = jsExecutor.ExecuteScript(script); - if (result != null) - { - return (long)result; - } + browser.WaitForElementToBeVisible(By.Id(elementId)); + var element = browser.FindElement(By.Id(elementId)); + return element.Location.Y; } - catch (OpenQA.Selenium.JavaScriptException) + catch (Exception ex) { - // JavaScript execution failed, retry + log += $"Attempt {i + 1}: - {ex.Message}. "; } - Thread.Sleep(delayBetweenRetriesMs); + if (i < retryCount - 1) + { + Thread.Sleep(delayBetweenRetriesMs); + } } - throw new Exception($"Failed to execute script after {retryCount} retries."); + throw new Exception($"Failed to get position for element '{elementId}' after {retryCount} retries. Debug log: {log}"); } } diff --git a/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs index 91c2b580fe60..5fe0f8a2ee37 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs @@ -41,7 +41,7 @@ public void ComponentMethods_HaveCircuitContext_OnInitialPageLoad() // Internal for reuse in Blazor Web tests internal static void TestCircuitContextCore(IWebDriver browser) { - browser.Equal("Circuit Context", () => browser.Exists(By.TagName("h1")).Text); + browser.Equal("Circuit Context", () => browser.Exists(By.Id("circuit-context-title")).Text); browser.Click(By.Id("trigger-click-event-button")); diff --git a/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs b/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs index ff7e56f9a363..37ecf327872a 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs @@ -40,7 +40,7 @@ protected override void InitializeAsyncCore() { Navigate(ServerPathBase); Browser.MountTestComponent(); - Browser.Equal("Graceful Termination", () => Browser.Exists(By.TagName("h1")).Text); + Browser.Equal("Graceful Termination", () => Browser.Exists(By.Id("graceful-termination-title")).Text); GracefulDisconnectCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Sink = _serverFixture.Host.Services.GetRequiredService(); diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs index 7dfaf3523287..49ca00e06915 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs @@ -317,6 +317,9 @@ public void RefreshCanFallBackOnFullPageReload(string renderMode) Browser.Navigate().Refresh(); Browser.Equal("Page with interactive components that navigate", () => Browser.Exists(By.TagName("h1")).Text); + // if we don't clean up the suppression, all subsequent navigations will be suppressed by default + EnhancedNavigationTestUtil.CleanEnhancedNavigationSuppression(this, skipNavigation: true); + // Normally, you shouldn't store references to elements because they could become stale references // after the page re-renders. However, we want to explicitly test that the element becomes stale // across renders to ensure that a full page reload occurs. @@ -677,11 +680,10 @@ public void CanUpdateHrefOnLinkTagWithIntegrity() } [Theory] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/60875")] - // [InlineData(false, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(false, false, false)] [InlineData(false, true, false)] [InlineData(true, true, false)] - // [InlineData(true, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(true, false, false)] // [InlineData(false, false, true)] programmatic navigation doesn't work without enhanced navigation [InlineData(false, true, true)] [InlineData(true, true, true)] @@ -692,8 +694,8 @@ public void EnhancedNavigationScrollBehavesSameAsBrowserOnNavigation(bool enable // or to the beginning of a fragment, regardless of the previous scroll position string landingPageSuffix = enableStreaming ? "" : "-no-streaming"; string buttonKeyword = programmaticNavigation ? "-programmatic" : ""; + EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation); Navigate($"{ServerPathBase}/nav/scroll-test{landingPageSuffix}"); - EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation, skipNavigation: true); // "landing" page: scroll maximally down and go to "next" page - we should land at the top of that page AssertWeAreOnLandingPage(); @@ -732,10 +734,10 @@ public void EnhancedNavigationScrollBehavesSameAsBrowserOnNavigation(bool enable } [Theory] - // [InlineData(false, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(false, false, false)] [InlineData(false, true, false)] [InlineData(true, true, false)] - // [InlineData(true, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(true, false, false)] // [InlineData(false, false, true)] programmatic navigation doesn't work without enhanced navigation [InlineData(false, true, true)] [InlineData(true, true, true)] @@ -745,8 +747,8 @@ public void EnhancedNavigationScrollBehavesSameAsBrowserOnBackwardsForwardsActio // This test checks if the scroll position is preserved after backwards/forwards action string landingPageSuffix = enableStreaming ? "" : "-no-streaming"; string buttonKeyword = programmaticNavigation ? "-programmatic" : ""; + EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation); Navigate($"{ServerPathBase}/nav/scroll-test{landingPageSuffix}"); - EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation, skipNavigation: true); // "landing" page: scroll to pos1, navigate away AssertWeAreOnLandingPage(); @@ -831,6 +833,8 @@ private void AssertScrollPositionCorrect(bool useEnhancedNavigation, long previo private void AssertEnhancedNavigation(bool useEnhancedNavigation, IWebElement elementForStalenessCheck, int retryCount = 3, int delayBetweenRetriesMs = 1000) { bool enhancedNavigationDetected = false; + string logging = ""; + string isNavigationSuppressed = ""; for (int i = 0; i < retryCount; i++) { try @@ -841,28 +845,32 @@ private void AssertEnhancedNavigation(bool useEnhancedNavigation, IWebElement el } catch (XunitException) { + var logs = Browser.GetBrowserLogs(LogLevel.Warning); + logging += $"{string.Join(", ", logs.Select(l => l.Message))}\n"; + isNavigationSuppressed = (string)((IJavaScriptExecutor)Browser).ExecuteScript("return sessionStorage.getItem('suppress-enhanced-navigation');"); + + logging += $" isNavigationSuppressed: {isNavigationSuppressed}\n"; // Maybe the check was done too early to change the DOM ref, retry } Thread.Sleep(delayBetweenRetriesMs); } - string expectedNavigation = useEnhancedNavigation ? "enhanced navigation" : "browser navigation"; + string expectedNavigation = useEnhancedNavigation ? "enhanced navigation" : "full page load"; string isStale = enhancedNavigationDetected ? "is not stale" : "is stale"; - var isNavigationSupressed = (string)((IJavaScriptExecutor)Browser).ExecuteScript("return sessionStorage.getItem('suppress-enhanced-navigation');"); - throw new Exception($"Expected to use {expectedNavigation} because 'suppress-enhanced-navigation' is set to {isNavigationSupressed} but the element from previous path {isStale}"); + throw new Exception($"Expected to use {expectedNavigation} because 'suppress-enhanced-navigation' is set to {isNavigationSuppressed} but the element from previous path {isStale}. logging={logging}"); } private void AssertWeAreOnLandingPage() { string infoName = "test-info-1"; - Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 20); + Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 30); Browser.Equal("Scroll tests landing page", () => Browser.Exists(By.Id(infoName)).Text); } private void AssertWeAreOnNextPage() { string infoName = "test-info-2"; - Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 20); + Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 30); Browser.Equal("Scroll tests next page", () => Browser.Exists(By.Id(infoName)).Text); } diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs index 799b915f8fdd..ffd0287063a0 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; public static class EnhancedNavigationTestUtil { + private static bool _isSuppressed; + public static void SuppressEnhancedNavigation(ServerTestBase fixture, bool shouldSuppress, bool skipNavigation = false) where TServerFixture : ServerFixture { @@ -20,16 +22,76 @@ public static void SuppressEnhancedNavigation(ServerTestBase browser.Exists(By.TagName("h1")).Text); + NavigateToOrigin(fixture); + } + + try + { + ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.length"); + } + catch (Exception ex) + { + throw new InvalidOperationException("Session storage not found. Ensure that the browser is on the correct origin by navigating to a page or by setting skipNavigation to false.", ex); } ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.setItem('suppress-enhanced-navigation', 'true')"); + + var suppressEnhancedNavigation = ((IJavaScriptExecutor)browser).ExecuteScript("return sessionStorage.getItem('suppress-enhanced-navigation');"); + Assert.True(suppressEnhancedNavigation is not null && (string)suppressEnhancedNavigation == "true", + "Expected 'suppress-enhanced-navigation' to be set in sessionStorage."); + _isSuppressed = true; } } + public static void CleanEnhancedNavigationSuppression(ServerTestBase fixture, bool skipNavigation = false) + where TServerFixture : ServerFixture + { + if (!_isSuppressed) + { + return; + } + + var browser = fixture.Browser; + + try + { + // First, ensure we're on the correct origin to access sessionStorage + try + { + // Check if we can access sessionStorage from current location + ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.length"); + } + catch + { + if (skipNavigation) + { + throw new InvalidOperationException("Session storage not found. Ensure that the browser is on the correct origin by navigating to a page or by setting skipNavigation to false."); + } + NavigateToOrigin(fixture); + } + ((IJavaScriptExecutor)browser).ExecuteScript($"sessionStorage.removeItem('suppress-enhanced-navigation')"); + } + catch (WebDriverException ex) when (ex.Message.Contains("invalid session id")) + { + // Browser session is no longer valid (e.g., browser was closed) + // Session storage is automatically cleared when browser closes, so cleanup is already done + // This is expected in some tests, so we silently return + return; + } + finally + { + _isSuppressed = false; + } + } + + private static void NavigateToOrigin(ServerTestBase fixture) + where TServerFixture : ServerFixture + { + // Navigate to the test origin to ensure the browser is on the correct state to access sessionStorage + fixture.Navigate($"{fixture.ServerPathBase}/"); + fixture.Browser.Exists(By.Id("session-storage-anchor")); + } + public static long GetScrollY(this IWebDriver browser) => Convert.ToInt64(((IJavaScriptExecutor)browser).ExecuteScript("return window.scrollY"), CultureInfo.CurrentCulture); diff --git a/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs b/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs index bd69e484a5f8..73a860124ecb 100644 --- a/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs +++ b/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs @@ -35,7 +35,7 @@ public void RenderSectionContent_CascadingParameterForSectionOutletIsDeterminedB Browser.FindElement(By.Id("render-section-outlet")).Click(); Browser.FindElement(By.Id("render-second-section-content")).Click(); - Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -46,7 +46,7 @@ public void ChangeCascadingValueForSectionContent_CascadingValueForSectionOutlet Browser.FindElement(By.Id("change-cascading-value")).Click(); - Browser.Equal("First Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -56,7 +56,7 @@ public void RenderTwoSectionContentsWithSameId_CascadingParameterForSectionOutle Browser.FindElement(By.Id("render-first-section-content")).Click(); Browser.FindElement(By.Id("render-section-outlet")).Click(); - Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -68,7 +68,7 @@ public void SecondSectionContentIdChanged_CascadingParameterForSectionOutletIsDe Browser.FindElement(By.Id("change-second-section-content-id")).Click(); - Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -80,7 +80,7 @@ public void SecondSectionContentDisposed_CascadingParameterForSectionOutletIsDet Browser.FindElement(By.Id("dispose-second-section-content")).Click(); - Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -92,7 +92,7 @@ public void FirstSectionContentDisposedThenRenderSecondSectionContent_CascadingP Browser.FindElement(By.Id("dispose-first-section-content")).Click(); Browser.FindElement(By.Id("render-second-section-content")).Click(); - Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -105,6 +105,6 @@ public void SectionOutletIdChanged_CascadingParameterForSectionOutletIsDetermine Browser.FindElement(By.Id("change-section-outlet-id")).Click(); - Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } } diff --git a/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs b/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs index 3509030754af..25106e9ba4f2 100644 --- a/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs +++ b/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs @@ -37,7 +37,7 @@ public void RenderSectionContent_ErrorBoundaryForSectionOutletContentIsDetermine Browser.FindElement(By.Id("error-button")).Click(); - Browser.Equal("Sorry!", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Sorry!", () => Browser.Exists(By.Id("error-content")).Text); } [Fact] @@ -88,7 +88,7 @@ public void FirstSectionContentDisposedThenRenderSecondSectionContent_ErrorBound Browser.FindElement(By.Id("render-second-section-content")).Click(); Browser.FindElement(By.Id("error-button")).Click(); - Browser.Equal("Sorry!", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Sorry!", () => Browser.Exists(By.Id("error-content")).Text); } [Fact] @@ -102,6 +102,6 @@ public void SectionOutletIdChanged_ErrorBoundaryForSectionOutletIsDeterminedByMa Browser.FindElement(By.Id("change-section-outlet-id")).Click(); Browser.FindElement(By.Id("error-button")).Click(); - Browser.Equal("Sorry!", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Sorry!", () => Browser.Exists(By.Id("error-content")).Text); } } diff --git a/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor b/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor index 967775faa4bb..8109f027687f 100644 --- a/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor +++ b/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor @@ -1,6 +1,6 @@ @inject NavigationManager navigationManager -

Graceful Termination

+

Graceful Termination

Send Email Download Link diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 6e8d20b391a2..a3bc250f0634 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -1,6 +1,8 @@ @using Microsoft.AspNetCore.Components.Rendering @using System.Web @inject NavigationManager NavigationManager + +

Select test: