Reqnroll and Playwright in .NET
Looking to start a new automation project in .NET and want to try out some of the newest frameworks out there? Today we will look at creating a project that combines the BDD features of Reqnroll with the modern browser handling from Playwright.
Tool Setup
For my example I will be using Visual Studio 2022
Reqnroll:
- Install the Reqnroll plugin for Visual Studio
- Create a new Reqnroll project (I used .NET 8/NUnit)
Playwright:
- Open up your nuget package manager and search for Playwright and add it to the project
- Install the browsers by running the script now available in the bin using powershell
bin/Debug/net8.0/playwright.ps1 install
Code Setup
The default Reqnroll project will come preloaded with some folders like:
- Features
- StepDefinitions
We will go ahead and add a Pages
folder as well.
Adding Hooks
For our bdd testing to spin up a playwright browser for each test, we will have to add a hook to run before each scenario. Under StepDefinitions
add a new item named hooks.cs
. We will add our pre and after test steps here.
Inside the hooks class let's create a Page
which will be available to other steps through the context.
using Microsoft.Playwright;
namespace ReqnrollProject1.StepDefinitions
{
[Binding]
public class Hooks
{
public IPage Page { get; private set; } = null!;
[BeforeScenario]
public async Task SetupTestAsync()
{
IPlaywright playwright = await Playwright.CreateAsync();
IBrowser browser = await playwright.Chromium.LaunchAsync(new() { Headless = false });
IBrowserContext context = await browser.NewContextAsync();
Page = await context.NewPageAsync();
}
}
}
Here we are initializing a headed chromium browser.
We can add any desired teardown steps as well. Let's take a screenshot after each scenario.
using Microsoft.Playwright;
using System.Text.RegularExpressions;
namespace ReqnrollProject1.StepDefinitions
{
[Binding]
public class Hooks(ScenarioContext scenarioContext)
{
public IPage Page { get; private set; } = null!;
private readonly ScenarioContext _scenarioContext = scenarioContext;
[BeforeScenario]
public async Task SetupTestAsync()
{
IPlaywright playwright = await Playwright.CreateAsync();
IBrowser browser = await playwright.Chromium.LaunchAsync(new() { Headless = false });
IBrowserContext context = await browser.NewContextAsync();
Page = await context.NewPageAsync();
}
[AfterScenario]
public async Task TakeScreenshotAsync()
{
string name = Regex.Replace(_scenarioContext.ScenarioInfo.Title, @"\s+", "");
await Page.ScreenshotAsync(new() { Path = $"./screenshots/{name}.png"});
}
}
}
Writing Tests
Now that all the configuration is done. We can add our first test. First let's create a feature file under the Features
directory.
testery.feature
:
Feature: Testery
@homepage
Scenario: Visit Testery Site
Given I navigate to testery.com
Then I see the testery links
When I click on contact link
Then I see the contact page
This simple test will navigate to testery.com verify certain elements exist on the homepage, then make sure the link to the contact page works.
Now let's make a step definition for the feature under the StepDefinitions
directory.
HomePageStepDefinitions.cs
using Microsoft.Playwright;
using ReqnrollProject1.Pages;
namespace ReqnrollProject1.StepDefinitions
{
[Binding]
public sealed class HomePageStepDefinitions(Hooks hooks, TesteryHomePage testeryHomePage)
{
private readonly IPage _page = hooks.Page;
private readonly TesteryHomePage _testeryHomePage = testeryHomePage;
[Given("I navigate to testery.com")]
public async Task GivenNavigateToAsync()
{
await _testeryHomePage.GoTo();
}
[When("I click on contact link")]
public async Task WhenIClickonContactLinkAsync()
{
await _testeryHomePage.ContactLink.ClickAsync();
}
[Then("I see the testery links")]
public async Task ThenISeeTheLinksAsync()
{
bool linkIsThere = await _testeryHomePage.PlatformLink.IsVisibleAsync();
bool linkTwoIsThere = await _testeryHomePage.ContactLink.IsVisibleAsync();
bool getStartedButton = await _testeryHomePage.GetStartedButton.IsVisibleAsync();
linkIsThere.Should().BeTrue();
linkTwoIsThere.Should().BeTrue();
getStartedButton.Should().BeTrue();
}
[Then("I see the contact page")]
public async Task ThenISeeTheContactPageAsync()
{
string url = _page.Url;
url.Should().Be("https://testery.com/contact");
}
}
}
Here we create the steps with use of the hooks.Page
context as well as the TesteryHomePage Page Object Model, which will we create later. For quick, direct actions on the current page we can call the page directly like page.Url
. For re-usable elements and actions, let's keep those under the POM.
Now in the POM we can define elements of interest as properties and actions as Tasks. Let's create the TesteryHomePage.cs
under the Pages
directory.
using Microsoft.Playwright;
using ReqnrollProject1.StepDefinitions;
namespace ReqnrollProject1.Pages
{
public class TesteryHomePage(Hooks hooks)
{
private readonly IPage _page = hooks.Page;
public ILocator PlatformLink => _page.Locator("a.nav-link[href='/platform']");
public ILocator ContactLink => _page.Locator("a.nav-link[href='/contact']");
public ILocator GetStartedButton => _page.GetByRole(AriaRole.Button, new() { Name = "Get Started" }).First;
public async Task GoTo()
{
await _page.GotoAsync("https://www.testery.com");
}
}
}
Everything is now ready for a test run!
Test Execution
Now you can start running the tests either through Visual Studio or the command line.
dotnet test
Parallelism
Lastly when we start to add more feature files it's best to start running thing in parallel to optimize our time. Simply add a new item under the project and add the parallelism setting.
parallel.cs
using NUnit.Framework;
[assembly: Parallelizable(ParallelScope.Fixtures)]
This will run each feature file (not scenario) in parallel.
Here is how my final project layout looks:
Happy Testing!