Automate Web Testing in C#: A Guide with PuppeteerSharp and SpecFlow
Mukarram Javid
Posted on November 9, 2024
Learn how to efficiently automate and test web applications in C# using PuppeteerSharp and SpecFlow for streamlined end-to-end testing.
This article may be longer than expected, but I hope you enjoy it.
We often need to automate tasks or test applications as developers or testers. In such cases, there is usually limited time for manual testing, making PuppeteerSharp an ideal tool for task automation.
Before starting, we need to install two NuGet packages by running the following commands in the Package Manager Console window.
Install-Package SpecFlow.NUnit
Install-Package PuppeteerSharp
I have discussed Puppeteer in a previous blog you can read it by clicking here.
I’ll have a small discussion regarding SpecFlow.
SpecFlow is a .NET tool used to support Behavior-Driven Development (BDD) by enabling you to write tests in plain language that are easy to read and understand. SpecFlow allows developers and non-technical stakeholders to collaborate on defining application behavior in terms of scenarios written in Gherkin syntax (Given-When-Then format)
Key Features of SpecFlow
- Gherkin Syntax: Write test scenarios in plain English, making them accessible to both technical and non-technical team members. Integration with .NET: SpecFlow is designed specifically for the .NET ecosystem, enabling seamless integration with tools like NUnit, MSTest, and xUnit.
- Automated Tests: Scenarios written in SpecFlow can be automatically converted into executable test cases, which can be run to verify application behavior.
- BDD Workflow: Facilitates a BDD approach, where teams define requirements through examples, making tests serve as both documentation and a verification tool.
By combining SpecFlow with tools like PuppeteerSharp, you can create powerful, readable automation tests for web applications, streamlining both development and testing.
Let’s dive into the coding part.
In this article, I will use Visual Studio for development. we might need to install an extension of SpecFlow in Visual Studio. You can either install it from VS 2022 or manually. It is totally upto you. I assume you have installed this extension.
We create a NUnit project for this.
We do not need UnitTest1.cs file. So we can delete this. Our project will look like this.
Now create some folders to manage our code.
- Utilities (Helpers will be placed in the Utilities folder)
- Features (All Feature files will be stored in the Features folder)
- Steps (Step definitions will be organized in the Steps folder)
For example, we want to do GoogleSearch.
Here will be our possible steps to do that search
- Go to Google.com
- Write some text i.e PuppeteerSharp in the search bar
- Press Enter
- Match the search result with provided string.
To define steps we need .feature file. we will create the file in Features folder. “GoogleSearch.feature”. When we add New Item, we will see the SpecFlow section (if you installed SpecFlow extension) in left side of window.
When we click on this section. we will see related items of SpecFlow. We need Feature File for SpecFlow. Select it and named it and click on Add button
After adding the feature file. Let’s breakdown of the contents of a Feature file:
1. Feature
The Feature keyword describes a high-level functionality or behavior of the application.
Example:
Feature: Google Search Functionality
As a user
I want to search for text on Google
So that I can verify the search results match the expected output
In order to: The goal or purpose of the feature.
As a: The role of the person interacting with the feature.
I want to: The actual action or behavior expected from the feature.
2. Scenario
Each Scenario describes a test case with a clear purpose. In this case, the scenario is to search for the term “PuppeteerSharp” on Google and verify that the results contain the expected string.
Scenario: Search for PuppeteerSharp on Google
3. Given, When, Then Steps
The Given, When, Then structure describes the flow of the scenario. Let’s map each of your steps to these terms.
- Given: Describes the initial state (here, that the user is on Google’s homepage).
- When: Describes actions taken by the user (entering text and pressing Enter).
- Then: Describes the expected outcome (the search result should match the expected text).
Here’s how your scenario would look:
Given I am on Google.com
When I enter "PuppeteerSharp" in the search bar
And I press Enter
Then the search results should contain the text "PuppeteerSharp"
Full Feature File Example
Combining the elements above, here’s the full Feature file:
# GoogleSearch.feature
Feature: Google Search Functionality
As a user
I want to search for text on Google
So that I can verify the search results match the expected output
Scenario: Search for PuppeteerSharp on Google
Given I am on Google.com
When I enter "PuppeteerSharp" in the search bar
And I press Enter
Then the search results should contain the text "PuppeteerSharp"
In SpecFlow, we will create methods for each step in a Step Definition file, which links the steps in your Feature file to the actual code that performs those actions. Each method corresponds to a specific step and uses attributes like [Given], [When], and [Then] to associate with the Gherkin steps.
Here’s an example Step Definition file for your steps:
[Binding]
public class GoogleSearchSteps
{
[Given(@"I am on Google.com")]
public async Task GivenIAmOnGoogleCom()
{
}
[When(@"I enter ""(.*)"" in the search bar")]
public async Task WhenIEnterTextInTheSearchBar(string searchText)
{
}
[When(@"I press Enter")]
public async Task WhenIPressEnter()
{
}
[Then(@"the search results should contain the text ""(.*)""")]
public async Task ThenTheSearchResultsShouldContainTheText(string expectedText)
{
}
}
To initialize the browser, we will create a file named BrowserHelper.cs in the Utilities folder and have the GoogleSearchSteps class inherit from it. In BrowserHelper.cs we initialize the browser using Puppeteer and GoogleSearchSteps inherit from it
BrowserHelper will look like this:
// BrowserHelper.cs
public class BrowserHelper
{
public static IBrowser _browser;
public static IPage _page;
public async Task Init()
{
var revision = await new BrowserFetcher(SupportedBrowser.Chromium).DownloadAsync();
_browser = await Puppeteer.LaunchAsync(new LaunchOptions()
{
Headless = false,
ExecutablePath = revision.GetExecutablePath()
});
_page = await _browser.NewPageAsync();
}
}
// GoogleSearchSteps
[Binding]
public class GoogleSearchSteps : BrowserHelper
{
}
One more class we might need named Hook.
In SpecFlow, the Hook class is used to execute specific code before or after certain events during test execution. Hooks provide a way to set up and tear down resources, prepare data, and perform cleanup tasks.
1. [Binding] Attribute
- Marks the Hooks class as a SpecFlow binding class, allowing SpecFlow to recognize and execute the hooks within this class.
- Ensures that the Hooks class is loaded and its hooks are applied to each test scenario.
2. Inheritance from BrowserHelper
- The Hooks class inherits from BrowserHelper, suggesting that BrowserHelper contains methods or properties related to initializing or managing a browser instance.
- This inheritance likely provides access to browser-related functionality used for automated testing (such as launching or closing a browser).
3. [BeforeScenario] Hook
- [BeforeScenario] is an attribute provided by SpecFlow that marks a method to be run before each test scenario begins.
- Purpose: This is often used to set up prerequisites for the test, such as initializing the browser, preparing test data, or configuring the environment.
- Implementation: In this example, FirstBeforeScenario():
- Calls the Init() method, which likely sets up a browser session.
- Init() is assumed to be a method from the BrowserHelper class.
- The method is asynchronous (async), allowing it to handle asynchronous browser setup processes if needed.
[BeforeScenario]
public async Task FirstBeforeScenario()
{
await Init(); // Initializes the browser session or other test prerequisites
}
4. [AfterScenario] Hook
- [AfterScenario] is another attribute provided by SpecFlow, but it is triggered after each test scenario has finished executing.
- Purpose: Commonly used to clean up resources or perform teardown operations after each test scenario, such as closing the browser, clearing test data, or resetting the environment.
- Implementation: In this example, AfterScenario():
- Calls _browser.CloseAsync(), which closes the browser session to free up resources.
- _browser is likely a browser instance that’s initialized in the - - ---- BrowserHelper class or Init() method.
- Again, the method is asynchronous, ensuring the browser shutdown operation completes before moving on.
[AfterScenario]
public async Task AfterScenario()
{
await _browser.CloseAsync(); // Closes the browser session or cleans up resources
}
Hooks.cs will look like this:
// Hooks.cs
[Binding]
public sealed class Hooks : BrowserHelper
{
[BeforeScenario]
public async Task FirstBeforeScenario()
{
await Init();
}
[AfterScenario]
public async Task AfterScenario()
{
await _browser.CloseAsync();
}
}
Let’s rebuild the project to resolve our dependencies. After building, open the Test Explorer to view our tests. The test window will then look like this:
To see Test Explorer;
View > Test Explorer or we can use Short key CRTL + E, T.
To run this test, right-click on the test SearchForPuppeteerSharpOnGoogle and select 'Run'. You will see the test running. The first time, it may take a bit longer because the compiler needs to download the Chromium browser. Once the browser is downloaded, the tests will begin to run.
If the test has been successfully run, we will see a green icon indicating that the test passed successfully.
You can find the source code from here.
This is done for now. Hopefully, you enjoyed it. Have a nice day. We will see next time and in a different blog.
Posted on November 9, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.