Skip to content

Commit 89a97b7

Browse files
authored
[dotnet] [test] In-process test webserver (#17339)
1 parent 0157a05 commit 89a97b7

28 files changed

Lines changed: 780 additions & 360 deletions

dotnet/Selenium.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
<Project Path="test/remote/Selenium.WebDriver.Remote.Tests.csproj" />
88
<Project Path="test/support/Selenium.WebDriver.Support.Tests.csproj" />
99
<Project Path="test/webdriver/Selenium.WebDriver.Tests.csproj" />
10+
<Project Path="test/testing.webserver/Selenium.Testing.WebServer.csproj" />
1011
</Folder>
1112
</Solution>

dotnet/private/dotnet_nunit_test_suite.bzl

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ _BROWSERS = {
2020
"--params=DriverServiceLocation=$(location @mac_chromedriver//:chromedriver)",
2121
"--params=BrowserLocation=$(location @mac_chrome//:Chrome.app)/Contents/MacOS/Chrome",
2222
],
23-
"@selenium//common:use_local_chromedriver": [],
24-
"//conditions:default": [
25-
"--where=SkipTest==True",
26-
],
23+
"//conditions:default": [],
2724
}),
2825
"data": chrome_data,
2926
"tags": [],
@@ -40,10 +37,7 @@ _BROWSERS = {
4037
"--params=DriverServiceLocation=$(location @mac_edgedriver//:msedgedriver)",
4138
"\"--params=BrowserLocation=$(location @mac_edge//:Edge.app)/Contents/MacOS/Microsoft Edge\"",
4239
],
43-
"@selenium//common:use_local_msedgedriver": [],
44-
"//conditions:default": [
45-
"--where=SkipTest==True",
46-
],
40+
"//conditions:default": [],
4741
}),
4842
"data": edge_data,
4943
"tags": [],
@@ -60,10 +54,7 @@ _BROWSERS = {
6054
"--params=DriverServiceLocation=$(location @mac_geckodriver//:geckodriver)",
6155
"--params=BrowserLocation=$(location @mac_firefox//:Firefox.app)/Contents/MacOS/firefox",
6256
],
63-
"@selenium//common:use_local_geckodriver": [],
64-
"//conditions:default": [
65-
"--where=SkipTest==True",
66-
],
57+
"//conditions:default": [],
6758
}),
6859
"data": firefox_data,
6960
"tags": [],

dotnet/test/remote/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ dotnet_nunit_test_suite(
1010
"//dotnet/test/webdriver:test-data",
1111
],
1212
flaky = True,
13+
project_sdk = "web",
1314
target_frameworks = ["net8.0"],
1415
deps = [
1516
"//dotnet/src/webdriver:webdriver-net8.0",
17+
"//dotnet/test/testing.webserver",
1618
"//dotnet/test/webdriver",
1719
nuget_package("NUnit"),
1820
nuget_package("Runfiles"),

dotnet/test/support/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ dotnet_nunit_test_suite(
1010
data = [
1111
"//dotnet/test/webdriver:test-data",
1212
],
13+
project_sdk = "web",
1314
target_frameworks = ["net8.0"],
1415
deps = [
1516
"//dotnet/src/support",
1617
"//dotnet/src/webdriver:webdriver-net8.0",
18+
"//dotnet/test/testing.webserver",
1719
"//dotnet/test/webdriver",
1820
nuget_package("Microsoft.Bcl.AsyncInterfaces"),
1921
nuget_package("Moq"),

dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<ItemGroup>
1818
<ProjectReference Include="..\..\src\support\Selenium.WebDriver.Support.csproj" />
1919
<ProjectReference Include="..\webdriver\Selenium.WebDriver.Tests.csproj" />
20+
<ProjectReference Include="..\testing.webserver\Selenium.Testing.WebServer.csproj" />
2021
</ItemGroup>
2122

2223
<ItemGroup>
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// <copyright file="AppServer.cs" company="Selenium Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
// </copyright>
19+
20+
using System;
21+
using System.Collections.Concurrent;
22+
using System.IO;
23+
using System.Linq;
24+
using System.Net;
25+
using System.Security.Cryptography;
26+
using System.Security.Cryptography.X509Certificates;
27+
using System.Threading.Tasks;
28+
using Microsoft.AspNetCore.Builder;
29+
using Microsoft.AspNetCore.Hosting;
30+
using Microsoft.AspNetCore.Http;
31+
using Microsoft.AspNetCore.Routing;
32+
using Microsoft.Extensions.DependencyInjection;
33+
using Microsoft.Extensions.FileProviders;
34+
using OpenQA.Selenium.Testing.WebServer.Handlers;
35+
36+
namespace OpenQA.Selenium.Testing.WebServer;
37+
38+
public class AppServer : IAsyncDisposable
39+
{
40+
private WebApplication? _app;
41+
private readonly string _webContentRoot = FindWebContentRoot();
42+
private readonly ConcurrentDictionary<string, string> _pages = new();
43+
44+
public async Task<(string HttpUrl, string HttpsUrl)> StartAsync()
45+
{
46+
var builder = WebApplication.CreateSlimBuilder();
47+
48+
var certificate = GenerateSelfSignedCertificate();
49+
50+
builder.WebHost.ConfigureKestrel(options =>
51+
{
52+
options.Listen(IPAddress.Loopback, 0);
53+
options.Listen(IPAddress.Loopback, 0, listenOptions =>
54+
{
55+
listenOptions.UseHttps(certificate);
56+
});
57+
});
58+
builder.Services.AddDirectoryBrowser();
59+
60+
_app = builder.Build();
61+
62+
MapEndpoints(_app);
63+
MapEndpoints(_app.MapGroup("/common"));
64+
65+
if (Directory.Exists(_webContentRoot))
66+
{
67+
var fileProvider = new PhysicalFileProvider(_webContentRoot);
68+
69+
_app.UseStaticFiles(new StaticFileOptions
70+
{
71+
FileProvider = fileProvider,
72+
ServeUnknownFileTypes = true
73+
});
74+
75+
_app.UseStaticFiles(new StaticFileOptions
76+
{
77+
FileProvider = fileProvider,
78+
RequestPath = "/common",
79+
ServeUnknownFileTypes = true
80+
});
81+
82+
_app.UseDirectoryBrowser(new DirectoryBrowserOptions
83+
{
84+
FileProvider = fileProvider
85+
});
86+
87+
_app.UseDirectoryBrowser(new DirectoryBrowserOptions
88+
{
89+
FileProvider = fileProvider,
90+
RequestPath = "/common"
91+
});
92+
}
93+
94+
await _app.StartAsync();
95+
96+
int httpPort = new Uri(_app.Urls.First(u => u.StartsWith("http://"))).Port;
97+
int httpsPort = new Uri(_app.Urls.First(u => u.StartsWith("https://"))).Port;
98+
99+
return ($"http://localhost:{httpPort}", $"https://localhost:{httpsPort}");
100+
}
101+
102+
public async Task StopAsync()
103+
{
104+
if (_app is not null)
105+
{
106+
await _app.StopAsync();
107+
await _app.DisposeAsync();
108+
_app = null;
109+
}
110+
}
111+
112+
public async ValueTask DisposeAsync()
113+
{
114+
await StopAsync();
115+
}
116+
117+
private void MapEndpoints(IEndpointRouteBuilder endpoints)
118+
{
119+
endpoints.MapGet("/basicAuth", BasicAuthHandler.Handle);
120+
endpoints.MapGet("/echo", (Delegate)EchoHandler.Handle);
121+
endpoints.MapGet("/cookie", CookieHandler.Handle);
122+
endpoints.MapGet("/encoding", EncodingHandler.Handle);
123+
endpoints.MapGet("/sleep", (Delegate)SleepHandler.Handle);
124+
endpoints.MapGet("/redirect", RedirectHandler.Handle);
125+
endpoints.MapGet("/page/{pageNumber}", PageHandler.Handle);
126+
endpoints.MapGet("/utf8/{*path}", (HttpContext context, string path) => Utf8Handler.Handle(context, path, _webContentRoot));
127+
endpoints.MapPost("/createPage", (Delegate)((HttpContext context) => CreatePageHandler.Handle(context, _pages)));
128+
endpoints.MapPost("/upload", (Delegate)UploadHandler.Handle);
129+
130+
endpoints.MapGet("/.well-known/web-identity", (HttpContext context) => FedCmHandler.HandleWebIdentity(context));
131+
endpoints.MapGet("/fedcm/config.json", (HttpContext context) => FedCmHandler.HandleConfig(context));
132+
endpoints.MapPost("/fedcm/id_assertion.json", (HttpContext context) => FedCmHandler.HandleIdAssertion(context));
133+
134+
endpoints.MapGet("/temp/{fileName}", (string fileName) => CreatePageHandler.ServePage(fileName, _pages));
135+
}
136+
137+
private static X509Certificate2 GenerateSelfSignedCertificate()
138+
{
139+
using var ecdsa = ECDsa.Create();
140+
var request = new CertificateRequest("CN=localhost", ecdsa, HashAlgorithmName.SHA256);
141+
return request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
142+
}
143+
144+
private static string FindWebContentRoot()
145+
{
146+
var info = new DirectoryInfo(AppContext.BaseDirectory);
147+
while (info is not null && info != info.Root)
148+
{
149+
string webPath = Path.Combine(info.FullName, "common", "src", "web");
150+
if (Directory.Exists(webPath))
151+
{
152+
return webPath;
153+
}
154+
info = info.Parent;
155+
}
156+
157+
return string.Empty;
158+
}
159+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
load("//dotnet:defs.bzl", "csharp_library")
2+
3+
csharp_library(
4+
name = "testing.webserver",
5+
testonly = True,
6+
srcs = glob(["**/*.cs"]),
7+
out = "Testing.WebServer",
8+
data = [
9+
"//common/src/web",
10+
],
11+
nullable = "enable",
12+
project_sdk = "web",
13+
run_analyzers = False,
14+
target_frameworks = ["net8.0"],
15+
visibility = [
16+
"//dotnet/test:__subpackages__",
17+
],
18+
)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// <copyright file="BasicAuthHandler.cs" company="Selenium Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
// </copyright>
19+
20+
using System;
21+
using System.Net;
22+
using System.Text;
23+
using Microsoft.AspNetCore.Http;
24+
25+
namespace OpenQA.Selenium.Testing.WebServer.Handlers;
26+
27+
public static class BasicAuthHandler
28+
{
29+
private const string ExpectedUser = "test";
30+
private const string ExpectedPassword = "test";
31+
32+
public static IResult Handle(HttpContext context)
33+
{
34+
string? authorization = context.Request.Headers.Authorization;
35+
36+
if (authorization is not null && authorization.StartsWith("Basic "))
37+
{
38+
string encoded = authorization["Basic ".Length..];
39+
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(encoded));
40+
string[] parts = decoded.Split(':', 2);
41+
42+
if (parts.Length == 2 && parts[0] == ExpectedUser && parts[1] == ExpectedPassword)
43+
{
44+
return Results.Content("<h1>authorized</h1>", "text/html; charset=utf-8");
45+
}
46+
}
47+
48+
context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"selenium-server\"";
49+
return Results.Text(string.Empty, statusCode: (int)HttpStatusCode.Unauthorized,
50+
contentType: "text/html; charset=utf-8");
51+
}
52+
}

0 commit comments

Comments
 (0)