Skip to content

Commit 98298e4

Browse files
committed
Add E2E tests
1 parent 1c9ce21 commit 98298e4

10 files changed

+163
-8
lines changed

src/Components/Server/src/Builder/InternalServerRenderMode.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace Microsoft.AspNetCore.Components.Server;
1010

11-
internal class InternalServerRenderMode(ServerComponentsEndpointOptions options = null) : InteractiveServerRenderMode
11+
internal class InternalServerRenderMode(ServerComponentsEndpointOptions options) : InteractiveServerRenderMode
1212
{
13-
public ServerComponentsEndpointOptions? Options { get; } = options ?? new() { EnableWebSocketCompression = true };
13+
public ServerComponentsEndpointOptions? Options { get; } = options;
1414
}

src/Components/Server/src/Builder/ServerComponentsEndpointOptions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class ServerComponentsEndpointOptions
1414
/// <summary>
1515
/// Gets or sets a value that indicates whether compression is enabled for the WebSocket connections.
1616
/// </summary>
17-
public bool EnableWebSocketCompression { get; set; }
17+
public bool EnableWebSocketCompression { get; set; } = true;
1818

1919
/// <summary>
2020
/// Gets or sets the <c>frame-ancestors</c> <c>Content-Security-Policy</c> to set in the
@@ -41,7 +41,8 @@ public class ServerComponentsEndpointOptions
4141

4242
/// <summary>
4343
/// Gets or sets a callback to configure the underlying <see cref="HttpConnectionDispatcherOptions"/>.
44-
/// If set, this callback takes precedence over <see cref="EnableWebSocketCompression"/>.
44+
/// If set, <see cref="ContentSecurityFrameAncestorPolicy"/> will be applied independent of the value of
45+
/// <see cref="EnableWebSocketCompression"/>.
4546
/// </summary>
4647
public Action<HttpConnectionDispatcherOptions> ConnectionOptions { get; set; }
4748
}

src/Components/Server/src/Builder/ServerRazorComponentsEndpointConventionBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static RazorComponentsEndpointConventionBuilder AddInteractiveServerRende
3838

3939
ComponentEndpointConventionBuilderHelper.AddRenderMode(builder, new InternalServerRenderMode(options));
4040

41-
if (options.EnableWebSocketCompression && options.ContentSecurityFrameAncestorPolicy != null)
41+
if ((options.EnableWebSocketCompression || options.ConnectionOptions is not null) && options.ContentSecurityFrameAncestorPolicy != null)
4242
{
4343
builder.AddEndpointFilter(new RequireCspFilter(options.ContentSecurityFrameAncestorPolicy));
4444
}

src/Components/test/E2ETest/Infrastructure/ServerFixtures/AspNetSiteServerFixture.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Reflection;
55
using Microsoft.AspNetCore.E2ETesting;
6+
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Hosting;
78

89
namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
@@ -17,6 +18,8 @@ public class AspNetSiteServerFixture : WebHostServerFixture
1718

1819
public BuildWebHost BuildWebHostMethod { get; set; }
1920

21+
public Action<IServiceProvider> UpdateHostServices { get; set; }
22+
2023
public GetContentRoot GetContentRootMethod { get; set; } = DefaultGetContentRoot;
2124

2225
public AspNetEnvironment Environment { get; set; } = AspNetEnvironment.Production;
@@ -40,12 +43,16 @@ protected override IHost CreateWebHost()
4043
host = E2ETestOptions.Instance.Sauce.HostName;
4144
}
4245

43-
return BuildWebHostMethod(new[]
46+
var result = BuildWebHostMethod(new[]
4447
{
4548
"--urls", $"http://{host}:0",
4649
"--contentroot", sampleSitePath,
4750
"--environment", Environment.ToString(),
4851
}.Concat(AdditionalArguments).ToArray());
52+
53+
UpdateHostServices?.Invoke(result.Services);
54+
55+
return result;
4956
}
5057

5158
private static string DefaultGetContentRoot(Assembly assembly)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.RegularExpressions;
5+
using Components.TestServer.RazorComponents;
6+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
7+
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
8+
using Microsoft.AspNetCore.E2ETesting;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.VisualStudio.TestPlatform.Utilities;
11+
using OpenQA.Selenium;
12+
using TestServer;
13+
using Xunit.Abstractions;
14+
15+
namespace Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests;
16+
17+
public abstract partial class AllowedWebSocketCompressionTests(
18+
BrowserFixture browserFixture,
19+
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
20+
ITestOutputHelper output)
21+
: ServerTestBase<BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>>>(browserFixture, serverFixture, output)
22+
{
23+
[Fact]
24+
public void EmbeddingServerAppInsideIframe_Works()
25+
{
26+
Navigate("/subdir/iframe");
27+
28+
var logs = Browser.GetBrowserLogs(LogLevel.Severe);
29+
30+
Assert.Empty(logs);
31+
32+
// Get the iframe element from the page, and inspect its contents for a p element with id inside-iframe
33+
var iframe = Browser.FindElement(By.TagName("iframe"));
34+
Browser.SwitchTo().Frame(iframe);
35+
Browser.Exists(By.Id("inside-iframe"));
36+
}
37+
}
38+
39+
public abstract partial class BlockedWebSocketCompressionTests(
40+
BrowserFixture browserFixture,
41+
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
42+
ITestOutputHelper output)
43+
: ServerTestBase<BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>>>(browserFixture, serverFixture, output)
44+
{
45+
[Fact]
46+
public void EmbeddingServerAppInsideIframe_WithCompressionEnabled_Fails()
47+
{
48+
Navigate("/subdir/iframe");
49+
50+
var logs = Browser.GetBrowserLogs(LogLevel.Severe);
51+
52+
Assert.True(logs.Count > 0);
53+
54+
Assert.Matches(ParseErrorMessage(), logs[0].Message);
55+
}
56+
57+
[GeneratedRegex(@"security - Refused to frame 'http://\d+\.\d+\.\d+\.\d+:\d+/' because an ancestor violates the following Content Security Policy directive: ""frame-ancestors 'none'"".")]
58+
private static partial Regex ParseErrorMessage();
59+
}
60+
61+
public partial class DefaultConfigurationWebSocketCompressionTests(
62+
BrowserFixture browserFixture,
63+
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
64+
ITestOutputHelper output)
65+
: BlockedWebSocketCompressionTests(browserFixture, serverFixture, output)
66+
{
67+
}
68+
69+
public partial class CustomConfigurationCallbackWebSocketCompressionTests : BlockedWebSocketCompressionTests
70+
{
71+
public CustomConfigurationCallbackWebSocketCompressionTests(
72+
BrowserFixture browserFixture,
73+
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
74+
ITestOutputHelper output) : base(browserFixture, serverFixture, output)
75+
{
76+
serverFixture.UpdateHostServices = services =>
77+
{
78+
var configuration = services.GetService<WebSocketCompressionConfiguration>();
79+
configuration.ConnectionDispatcherOptions = options =>
80+
options.WebSockets.WebSocketAcceptContextFactory = context =>
81+
new Http.WebSocketAcceptContext { DangerousEnableCompression = true };
82+
};
83+
}
84+
}
85+
86+
public partial class CompressionDisabledWebSocketCompressionTests : AllowedWebSocketCompressionTests
87+
{
88+
public CompressionDisabledWebSocketCompressionTests(
89+
BrowserFixture browserFixture,
90+
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
91+
ITestOutputHelper output) : base(
92+
browserFixture, serverFixture, output)
93+
{
94+
serverFixture.UpdateHostServices = services =>
95+
{
96+
var configuration = services.GetService<WebSocketCompressionConfiguration>();
97+
configuration.IsCompressionEnabled = false;
98+
};
99+
}
100+
}
101+
102+
public partial class SelfFrameAncestorWebSocketCompressionTests : AllowedWebSocketCompressionTests
103+
{
104+
public SelfFrameAncestorWebSocketCompressionTests(
105+
BrowserFixture browserFixture,
106+
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
107+
ITestOutputHelper output)
108+
: base(browserFixture, serverFixture, output)
109+
{
110+
serverFixture.UpdateHostServices = services =>
111+
{
112+
var configuration = services.GetService<WebSocketCompressionConfiguration>();
113+
configuration.CspPolicy = "'self'";
114+
};
115+
}
116+
}
117+

src/Components/test/testassets/Components.TestServer/BlazorWebServerStartup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public void ConfigureServices(IServiceCollection services)
2020
{
2121
services.AddRazorComponents()
2222
.AddInteractiveServerComponents();
23+
services.AddSingleton<ResourceRequestLog>();
2324

2425
// Since tests run in parallel, we use an ephemeral key provider to avoid filesystem
2526
// contention issues.
@@ -42,7 +43,7 @@ public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env,
4243
{
4344
endpoints.MapRazorComponents<Root>()
4445
.AddInteractiveWebAssemblyRenderMode()
45-
.AddInteractiveServerRenderMode(options => options.EnableWebSocketCompression = true);
46+
.AddInteractiveServerRenderMode();
4647
});
4748
});
4849
}

src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public void ConfigureServices(IServiceCollection services)
3636
services.AddHttpContextAccessor();
3737
services.AddSingleton<AsyncOperationService>();
3838
services.AddCascadingAuthenticationState();
39+
services.AddSingleton<WebSocketCompressionConfiguration>();
3940

4041
var circuitContextAccessor = new TestCircuitContextAccessor();
4142
services.AddSingleton<CircuitHandler>(circuitContextAccessor);
@@ -69,7 +70,13 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
6970
{
7071
endpoints.MapRazorComponents<TRootComponent>()
7172
.AddAdditionalAssemblies(Assembly.Load("Components.WasmMinimal"))
72-
.AddInteractiveServerRenderMode()
73+
.AddInteractiveServerRenderMode(options =>
74+
{
75+
var config = app.ApplicationServices.GetRequiredService<WebSocketCompressionConfiguration>();
76+
options.EnableWebSocketCompression = config.IsCompressionEnabled;
77+
options.ContentSecurityFrameAncestorPolicy = config.CspPolicy;
78+
options.ConnectionOptions = config.ConnectionDispatcherOptions;
79+
})
7380
.AddInteractiveWebAssemblyRenderMode(options => options.PathPrefix = "/WasmMinimal");
7481

7582
NotEnabledStreamingRenderingComponent.MapEndpoints(endpoints);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@page "/iframe"
2+
3+
<iframe src="embedded"></iframe>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@page "/embedded"
2+
@rendermode Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer
3+
4+
<p id="inside-iframe">This is some content embedded inside an iframe</p>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Http.Connections;
5+
6+
namespace TestServer;
7+
8+
public class WebSocketCompressionConfiguration
9+
{
10+
public bool IsCompressionEnabled { get; set; } = true;
11+
12+
public string CspPolicy { get; set; } = "'none'";
13+
14+
public Action<HttpConnectionDispatcherOptions> ConnectionDispatcherOptions { get; set; }
15+
}

0 commit comments

Comments
 (0)