Leveraging Selenium, NUnit and C# for UI tests - Part 2

In this article, we establish Selenium with Visual Studio and C# and provide some practical examples.

What is Selenium ?

Selenium is a suite of tools designed for automating web browsers. It provides a way for developers to write scripts in various programming languages, including Java, C#, Python, and others, to automate interactions with web applications. Selenium supports various browsers, including Chrome, Firefox, Safari, and Internet Explorer, making it a versatile tool for browser automation and testing.

Important

Selenium is not exclusively designed for UI tests and can be utilized for various tasks, such as capturing periodic screenshots of specific web pages. It is fundamentally a browser automation tool.

Boring web-based administration tasks can (and should) also be automated as well.

Selenium consists of three main components: Selenium WebDriver (discussed in this post), Selenium IDE (to be discussed next), and Selenium Grid.

  • Selenium WebDriver is a set of language-specific bindings that enable interaction with a web browser, providing instructions for the browser to follow.

  • Selenium IDE are a series of Chrome, Firefox and Edge extensions that will do simple record-and-playback of interactions with the browser.

  • Selenium Grid enables us to scale by distributing and running tests on several machines and manage multiple environments. We will not cover this feature in these posts.

These initial definitions may appear somewhat abstract at first glance, and the purpose of this article is to provide clarity on these concepts.

How to set up Selenium ?

Selenium can be utilized with various programming languages, and for the sake of convenience, we are using C# here. However, the principles and concepts discussed are also applicable to languages like Java or Python.

We will also use NUnit as the test runner, but feel free to choose another one that you are familiar with. We assume that the reader is acquainted with this tool.

  • In Visual Studio, create a blank solution and name it EOCS.SeleniumTests for example.

  • In this solution, add a new NUnit Test Project and name it EOCS.SeleniumTests.Tests.

  • In this project, add the Selenium.WebDriver and Selenium.Support NuGet packages.

Which example will we use to implement this ?

In a real-world scenario, we typically test our own applications. While this may seem obvious, for our demonstration, we will use an existing website to illustrate our examples. We will perform some UI tests on Staples (https://www.staples.com/). We selected it because it is one of the largest ecommerce sites globally.

Similar to many websites nowadays, Staples presents a cookies disclaimer when we first arrive, asking whether we want to accept or decline all cookies. While this may appear straightforward for a human user, when conducting tests, we need to instruct Selenium to account for this action.

Important

When a new test is executed, Selenium treats it as if it is the first visit to the site. Consequently, the cookies disclaimer will always be displayed, and we need to instruct Selenium to ignore it.

Enough theory, code please !

  • In the NUnit project, add a new class named ChromeTests.cs and add to it the following code.
 1[TestFixture]
 2public class ChromeTests
 3{
 4    IWebDriver driver;
 5
 6    [SetUp]
 7    public void Setup()
 8    {
 9        driver = new ChromeDriver();
10        driver.Navigate().GoToUrl("https://www.staples.com/");
11    }
12
13    [TearDown]
14    public void TearDown()
15    {
16        driver.Quit();
17    }    
18}

Here, we are instructing Selenium to open a new Chrome browser (referred to as a driver in Selenium terminology) that will be used for ALL the tests conducted in this file and navigate to the specified URL (https://www.staples.com/).

We also specify that the browser instance must be terminated when all the tests have been executed (TearDown method in NUnit).

Test 1

We will start by adding a very simple test: checking that the page title is as expected.

 1[TestFixture]
 2public class ChromeTests
 3{
 4    IWebDriver driver;
 5
 6    [SetUp]
 7    public void Setup()
 8    {
 9        driver = new ChromeDriver();
10        driver.Navigate().GoToUrl("https://www.staples.com/");
11    }
12
13    [TearDown]
14    public void TearDown()
15    {
16        driver.Quit();
17    }
18
19	 [Test]
20	public void CheckPageTitle()
21	{
22		var title = driver.Title;
23		Assert.AreEqual("Staples® Official Online Store", title);
24	}   
25}

When we run this test, we can see that it passes.

Test 2

Next, we will proceed with a more practical test: checking that the cart button is correctly displayed when the home page is loaded.

The cart button is accessible via a div with the id "cartMenuHolder", as can be seen in the following screenshot of the DOM.

Information

This process of finding the correct element to interact with is quite cumbersome, and we will address it in the next article.

  • Add the following code in the class.
 1[Test]
 2public void CheckCartButtonIsDisplayed()
 3{
 4    var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));
 5    
 6    var a = wait.Until<IWebElement>(d =>
 7    {
 8        var divCart = driver.FindElement(By.Id("cartMenuHolder"));
 9        var a = divCart.FindElement(By.TagName("a"));
10        if (a.Displayed) return a;
11        return null;
12    });
13
14    Assert.IsNotNull(a);
15}

With this code, we instruct Selenium to find the cart button within a 2-second timeout and to repeat this operation again and again as long as the element has not been found. In Selenium jargon, this process involves establishing a waiting strategy.

Important

Testing UIs is particularly challenging for this reason: elements are not always displayed in a deterministic manner, and we need to wait for them to appear. However, determining how much time to wait is quite challenging. In this case, we consider that the cart must be displayed in less than 2 seconds; otherwise, it would be deemed an unacceptable delay for a user and should be considered a bug.

Synchronizing the code with the current state of the browser is one of the biggest challenges with Selenium, and doing it well is an advanced topic.

When we run this test, we can see that it passes.

Test 3

We will now perform one last test and verify that the search feature works as expected: when an item is searched, the site correctly redirects to the corresponding page. This test is more challenging and involves direct interaction with the browser: we need to send a search string to the input text and check that we are correctly redirected. In fact, this is where Selenium excels.

 1[Test]
 2public void CheckSearchRedirects()
 3{
 4    var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));            
 5    wait.IgnoreExceptionTypes(typeof(NoSuchElementException), typeof(ElementNotVisibleException));
 6
 7	// Find the search input text
 8    var searchInput = wait.Until<IWebElement>(d =>
 9    {
10        var e = d.FindElement(By.Id("searchInput"));
11        if (e.Displayed)
12            return e;
13        return null;
14    });
15    
16	// Find the button to perform the search
17    var searchButton = wait.Until<IWebElement>(d =>
18    {
19        var e = d.FindElement(By.CssSelector(".huFWMd"));
20        if (e.Displayed)
21            return e;
22        return null;
23    });
24
25	// Fill the search input text with "card"
26    searchInput.SendKeys("card");
27	
28	// Clicks on the button
29    searchButton.Click();
30
31    var expectedUrl = "https://www.staples.com/card/directory_card";
32    wait.Until(d =>
33    {
34        return d.Url == expectedUrl;
35    });
36
37    var redirectURL = driver.Url;
38    Assert.AreEqual("https://www.staples.com/card/directory_card", redirectURL);    
39}

This code is quite self-explanatory and reproduces the steps we described in plain English. We can observe how Selenium interacts with the browser using the SendKeys and Click methods.

But when we run this test, we can see that it fails.

The error message is quite explicit (The Other element would receive the click: <iframe src="https://consent...). Another element receives the click, and this element is an iframe. It is, in fact, the cookies disclaimer that we mentioned earlier. As nobody validates this popup, it is always in the foreground and receives the focus instead of our button. Our task will be, therefore, to simulate the validation of this popup.

After inspecting the DOM, we can implement the code below. It must be placed in the SetUp method since the disclaimer must be validated for each test.

 1[SetUp]
 2public void Setup()
 3{
 4    driver = new ChromeDriver();
 5    driver.Navigate().GoToUrl("https://www.staples.com/");
 6
 7    driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(5000);
 8
 9    var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));
10    wait.IgnoreExceptionTypes(typeof(NoSuchElementException), typeof(ElementNotVisibleException));
11
12    var iframe = wait.Until<IWebElement>(d =>
13    {
14        var disclaimer = d.FindElement(By.XPath("//iframe[@name='trustarc_cm']"));
15        if (disclaimer.Displayed) return disclaimer;
16        return null;
17    }); 
18
19    // Switch to the frame
20    driver.SwitchTo().Frame(iframe);
21
22    // Now we can click the button.
23    var button = wait.Until<IWebElement>(d =>
24    {
25        var b = d.FindElement(By.XPath("//a[@class='required']"));
26        if (b.Displayed) return b;
27        return null;
28    });
29
30    // Simulate a click
31    button.Click();
32
33    driver.SwitchTo().DefaultContent();
34
35    wait.Until(d => !d.FindElements(By.ClassName("truste_overlay")).Any());
36}

In this scenario, we need to initially locate the iframe, switch to it (as the buttons inside it are not directly accessible via our current document), simulate a click on the button, and finally switch back to the default document. Each of these operations is executed using a waiting strategy to prevent race conditions.

Important

By the way, note how the FindElement method can utilize various selectors such as by ID, by tag, by name, and also by XPath, as demonstrated above.

If we run the test again, we can see that it now passes.

So far, so good. We are now equipped to perform various UI tests using the following process.

  • Find an element in the DOM (by establishing a waiting strategy if need be) with the FindElement method

  • Take action on this element with the SendKeys or Click methods

  • Check that this action triggered expected behaviors

However, we acknowledge that the first step (finding elements in the DOM) can be tedious. Manually inspecting the source code to search for a div, a button, and similar elements is cumbersome. It would be more practical to have a tool to easily perform this task. Fortunately, this is precisely the goal of Selenium IDE.

Leveraging Selenium, NUnit and C# for UI tests - Part 3