I have the following code to start up a kestrel server on a random port.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Abc;

public sealed class KestrelWebAppFactory : WebApplicationFactory<Program>
{
    protected override IHost CreateHost(IHostBuilder builder)
    {
        // Build the TestServer host first (required by WebApplicationFactory)
        var testHost = builder.Build();

        // Reconfigure the builder to use a real Kestrel server on a dynamic port
        builder.ConfigureWebHost(webHost =>
        {
            webHost.UseKestrel();
            webHost.UseUrls("http://127.0.0.1:0");
        });

        // Build & start the Kestrel host
        var kestrelHost = builder.Build();
        kestrelHost.Start();

        // Wait for Kestrel to replace :0 with the actual port
        var server = kestrelHost.Services.GetRequiredService<IServer>();
        var addressesFeature = server.Features.Get<IServerAddressesFeature>()!;

        // Small spin-wait until Kestrel publishes a concrete port
        var sw = System.Diagnostics.Stopwatch.StartNew();
        string bound = null;
        while (sw.Elapsed < TimeSpan.FromSeconds(5))
        {
            bound = addressesFeature.Addresses.FirstOrDefault(a => !a.EndsWith(":0", StringComparison.Ordinal));
            if (bound is not null) break;
            Thread.Sleep(25);
        }
        if (bound is null)
            throw new InvalidOperationException("Kestrel did not publish a bound address.");

        // Point the factory’s HttpClient at the real server so Playwright can use it
        ClientOptions.BaseAddress = new Uri(bound);

        // Start and return the TestServer host (WAF expects this)
        testHost.Start();
        return testHost;
    }
}

Then I have the following fixture that I use on my test

namespace AbcE2ETests;

// Hosts the Kestrel server and Playwright browser for a test class.
public class ServerFixture : IAsyncLifetime
{
    private const string PlaywrightContextStoragePath = "/temp/playwrightstate.json";

    public KestrelWebAppFactory Factory { get; private set; }

    public Uri BaseUrl => Factory.ClientOptions.BaseAddress;
    public IPlaywright Playwright { get; private set; }
    public IBrowser Browser { get; private set; }
    public IBrowserContext Context { get; private set; }
    public IPage Page { get; private set; }

    public virtual async Task InitializeAsync()
    {
        Factory = new KestrelWebAppFactory();
        await WaitForServerStartAsync();

        // Initialize Playwright and a fresh browser context/page for this fixture
        await SignInAsync();
    }

    public string GetFullUrl(string relativeUrl) => new Uri(BaseUrl, relativeUrl).ToString();

    public async ValueTask EnterValuesByNameAsync(Dictionary<string, string> namesAndValues)
    {
        foreach (var kvp in namesAndValues)
        {
            ILocator input = Page.Locator($"input[name='{kvp.Key}']").First;
            await input.WaitForAsync();
            await input.FocusAsync();
            Task setValueTask = kvp.Value switch
            {
                BrowserConstants.FormValues.Checkbox.Checked => input.CheckAsync(),
                BrowserConstants.FormValues.Checkbox.Unchecked => input.UncheckAsync(),
                _ => input.FillAsync(kvp.Value)
            };
            await setValueTask;
        }
    }

    public async ValueTask PressEnterAsync() =>
        await Page.Keyboard.PressAsync(BrowserConstants.Keys.Enter);

    public async ValueTask NavigateToAsync(string relativeUrl) =>
        await Page.GotoAsync(GetFullUrl(relativeUrl));

    private async Task SignInAsync()
    {
        await InitializePlaywrightAsync(readFromStoragePath: false);
        await NavigateToAsync("/Account/LogIn");
        await EnterValuesByNameAsync(new Dictionary<string, string>
        {
            ["Input.TenantCode"] = "ASL",
            ["Input.Email"] = "[email protected]",
            ["Input.Password"] = "SuperSecretPassword"
        });

        await PressEnterAsync();
        await Page.WaitForURLAsync(GetFullUrl("/"));
        await Context.StorageStateAsync(new()
        {
            // relative to test bin folder
            Path = PlaywrightContextStoragePath
        });
        await DisposePlaywrightAsync();

        await InitializePlaywrightAsync(readFromStoragePath: true);
        await Task.Delay(300_000);
    }

    // Let descendants tweak context options (e.g., viewport, locale, storage state)
    protected virtual void ConfigureContextOptions(BrowserNewContextOptions options) { }

    protected async Task WaitForServerStartAsync()
    {
        HttpClient http = Factory.CreateClient();
        var sw = System.Diagnostics.Stopwatch.StartNew();
        while (sw.Elapsed < TimeSpan.FromSeconds(5))
        {
            try
            {
                HttpResponseMessage response = await http.GetAsync(Factory.ClientOptions.BaseAddress);
                if (response.IsSuccessStatusCode)
                    return;
            }
            catch { }
        }
        throw new InvalidOperationException("Server failed to start");
    }

    public virtual async Task DisposeAsync()
    {
        await DisposePlaywrightAsync();
        Factory?.Dispose();
        Factory = null;
    }

    private async ValueTask DisposePlaywrightAsync()
    {
        try
        {
            if (Context is not null)
                await Context.CloseAsync();
            if (Browser is not null)
                await Browser.CloseAsync();
        }
        finally
        {
            Playwright?.Dispose();

            Context = null;
            Browser = null;
            Playwright = null;
        }
    }

    private async Task InitializePlaywrightAsync(bool readFromStoragePath)
    {
        Playwright = await Microsoft.Playwright.Playwright.CreateAsync();
        Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
        {
            Headless = false
        });

        var ctxOptions = new BrowserNewContextOptions
        {
            BaseURL = Factory.ClientOptions.BaseAddress.ToString(),
            StorageStatePath = readFromStoragePath ? PlaywrightContextStoragePath : null
        };
        ConfigureContextOptions(ctxOptions);

        Context = await Browser.NewContextAsync(ctxOptions);
        Page = await Context.NewPageAsync();
    }

}

This works great, except when the sign-in is completed and I am redirected to / I just get a blank screen.

0

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.