Shane Cleary

On t'internet!

Selenium

Selenium Webdriver C# Tips and Tricks

What is Selenium?

Selenium Webdriver is a free, open-source software tool for testing web applications. Tests can be written in a number of languages including C# and Java. The tests can be run against most modern web browsers.

See video below of the automated test run using Selenium Webdriver in Chrome:



Selenium was originally developed by Jason Huggins in 2004 as an internal tool at ThoughtWorks. Selenium Remote Control (RC) was then developed by a team at ThoughtWorks and open sourced.

Huggins later joined Google and continued with development of Selenium-RC, at the same time Simon Stewart at ThoughtWorks developed a superior browser automation tool called WebDriver. The projects were merged in 2009 to become Selenium Webdriver.

In 2008, Philippe Hanrigou at ThoughtWorks made Selenium Grid, which provides a hub to allow the running of multiple Selenium tests concurrently on any number of local or remote systems. The ability to run tests on remote browser instances is useful to spread the load of testing across several machines and to run tests in browsers running on different platforms or operating systems. The latter is particularly useful in cases where not all browsers to be used for testing can run on the same platform.

The name Selenium comes from a joke made by Huggins in an email, mocking a competitor named Mercury, saying that you can cure mercury poisoning by taking selenium supplements.


Setting up Selenium WebDriver resources

Right click on References and select 'Manage NuGet Packages'. Search for Selenium Webdriver, and Selenium Support

Download Chrome Driver. Drop in solutions folder. Then right click solution and click add -> existing item. Right click on Properties for Chrome.exe. Select 'Copy if newer'

Get Chrome Driver Dynamically

public void Test1()
{
	Driver = GetChromeDriver();
	Driver.Manage().Window.Maximize();
	Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
}
		

public IWebDriver GetChromeDriver()
{
    var outPutDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    return new ChromeDriver(outPutDirectory);
}

		

Locating web elements with WebDriver
  1. ID - Best way to Identify, always unique. Tell your developers to make all elements with ids
  2. ClassName - It's ok, use only if you must
  3. Name
  4. Link Text
  5. PartialLinkText
  6. XPath
  7. CssSelector
  8. DOM

//Find an element by ID
var idElement = driver.FindElement(By.Id("idExample"));
idElement.Click();

//Find Element by Class - must check if unique identifier
private IWebElement element;
element = driver.FindElement(By.ClassName("buttonClassExample"));

//Find Element by Name
element = driver.FindElement(By.Name("NameExample")):
element.Click();

//Find Element by Linktext
var linkTextElement = driver.FindElement(By.LinkText("Click me using this link text!"));
linkTextElement.Click();

//Find Element by Partial Link Text  will tell you it's a link
var linkTextElement = driver.FindElement(By.PartialLinkText(""));
linkTextElement.Click();

		

Find Element using XPath

XPath is the standard navigation tool for XML

and an HTML document is also an XML document (xHTML)

Used when no element Id or name. We have to dig down to find the element


Absolute Xpath (Don't use)

Easiest way to identify

Cons: Long, Any change in the structure will break the locator


Relative XPath

Cleaner way to identify elements

More stable

Cons: Takes more time

/ Selects from the root node

// Selects nodes in the document from the current node that match the selection no matter where they are

. Selects the current node

.. Selects the parent of the current node

@ Selects attributes

Example

//input[@value='XPath Button 2']


Using attributes in xpath

//*[@id='']

// - go through all the nodes

* - regardless of the tag (Wild card char - very useful for skipping through code)

@id= - at id attribute equal to


//xpath href attribute

//[@href='/icon-sucess/]


How to identify a button

Using class: //*[@class='cvb '] (may not be unique)

//a[contains(text(),'Click Me')]

xpath function 'contains' and text()

//span[contains(text(), 'Email')]


Upload a File with selenium

Include: using System.Reflection; using System.Threading; using System.Windows.Forms;

internal void UploadDocument()
{
            BrowseButton.Click();

            string document = $"{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}\\A Test document.docx";
            Thread.Sleep(1000);
            SendKeys.SendWait(document);
            SendKeys.SendWait(@"{Enter}");
            Thread.Sleep(1500);
}
		

Page Object Model (POM)

POM is a design pattern, according to which there should be a class for each web page. The class should have 3 regions.

Region 1: All the web elements

Region 2: The actions that can be performed on page

Region 3: All the navigation

#region Webelement

#region Action

#region Navigation

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using NUnit.Framework;
using System.Threading;

namespace FakeTests
{
    internal class LoginPage : BaseTest
    {
        #region Webelement
        public LoginPage(IWebDriver driver) : base(driver) { }

        private IWebElement Submit => Driver.FindElement(By.XPath("//*[@class='btn btn-login control']"));
        private IWebElement Password => Driver.FindElement(By.Id("field2"));
        private IWebElement EmailField => Driver.FindElement(By.Id("field1"));
        private IWebElement CompanyCode => Driver.FindElement(By.Id("code"));
        private IWebElement RegisterButton => Driver.FindElement(By.XPath("//*[@ng-click='validateCode()']"));
        private IWebElement ForgotPasswordLink => Driver.FindElement(By.LinkText("Forgot Your password?"));
        #endregion


        #region Action
        public bool IsVisible
        {
            get
            {
                return Driver.Title.Contains("Fake Fake Fake");
            }
            set
            {

            }
        }       

        internal CompaniesPage Login(string email, string pass)
        {
            try
            {
                EmailField.SendKeys(email);
                Password.SendKeys(pass);
                Submit.Click();
            }
            catch (Exception e)
            {
                Console.WriteLine("Problem logging in" + e.Message);
            }

            return new CompaniesPage(Driver);

        }

        internal RegistrationPage EnterCompanyCode(string companyCode)
        {
            try
            {
                CompanyCode.Clear();
                CompanyCode.SendKeys(companyCode);

                RegisterButton.Click();
            }
            catch(Exception e)
            {
                Console.WriteLine("Something went wrong entering the company code " + e.Message);
                Thread.Sleep(1000);
            }

            return new RegistrationPage(Driver);
        }             

        internal ResetPasswordPage ResetPassword()
        {
            ForgotPasswordLink.Click();
            return new ResetPasswordPage(Driver);
        }

        internal Dashboard LoginAsCM(string email, string pass)
        {
            WebDriverWait waitForElement = new WebDriverWait(Driver, TimeSpan.FromSeconds(6));

            EmailField.SendKeys(email);
            Password.SendKeys(pass);
            Submit.Click();

            try
            {
                waitForElement.Until(ExpectedConditions.ElementIsVisible(By.XPath("//*[@ng-click='ok()']")));
                Driver.FindElement(By.XPath("//*[@ng-click='ok()']")).Click();
            }
            catch (Exception e)
            {
                Console.WriteLine("No Dialog! Check if first time logging in " + e.Message);
            }

            return new Dashboard(Driver);
        }
        #endregion

        #region Navigation
        internal void GoTo()
        {
            Driver.Navigate().GoToUrl("https://test.FakeSite.com");
        }
        #endregion
    }
}
		

Keeping classes small and solution organized

Some pages such as Home Pages or long forms can have a huge number of elements so in order to keep the class size small you can break down the page to smaller size by creating a public property.

using System;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;

namespace Test123
{
    internal class AddDataProcessingActivityPage : BaseApplicationPage
    {
        public AddDataProcessingActivityPage(IWebDriver driver) : base(driver)
        {
            Activities = new Activities(driver);
            Data = new Data(driver);
            ITSystems = new ITSystems(driver);
            Policies = new Policies(driver);
            Risks = new Risks(driver);
        }

        public Activities Activities { get; set; }
        public Data Data { get; set; }
        public ITSystems ITSystems { get; set; }
        public Policies Policies { get; set; }
        public Risks Risks { get; set; }

        //Page Elements
        private IWebElement SaveButton => Driver.FindElement(By.XPath("//*[@id='frmActivity']/div/div[2]/div/button"));
        public IWebElement SaveSuccessMessage => Driver.FindElement(By.XPath("//*[@class='alert alert-success alert-dismissible']"));


        //Page Methods
        internal void FormSaveSuccessMessage()...
         
        internal void VerifyLabelIsShown(string label)...  

        internal void Save()...       
  
    }
    
}
		

using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;

namespace Fake
{
    internal class Risks : BaseApplicationPage
    {
        private IWebDriver driver;

        public Risks(IWebDriver driver) : base(driver)
        {
            this.driver = driver;
        }

        //Create Risk button
        public IWebElement CreateRiskMenuButton => Driver.FindElement(By.XPath("//*[@href='#create-risk']"));
        public IWebElement RiskDescriptionTextbox => Driver.FindElement(By.Id("risk-desc"));
        public IWebElement CreateRiskOKButton => Driver.FindElement(By.Id("btnSaveRisk"));


        //Risk Data
        public IWebElement Risk => Driver.FindElement(By.XPath("//*[@id='risk_data']/div/div/div[1]"));
        public IWebElement PolicyRisk => Driver.FindElement(By.XPath("//*[@id='risk_policies']/div/div/div[1]"));
        public IWebElement DataRisk => Driver.FindElement(By.XPath("//*[@id='risk_data']/div/div[1]/div[1]"));


        internal void AssertThatPolicyDataRiskMessageIsShown(string risk)
        {
            Assert.AreEqual(risk, DataRisk.Text);
        }

        internal void SaveCreateRisk()...

        internal void CreateRiskDescription(string description)...

        internal void AssertThatPolicyRiskMessageIsShown(string message)...

        internal void AssertThatRiskMessageIsShown(string riskMessage)...
        
    }
}
		


Uploading documents

As Selenium can't type into a windows form you can use System.Windows.Forms; The file will be uploaded from the bin folder.

internal void UploadEmployeesAttachFile(string file)
{
    AttachFileButton.Click();          

    string document = $"{Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}\\" + file + "";
    Thread.Sleep(1000);
    SendKeys.SendWait(document);
    SendKeys.SendWait(@"{Enter}");
    Thread.Sleep(1500);
         
}

		

Working with tabs

In code below a link is clicked that opens a new tab in browser. We want to focues WebDriver on new tab, close the tab and return to original tab

	//Wait for the tab to open fully
    Thread.Sleep(2000);

    //Switch to the new tab 
    string newTab = Driver.WindowHandles.Last();
    Driver.SwitchTo().Window(newTab);

    Thread.Sleep(500);
    //Close the current tab
    Driver.Close();

    //Return to the original tab
    Driver.SwitchTo().Window(Driver.WindowHandles.First());

    Thread.Sleep(500);

		


Scrolling Element into view

use the: using OpenQA.Selenium.Interactions; class to move to an element

	IWebElement element = Driver.FindElement(By.Id(x));
	Actions actions = new Actions(Driver);
	actions.MoveToElement(element);
	actions.Perform();

	element.Click();

		

The above will scroll the element into view and then click on the element.


Scrolling Element into view with IJavaScriptExecutor

Scrolls the element into view and then applies an offset to ensure navigation pane not blocking element.

	try
        {
            IJavaScriptExecutor js = (IJavaScriptExecutor)Driver;

            var elem = Driver.FindElement(By.ID("SomeElement"));

            ScrollTo(0, elem.Location.Y - 75); // Make sure element is in the view but below the top navigation pane
                                                       
            Driver.FindElement(By.XPath("SomeElement")).Click();
        }

		

Scroll To

	public void ScrollTo(int xPosition = 0, int yPosition = 0)
        {
            IJavaScriptExecutor js = (IJavaScriptExecutor)Driver;

            var ls = String.Format("window.scrollTo({0}, {1})", xPosition, yPosition);
            js.ExecuteScript(ls);
        }

		

Assert Value

Get the value from a field and assert are equal

	ElementValue = SomeElement.GetAttribute(“value”);
	Assert.AreEqual(Element, ElementValue);

		

Parallel Testing With Webdriver (locally)

Create a new project in Visual Studio and add a Class Library. Right click on References and Manage NuGet Packages. Install Selenium Support which will also install Selenium Webdriver. Install NUnit (a popular open source test runner).

Then go to Tools -> Extensions and Updates. Search for nunit and install NUnit3 Test Adapter. Check the References for your project, you should see:

Nunit.framework

WebDriver

WebDriver.Support

Write your tests and tag tests with NUnit test attributes.

[TestFixture] – goes over the class

[Parallelizable] – goes over class, needed for running tests in parallel

[SetUp] – Declare driver, runs for each test

[Test] – goes above each test in class file

The framework is then going to know to run the tests in NUnit. Unfortunately tests in the same class cannot be run in parallel, each parallel test will have to be in a separate class file.

using NUnit.Framework;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ParellelCourseTests
{
    [TestFixture]
	[Parallelizable]
    public class Class1
    {
        private IWebDriver Driver;

        [SetUp]
        public void Setup()
        {
            Driver = new ChromeDriver();
        }

        [Test]
        public void LocalTest1()
        {
            Driver.Navigate().GoToUrl("https://www.shanecleary.net");
        }

        [Test]
        public void LocalTest2()
        {
            Driver.Navigate().GoToUrl("https://www.shanecleary.net/Bitcoin.html");
        }
    }
}
	

		

© Shane Cleary