From c9eee74192d6cc67ce4a46022cf242bf707b7bac Mon Sep 17 00:00:00 2001 From: ladeak Date: Thu, 16 Jul 2026 22:23:56 +0200 Subject: [PATCH 01/12] First browser test --- .github/workflows/CI.yml | 7 +++- CHttpTools.slnx | 3 +- tests/CHttp.Tests/testCert.pfx | Bin 2660 -> 2693 bytes .../CHttpServer.BrowserTests.csproj | 33 ++++++++++++++++ .../Http3BrowserTests.cs | 19 +++++++++ tests/CHttpServer.BrowserTests/TestFixture.cs | 37 ++++++++++++++++++ .../Http3/Http3IntegrationTests.cs | 1 - tests/CHttpServer.Tests/TestServer.cs | 6 ++- tests/dev_cert.ps1 | 28 +++++++++++++ 9 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 tests/CHttpServer.BrowserTests/CHttpServer.BrowserTests.csproj create mode 100644 tests/CHttpServer.BrowserTests/Http3BrowserTests.cs create mode 100644 tests/CHttpServer.BrowserTests/TestFixture.cs create mode 100644 tests/dev_cert.ps1 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 84fe978..66cc5b2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -44,4 +44,9 @@ jobs: npm install npm run vsce-deploy-win-x64 npm run vsce-deploy-linux-x64 - popd \ No newline at end of file + popd + - name: Invoke Playwright installation + run: ./tests/IntegrationTests/bin/${{ env.CONFIGURATION }}/${{ env.DOTNET_VERSION }}/playwright.ps1 install --with-deps + shell: pwsh + - name: Broswer Tests + run: dotnet test ./tests/CHttpServer.BrowserTests --no-build -c ${{ env.CONFIGURATION }} \ No newline at end of file diff --git a/CHttpTools.slnx b/CHttpTools.slnx index eb2d6f5..2306fd8 100644 --- a/CHttpTools.slnx +++ b/CHttpTools.slnx @@ -1,8 +1,9 @@ - + + diff --git a/tests/CHttp.Tests/testCert.pfx b/tests/CHttp.Tests/testCert.pfx index 5cf1513e1d1997bbcdcca36c8d939c5ba714ab1d..8914c5e20afcef4b0cfdc8ccc7d9e5a0e70e9cb8 100644 GIT binary patch delta 2624 zcmZXUc{J2tAIE2y84R+^7P1wRu}g?-MfN2`hHP0A5%K#n*|P8Zh(XzZcBbsIG$gX` z$~M`{I?cp0J?B}@bDne0=X^f*{eItj|GKYL)(AELfr5uofGNpM1x6zlc_;FO9!v!; zK*6s=P;fD_EP_D6sQ$--oWA)Qww^kdQaVg#2GC7F6{O1=KPtQ&iq5d#U35|REZ&545N{M)Q9=2=^D0OU*TIt%Wk;VR8JU(5x1KQJ&Oy+9f|oza2$im`=LOV6PTJ9J-cdb5C#}@x9N8(f4Lo>gYBKi zWjoh9i0bm4{8&Bzvq>P<;`Er;X+1!-y1EuWAJsKS=&-GHP8!#Wj#P%VcLZFE3yBo3 zke*IkVSVQ}PH5V!EyCk6a-SWC6g?~!?ptWAq5TrfI+F4bz{-AEwrA$KGBUIg&X$SM z<7cDuo!I^K($1{db%hhofbHvDuHi)>=3rH3J!cz|_+Co-)^ED1!jRj35{ts_}Q2+IyrL?C3LxKrC;P%>(IV z-Su@gTDqhVfbYp)jV$Y_Ye>Gq&RVTa{Hi`pky%CDfFsQd8y{riUf|DeLJs1q+EPfv zJLXROL_f9D7Zvga8G`}SG5~q9b6p1a z>P}AtK5NB5I;cmjeeo0uZ`d-&iL`95YuHS+#6tGp%(dfsS-WXA@SGJ=<=TSTz922i zNQ<_ndGrKs^+oF^QQdX&?=F*TmQkYg-qxr7bl(}fn8)p*>=7cRgAOdMyFCro>Be}~ zd({B&H_5F`dlA3yi!Bm4>}DYj=A~bGrrfLQn1^xU^lCPeRqhmEjc(g=oo!9ZmtgS` zuQ`+P#W~{_vx54`^T)iVXC8KpD5a;p`q1-Lq^`ph%ztOzerC;^==Dh9vnyeZupenU z;HCx}S5JhbSy_L{!i~MAmA=E1-+FDrcN@5D#WOKZ%@9yzF>HCyC`}4k4Us#9oh?WM z_hzhhQdyqimrgG~$Zb+Jd5(+`9*^f;+NIOmb2A^Lcb^NHRev|z??B)i{S&w}tWrk21|UC> z6G#H&0CFZT2apU%3Zy{p5+G@iJ=t+2PqJi3fm{+GMY8`VBSB8dll4F8KYJ(gi~v0X z$)q+y9e|7fd6)`DW-~RJ%@7JO2!W#f{a*+C|FmW!EoCa->NfpD>rfQs2t`4o>VkyV zK*c+;&>t>A7p(M6#zo$7=0!JbvAa#bF&a3xtK=-;a4s9}5{K5l_D1K2BxgG-13T!Z zIM#iew&#Y%LA<;(CuUB-lAp(3rUkcWmCrMgh12tHVfW+~3p422N7Zxwvhnr!3K6l# zq*eany$YG*<1mHmMr~K?q!%kn$JivEL?|?`tB~vz${AC6K5O7^qxj6qf942R&TtV= zLWhh6{P%qw7(UF3y15`5NHSk*0DFknb8GPlYeX?|rr!QdQTQM$YfR)C(c9B1S8s}I zj5sKFb9FR8YUAWyJ_{rIC6A2vr@0I1wgY=~``-&PO&Ux`17I5EAU7Gh zofRG7C%m!G-rFYchyICt=Hn88ISSR+ocC1co+c4CEYg&(U9S{85sM2RehzfNCUhjZ z@!?k&ly5$n5o@_G*Sbo#x0_OHxMm>utv2Ws1u%^%%hyzz8f4fkHaXRa8Zs6NT?*3L zO9FpzDGL)8sDyLi7;+yg0sK`B^*(=fyyoT>5>4t1SSv727zXb*uA5pVwRL;sH_COJQ#Jp_?%KWuc+D z(NH?kl6}YGnKww4QH%ZGEnvcpA^f&nb6#@8XZg9CBPSLENfKvp=76sk?Mb9YWC5LX z6=w6qQgI4Iu$=`lwgILHNkNw-sXtwjvN-k?yA)5D^d5dPbC?^^JSa9lxMp4PSr1vhX zD`R>K3%t8R6?(rctM-$=*B4?(m)tm<^h-@|Yu;v-%EGrWw9H!_4+R^Ve}=`~y_o37 z=w^-+_;~4o{+#Vdc`l3Vte5*k5lNplSG0hC!q$T|UDC{INAH2}-ew=-CV~$^1%rxT zprqh}u*#W7=&r36G6`I~_-ZxHJ<>X(U=_kD9d$R;6*Gg$zp!M7jblY^zM2*P1BQP9 DRsqhz delta 2567 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 + + + net11.0 + enable + enable + false + true + true + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs new file mode 100644 index 0000000..b64432a --- /dev/null +++ b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs @@ -0,0 +1,19 @@ +namespace TestCHttpServerApplication.Tests; + +public class Http3BrowserTests : IClassFixture +{ + private readonly TestFixture _fixture; + + public Http3BrowserTests(TestFixture testFixture) + { + _fixture = testFixture; + } + + [Fact] + public async Task Test1() + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync("https://127.0.0.1:7297/protocol"); + Assert.Contains("HTTP/3", (await page.ContentAsync())); + } +} diff --git a/tests/CHttpServer.BrowserTests/TestFixture.cs b/tests/CHttpServer.BrowserTests/TestFixture.cs new file mode 100644 index 0000000..f0f6f10 --- /dev/null +++ b/tests/CHttpServer.BrowserTests/TestFixture.cs @@ -0,0 +1,37 @@ +using System.Runtime.Versioning; +using CHttpServer.Tests; +using Microsoft.Playwright; + +namespace TestCHttpServerApplication.Tests; + +public class TestFixture : IAsyncDisposable +{ + public TestServer? Server { get; private set; } + + public IPlaywright PlaywrightHost { get; private set; } + + public IBrowserContext Browser { get; private set; } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("windows")] + public TestFixture() + { + Server = new TestServer(); + Server.RunAsync(7297, false, useHttp3: true).GetAwaiter().GetResult(); + PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult(); + Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new() + { + Headless = false, + Args = ["--origin-to-force-quic-on=127.0.0.1:7297", "--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="] + }).GetAwaiter().GetResult(); + } + + public async ValueTask DisposeAsync() + { + if (Server == null) + return; + await Server.DisposeAsync(); + await Browser.DisposeAsync(); + PlaywrightHost.Dispose(); + } +} diff --git a/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs b/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs index e0e15c4..3edf5f6 100644 --- a/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs +++ b/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs @@ -1,5 +1,4 @@ using System.Net; -using System.Net.Http.Json; using System.Text; namespace CHttpServer.Tests.Http3; diff --git a/tests/CHttpServer.Tests/TestServer.cs b/tests/CHttpServer.Tests/TestServer.cs index 1f9442f..e81c722 100644 --- a/tests/CHttpServer.Tests/TestServer.cs +++ b/tests/CHttpServer.Tests/TestServer.cs @@ -6,7 +6,7 @@ namespace CHttpServer.Tests; -public class TestServer : IAsyncDisposable, IDisposable +public sealed class TestServer : IAsyncDisposable, IDisposable { private WebApplication? _app; private bool _isDisposed; @@ -39,6 +39,10 @@ public Task RunAsync(int port = 7222, bool usePriority = false, bool useHttp3 = { return "TypedResults.NoContent()"; }); + _app.MapGet("/protocol", (HttpContext ctx) => + { + return ctx.Request.Protocol; + }); _app.MapGet("/nocontent", () => { return TypedResults.NoContent(); diff --git a/tests/dev_cert.ps1 b/tests/dev_cert.ps1 new file mode 100644 index 0000000..e24b931 --- /dev/null +++ b/tests/dev_cert.ps1 @@ -0,0 +1,28 @@ +# Define the certificate parameters +$certParams = @{ + Subject = "CN=localhost" + Type = "SSLServerAuthentication" # Adds Server EKU (1.3.6.1.5.5.7.3.1) + CertStoreLocation = "Cert:\LocalMachine\My" # Installs to the machine's Personal store + + # Manually define SANs via OID 2.5.29.17 to ensure IP addresses are formatted correctly + TextExtension = @( + "2.5.29.17={text}DNS=localhost&DNS=*.dev.localhost&DNS=*.dev.internal&DNS=host.docker.internal&DNS=host.containers.internal&IPAddress=127.0.0.1&IPAddress=0000:0000:0000:0000:0000:0000:0000:0001" + ) + + # Key & Security Properties + KeyAlgorithm = "RSA" + KeyLength = 2048 + HashAlgorithm = "SHA256" + KeyExportPolicy = "Exportable" # Set to exportable so you can use it inside Docker/containers + + # Modern browsers (Chrome, Safari, Edge) reject certificates valid for more than 398 days. + # We set this to 397 days to maintain maximum compatibility. + NotAfter = (Get-Date).AddDays(397) +} + +# Generate the certificate +$cert = New-SelfSignedCertificate @certParams + +# Output details to console +Write-Host "Certificate generated successfully!" -ForegroundColor Green +$cert | Format-List Subject, Thumbprint, NotAfter \ No newline at end of file From 944d10bb18156b16a8f846882932173f27f44574 Mon Sep 17 00:00:00 2001 From: ladeak Date: Thu, 16 Jul 2026 22:38:16 +0200 Subject: [PATCH 02/12] Update path --- .github/workflows/CI.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 66cc5b2..e57280b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,6 +11,7 @@ on: env: CONFIGURATION: Release DOTNET_VERSION: 11.0.x + OUTPUT_FOLDER: net11.0 jobs: build-and-deploy: runs-on: ubuntu-latest @@ -45,8 +46,8 @@ jobs: npm run vsce-deploy-win-x64 npm run vsce-deploy-linux-x64 popd - - name: Invoke Playwright installation - run: ./tests/IntegrationTests/bin/${{ env.CONFIGURATION }}/${{ env.DOTNET_VERSION }}/playwright.ps1 install --with-deps + - name: Playwright installation + run: ./tests/CHttpServer.BrowserTests/bin/${{ env.CONFIGURATION }}/${{ env.OUTPUT_FOLDER }}/playwright.ps1 install --with-deps shell: pwsh - name: Broswer Tests run: dotnet test ./tests/CHttpServer.BrowserTests --no-build -c ${{ env.CONFIGURATION }} \ No newline at end of file From 89cb1baa9e3d82c63f35cc5e305cdeb688c67d20 Mon Sep 17 00:00:00 2001 From: ladeak Date: Thu, 16 Jul 2026 23:05:59 +0200 Subject: [PATCH 03/12] Headless browser --- .github/workflows/CI.yml | 2 +- tests/CHttpServer.BrowserTests/TestFixture.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e57280b..27fbedb 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -47,7 +47,7 @@ jobs: npm run vsce-deploy-linux-x64 popd - name: Playwright installation - run: ./tests/CHttpServer.BrowserTests/bin/${{ env.CONFIGURATION }}/${{ env.OUTPUT_FOLDER }}/playwright.ps1 install --with-deps + run: ./tests/CHttpServer.BrowserTests/bin/${{ env.CONFIGURATION }}/${{ env.OUTPUT_FOLDER }}/playwright.ps1 install --with-deps chromium shell: pwsh - name: Broswer Tests run: dotnet test ./tests/CHttpServer.BrowserTests --no-build -c ${{ env.CONFIGURATION }} \ No newline at end of file diff --git a/tests/CHttpServer.BrowserTests/TestFixture.cs b/tests/CHttpServer.BrowserTests/TestFixture.cs index f0f6f10..913ff07 100644 --- a/tests/CHttpServer.BrowserTests/TestFixture.cs +++ b/tests/CHttpServer.BrowserTests/TestFixture.cs @@ -21,7 +21,7 @@ public TestFixture() PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult(); Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new() { - Headless = false, + Headless = true, Args = ["--origin-to-force-quic-on=127.0.0.1:7297", "--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="] }).GetAwaiter().GetResult(); } From c8f44daf8933cf5cddf2463a950b49c697b92179 Mon Sep 17 00:00:00 2001 From: ladeak Date: Thu, 16 Jul 2026 23:14:25 +0200 Subject: [PATCH 04/12] CI parallel --- .github/workflows/CI.yml | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 27fbedb..c018c0c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,7 +13,7 @@ env: DOTNET_VERSION: 11.0.x OUTPUT_FOLDER: net11.0 jobs: - build-and-deploy: + build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -21,10 +21,6 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Install Node.js - uses: actions/setup-node@v6 - with: - node-version: 26.x - name: Install Package libmsquic run: | sudo apt update @@ -39,6 +35,24 @@ jobs: dotnet test ./tests/TestWebApplication.Tests --no-build -c ${{ env.CONFIGURATION }} dotnet test ./tests/CHttp.Api.Tests --no-build -c ${{ env.CONFIGURATION }} dotnet test ./tests/CHttpServer.Tests --no-build -c ${{ env.CONFIGURATION }} + + build-and-test-node: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 26.x + - name: Install Package libmsquic + run: | + sudo apt update + sudo apt install -y libmsquic + shell: sh - name: Publish VSCE run: | pushd ./src/VSCodeExt/ @@ -46,8 +60,9 @@ jobs: npm run vsce-deploy-win-x64 npm run vsce-deploy-linux-x64 popd - - name: Playwright installation - run: ./tests/CHttpServer.BrowserTests/bin/${{ env.CONFIGURATION }}/${{ env.OUTPUT_FOLDER }}/playwright.ps1 install --with-deps chromium - shell: pwsh - - name: Broswer Tests - run: dotnet test ./tests/CHttpServer.BrowserTests --no-build -c ${{ env.CONFIGURATION }} \ No newline at end of file + - name: Browser CHttp Server Tests + run: | + dotnet build ./tests/CHttpServer.BrowserTests -c ${{ env.CONFIGURATION }} + ./tests/CHttpServer.BrowserTests/bin/${{ env.CONFIGURATION }}/${{ env.OUTPUT_FOLDER }}/playwright.ps1 install --with-deps chromium + dotnet test ./tests/CHttpServer.BrowserTests -c ${{ env.CONFIGURATION }} + shell: pwsh \ No newline at end of file From 172c946e473772b887fdc8a5eb665500d952ce0d Mon Sep 17 00:00:00 2001 From: ladeak Date: Sun, 19 Jul 2026 00:05:18 +0200 Subject: [PATCH 05/12] Some HTTP/2 and HTTP/3 browser tests - Fix related to Host header caching in HTTP/2 --- .../CHttpServer/RequestHeaderCollection.cs | 20 ++----- .../Http2BrowserTests.cs | 52 +++++++++++++++++ .../{TestFixture.cs => Http2TestFixture.cs} | 12 ++-- .../Http3BrowserTests.cs | 50 +++++++++++++--- .../Http3TestFixture.cs | 39 +++++++++++++ .../Http3/Http3IntegrationTests.cs | 2 +- tests/CHttpServer.Tests/TestServer.cs | 57 +++++++++++++++++-- 7 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 tests/CHttpServer.BrowserTests/Http2BrowserTests.cs rename tests/CHttpServer.BrowserTests/{TestFixture.cs => Http2TestFixture.cs} (71%) create mode 100644 tests/CHttpServer.BrowserTests/Http3TestFixture.cs diff --git a/src/CHttpServer/CHttpServer/RequestHeaderCollection.cs b/src/CHttpServer/CHttpServer/RequestHeaderCollection.cs index cdb566a..cbe20a6 100644 --- a/src/CHttpServer/CHttpServer/RequestHeaderCollection.cs +++ b/src/CHttpServer/CHttpServer/RequestHeaderCollection.cs @@ -139,17 +139,12 @@ private bool TrySetKnownHeader(ReadOnlySpan rawKey, ReadOnlySpan raw { Span utf16Value = stackalloc char[rawValue.Length]; Encoding.Latin1.GetChars(rawValue, utf16Value); - if (_hostValue.Count > 0 && utf16Value.SequenceEqual(_hostValue[0].AsSpan())) - { - key = "Host"; - value = _hostValue; - return true; - } + if (_hostValue.Count == 0 || !utf16Value.SequenceEqual(_hostValue[0].AsSpan())) + _hostValue = new StringValues(utf16Value.ToString()); - _hostValue = new StringValues(utf16Value.ToString()); - _isHostValueSet = true; key = "Host"; value = _hostValue; + _isHostValueSet = true; return true; } @@ -164,12 +159,9 @@ private bool TrySetKnownHeader(string key, ReadOnlySpan rawValue) { Span utf16Value = stackalloc char[rawValue.Length]; Encoding.Latin1.GetChars(rawValue, utf16Value); - if (_hostValue.Count > 0 && utf16Value.SequenceEqual(_hostValue[0].AsSpan())) - { - key = "Host"; - return true; - } - _hostValue = new StringValues(utf16Value.ToString()); + if (_hostValue.Count == 0 || !utf16Value.SequenceEqual(_hostValue[0].AsSpan())) + _hostValue = new StringValues(utf16Value.ToString()); + _isHostValueSet = true; return true; } diff --git a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs new file mode 100644 index 0000000..eaac8f2 --- /dev/null +++ b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs @@ -0,0 +1,52 @@ +using Microsoft.Playwright; + +namespace CHttpServer.BrowserTests; + +public class Http2BrowserTests(Http2TestFixture testFixture) : IClassFixture +{ + private readonly Http2TestFixture _fixture = testFixture; + private readonly string _url = $"https://127.0.0.1:{testFixture.Port}"; + + [Fact] + public async Task Http2Protocol() + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/protocol"); + Assert.Contains("HTTP/2", await page.ContentAsync()); + } + + [Fact] + public async Task Sse() + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > button:nth-child(3)"); + var sse = await page.WaitForSelectorAsync("#sse", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 }); + Assert.NotNull(sse); + var content = await sse.InnerHTMLAsync(); + Assert.Contains("sse 0", content); + Assert.Contains("sse 1", content); + } + + [Fact] + public async Task FormPost() + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > form > button"); + await page.WaitForURLAsync($"{_url}/protocol"); + } + + [Fact] + public async Task GetFetchJs() + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > button:nth-child(2)"); + var output = await page.WaitForSelectorAsync("#output", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 }); + Assert.NotNull(output); + var content = await output.InnerHTMLAsync(); + Assert.Contains("\"some content\"", content); + } +} + diff --git a/tests/CHttpServer.BrowserTests/TestFixture.cs b/tests/CHttpServer.BrowserTests/Http2TestFixture.cs similarity index 71% rename from tests/CHttpServer.BrowserTests/TestFixture.cs rename to tests/CHttpServer.BrowserTests/Http2TestFixture.cs index 913ff07..73dcfae 100644 --- a/tests/CHttpServer.BrowserTests/TestFixture.cs +++ b/tests/CHttpServer.BrowserTests/Http2TestFixture.cs @@ -2,10 +2,12 @@ using CHttpServer.Tests; using Microsoft.Playwright; -namespace TestCHttpServerApplication.Tests; +namespace CHttpServer.BrowserTests; -public class TestFixture : IAsyncDisposable +public sealed class Http2TestFixture : IAsyncDisposable { + internal int Port => 7294; + public TestServer? Server { get; private set; } public IPlaywright PlaywrightHost { get; private set; } @@ -14,15 +16,15 @@ public class TestFixture : IAsyncDisposable [SupportedOSPlatform("linux")] [SupportedOSPlatform("windows")] - public TestFixture() + public Http2TestFixture() { Server = new TestServer(); - Server.RunAsync(7297, false, useHttp3: true).GetAwaiter().GetResult(); + Server.RunAsync(Port, false, useHttp3: false).GetAwaiter().GetResult(); PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult(); Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new() { Headless = true, - Args = ["--origin-to-force-quic-on=127.0.0.1:7297", "--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="] + Args = ["--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="] }).GetAwaiter().GetResult(); } diff --git a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs index b64432a..962a29d 100644 --- a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs @@ -1,19 +1,53 @@ -namespace TestCHttpServerApplication.Tests; +using System.Runtime.InteropServices; +using Microsoft.Playwright; -public class Http3BrowserTests : IClassFixture +namespace CHttpServer.BrowserTests; + +public class Http3BrowserTests(Http3TestFixture testFixture) : IClassFixture { - private readonly TestFixture _fixture; + private readonly Http3TestFixture _fixture = testFixture; + private readonly string _url = $"https://127.0.0.1:{testFixture.Port}"; - public Http3BrowserTests(TestFixture testFixture) + [Fact] + public async Task Http3Protocol() { - _fixture = testFixture; + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/protocol"); + Assert.Contains("HTTP/3", await page.ContentAsync()); } [Fact] - public async Task Test1() + public async Task Sse() + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > button:nth-child(3)"); + var sse = await page.WaitForSelectorAsync("#sse", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 }); + Assert.NotNull(sse); + var content = await sse.InnerHTMLAsync(); + Assert.Contains("sse 0", content); + Assert.Contains("sse 1", content); + } + + [Fact(Skip = "WIP")] + public async Task FormPost() { var page = await _fixture.Browser.NewPageAsync(); - await page.GotoAsync("https://127.0.0.1:7297/protocol"); - Assert.Contains("HTTP/3", (await page.ContentAsync())); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > form > button"); + await page.WaitForURLAsync($"{_url}/protocol"); + } + + [Fact] + public async Task GetFetchJs() + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > button:nth-child(2)"); + var output = await page.WaitForSelectorAsync("#output", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 }); + Assert.NotNull(output); + var content = await output.InnerHTMLAsync(); + Assert.Contains("\"some content\"", content); } } + diff --git a/tests/CHttpServer.BrowserTests/Http3TestFixture.cs b/tests/CHttpServer.BrowserTests/Http3TestFixture.cs new file mode 100644 index 0000000..b08fd8c --- /dev/null +++ b/tests/CHttpServer.BrowserTests/Http3TestFixture.cs @@ -0,0 +1,39 @@ +using System.Runtime.Versioning; +using CHttpServer.Tests; +using Microsoft.Playwright; + +namespace CHttpServer.BrowserTests; + +public sealed class Http3TestFixture : IAsyncDisposable +{ + internal int Port => 7295; + + public TestServer? Server { get; private set; } + + public IPlaywright PlaywrightHost { get; private set; } + + public IBrowserContext Browser { get; private set; } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("windows")] + public Http3TestFixture() + { + Server = new TestServer(); + Server.RunAsync(Port, false, useHttp3: true).GetAwaiter().GetResult(); + PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult(); + Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new() + { + Headless = true, + Args = [$"--origin-to-force-quic-on=127.0.0.1:{Port}", "--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="] + }).GetAwaiter().GetResult(); + } + + public async ValueTask DisposeAsync() + { + if (Server == null) + return; + await Server.DisposeAsync(); + await Browser.DisposeAsync(); + PlaywrightHost.Dispose(); + } +} diff --git a/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs b/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs index 3edf5f6..699aaa8 100644 --- a/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs +++ b/tests/CHttpServer.Tests/Http3/Http3IntegrationTests.cs @@ -346,7 +346,7 @@ public async Task TestServerSideEvents() var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, TestContext.Current.CancellationToken); Assert.True(response.IsSuccessStatusCode); var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.Equal("data: some content\n\ndata: some content\n\n", content); + Assert.Equal("data: sse 0\n\ndata: sse 1\n\n", content); } } diff --git a/tests/CHttpServer.Tests/TestServer.cs b/tests/CHttpServer.Tests/TestServer.cs index e81c722..ee3ce75 100644 --- a/tests/CHttpServer.Tests/TestServer.cs +++ b/tests/CHttpServer.Tests/TestServer.cs @@ -1,5 +1,7 @@ -using System.Security.Cryptography.X509Certificates; +using System.Net; +using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -30,7 +32,7 @@ public Task RunAsync(int port = 7222, bool usePriority = false, bool useHttp3 = //{ // o.Listen(IPAddress.Loopback, port, lo => // { - // lo.UseHttps(); + // lo.UseHttps(X509CertificateLoader.LoadPkcs12FromFile("testCert.pfx", "testPassword")); // lo.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http3; // }); //}); @@ -94,7 +96,7 @@ async IAsyncEnumerable GetStream() foreach (var i in Enumerable.Range(0, 2)) { await Task.Delay(1000); - yield return "some content"; + yield return $"sse {i}"; } } return Results.ServerSentEvents(GetStream()); @@ -178,7 +180,54 @@ async IAsyncEnumerable GetStream() ctx.Response.StatusCode = 204; } }); - return _app.StartAsync(); //.RunAsync(); + _app.MapPost("/posthiddenfield", ([FromForm(Name = "field")] string field) => + { + return TypedResults.LocalRedirect("/protocol"); + }); + _app.MapGet("/html", async (HttpContext ctx) => + { + var baseUrl = ctx.Request.Host; + await ctx.Response.WriteAsync($$""" + + + +
+ + +
+ + + + + + + + + """); + }); + return _app.StartAsync(); } public async ValueTask DisposeAsync() From ff4f61a10f70c987cec740123a9c6d0967e7cfb2 Mon Sep 17 00:00:00 2001 From: ladeak Date: Sun, 19 Jul 2026 11:21:26 +0200 Subject: [PATCH 06/12] Repeating tests --- .../CHttpServer/Http3/Http3Connection.cs | 1 + .../Http2BrowserTests.cs | 3 ++ .../Http2TestFixture.cs | 6 +-- .../Http3BrowserTests.cs | 46 ++++++++++++------- .../Http3TestFixture.cs | 8 ++-- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs b/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs index 3b8c935..dcd2c7b 100644 --- a/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs +++ b/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs @@ -170,6 +170,7 @@ private async Task TryWriteGoAwayAsync() catch (QuicException ex) when ( ex.QuicError == QuicError.ConnectionAborted || ex.QuicError == QuicError.StreamAborted + || ex.QuicError == QuicError.InternalError || ex.QuicError == QuicError.ConnectionTimeout) { // Connection already aborted by the client. diff --git a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs index eaac8f2..81cb7c8 100644 --- a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs @@ -13,6 +13,7 @@ public async Task Http2Protocol() var page = await _fixture.Browser.NewPageAsync(); await page.GotoAsync($"{_url}/protocol"); Assert.Contains("HTTP/2", await page.ContentAsync()); + await page.CloseAsync(); } [Fact] @@ -26,6 +27,7 @@ public async Task Sse() var content = await sse.InnerHTMLAsync(); Assert.Contains("sse 0", content); Assert.Contains("sse 1", content); + await page.CloseAsync(); } [Fact] @@ -35,6 +37,7 @@ public async Task FormPost() await page.GotoAsync($"{_url}/html"); await page.ClickAsync("body > form > button"); await page.WaitForURLAsync($"{_url}/protocol"); + await page.CloseAsync(); } [Fact] diff --git a/tests/CHttpServer.BrowserTests/Http2TestFixture.cs b/tests/CHttpServer.BrowserTests/Http2TestFixture.cs index 73dcfae..ae23539 100644 --- a/tests/CHttpServer.BrowserTests/Http2TestFixture.cs +++ b/tests/CHttpServer.BrowserTests/Http2TestFixture.cs @@ -8,7 +8,7 @@ public sealed class Http2TestFixture : IAsyncDisposable { internal int Port => 7294; - public TestServer? Server { get; private set; } + public TestServer Server { get; private set; } public IPlaywright PlaywrightHost { get; private set; } @@ -30,10 +30,8 @@ public Http2TestFixture() public async ValueTask DisposeAsync() { - if (Server == null) - return; - await Server.DisposeAsync(); await Browser.DisposeAsync(); PlaywrightHost.Dispose(); + await Server.DisposeAsync(); } } diff --git a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs index 962a29d..c808be3 100644 --- a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs @@ -1,5 +1,4 @@ -using System.Runtime.InteropServices; -using Microsoft.Playwright; +using Microsoft.Playwright; namespace CHttpServer.BrowserTests; @@ -11,9 +10,13 @@ public class Http3BrowserTests(Http3TestFixture testFixture) : IClassFixture form > button"); - await page.WaitForURLAsync($"{_url}/protocol"); + for (int i = 0; i < 10; i++) + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > form > button"); + await page.WaitForURLAsync($"{_url}/protocol"); + await page.CloseAsync(); + } } [Fact] public async Task GetFetchJs() { - var page = await _fixture.Browser.NewPageAsync(); - await page.GotoAsync($"{_url}/html"); - await page.ClickAsync("body > button:nth-child(2)"); - var output = await page.WaitForSelectorAsync("#output", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 }); - Assert.NotNull(output); - var content = await output.InnerHTMLAsync(); - Assert.Contains("\"some content\"", content); + for (int i = 0; i < 100; i++) + { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync($"{_url}/html"); + await page.ClickAsync("body > button:nth-child(2)"); + var output = await page.WaitForSelectorAsync("#output", new PageWaitForSelectorOptions() { State = WaitForSelectorState.Visible, Timeout = 10_000 }); + Assert.NotNull(output); + var content = await output.InnerHTMLAsync(); + Assert.Contains("\"some content\"", content); + await page.CloseAsync(); + } } } diff --git a/tests/CHttpServer.BrowserTests/Http3TestFixture.cs b/tests/CHttpServer.BrowserTests/Http3TestFixture.cs index b08fd8c..d360b5c 100644 --- a/tests/CHttpServer.BrowserTests/Http3TestFixture.cs +++ b/tests/CHttpServer.BrowserTests/Http3TestFixture.cs @@ -8,7 +8,7 @@ public sealed class Http3TestFixture : IAsyncDisposable { internal int Port => 7295; - public TestServer? Server { get; private set; } + public TestServer Server { get; private set; } public IPlaywright PlaywrightHost { get; private set; } @@ -23,17 +23,15 @@ public Http3TestFixture() PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult(); Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new() { - Headless = true, + Headless = false, Args = [$"--origin-to-force-quic-on=127.0.0.1:{Port}", "--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="] }).GetAwaiter().GetResult(); } public async ValueTask DisposeAsync() { - if (Server == null) - return; - await Server.DisposeAsync(); await Browser.DisposeAsync(); PlaywrightHost.Dispose(); + await Server.DisposeAsync(); } } From 218833f21e8368a76e80ede27fc332d6e3e4aa6b Mon Sep 17 00:00:00 2001 From: ladeak Date: Sun, 19 Jul 2026 11:24:26 +0200 Subject: [PATCH 07/12] H3 headless --- tests/CHttpServer.BrowserTests/Http3TestFixture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CHttpServer.BrowserTests/Http3TestFixture.cs b/tests/CHttpServer.BrowserTests/Http3TestFixture.cs index d360b5c..1c6e8c9 100644 --- a/tests/CHttpServer.BrowserTests/Http3TestFixture.cs +++ b/tests/CHttpServer.BrowserTests/Http3TestFixture.cs @@ -23,7 +23,7 @@ public Http3TestFixture() PlaywrightHost = Playwright.CreateAsync().GetAwaiter().GetResult(); Browser = PlaywrightHost.Chromium.LaunchPersistentContextAsync("/tmp/chrome-profile-integrationtest", new() { - Headless = false, + Headless = true, Args = [$"--origin-to-force-quic-on=127.0.0.1:{Port}", "--ignore-certificate-errors-spki-list=5QveYGg8xaCnnZWvkC9Y6v9lQVmF2BCozvds6Cn6F6k="] }).GetAwaiter().GetResult(); } From bd838a4657ced10f41732af0dbcb0b11f34a5c61 Mon Sep 17 00:00:00 2001 From: ladeak Date: Sun, 19 Jul 2026 11:39:29 +0200 Subject: [PATCH 08/12] Close page and more elaborate closing --- src/CHttpServer/CHttpServer/Http3/Http3Connection.cs | 5 ++++- tests/CHttpServer.BrowserTests/Http2BrowserTests.cs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs b/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs index dcd2c7b..de6f605 100644 --- a/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs +++ b/src/CHttpServer/CHttpServer/Http3/Http3Connection.cs @@ -170,11 +170,14 @@ private async Task TryWriteGoAwayAsync() catch (QuicException ex) when ( ex.QuicError == QuicError.ConnectionAborted || ex.QuicError == QuicError.StreamAborted - || ex.QuicError == QuicError.InternalError || ex.QuicError == QuicError.ConnectionTimeout) { // Connection already aborted by the client. } + catch (QuicException ex) + { + Debug.WriteLine(ex); + } } private async Task HandleStreamAsync( diff --git a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs index 81cb7c8..a8da884 100644 --- a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs @@ -50,6 +50,7 @@ public async Task GetFetchJs() Assert.NotNull(output); var content = await output.InnerHTMLAsync(); Assert.Contains("\"some content\"", content); + await page.CloseAsync(); } } From ec8b14c8cc1978afb4b641df8c3ee6905f4694ab Mon Sep 17 00:00:00 2001 From: ladeak Date: Sun, 19 Jul 2026 12:20:08 +0200 Subject: [PATCH 09/12] Wait for content as opposed to URL --- tests/CHttpServer.BrowserTests/Http2BrowserTests.cs | 2 +- tests/CHttpServer.BrowserTests/Http3BrowserTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs index a8da884..2b072c8 100644 --- a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs @@ -36,7 +36,7 @@ public async Task FormPost() var page = await _fixture.Browser.NewPageAsync(); await page.GotoAsync($"{_url}/html"); await page.ClickAsync("body > form > button"); - await page.WaitForURLAsync($"{_url}/protocol"); + Assert.Contains("HTTP/2", await page.ContentAsync()); await page.CloseAsync(); } diff --git a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs index c808be3..5524acc 100644 --- a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs @@ -41,7 +41,7 @@ public async Task FormPost() var page = await _fixture.Browser.NewPageAsync(); await page.GotoAsync($"{_url}/html"); await page.ClickAsync("body > form > button"); - await page.WaitForURLAsync($"{_url}/protocol"); + Assert.Contains("HTTP/3", await page.ContentAsync()); await page.CloseAsync(); } } From a4c69a4f061afc6c83d4dbfb55fc43a767da2c62 Mon Sep 17 00:00:00 2001 From: ladeak Date: Sun, 19 Jul 2026 19:03:37 +0200 Subject: [PATCH 10/12] Use RunAndWaitForResponseAsync --- tests/CHttpServer.BrowserTests/Http2BrowserTests.cs | 5 ++++- tests/CHttpServer.BrowserTests/Http3BrowserTests.cs | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs index 2b072c8..cb2bb54 100644 --- a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs @@ -35,7 +35,10 @@ public async Task FormPost() { var page = await _fixture.Browser.NewPageAsync(); await page.GotoAsync($"{_url}/html"); - await page.ClickAsync("body > form > button"); + await page.RunAndWaitForResponseAsync(async () => + { + await page.ClickAsync("body > form > button"); + }, $"{_url}/protocol"); Assert.Contains("HTTP/2", await page.ContentAsync()); await page.CloseAsync(); } diff --git a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs index 5524acc..27c425e 100644 --- a/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http3BrowserTests.cs @@ -40,7 +40,10 @@ public async Task FormPost() { var page = await _fixture.Browser.NewPageAsync(); await page.GotoAsync($"{_url}/html"); - await page.ClickAsync("body > form > button"); + await page.RunAndWaitForResponseAsync(async () => + { + await page.ClickAsync("body > form > button"); + }, $"{_url}/protocol"); Assert.Contains("HTTP/3", await page.ContentAsync()); await page.CloseAsync(); } @@ -49,7 +52,7 @@ public async Task FormPost() [Fact] public async Task GetFetchJs() { - for (int i = 0; i < 100; i++) + for (int i = 0; i < 10; i++) { var page = await _fixture.Browser.NewPageAsync(); await page.GotoAsync($"{_url}/html"); From 019513c06eb07a2251110d9fb67b77dc074af6e3 Mon Sep 17 00:00:00 2001 From: ladeak Date: Sun, 19 Jul 2026 20:22:40 +0200 Subject: [PATCH 11/12] Antiforgery token --- tests/CHttpServer.Tests/TestServer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CHttpServer.Tests/TestServer.cs b/tests/CHttpServer.Tests/TestServer.cs index ee3ce75..5d1ebcf 100644 --- a/tests/CHttpServer.Tests/TestServer.cs +++ b/tests/CHttpServer.Tests/TestServer.cs @@ -33,7 +33,7 @@ public Task RunAsync(int port = 7222, bool usePriority = false, bool useHttp3 = // o.Listen(IPAddress.Loopback, port, lo => // { // lo.UseHttps(X509CertificateLoader.LoadPkcs12FromFile("testCert.pfx", "testPassword")); - // lo.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http3; + // lo.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2AndHttp3; // }); //}); _app = builder.Build(); @@ -183,7 +183,7 @@ async IAsyncEnumerable GetStream() _app.MapPost("/posthiddenfield", ([FromForm(Name = "field")] string field) => { return TypedResults.LocalRedirect("/protocol"); - }); + }).DisableAntiforgery(); _app.MapGet("/html", async (HttpContext ctx) => { var baseUrl = ctx.Request.Host; From 3d3e8460056d8aa0c6a4b156a6b23465af28e5d6 Mon Sep 17 00:00:00 2001 From: ladeak Date: Mon, 20 Jul 2026 00:19:58 +0200 Subject: [PATCH 12/12] Intercept and continue --- tests/CHttpServer.BrowserTests/Http2BrowserTests.cs | 8 ++++++++ tests/CHttpServer.Tests/TestServer.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs index cb2bb54..029a13a 100644 --- a/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs +++ b/tests/CHttpServer.BrowserTests/Http2BrowserTests.cs @@ -35,6 +35,14 @@ public async Task FormPost() { var page = await _fixture.Browser.NewPageAsync(); await page.GotoAsync($"{_url}/html"); + + // Intercept all requests ("**/*" is a glob pattern for everything) + await page.RouteAsync("**/*", async route => + { + // Let the request continue normally + await route.ContinueAsync(); + }); + await page.RunAndWaitForResponseAsync(async () => { await page.ClickAsync("body > form > button"); diff --git a/tests/CHttpServer.Tests/TestServer.cs b/tests/CHttpServer.Tests/TestServer.cs index 5d1ebcf..983f624 100644 --- a/tests/CHttpServer.Tests/TestServer.cs +++ b/tests/CHttpServer.Tests/TestServer.cs @@ -191,7 +191,7 @@ await ctx.Response.WriteAsync($$""" -
+