Part 6. Extensions

Extension Methods

Due to the way we structure our framework, we can create extensions methods that we can use as extensions on methods involving different object types, such as clicking on a WebElement, explicitly waiting for a WebDriver, or waiting for a WebElement to be displayed or clickable etc.

WebElement Extensions

We will begin each method name in the ‘WebElementExtensions.cs’ class file with ‘We’ (for WebElement).

Before we begin, make sure to make the WebElementExtensions class ‘public static’.

WeHighlightElement() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for making the WebDriver wait for a specified number of seconds to use JavaScript to highlight a given element with an appropriate border…
    using OpenQA.Selenium;
    using ProductAutomation.Utils.Selenium;
    
    using static ProductAutomation.Utils.Selenium.Settings;
    
    namespace ProductAutomation.Utils.Extensions
    {
        public static class WebElementExtensions
        {
            private static IWebDriver _driver = Driver.CurrentDriver;
    
            public static void WeHighlightElement(this IWebElement element)
            {
                var js = (IJavaScriptExecutor) _driver;
                js.ExecuteScript(weHighlightedColour, element);
            }
        }
    }

    Let’s also add the javascript for ‘WeHighlightedColour’ in the ‘Settings.cs’ class file of the ‘Utils.Selenium’ namespace (of course you can also customise this to change the colour and size of the border etc.)…

    public static string WeHighlightedColour = "arguments[0].style.border='5px solid green'";

    The whole Settings class should look like below at this point…

    namespace ProductAutomation.Utils.Selenium 
    { 
        public class Settings 
        { 
            public static string baseUrl = "https://start.duckduckgo.com"; 
            public static string weHighlightedColour = "arguments[0].style.border='5px solid green'";
        } 
    }

WeElementIsEnabled() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for making the WebDriver wait for a specified number of seconds to see if an element is enabled…
    public static bool WeElementIsEnabled(this IWebElement element, int sec = 10)
    {
        var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
        return wait.Until(d =>
            {
                try
                {
                    element.WeHighlightElement();
                    return element.Enabled;
                }
                catch (StaleElementReferenceException)
                {
                    return false;
                }
            });
    }

    The whole thing should look like below now…

    using System;
    using OpenQA.Selenium;
    using OpenQA.Selenium.Support.UI;
    using ProductAutomation.Utils.Selenium;
    
    using static ProductAutomation.Utils.Selenium.Settings;
    
    namespace ProductAutomation.Utils.Extensions
    {
        public static class WebElementExtensions
        {
            private static IWebDriver _driver = Driver.CurrentDriver;
    
            public static void WeHighlightElement(this IWebElement element)
            {
                var js = (IJavaScriptExecutor) _driver;
                js.ExecuteScript(weHighlightedColour, element);
            }
    
            public static bool WeElementIsEnabled(this IWebElement element, int sec = 10)
            {
                var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
                return wait.Until(d =>
                {
                   try
                   {
                       element.WeHighlightElement();
                       return element.Enabled;
                   } 
                   catch (StaleElementReferenceException)
                   {
                       return false;
                   }
                });
            }
        }
    }

WeSelectDropdownOptionByIndex() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for selecting a dropdown option based on its index position, if that element is enabled…
    public static void WeSelectDropdownOptionByIndex(this IWebElement element, 
                                                     string text, int sec = 10)
    {
        element.WeElementIsEnabled(sec);
        new SelectElement(element).SelectByText(text);
    }

WeSelectDropdownOptionByText() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for selecting a dropdown option based on the string/text of that option, again if that element is enabled…
    public static void WeSelectDropdownOptionByText(this IWebElement element, string text, int sec = 10)
    {
        element.WeElementIsEnabled(sec);
        new SelectElement(element).SelectByText(text);
    }

WeSelectDropdownOptionByValue() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for selecting a dropdown option based on the value of that option, again if that element is enabled…
    public static void WeSelectDropdownOptionByValue(this IWebElement element, string value, int sec = 10)
    {
        element.WeElementIsEnabled(sec);
        new SelectElement(element).SelectByValue(value);
    }

WeGetAttribute() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for getting the value of a specified attribute on an element…
    public static string WeGetAttribute(this IWebElement element, string attribute)
    {
        return element.GetAttribute(attribute);
    }

WeElementIsDisplayed() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for making the WebDriver wait for a specified number of seconds to see if an element is displayed…
    public static bool WeElementIsDisplayed(this IWebElement element, int sec = 10)
    {   
        var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
        return wait.Until(d =>
            {
                try
                {
                    element.WeHighlightElement();
                    return element.Displayed;
                }
                catch (NoSuchElementException)
                {
                    return false;
                }
            });
    }

WeSendKeys() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for sending keys / a given string to an IWebElement (and clearing the IWebElement first of existing text if necessary), after first waiting to see if the element is displayed…
    public static void WeSendKeys(this IWebElement element, string text, int sec = 10, bool clearFirst = false)
    {
        element.WeElementIsDisplayed(sec);
        if (clearFirst) element.Clear();
         element.SendKeys(text);
    }

WeElementToBeClickable() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for making the WebDriver wait a set amount of time to see if an element is clickable…
    public static void WeElementToBeClickable(this IWebElement element, int sec = 10)
    {
        var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
        wait.Until(c => element.Enabled);
    }

WeClick() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for clicking on an element, after having first waited for the element to be clickable and then highlighted the element with a border using JavaScript…
    public static void WeClick(this IWebElement element, int sec = 10)
    {
        element.WeElementToBeClickable(sec);
        element.WeHighlightElement();
        element.Click();
    }

WeSwitchTo() Extension Method

  1. Open up the ‘WebElementExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for switching focus to a different iFrame, after having first waited for the iFrame to be clickable and having highlighted it with a border…
    public static void WeSwitchTo(this IWebElement iframe, int sec = 10)
    {
        iframe.WeElementToBeClickable(sec);
        iframe.WeHighlightElement();
        Browser().SwitchTo().Frame(iframe);
    }

The whole thing should look like below…

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using ProductAutomation.Utils.Selenium;

using static ProductAutomation.Utils.Selenium.Settings;

namespace ProductAutomation.Utils.Extensions
{
    public static class WebElementExtensions
    {
        private static IWebDriver _driver = Driver.CurrentDriver;

        public static void WeHighlightElement(this IWebElement element)
        {
            var js = (IJavaScriptExecutor) _driver;
            js.ExecuteScript(weHighlightedColour, element);
        }

        public static bool WeElementIsEnabled(this IWebElement element, int sec = 10)
        {
            var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
            return wait.Until(d =>
            {
               try
               {
                   element.WeHighlightElement();
                   return element.Enabled;
               } 
               catch (StaleElementReferenceException)
               {
                   return false;
               }
            });
        }

        public static void WeSelectDropdownOptionByIndex(this IWebElement element, string text, int sec = 10)
        {
            element.WeElementIsEnabled();
            new SelectElement(element).SelectByText(text);
        }

        public static void WeSelectDropdownOptionByValue(this IWebElement element, string value, int sec = 10)
        {
            element.WeElementIsEnabled(sec);
            new SelectElement(element).SelectByValue(value);
        }

        public static string WeGetAttribute(this IWebElement element, string attribute)
        {
            return element.GetAttribute(attribute);
        }

        public static bool WeElementIsDisplayed(this IWebElement element, int sec = 10)
        {
            var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
            return wait.Until(d =>
            {
               try
               {
                   element.WeHighlightElement();
                   return element.Displayed;
               } 
               catch (StaleElementReferenceException)
               {
                   return false;
               }
            });
        }

        public static void WeSendKeys(this IWebElement element, string text, int sec = 10, bool clearFirst = false)
        {
            element.WeElementIsDisplayed(sec);
            if (clearFirst) element.Clear();
            element.SendKeys(text);
        }

        public static void WeElementToBeClickable(this IWebElement element, int sec = 10)
        {
            var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
            wait.Until(c => element.Enabled);
        }

        public static void WeClick(this IWebElement element, int sec = 10)
        {
            element.WeElementToBeClickable();
            element.WeHighlightElement();
            element.Click();
        }

        public static void WeSwitchTo(this IWebElement iframe, int sec = 10)
        {
            iframe.WeElementToBeClickable(sec);
            iframe.WeHighlightElement();
            _driver.SwitchTo().Frame(iframe);
        }
    }
}

WebDriverExtensions

We will begin each method name in the ‘WebDriverExtensions.cs’ class file with ‘Wd’ (for WebDriver).

Again, let’s make the WebDriverExtensions class ‘public static’ first.

WdHighlight() Extension Method

  1. Open up the ‘WebDriverExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for using WebDriver’s ‘By’ locator to highlight a located element…
    using System;
    using OpenQA.Selenium;
    using OpenQA.Selenium.Support.UI;
    using ProductAutomation.Utils.Selenium;
    
    using static ProductAutomation.Utils.Selenium.Settings;
    
    namespace ProductAutomation.Utils.Extensions
    {
        public static class WebDriverExtensions
        {
            private static IWebDriver _driver = Driver.CurrentDriver;
    
            public static object WdHighlight(this By locator)
            {
                var myLocator = _driver.FindElement(locator);
                var js = (IJavaScriptExecutor) _driver;
                return js.ExecuteScript(weHighlightedColour, myLocator);
            }
        }
    }

WdFindElement() Extension Method

  1. Open up the ‘WebDriverExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for making WebDriver wait a set amount of time to find a located element…
    public static IWebElement WdFindElement(this By locator, int sec = 10)
    {       
        var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(sec));
        return wait.Until(drv =>
            {
                try
                {
                    locator.WdHighlight();
                    return drv.FindElement(locator);
                }
                catch (NoSuchElementException)
                {
                    return null;
                }
            });
    }

    Ensure the correct ‘using’ Directives have been added again after this 2nd method…

    using System;
    using OpenQA.Selenium.Support.UI;

WdSendKeys() Extension Method

  1. Open up the ‘WebDriverExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for sending keys / a string to a found element…
    public static void WdSendKeys(this By locator, string text, int sec = 10, bool clearFirst = false)
    {
        if (clearFirst) locator.WdFindElement(sec).Clear();
        locator.WdFindElement(sec).SendKeys(text);
    }

WdClickByIndex() Extension Method

  1. Open up the ‘WebDriverExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for clicking an element within a list of elements, based on its index position…
    public static void WdClickByIndex(this By locator, int index = 0, int sec = 10)
    {
        var myLocator = _driver.FindElements(locator);
        myLocator[index].Click();
    }

WdClick() Extension Method

  1. Open up the ‘WebDriverExtensions.cs’ class file in the ‘Utils.Extensions’ namespace and add the following extension method for clicking on an element, after having found it…
    public static void WdClick(this By locator, int sec = 10)
    {
        locator.WdFindElement(sec).Click();
    }

Refactoring our Test Scenario to use the Extensions

We can now use our extensions (static methods) to replace previous lines of code in our test methods.  Over time, this helps us follow DRY principles (Don’t Repeat Yourself) as well as make our code easier to read ?

‘SearchFor()’ method

  1. Edit the ‘SearchFor()’ method in the BasePage.cs file so it uses the WdSendKeys() extension method, with the whole thing looking like below…
    using System;
    using OpenQA.Selenium;
    using ProductAutomation.Utils.Extensions;
    using ProductAutomation.Utils.Selenium;
    
    using static ProductAutomation.Utils.Selenium.Settings;
    
    namespace ProductAutomation.Pages
    {
        public class BasePage
        {
            public By SearchField => By.Id("search_form_input_homepage");
    
            public IWebDriver driver = Driver.CurrentDriver;
            public string GetTitle => Driver.CurrentDriver.Title;
            public string GetUrl => Driver.CurrentDriver.Url;
            public string GetPageSource => Driver.CurrentDriver.PageSource;
    
            public void NavigateBaseUrl()
            {
                driver.Navigate().GoToUrl(baseUrl);
                driver.Manage().Window.Maximize();
                Console.WriteLine(" :: The base URL is navigated to");
            }
    
            public SearchResultsPage SearchFor(string searchTerm)
            {
                SearchField.WdSendKeys(searchTerm);
                return new SearchResultsPage();
            }
        }
    }

‘When they view the first result’ assertion

  1. Open up the ‘RedditSteps.cs’ class file and edit the assertion in the ‘When they view the first result’ step so it uses the ‘WeElementIsDisplayed()’ extension method from the ‘WebElementExtensions’ class (make sure the using directive for WebElementExtensions is added), with the whole thing now looking like below…
    using NUnit.Framework;
    using OpenQA.Selenium;
    using ProductAutomation.Pages;
    using ProductAutomation.Utils.Extensions;
    using TechTalk.SpecFlow;
    
    namespace ProductAutomation.Steps.RedditSteps
    {
        [Binding]
        public class RedditSteps
        {
            private SearchResultsPage searchResultsPage = new SearchResultsPage();
            private RedditPage redditPage = new RedditPage();
    
            [When(@"they view the first result")]
            public void WhenIViewTheFirstResult()
            {
                IWebElement searchResults = searchResultsPage.driver.FindElement(searchResultsPage.SearchResultsContainer);
                Assert.IsTrue(searchResults.WeElementIsDisplayed());
                searchResultsPage.SelectFirstListedSearchResult();
            }
    
            [Then(@"they see the Reddit homepage")]
            public void ThenTheySeeTheRedditHomePage()
            {
                IWebElement redditPageContent = redditPage.driver.FindElement(redditPage.redditContentArea); 
                Assert.IsTrue(redditPageContent.Displayed);
            }
        }
    }

‘SelectFirstListedSearchResult()’ method

  1. Within the ‘SearchResultsPage.cs’ class file, edit the ‘SelectFirstListedSearchResult()’ method so it uses the ‘WdClickByIndex()’ extension method…
    using System.Collections.Generic;
    using OpenQA.Selenium;
    using ProductAutomation.Utils.Extensions;
    
    namespace ProductAutomation.Pages
    {
        public class SearchResultsPage : BasePage
        {
            public By SearchResultsContainer => By.Id("links");
            public By SearchResultLinks => By.ClassName("result__a");
    
            public RedditPage SelectFirstListedSearchResult()
            {
                SearchResultLinks.WdClickByIndex(0);
                return new RedditPage();
            }
        }
    }

‘Then they see the Reddit home page’ assertion

  1. Open up the ‘RedditSteps.cs’ file and edit the assertion in the ‘Then they see the Reddit homepage’ step so it uses the ‘WeElementIsDisplayed()’ extension method…
    using NUnit.Framework;
    using OpenQA.Selenium;
    using ProductAutomation.Pages;
    using ProductAutomation.Utils.Extensions;
    using TechTalk.SpecFlow;
    
    namespace ProductAutomation.Steps.RedditSteps
    {
        [Binding]
        public class RedditSteps
        {
            private SearchResultsPage searchResultsPage = new SearchResultsPage();
            private RedditPage redditPage = new RedditPage();
    
            [When(@"they view the first result")]
            public void WhenIViewTheFirstResult()
            {
                IWebElement searchResults = searchResultsPage.driver.FindElement(searchResultsPage.SearchResultsContainer);
                Assert.IsTrue(searchResults.WeElementIsDisplayed());
                searchResultsPage.SelectFirstListedSearchResult();
            }
    
            [Then(@"they see the Reddit homepage")]
            public void ThenTheySeeTheRedditHomePage()
            {
                IWebElement redditPageContent = redditPage.driver.FindElement(redditPage.redditContentArea); 
                Assert.IsTrue(redditPageContent.WeElementIsDisplayed());
            }
        }
    }

Run the test scenario and it should now pass. We can run our tests using the command…

dotnet test