Introduction
This post is a follow up to my previous post on How to use Playwright with multiple browsers in headless or headless mode. It covers how to use Playwright to test a solution containing two separate projects – a Blazor WASM project, and an ASP.NET Web API project.
I will demonstrate how to create a simple Solution that contains the Blazor WASM project, an ASP.NET Web API project and a NUnit test project with some sample Playwright tests. These tests can also be run locally, or with GitHub actions – the final solution includes the .yml file to set all this up.
Also, to make the tests realistic, the API project makes use of Entity Framework with an SQLite database so that there is an external dependency. The application follows the original template design of getting weather forecasts, but instead of these forecasts being hardcoded, they are retrieved from the SQLite database. I also added CORS Middleware and configured it to allow any origin.
The project will not make use of ASP.NET Core’s WebApplicationFactory class, but instead manually start up the Web API and Blazor WASM projects. Blazor WASM does not make use of xx deps json and Microsoft does not appear to be interested in supporting it.
I will be making use of NUnit as the testing framework as it is well supported for writing integration tests with Playwright
I also set up specific launch settings for running integration tests with an IntegrationTest environment variable. There is nothing in the projects that currently make use of this, but it is how I normally set things up so that I can check for this variable later if needed. There is also an IntegrationTest specific appsettings.json file in the API project which is configured to use a different database file name.
The complete solution can be found on my GitHub profile: MorneZaayman/Playwright-for-integration-end-to-end-testing-of-a-Blazor-WASM-and-ASP.NET-API-application
Setup Database
To ensure that all the tests in a TestFixture class have a fresh database, you need to add some code in the test class within the [OneTimeTearDown] and [OneTimeTearDown] annotated methods. This code will delete the database if it exists after waiting for it to be ready. This ensures that the database does not exist before tests are run, and the API project will create it, with its EnsureCreated method. Including the code in the tear-down methods ensure the test suite cleans up after itself. You can also put this code in a method such as DeleteDatabase() so that you do not have to write it twice.
This code makes use of some clever internal methods to wait until the database file is ready to be deleted. Thanks to Gordon Thompson for them. Note, there are some security issues related to these methods, but for use in a test suite only, they are fine.
private string solutionDirectory = Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.Parent.FullName;
private void DeleteDatabase()
{
if (File.Exists(@$"{solutionDirectory}{Path.DirectorySeparatorChar}MzansiBytes.IntegrationTest.db"))
{
WaitForFile(@$"{solutionDirectory}{Path.DirectorySeparatorChar}MzansiBytes.API{Path.DirectorySeparatorChar}MzansiBytes.IntegrationTest.db");
File.Delete(@$"{solutionDirectory}{Path.DirectorySeparatorChar}MzansiBytes.API{Path.DirectorySeparatorChar}MzansiBytes.IntegrationTest.db");
}
static bool IsFileReady(string filename)
{
// If the file can be opened for exclusive access it means that the file is no longer locked by another process.
try
{
using (FileStream inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
return inputStream.Length > 0;
}
catch (Exception)
{
return false;
}
}
static void WaitForFile(string filename)
{
// This will lock the execution until the file is ready.
while (!IsFileReady(filename)) { }
}
}
Configure starting the API project
The API project needs to start before any of the tests can run. Add a private field to the test class to keep track of the process.
private Process _apiProcess;
Then, inside the OneTimeSetup method, add the following code to start the API project.
_apiProcess = Process.Start(new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "run --configuration Release --launch-profile IntegrationTest",
WorkingDirectory = @$"{solutionDirectory}{Path.DirectorySeparatorChar}MzansiBytes.API"
});
In the OneTimeTearDown method, we also need to ensure the process is killed. Add the following line:
_apiProcess?.Kill();
Configure starting the WASM project
This is like the API project. The WASM also project needs to start before any of the tests can run. Add a private field to the test class to keep track of the process.
private Process _wasmProcess;
Then, inside the OneTimeSetup method, add the following code to start the WASM project.
_wasmProcess = Process.Start(new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "run --configuration Release --launch-profile IntegrationTest",
WorkingDirectory = @$"{solutionDirectory}{Path.DirectorySeparatorChar}MzansiBytes.WASM"
});
In the OneTimeTearDown method, we also need to ensure the process is killed. Add the following line:
_wasmProcess?.Kill();
Configure GitHub Actions
Add the following .yml file to build off the master branch. It runs on the latest Windows VM, sets up Playwright, .NET 7 and then restores, builds, and runs the Playwright tests in Chromium, Firefox and Webkit.
name: Integration Tests
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
Chromium:
name: Chromium Integration Tests
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.x.x
- name: Restore .NET dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test Chromium
run: dotnet test --no-build --verbosity normal --settings MzansiBytes.IntegrationTests/runsettings/chromium.runsettings
Firefox:
name: Firefox Integration Tests
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.x.x
- name: Restore .NET dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test Firefox
run: dotnet test --no-build --verbosity normal --settings MzansiBytes.IntegrationTests/runsettings/firefox.runsettings
Webkit:
name: Webkit Integration Tests
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.x.x
- name: Restore .NET dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test Firefox
run: dotnet test --no-build --verbosity normal --settings MzansiBytes.IntegrationTests/runsettings/webkit.runsettings