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!