
End to End Testing using Playwright in Blazor WASM
Author - Abdul Rahman (Bhai)
Blazor
34 Articles
Table of Contents
What we gonna do?
In this blog post, we will learn how to perform end-to-end testing of a Blazor WebAssembly application using Playwright. Playwright is a open sources tool that enables reliable end-to-end testing for modern web apps with a single API. It is developed by Microsoft and is a great tool for end-to-end testing of web applications.
Note that in this example we are using Blazor WASM, but the same testing principle can be applied to any ASP.NET projects like MVC, RazorPages, etc.
Why we gonna do?
End to end testing is essential one to be added to test suite because of the following reasons:
- User Workflow Validation: Simulates real user interactions across the application, ensuring workflows function as expected.
- Integration Assurance: Verifies seamless communication between different components like APIs, databases, and UIs.
- Regression Prevention: Detects bugs caused by code changes in interconnected systems.
- Cross-Environment Testing: Confirms consistent behavior across multiple deployment environments.
- Improved User Experience: Ensures the application meets user expectations in real-world scenarios.
Playwright integrates with .NET via NUnit or MSTest, offering advanced features like element locators, assertions, and codegen for recording tests.
How we gonna do?
The first step is to create e2e test project in a separate solution to avoid conflicts with existing unit and integration tests. Create a new solution and add a new project of type NUnit Test Project (.NET Core) using the following cli command.
mkdir E2E dotnet new project nunit-playwright --name EndToEndTests cd EndToEndTests dotnet new sln --name ILoveDotNet.EndToEnd.Tests dotnet sln add EndToEndTests.csprojThis will create sln and csproj as shown below.

- Now updated all the nuget packages inside test project.
Default Unit Test includes a PageTest base class with a Page object for browser interaction. I have updated the test to Navigate to I Love .NET's site, assert the title as shown below.
namespace EndToEndTests; [Parallelizable(ParallelScope.Self)] [TestFixture] public class HomePageTests : PageTest { [Test] public async Task HomepageHasCorrectContentAsync() { await Page.GotoAsync("https://localhost:7176/"); // Expect a title "to contain" a substring. await Expect(Page).ToHaveTitleAsync(new Regex("I ❤️ DotNet")); } }Now run the Project and run the test. I'm running this in mac for first time and noticed the below error.

I resolved the above error by installing powershell in mac as shown below

Note: You need to do playwright.ps1 install everytime you update the nuget packages in this test project.
Now we need to run /bin/Debug/net10.0/playwright.ps1 install as shown below.

Now run the test and it should pass as shown below.

Recording a test to create it automatically
Playwright provides a feature to record the test and generate the code for it. This is very useful to create the test quickly.
To record the test, run the following command playwright.ps1 codegen as shown below.

This will now open the browser and code generation window. Make sure to select NUnit in the window as shown below.

you can now browse to your application url and perform the actions you want using the highlighted toolbar in the above browser to record. Once you are done, you can stop the recording and it will generate the code for you as shown below.

Now you can copy the code and paste it in your test project and run the test. Note that sometimes you might need to adjust the code to make it work.

Configuring Base URL from Run Settings
Hardcoding base URLs in tests limits flexibility, especially when testing across multiple environments like QA and staging. To address this, create a BaseTest class that dynamically manages the base URL using an environment variable, with an optional fallback or exception if the variable is unset. Eliminate hardcoded values by adding a .runsettings file for centralized configuration.a This way we can avoid code changes for URL for different environments.
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
<WEB_URL>https://localhost:7176</WEB_URL>
</EnvironmentVariables>
</RunConfiguration>
</RunSettings>
Update test classes to inherit from BaseTest and reference the BaseUrl property for easy maintenance. This setup enables seamless testing across different environments without code changes. Debugging is simplified with Visual Studio's configuration options, like the auto-detected .runsettings file. This approach enhances scalability and efficiency in end-to-end testing workflows.
namespace EndToEndTests.Utilities;
public class BaseTest : PageTest
{
public string BaseUrl { get; private set; } = null!;
public BaseTest()
{
BaseUrl = Environment.GetEnvironmentVariable("WEB_URL") ?? "https://DEFINE_AS_ENV_VAR_OR_RUNSETTINGS";
}
}
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class HomePageTests : BaseTest
{
[Test]
public async Task HomepageHasCorrectContentAsync()
{
await Page.GotoAsync(BaseUrl);
// Expect a title "to contain" a substring.
await Expect(Page).ToHaveTitleAsync(new Regex("I ❤️ DotNet"));
}
}
Note that you need to run test with dotnet test EndToEndTests.csproj --settings .runsettings after making above changes.
Debugging and Screenshots
Troubleshooting Playwright tests is essential when logic issues or test failures occur. Failed tests display valuable information in the test output, including expected vs. actual results and locator details. Debugging helps pinpoint issues, though locators may not always provide meaningful insights.
Enhance troubleshooting by taking screenshots during test execution using await Page.ScreenshotAsync(). This captures the app's state at specific points, aiding in diagnosing issues effectively. This approach simplifies debugging and improves test reliability.
[Test]
public async Task TestGeneratedUsingPlayWrightRecorderAsync()
{
await Page.GotoAsync(BaseUrl);
await Expect(Page.Locator("#brand")).ToContainTextAsync("I ❤️ .NET");
await Page.ScreenshotAsync(new()
{
Path = "ILoveDotNetBrandAsserted.png"
});
await Expect(Page.Locator("#main")).ToContainTextAsync("👉🏼 Click here to Join I ❤️ .NET WhatsApp Channel to get 🔔 notified about new articles and other updates.");
}
This will generate screenshots of the page while executing that exact line in test and store them in bin/debug/net10.0 folder as shown below.

Recording and testing complete user flow
Now lets use the recorder to test a complete login flow from Securing Blazor WASM with OAuth and OIDC using Identity Server page. In the above section I explained on how to use recorder so I'm sharing the final code here directly. I'm using the command pwsh playwright.ps1 codegen --target csharp-nunit https://localhost:7176/ to get started

I'm altering above test a little to fix and make it work as shown below.
using EndToEndTests.Utilities;
using Microsoft.Playwright;
using System.Reflection;
namespace EndToEndTests;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class WorkflowTests : BaseTest
{
[Test]
public async Task CompleteLoginWorkFlowAsync()
{
await Page.GotoAsync($"{BaseUrl}/blogs/securing-blazor-wasm-with-oauth-and-oidc-using-identity-server");
await Page.Locator("#blazordemologin").ClickAsync();
await Page.GetByPlaceholder("Username").FillAsync("alice");
await Page.GetByPlaceholder("Username").PressAsync("Tab");
await Page.GetByPlaceholder("Password").FillAsync("alice");
await Page.GetByPlaceholder("Password").PressAsync("Enter");
await Expect(Page.Locator("#main")).ToContainTextAsync("Welcome, Alice Smith");
await Page.Locator("#blazordemologout").ClickAsync();
await Page.GetByRole(AriaRole.Link, new() { Name = "here" }).ClickAsync();
await Expect(Page).ToHaveTitleAsync(new Regex("Securing Blazor WASM with OAuth and OIDC using Identity Server - I ❤️ DotNet"));
await Expect(Page.Locator("#blazordemologin")).ToContainTextAsync("Login");
}
}
Note that for demo purpose I have hardcoded the username and password in the test. You can use environment variables or any other secure way to store them.
Traces and Trace Viewer
The Playwright Trace Viewer is a powerful tool for troubleshooting tests by recording and visualizing test execution steps. To enable tracing, implement Setup and TearDown methods in your NUnit test class . The Setup method starts a trace, capturing screenshots, snapshots, and sources, while the TearDown method generates a trace zip file.
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class WorkflowTests : BaseTest
{
[SetUp]
public async Task Setup()
{
await Context.Tracing.StartAsync(new()
{
Title = TestContext.CurrentContext.Test.ClassName + "." + TestContext.CurrentContext.Test.Name,
Screenshots = true,
Snapshots = true,
Sources = true
});
}
[TearDown]
public async Task TearDown()
{
// This will produce e.g.:
// bin/Debug/net8.0/playwright-traces/LoggedInCheckOutCancellation.zip
await Context.Tracing.StopAsync(new()
{
Path = Path.Combine(
TestContext.CurrentContext.WorkDirectory,
"playwright-traces",
$"{TestContext.CurrentContext.Test.Name}.zip"
)
});
}
.. // Tests go here
}
Use the Playwright PowerShell script pwsh playwright.ps1 show-trace ./playwright-traces/CompleteLoginWorkFlowAsync command or drag the trace file into the Trace Viewer. It provides detailed insights, including test actions, timings, screenshots, and assertions. This visual debugging approach simplifies test analysis, helping identify and resolve issues effectively.
Replace the CompleteLoginWorkFlowAsync with your test name in above command.

The trace analysis will help to analysis more details like time taken to load, wait time, error happened in console, timeline and each step details as shown below.

Summary
In this blog post, we learned how to perform end-to-end testing of a Blazor WebAssembly application using Playwright. We discussed the importance of end-to-end testing, the benefits of using Playwright, and how to create and run tests. We also covered recording tests, configuring base URLs, debugging, taking screenshots, and testing complete user flows. By following these steps, you can enhance your testing workflows and ensure your Blazor WebAssembly applications are reliable and user-friendly.