Scott Hanselman

Updating to .NET 8, updating to IHostBuilder, and running Playwright Tests within NUnit headless or headed on any OS

March 07, 2024 Comment on this post [54] Posted in ASP.NET | DotNetCore
Sponsored By

All the Unit Tests passI've been doing not just Unit Testing for my sites but full on Integration Testing and Browser Automation Testing as early as 2007 with Selenium. Lately, however, I've been using the faster and generally more compatible Playwright. It has one API and can test on Windows, Linux, Mac, locally, in a container (headless), in my CI/CD pipeline, on Azure DevOps, or in GitHub Actions.

For me, it's that last moment of truth to make sure that the site runs completely from end to end.

I can write those Playwright tests in something like TypeScript, and I could launch them with node, but I like running end unit tests and using that test runner and test harness as my jumping off point for my .NET applications. I'm used to right clicking and "run unit tests" or even better, right click and "debug unit tests" in Visual Studio or VS Code. This gets me the benefit of all of the assertions of a full unit testing framework, and all the benefits of using something like Playwright to automate my browser.

In 2018 I was using WebApplicationFactory and some tricky hacks to basically spin up ASP.NET within .NET (at the time) Core 2.1 within the unit tests and then launching Selenium. This was kind of janky and would require to manually start a separate process and manage its life cycle. However, I kept on with this hack for a number of years basically trying to get the Kestrel Web Server to spin up inside of my unit tests.

I've recently upgraded my main site and podcast site to .NET 8. Keep in mind that I've been moving my websites forward from early early versions of .NET to the most recent versions. The blog is happily running on Linux in a container on .NET 8, but its original code started in 2002 on .NET 1.1.

Now that I'm on .NET 8, I scandalously discovered (as my unit tests stopped working) that the rest of the world had moved from IWebHostBuilder to IHostBuilder five version of .NET ago. Gulp. Say what you will, but the backward compatibility is impressive.

As such my code for Program.cs changed from this

public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();

to this:

public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).
ConfigureWebHostDefaults(WebHostBuilder => WebHostBuilder.UseStartup<Startup>());

Not a major change on the outside but tidies things up on the inside and sets me up with a more flexible generic host for my web app.

My unit tests stopped working because my Kestral Web Server hack was no longer firing up my server.

Here is an example of my goal from a Playwright perspective within a .NET NUnit test.

[Test]
public async Task DoesSearchWork()
{
await Page.GotoAsync(Url);

await Page.Locator("#topbar").GetByRole(AriaRole.Link, new() { Name = "episodes" }).ClickAsync();

await Page.GetByPlaceholder("search and filter").ClickAsync();

await Page.GetByPlaceholder("search and filter").TypeAsync("wife");

const string visibleCards = ".showCard:visible";

var waiting = await Page.WaitForSelectorAsync(visibleCards, new PageWaitForSelectorOptions() { Timeout = 500 });

await Expect(Page.Locator(visibleCards).First).ToBeVisibleAsync();

await Expect(Page.Locator(visibleCards)).ToHaveCountAsync(5);
}

I love this. Nice and clean. Certainly here we are assuming that we have a URL in that first line, which will be localhost something, and then we assume that our web application has started up on its own.

Here is the setup code that starts my new "web application test builder factory," yeah, the name is stupid but it's descriptive. Note the OneTimeSetUp and the OneTimeTearDown. This starts my web app within the context of my TestHost. Note the :0 makes the app find a port which I then, sadly, have to dig out and put into the Url private for use within my Unit Tests. Note that the <Startup> is in fact my Startup class within Startup.cs which hosts my app's pipeline and Configure and ConfigureServices get setup here so routing all works.

private string Url;
private WebApplication? _app = null;

[OneTimeSetUp]
public void Setup()
{
var builder = WebApplicationTestBuilderFactory.CreateBuilder<Startup>();

var startup = new Startup(builder.Environment);
builder.WebHost.ConfigureKestrel(o => o.Listen(IPAddress.Loopback, 0));
startup.ConfigureServices(builder.Services);
_app = builder.Build();

// listen on any local port (hence the 0)
startup.Configure(_app, _app.Configuration);
_app.Start();

//you are kidding me
Url = _app.Services.GetRequiredService<IServer>().Features.GetRequiredFeature<IServerAddressesFeature>().Addresses.Last();
}

[OneTimeTearDown]
public async Task TearDown()
{
await _app.DisposeAsync();
}

So what horrors are buried in WebApplicationTestBuilderFactory? The first bit is bad and we should fix it for .NET 9. The rest is actually every nice, with a hat tip to David Fowler for his help and guidance! This is the magic and the ick in one small helper class.

public class WebApplicationTestBuilderFactory 
{
public static WebApplicationBuilder CreateBuilder<T>() where T : class
{
//This ungodly code requires an unused reference to the MvcTesting package that hooks up
// MSBuild to create the manifest file that is read here.
var testLocation = Path.Combine(AppContext.BaseDirectory, "MvcTestingAppManifest.json");
var json = JsonObject.Parse(File.ReadAllText(testLocation));
var asmFullName = typeof(T).Assembly.FullName ?? throw new InvalidOperationException("Assembly Full Name is null");
var contentRootPath = json?[asmFullName]?.GetValue<string>();

//spin up a real live web application inside TestHost.exe
var builder = WebApplication.CreateBuilder(
new WebApplicationOptions()
{
ContentRootPath = contentRootPath,
ApplicationName = asmFullName
});
return builder;
}
}

The first 4 lines are nasty. Because the test runs in the context of a different directory and my website needs to run within the context of its own content root path, I have to force the content root path to be correct and the only way to do that is by getting the apps base directory from a file generated within MSBuild from the (aging) MvcTesting package. The package is not used, but by referencing it it gets into the build and makes that file that I then use to pull out the directory.

If we can get rid of that "hack" and pull the directory from context elsewhere, then this helper function turns into a single line and .NET 9 gets WAY WAY more testable!

Now I can run my Unit Tests AND Playwright Browser Integration Tests across all OS's, headed or headless, in docker or on the metal. The site is updated to .NET 8 and all is right with my code. Well, it runs at least. ;)

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
March 07, 2024 16:10
Wow! useful tricks in this post! Welcome back Scott!
And the .NET backward compatibility is very remarkable
March 08, 2024 5:04
My dude shows up after 14 months and just casually drops more knowledge. No mention of the absence. What a king.
March 08, 2024 15:06
Hi Scott,

Great article as always. I can make use of this code in my every day work :):)

Good to have you back.
March 08, 2024 15:07
I'm gone to say to my little brother, that he should also visit
this weblog on regular basis to obtain updated from most
recent reports.
March 08, 2024 15:50
Howdy would you mind sharing which blog platform you're using?
I'm looking to start my own blog in the near future but I'm
having a hard time choosing between BlogEngine/Wordpress/B2evolution and Drupal.
The reason I ask is because your design and style seems different then most blogs and I'm looking for something unique.
P.S Sorry for being off-topic but I had to ask!
March 08, 2024 16:50
Hi there every one, here every person is sharing these kinds of familiarity, thus it's nice to read this weblog, and I used to pay a quick visit this web site every day.
March 08, 2024 18:06
This is really interesting, You're a very skilled
blogger. I've joined your feed and look forward to seeking more of your great post.
Also, I have shared your web site in my social networks!
March 08, 2024 19:41
Hello! Do you know if they make any plugins to help with Search
Engine Optimization? I'm trying to get my
blog to rank for some targeted keywords but I'm
not seeing very good success. If you know of any please share.
Kudos!

my web-site; Glen
March 08, 2024 20:57
Marvelous, what a website it is! This weblog provides valuable information to us, keep it up.
March 09, 2024 9:25
Nice answers in return of this issue with genuine arguments and explaining all concerning that.
March 09, 2024 21:19
March 10, 2024 3:47
Hello! I understand this is kind of off-topic however I needed to ask.
Does running a well-established blog like yours require a massive amount work?
I'm brand new to running a blog however I do write in my diary daily.
I'd like to start a blog so I will be able to share my personal experience and views online.
Please let me know if you have any ideas or tips for new aspiring blog
owners. Thankyou!
March 10, 2024 9:14
Wow, awesome blog layout! How long have you been blogging
for? you made blogging look easy. The overall look of your web site is wonderful,
as well as the content!
March 10, 2024 15:11
This paragraph is in fact a nice one it helps
new internet users, who are wishing in favor of blogging.
March 10, 2024 15:34
I've been browsing online greater than 3 hours these days, yet I by
no means found any interesting article like yours. It
is lovely worth enough for me. In my view, if all webmasters and bloggers
made excellent content as you did, the net might be much more
useful than ever before.
March 10, 2024 16:47
Hello there! I could have sworn I've been to this blog before but after checking through some of the post I realized it's new to me.
Anyhow, I'm definitely delighted I found it and I'll be
book-marking and checking back often!
March 10, 2024 17:00
Does your site have a contact page? I'm having trouble locating
it but, I'd like to shoot you an email. I've got some recommendations for your blog you might be interested in hearing.

Either way, great website and I look forward to seeing it expand over time.
March 10, 2024 17:28
Hey there! Would you mind if I share your blog with my facebook group?
There's a lot of folks that I think would really
appreciate your content. Please let me know.
Thanks
March 10, 2024 19:03
Hmm it seems like your blog ate my first comment (it
was extremely long) so I guess I'll just sum it up what I wrote and say, I'm thoroughly enjoying your blog.
I as well am an aspiring blog blogger but I'm still
new to everything. Do you have any tips and hints for first-time
blog writers? I'd really appreciate it.
March 10, 2024 21:16
Excellent blog here! Also your site loads up fast! What host are you using?

Can I get your affiliate link to your host? I wish my web site loaded
up as quickly as yours lol
March 10, 2024 22:49
Hi there, this weekend is fastidious in favor
of me, as this time i am reading this great informative article here at
my residence.
March 11, 2024 3:45
magnificent points altogether, you simply won a emblem new reader.
What might you suggest in regards to your put up that you just made
a few days in the past? Any certain?
March 11, 2024 8:09
Greetings! Very helpful advice within this post! It is the little changes
which will make the most significant changes. Many thanks for sharing!
March 11, 2024 15:00
Every weekend i used to go to see this website, as i wish
for enjoyment, as this this web page conations in fact good
funny data too.
March 11, 2024 22:00
Have you ever thought about publishing an ebook or guest authoring on other sites?
I have a blog based upon on the same topics you discuss and would really like to have you share some stories/information. I know
my viewers would appreciate your work. If you are even remotely interested, feel free to shoot me an email.
March 12, 2024 5:21
I'm not sure why but this website is loading extremely slow
for me. Is anyone else having this issue or is it a issue on my end?
I'll check back later and see if the problem still exists.
March 12, 2024 8:41
A person essentially lend a hand to make significantly articles I'd state.
This is the very first time I frequented your website page and
so far? I surprised with the research you made to create this particular publish incredible.
Excellent process!
March 12, 2024 9:01
Every weekend i used to go to see this web site, because i want enjoyment, for the reason that this this web site conations really good funny stuff too.
March 12, 2024 10:11
I loved as much as you will receive carried out right
here. The sketch is attractive, your authored material stylish.
nonetheless, you command get got an nervousness over that you wish be delivering the following.
unwell unquestionably come more formerly again as exactly the same nearly
a lot often inside case you shield this increase.
March 12, 2024 10:50
This blog was... how do you say it? Relevant!! Finally I've found something that helped me.
Many thanks!
March 12, 2024 11:48
Today, I went to the beach front with my children. I found a
sea shell and gave it to my 4 year old daughter and said "You can hear the ocean if you put this to your ear." She put the shell to her ear and screamed.
There was a hermit crab inside and it pinched her ear.

She never wants to go back! LoL I know this is completely off topic but I had to
tell someone!
March 12, 2024 12:28
Whats up are using Wordpress for your blog platform?
I'm new to the blog world but I'm trying to get started and set up my own. Do you need any html coding knowledge to make
your own blog? Any help would be really appreciated!
March 12, 2024 12:43
nit: Should "I like running end unit tests" have been "I like running NUnit tests"?
March 12, 2024 14:13
Wow, marvelous blog layout! How long have you been blogging for?
you make blogging look easy. The overall look of your website is magnificent, as well
as the content!
March 12, 2024 15:38
Coolest guy in .NET dishes it out again 😎💖
Fil
March 12, 2024 16:56
Hey very interesting blog!
March 13, 2024 2:03
Its such as you read my mind! You appear to understand so much
about this, like you wrote the ebook in it or something. I think that you can do with a few % to
power the message house a little bit, but other than that,
that is excellent blog. A great read. I'll definitely be back.
March 13, 2024 3:06
It's actually a nice and useful piece of information. I
am happy that you just shared this helpful information with us.
Please keep us up to date like this. Thank you for sharing.
March 13, 2024 4:24
Pretty! This was a really wonderful article. Many thanks for supplying
these details.
March 13, 2024 5:12
Definitely consider that which you stated.
Your favorite justification appeared to be on the internet the easiest thing to
take into accout of. I say to you, I definitely get irked whilst other folks think about worries that they plainly do
not recognize about. You managed to hit the nail upon the top
and outlined out the entire thing with no need side effect , other
folks could take a signal. Will likely be back to get more.
Thanks
March 13, 2024 5:47
We stumbled over here from a different page and thought I should check things out.
I like what I see so i am just following you. Look forward to going over your web
page repeatedly.
March 13, 2024 13:55
This text is worth everyone's attention. Where can I find out
more?
March 13, 2024 15:13
I am sure this article has touched all the internet viewers,
its really really fastidious post on building up new webpage.
March 13, 2024 16:27
Great beat ! I wish to apprentice while you amend your
website, how could i subscribe for a blog site? The account aided me a appropriate
deal. I had been tiny bit familiar of this
your broadcast provided vivid transparent idea
March 13, 2024 17:26
Hi, after reading this amazing post i am too cheerful to share my familiarity here with mates.
March 14, 2024 1:11
I am in fact thankful to the owner of this website who has shared this impressive piece of writing at here.
March 14, 2024 4:31
Terrific work! This is the type of info that should be shared
across the internet. Disgrace on Google for now not positioning this submit upper!
Come on over and talk over with my site . Thanks =)
March 14, 2024 8:38
Hi, I do believe this is a great blog. I stumbledupon it ;)
I may revisit yet again since i have saved as a favorite
it. Money and freedom is the best way to change, may you be
rich and continue to guide others.
March 14, 2024 12:41
Hello! I could have sworn I've visited this website
before but after browsing through many of the articles I realized it's new to me.
Anyways, I'm definitely delighted I found it and I'll be
book-marking it and checking back frequently!
March 14, 2024 13:58
I am extremely impressed with your writing skills as well as
with the layout on your weblog. Is this a paid theme or did you modify it yourself?
Anyway keep up the excellent quality writing, it is rare to see a great blog like
this one today.
March 14, 2024 17:43
Ridiculous quest there. What happened after? Thanks!
March 14, 2024 18:26
Yes! Finally something about slotxo.
March 15, 2024 8:33
Highly descriptive article, I enjoyed that a lot. Will there be a
part 2?
March 15, 2024 13:36
Thanks for another excellent article. Where
else could anybody get that type of information in such
an ideal means of writing? I've a presentation next week, and I'm at the search
for such info.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.