101 lines
3.5 KiB
C#
101 lines
3.5 KiB
C#
using Microsoft.Playwright;
|
|||
|
|
|
||
|
|
[assembly: AssemblyFixture(typeof(decePubClient.E2E.Support.Browser))]
|
||
|
|
|
||
|
|
namespace decePubClient.E2E.Support
|
||
|
|
{
|
||
|
|
// One Chromium for the whole run. *.test names go to Caddy on 127.0.0.1:6443 (Chromium's own --resolve), whose
|
||
|
|
// certificates come from the pasture's private CA, so certificate errors are ignored; nothing else is reachable.
|
||
|
|
public sealed class Browser : IAsyncLifetime
|
||
|
|
{
|
||
|
|
IPlaywright _playwright;
|
||
|
|
public IBrowser Chromium { get; private set; }
|
||
|
|
|
||
|
|
public async ValueTask InitializeAsync()
|
||
|
|
{
|
||
|
|
_playwright = await Playwright.CreateAsync();
|
||
|
|
Chromium = await _playwright.Chromium.LaunchAsync(new()
|
||
|
|
{
|
||
|
|
Headless = Environment.GetEnvironmentVariable("E2E_HEADED") != "1",
|
||
|
|
Args = ["--host-resolver-rules=MAP *.test 127.0.0.1:6443"]
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public async ValueTask DisposeAsync()
|
||
|
|
{
|
||
|
|
if (Chromium is not null)
|
||
|
|
await Chromium.CloseAsync();
|
||
|
|
_playwright?.Dispose();
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<Session> Open(string storageState = null)
|
||
|
|
{
|
||
|
|
var context = await Chromium.NewContextAsync(new()
|
||
|
|
{
|
||
|
|
IgnoreHTTPSErrors = true,
|
||
|
|
Locale = "en-GB",
|
||
|
|
TimezoneId = "Europe/Rome",
|
||
|
|
ViewportSize = new() { Width = 1440, Height = 900 },
|
||
|
|
ServiceWorkers = ServiceWorkerPolicy.Block,
|
||
|
|
StorageStatePath = storageState
|
||
|
|
});
|
||
|
|
return new Session(context, await context.NewPageAsync());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A browser tab and what it did: every request's host, every console error, every 5xx and Blazor's error bar. The
|
||
|
|
// guards hold for every test: the app talks only to its own site and to PrivaPub, and never fails silently.
|
||
|
|
public sealed class Session : IAsyncDisposable
|
||
|
|
{
|
||
|
|
public IBrowserContext Context { get; }
|
||
|
|
public IPage Page { get; }
|
||
|
|
public List<string> Hosts { get; } = [];
|
||
|
|
public List<string> ConsoleErrors { get; } = [];
|
||
|
|
public List<string> ServerErrors { get; } = [];
|
||
|
|
public List<(string Url, string Authorization)> ApiCalls { get; } = [];
|
||
|
|
|
||
|
|
public Session(IBrowserContext context, IPage page)
|
||
|
|
{
|
||
|
|
Context = context;
|
||
|
|
Page = page;
|
||
|
|
page.Request += (_, request) =>
|
||
|
|
{
|
||
|
|
var uri = new Uri(request.Url);
|
||
|
|
if (uri.Scheme is "http" or "https")
|
||
|
|
lock (Hosts)
|
||
|
|
Hosts.Add(uri.Host);
|
||
|
|
if (uri.Host == "privapub.test")
|
||
|
|
lock (ApiCalls)
|
||
|
|
ApiCalls.Add((request.Url, request.Headers.TryGetValue("authorization", out var auth) ? auth : null));
|
||
|
|
};
|
||
|
|
page.Console += (_, message) =>
|
||
|
|
{
|
||
|
|
if (message.Type == "error")
|
||
|
|
lock (ConsoleErrors)
|
||
|
|
ConsoleErrors.Add(message.Text);
|
||
|
|
};
|
||
|
|
page.Response += (_, response) =>
|
||
|
|
{
|
||
|
|
if (response.Status >= 500)
|
||
|
|
lock (ServerErrors)
|
||
|
|
ServerErrors.Add($"{response.Status} {response.Url}");
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task AssertGuards()
|
||
|
|
{
|
||
|
|
Assert.All(Hosts.Distinct(), host => Assert.Contains(host, new[] { "decepub.test", "privapub.test" }));
|
||
|
|
Assert.Empty(ServerErrors);
|
||
|
|
Assert.False(await Page.Locator("#blazor-error-ui").IsVisibleAsync(), "Blazor's error bar is showing");
|
||
|
|
// /clientapi carries the root's JWT and /api a persona's token: no credential ever crosses over
|
||
|
|
var clientApi = ApiCalls.Where(c => c.Authorization is not null && new Uri(c.Url).AbsolutePath.StartsWith("/clientapi/"))
|
||
|
|
.Select(c => c.Authorization).ToHashSet();
|
||
|
|
var mastodonApi = ApiCalls.Where(c => c.Authorization is not null && new Uri(c.Url).AbsolutePath.StartsWith("/api/"))
|
||
|
|
.Select(c => c.Authorization).ToHashSet();
|
||
|
|
Assert.Empty(clientApi.Intersect(mastodonApi));
|
||
|
|
Assert.All(clientApi, auth => Assert.Equal(3, auth["Bearer ".Length..].Split('.').Length));
|
||
|
|
}
|
||
|
|
|
||
|
|
public async ValueTask DisposeAsync() => await Context.CloseAsync();
|
||
|
|
}
|
||
|
|
}
|