How to start ASP.NET Core Web API site for testing using Selenium How to start ASP.NET Core Web API site for testing using Selenium selenium selenium

How to start ASP.NET Core Web API site for testing using Selenium


dotnet run works well to get the application started, but when I test with Selenium, I like to have more control over some of the services (e.g. skip starting background services, bypass authentication, etc.).

Here is a class that I am using for this. I tried to stick close to the WebApplicationFactory pattern.

// tests/MyApp.Tests/SeleniumServerFactory.csusing System;using System.IO;using System.Linq;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.Hosting.Server;using Microsoft.AspNetCore.Hosting.Server.Features;using Microsoft.AspNetCore.TestHost;using Microsoft.Extensions.Hosting;namespace MyApp.Tests{    /// <summary>    /// Fixture class to start a server instance to do browser/UI tests.    /// </summary>    public class SeleniumServerFactory<TStartup> : IDisposable        where TStartup : class    {        private readonly IHost _host;        public Uri BaseAddress { get; }        public SeleniumServerFactory()        {            IHostBuilder builder = Host.CreateDefaultBuilder()                .ConfigureWebHostDefaults(webBuilder =>                {                    // Copied from Program.cs                    // ...                    webBuilder.UseStartup<TStartup>();                    // Assumes your project is in src/MyApp.                    // This can be updated based on your solution layout.                    var relativePathToProject = Path.Join("src", "MyApp");                    webBuilder.UseSolutionRelativeContentRoot(relativePathToProject);                    // Use a dynamic port to prevent overlapping tests from breaking                    webBuilder.UseUrls("http://127.0.0.1:0");                });            // Apply your configuration overrides below to the builder.            ConfigureServices(builder);            // Start the host in the background.            // Shut it down in the Dispose method below.            _host = builder.Build();            _host.Start();            // Store base address so that tests can pass it to the browser.            BaseAddress = new Uri(_host.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>().Addresses.First());        }        public void ConfigureServices(IWebHostBuilder builder)        {            // Add your authentication overrides, etc. here ...        }        public void Dispose()        {            _host.Dispose();        }    }}

I then use this class as a fixture in my xUnit tests:

// tests/MyApp.Tests/Browser/SeleniumTests.csusing System;using OpenQA.Selenium;using OpenQA.Selenium.Firefox;using Xunit;namespace MyApp.Tests{    public class SeleniumTests        : IClassFixture<SeleniumServerFactory<Startup>>    {        private readonly Uri _baseAddress;        public SeleniumTests(SeleniumServerFactory<Startup> factory)        {            _baseAddress = factory.BaseAddress;        }        [Fact]        public void TitleShouldStartWithAppName()        {            using browser = new FirefoxDriver();            browser.Navigate().GoToUrl(_baseAddress);            Assert.StartsWith("MyApp - ", browser.Title);        }    }}

(I based this loosely off of Scott Hanselman's article. However, this only covers ASP.NET Core 2.1 and some changes have been made to the TestServer class that make this no longer work in ASP.NET Core 3.0+, so I updated my example to work with ASP.NET Core 3.0+.)

There's probably some better abstraction that could be done, but this works for me so far. To dive deeper, cf.: