Browse Category: Blog

Smart click (part 2)

Here is the explanation how you can implement your own Click() method and what is behind the scenes. I will describe the method using a pure IWebElement instance. To be able to invoke the method out of an IWebElement, it should be declared as an IWebElement interface extension method. The method will have a couple of overridden versions for different sets of input parameters.

Ok, let’s start. First of all let’s define what the method should do:

  • It checks if the clicking action may be done
  • It checks if all the necessary conditions for successful clicking are met (for example, some other elements are displayed in the specified state)
  • It clicks the element
  • It verifies if the clicking action has been performed and checks the result of the click

How it does it:

  1. It verifies the element exists, is visible and is available for clicking
  2. It starts the loop. The number of iterations is presented by one of the parameters
  3. It invokes a method which is passed as an input parameter and verifies preconditions for successful clicking (checks if some necessary elements are in the correct state, clicks some other elements to meet the expected conditions, and performs all the actions required to prepare your web element for successful clicking)
  4. It tries to click the element with one of the approaches passed as a parameters (list of clicking actions)
  5. It invokes a method passed as a parameter which verifies that the clicking action has been performed and the expected result has been achieved.
  6. If the expected result is not met, it proceeds to the next clicking action from the parameter list

This is the end of the theory, now let’s begin with practice.

Create a simple test aka testing Google Search page:

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

namespace SmartClick
{
   [TestFixture]
   public class TestClass
   {
      IWebDriver driver;

      [SetUp]
      public void Setup()
      {
         driver = new FirefoxDriver();
      }

      [TearDown]
      public void Teardown()
      {
         driver.Quit();
      }

      [Test]
      public void GoogleSearch()
      {
         driver.Navigate().GoToUrl("http://www.google.com ");
         IWebElement query = driver.FindElement(By.Name("q"));
         query.SendKeys("selenium");

         IWebElement btnSearch = driver.FindElement(By.Name("btnG"));
         //assume this method is working fine now but does not work with previous version of your browser (or with another browser) and the customer just eager to run your tests with both versions
         btnSearch.Click();

         WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
         wait.Until((d) => { return d.Title.StartsWith("selenium"); });
         Assert.AreEqual("selenium - Google Search", driver.Title);
      }
   }
}

Now let’s start writing our own clicking method. I named it SmartClick(). Add a static class to the namespace and put a static void method SmartClick() into it:

public static class Extensions
{
   public static void SmartClick(this IWebElement element)
   {
      element.Click();
   }
}

Now you can replace btnSearch.Click(); with btnSearch.SmartClick();

Or you can invoke the method this way:

Extensions.SmartClick(btnSearch);

because it is static.

Now let’s check that you did not pass null as a parameter and the element is displayed and enabled for clicking (step 1)

public static class Extensions
{
   public static void SmartClick(this IWebElement element)
   {
      if (element == null) 
      { 
         //add 'Element is null' error message into your log file or throw NoSuchElement or NullReference exception 
      }
      if(!element.Displayed) 
      { 
         //add 'Element is not displayed and cannot be clicked' error message into your log file 
         //you may add a screenshot here
      }
      if(!element.Enabled) 
      {
         //add 'Element is not displayed and cannot be clicked' error message into your log file
         //plus a screenshot
      }
      element.Click();
   }
}

Step 2 – wrap the clicking action into a loop. There is no sense in it right now but in the future it will be used for multiple attempts to click the element if it is necessary. The number of iterations is one of the method parameters with value 1 as default.

public static class Extensions
{
   public static void SmartClick(this IWebElement element, int iterations = 1)
   {
      int i = iterations;
   
      if (element == null) 
      { 
         //add 'Element is null' error message into your log file or throw NoSuchElement or NullReference exception 
      }
      if(!element.Displayed) 
      { 
         //add 'Element is not displayed and cannot be clicked' error message into your log file 
         //you may add a screenshot here
      }
      if(!element.Enabled) 
      {
         //add 'Element is not displayed and cannot be clicked' error message into your log file
         //plus a screenshot
      }
      
      while (i > 0)
      {
         i--;
         element.Click();
      }      
   }
}

Inside the loop, you can do any precondition, clicking actions and post-clicking verification to make successful click.

Step 3 – invoke a method that checks or does some preparation for successful clicking actions. You can put here almost any method which will be doing almost anything you need. To do this, you have to create a delegate for a method and then to initialize it, pass it as a parameter to the method and invoke it inside the loop. Add the following delegate to the namespace:

public delegate bool CustomFunction(params object[] obj);

and pass the delegate as a parameter to the SmartClick method:

public static void SmartClick(this IWebElement element, CustomFunction precondition = nullint iterations = 1)
   {
      int i = iterations;
      string elementName = element.Text;

      if (precondition == null)
         precondition = x => true; //if you did not specify the method let's assume it has passed (return true)
      
      while (i > 0)
      {
         i--;
         if (!precondition.Invoke())
         {
            //add string.Format("Preconditions for clicking '{0}' web element are not met", elementName) error message into your log file
            //screenshot
            continue; //there is no sense to do clicking action if precondition method has failed. Return back to the beginning of the loop and start again from the very beginning
         }
         if (element == null)   
         {   
            //add 'Element is null' error message into your log file or throw NoSuchElement or NullReference exception   
         }
         if(!element.Displayed)   
         {   
            //add 'Element is not displayed and cannot be clicked' error message into your log file   
            //you may add a screenshot here  
         }  
         if(!element.Enabled)   
        {  
           //add 'Element is not displayed and cannot be clicked' error message into your log file      //plus a screenshot  
         }
         element.Click();
      }      
   }

Now you can invoke the SmartClick method in this way:

btnSearch.SmartClick(x =>
{
   try
   {
      IWebElement query = driver.FindElement(By.Name("q"));
      query.SendKeys("selenium");
      return true;
   }
   catch (Exception e)
   {
      //add "SmartClick preconditions error: " + e.Message error message into your log file
      return false;
   }
});

Above I passed an anonymous method as a parameter but actually there are a lot of ways to use this delegate – and this is your choice how to do it. A couple of examples:

btnSearch.SmartClick();//preconditions are always true because precondition parameter is null by default
btnSearch.SmartClick(x => SomeMethod("blah", "blah-blah", "blah-blah-blah"));//invokes precondition method with parameters
CustomFunction myDelegate = SomeMethod;
btnSearch.SmartClick(myDelegate);//using the delegate instance

Step 4 – clicking the element. I mentioned that the method must perform different clicking actions on the web element according to input parameters. Therefore there must be additional parameter which specifies the action. You are free to implement it as you wish but I used an enumerator for it. I pass its value to the method and then handle it inside the switch operator.

Add a new enumerator inside the namespace

public enum ClickAction
{
   Click,
   Submit,
   DoubleClick,
   LeftMBClick,
   RightMBClick,
   JSClick,
   CursorClick,
   SendKeyEnter,
   SendKeyReturn,
   SendKeySpacebar
}

This is the list of clicking actions I sometimes use in relation to specific web elements. You can use some of them or implement your own ones. Keep it in mind that some of the actions used here require an instance of IWebDriver, therefore you have to pass it to the method as a parameter or to make it available for use inside the method.

Now update the method with new input parameters and inner implementation:

public static void SmartClick(this IWebElement element, IWebDriver driver = null, CustomFunction precondition = nullint iterations = 1, params ClickAction[] clicks)
   {
      int i = iterations;

      //if you do not specify what actions to use for clicking the following default actions will be used
      ClickAction[] clickActions = clicks != null ? clicks : new ClickAction[] { ClickAction.Click, ClickAction.LeftMBClick, ClickAction.JSClick };
   
      string elementName = element.Text;

      if (precondition == null)
         precondition = x => true; //if you did not specify the method let's assume it has passed (return true)
      
      while (i > 0)
      {
         i--;
         if (!precondition.Invoke())
         {
            //add string.Format("Preconditions for clicking '{0}' web element are not met", elementName) error message into your log file
            //screenshot
            continue; //there is no sense to do clicking action if precondition method has failed. Return back to the beginning of the loop and start again from the very beginning
         }
         if (element == null)   
        {
           //add 'Element is null' error message into your log file or throw NoSuchElement or NullReference exception   
        }  
        if(!element.Displayed)   
        {    
           //add 'Element is not displayed and cannot be clicked' error message into your log file      
          //you may add a screenshot here  
        }  
        if(!element.Enabled)   
        {  
           //add 'Element is not displayed and cannot be clicked' error message into your log file  
          //plus a screenshot  
        }
         foreach (var action in clickActions)
         {
            //here you may add string.Format("Clicking '{0}' web element with {1} click action", elementName, action) message into your log file
            switch (action)
            {
               case ClickAction.Click:
                  try
                  {
                      element.Click();
                  }
                  catch (Exception e)
                  {
                      //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.Submit:
                  try
                  {
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.DoubleClick:
                  try
                  {
                     var builder = new Actions(driver);
                     builder.DoubleClick().Build().Perform();
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  } 
                  break;
              
               case ClickAction.RightMBClick:
                  if (driver == null)
                  {
                     //"SmartClick clicking error: IWebDriver instance must be passed as a parameter and connot be null if you use clicking with right mouse button action" error message
                     break;
                  }
                  try
                  {
                      var builder = new Actions(driver);
                      builder.MoveToElement(element).ContextClick(element).Build().Perform();
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.JSClick: 
                  if (driver == null)
                  {
                     //"SmartClick clicking error: IWebDriver instance must be passed as a parameter and connot be null if you use clicking with JavaScript action" error message
                     break;
                  }
                  try
                  {
                     ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click()", element);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.CursorClick:
                  try
                  {
                     var X = element.Location.X;
                     var Y = element.Location.Y;
                     SetCursorPos(X, Y);
                     mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.SendKeyEnter:
                  try
                  {
                     element.SendKeys(Keys.Enter);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.SendKeyReturn:
                  try
                  {
                     element.SendKeys(Keys.Return);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.SendKeySpacebar:
                  try
                  {
                     element.SendKeys(Keys.Space);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
            }
            //For now this part of code is placed here just to avoid multiple clicking on the same web element. It will be replaced with Step 5 on the next stage
           if(true)
               break;
         }
      }      
   }

The ClickAction.CursorClick action requires the use of some additional variables. Add them to the Extensions class:

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern void mouse_event(uint dwFlags, int dx, int dy, uint cButtons, uint dwExtraInfo);

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern long SetCursorPos(int X, int Y);

private const int MOUSEEVENTF_LEFTDOWN = 0x02;
private const int MOUSEEVENTF_LEFTUP = 0x04;

Also you need to add reference to System.Drawing library and use following namespaces:

using System;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using OpenQA.Selenium.Interactions;
using System.Runtime.InteropServices;

Steps 5 – invoking a method passed as a parameter which verifies that the clicking action has been performed and the expected result matches the actual one.

To be sure that a clicking action has been successfully done and the expected result has occurred, you can pass one more function to the method and invoke it after every attempt to click the web element. This function has the same signature as the method which verifies preconditions. Let’s name it ‘postcondition’.

If you specify your own method which will verify post-click conditions or you can not to specify the method itself but pass IWebDriver instance to the method – in this case the method will use default actions defined in the method. If you specify neither post-click verification conditions method nor IWebDriver instance there no verification will be performed. Here is an example of how I implemented it, but you can change the functionality in your own way.

public static void SmartClick(this IWebElement element, IWebDriver driver = null, CustomFunction precondition = null, CustomFunction postcondition = nullint iterations = 1, params ClickAction[] clicks)
   {
      int i = iterations;

      //if you do not specify what actions to use for clicking the following default actions will be used
      ClickAction[] clickActions = clicks != null ? clicks : new ClickAction[] { ClickAction.Click, ClickAction.LeftMBClick, ClickAction.JSClick };
   
      string elementName = element.Text;

      if (precondition == null)
         precondition = x => true; //if you did not specify the method let's assume it has passed (return true)

      if (postcondition == null)
      {
         if (driver == null)
         {
            //"SmartClick: IWebDriver is null, no postcondition verification provided" message to log file
            postcondition = x => true//assume if you do not pass IWebDriver to the mathod you do not need any post-click conditions verification. Therefore, this method always returns true
         }
         else
         {
            //"SmartClick: No click verification conditions are specified. Default verification wil be used." message to log file
            var numOfWindows = driver.WindowHandles.Count;
            var pageHashCode = PageHashCode(driver);
            var pageTitle = driver.Title;
            var pageUrl = driver.Url;
            //the method returns true if any of the following conditions are met, otherwise it returns false
            postcondition = delegate
            {
               //page title is changed
               if (pageTitle != driver.Title)
               {
                  //log message "SmartClick: Click validation - Title of the page has changed"
                  //screenshot
                  return true;
               }
               //page url address is changed
               if (pageUrl != driver.Url)
               {
                  //log message "SmartClick: Click validation - Url address of the page has changed"
                  //screenshot
                  return true;
               }
               //alert pop-up is displayed
               try
               {
                  driver.SwitchTo().Alert();
                  //log message "SmartClick: "Element_ClickWhile: Click validation - Alert pop-up is displayed"
                  //screenshot
                  return true;
               }
               catch {  }
               //number of opened windows or tabs is changed
               if (numOfWindows != driver.WindowHandles.Count)
               {
                  //log message "SmartClick: "Element_ClickWhile: Click validation - Number of opened windows (tabs) has changed"
                  //screenshot
                  return true;
               }
               //page hash code is changed. It indicates that some changes has occurred on the page
               if (pageHashCode != PageHashCode(driver))
               {
                  //log message "SmartClick: "Element_ClickWhile: Click validation - Hash code of the page has changed"
                  //screenshot
                  return true;
               }
               return false;
            }
         }
      }
      
      while (i > 0)
      {
         i--;
         if (!precondition.Invoke())
         {
            //add string.Format("Preconditions for clicking '{0}' web element are not met", elementName) error message into your log file
            //screenshot
            continue; //there is no sense to do clicking action if precondition method has failed. Return back to the beginning of the loop and start again from the very beginning
         }
         if (element == null)   
         {   
            //add 'Element is null' error message into your log file or throw NoSuchElement or NullReference exception   
         }  
         if(!element.Displayed)   
         {   
            //add 'Element is not displayed and cannot be clicked' error message into your log file   
            //you may add a screenshot here  
         }  
         if(!element.Enabled)   
         {  
            //add 'Element is not displayed and cannot be clicked' error message into your log file  
            //plus a screenshot  
         }
         foreach (var action in clickActions)
         {
            //here you may add string.Format("Clicking '{0}' web element with {1} click action", elementName, action) message into your log file
            switch (action)
            {
               case ClickAction.Click:
                  try
                  {
                      element.Click();
                  }
                  catch (Exception e)
                  {
                      //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.Submit:
                  try
                  {
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.DoubleClick:
                  try
                  {
                     var builder = new Actions(driver);
                     builder.DoubleClick().Build().Perform();
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  } 
                  break;
              
               case ClickAction.RightMBClick:
                  if (driver == null)
                  {
                     //"SmartClick clicking error: IWebDriver instance must be passed as a parameter and connot be null if you use clicking with right mouse button action" error message
                     break;
                  }
                  try
                  {
                      var builder = new Actions(driver);
                      builder.MoveToElement(element).ContextClick(element).Build().Perform();
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.JSClick: 
                  if (driver == null)
                  {
                     //"SmartClick clicking error: IWebDriver instance must be passed as a parameter and connot be null if you use clicking with JavaScript action" error message
                     break;
                  }
                  try
                  {
                     ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click()", element);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.CursorClick:
                  try
                  {
                     var X = element.Location.X;
                     var Y = element.Location.Y;
                     SetCursorPos(X, Y);
                     mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.SendKeyEnter:
                  try
                  {
                     element.SendKeys(Keys.Enter);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.SendKeyReturn:
                  try
                  {
                     element.SendKeys(Keys.Return);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
               case ClickAction.SendKeySpacebar:
                  try
                  {
                     element.SendKeys(Keys.Space);
                  }
                  catch (Exception e)
                  {
                     //"SmartClick clicking error: " + e.Message error message
                  }
                  break;
            }
            if (postcondition())
               return;
            //add string.Format("Post-conditions for clicking '{0}' web element are not met", elementName) error message into your log file
            //screenshot
         }
      }      
   }

Note: In the code above I invoked a method named PageHashCode(). This is a custom method which gets html-code of the current page (IWebDriver.PageSource property) and computes its hash code. The result is returned as a string. Here is the code of the method. Put it into the Extensions class:

static string PageHashCode(IWebDriver driver)
{
   MD5 md5 = MD5.Create();
   byte[] bytesArray = Encoding.ASCII.GetBytes(driver.PageSource);
   byte[] hashCode = md5.ComputeHash(bytesArray);
   var sb = new StringBuilder();
   foreach (byte t in hashCode)
      sb.Append(t.ToString("X2"));
   return sb.ToString();
}

Step 6 – If the expected result is not met, it tries to clicking the elements with the next clicking action from the list.

You can implement it in many ways according to your objectives. For example, you may just add an error message to log file or your can throw an exception. You should add the code for this right after the switch() operator.

Also, I recommend to add one more input parameter to the method: a delay between the clicking attempts. Obviously, after a click on a web element it takes some time for something to happen. Usually, when you click a button, a request is sent to a server, and then the server sends a response which your browser handles and displays the final result. Sometimes it happens almost immediately and sometimes it may be a time-consuming process. So, you can pass a delay parameter (period of time in seconds during which the method should do nothing, assuming this time is enough for performing all the actions described above) with some default value, for example 1 second.

public static void SmartClick(this IWebElement element, IWebDriver driver = null, CustomFunction precondition = null, CustomFunction postcondition = nullint iterations = 1, int delay = 1, params ClickAction[] clicks)
{
   //method code
}

Put its use right after switch() operator

foreach (var action in clickActions)
{        
   ...
   switch (action)
   {
        ... 
   }
   Thread.Sleep(delay * 1000);
   if(postcondition())
      ...
}

Note: you may use global variables instead of the method parameters. For example, you can store such variables as delay or IWebDriver instance in external classes and have them visible all over the project. In that case, you do not need pass them as a parameter. Anyway, you are free to modify the method as you wish. This is just a common point of view, one of possible implementations.

In the next part, I will show how the method may be used in a test.

 

 

Smart click (part 3)

In this part I decided to show some examples of using the SmartClick() method just to make the way it works easier to understand.

Example 1 – the simplest. In 99% of cases you can use it without any parameters just like an IWebElement interface extension method:

public void GoogleSearch()
{
   IWebDriver driver = new FirefoxDriver();
   driver.Navigate().GoToUrl("http://www.google.com");
   IWebElement query, btnSearch;
   WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));

   query = driver.FindElement(By.Name("q"));
   query.Clear();
   query.SendKeys("selenium");

   btnSearch = driver.FindElement(By.Name("btnG"));

   btnSearch.SmartClick();

   wait.Until((d) => { return d.Title.StartsWith("selenium"); });
   Assert.AreEqual("selenium - Google Search", driver.Title);
}

In this case SmartClick works like native method Click() with some additional functionalities, such as logging and exception handling. It verifies that the element is not null, is visible and enabled, ignores the precondition method, uses the first click action from the list of default ones (in our case it is IWebElement.Click(), but you can replace it with any other) and finishes its work (because the postcondition method always returns true and no verification is provided). Therefore, SmartClick() is equal to Click() plus event messages logging plus handling exceptions.

Example 2 – using IWebDriver. You can invoke the method with instance of IWebDriver as a parameter to make click actions more robust

btnSearch.SmartClick(driver);

The method after clicking the btnSearch element verifies that any of the following events has occurred: title or url address or page source of the page has changed, number of opened tabs or windows has changed, or an alert message is displayed. If any of the conditions is met it means that the click action has been performed over btnSearch. If not – you will have an error message in your log file (or you can rewrite this behavior according to your needs).

Example 3. If you pass an additional parameter to the method – number of iterations – it will try clicking the element as many times as the iterations parameter specifies until one of the conditions above is met.

btnSearch.SmartClick(driver: driver, iterations: 3);

Example 4. Or if you pass different click actions, it will try clicking the btnSearch with all these actions until the conditions above are met:

btnSearch.SmartClick(driver: driver, clicks: ClickAction.Click, ClickAction.DoubleClick, ClickAction.JSClick);

Example 5 – using a precondition delegate. One more way to invoke the method is to pass a precondition method to it like this:

public void GoogleSearch()
{
   IWebDriver driver = new FirefoxDriver();
   driver.Navigate().GoToUrl("http://www.google.com");
   IWebElement query, btnSearch;
   WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));

   btnSearch = driver.FindElement(By.Name("btnG"));

   btnSearch.SmartClick(driver, x =>
      {
         try
         {
            query = driver.FindElement(By.Name("q"));
            query.Clear();
            query.SendKeys("selenium");
            return true;
         }
         catch (Exception e)
         {
            //add "SmartClick preconditions error: " + e.Message error message into your log file
            return false;
         }
      });

   wait.Until((d) => { return d.Title.StartsWith("selenium"); });
   Assert.AreEqual("selenium - Google Search", driver.Title);
}

Here the method does all actions inside the passed anonymous method first and then clicks btnSearch. In this example it is not very useful but it might help if btnSearch was disabled until you entered a query into the search field. In such case, you can perform some preconditional actions first and then click the button. It also may be useful if your web element is a part of a dropdown menu and you have to hover or click some parent menu item first to make the element visible.

Example 6. The same thing can be done with post-conditions:

public void GoogleSearch()
{
   IWebDriver driver = new FirefoxDriver();
   driver.Navigate().GoToUrl("http://www.google.com");
   IWebElement query, btnSearch;
   WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));

   query = driver.FindElement(By.Name("q"));
   query.Clear();
   query.SendKeys("selenium");

   btnSearch = driver.FindElement(By.Name("btnG"));

   btnSearch.SmartClick(postcondition: x => { return wait.Until((d) => { return d.Title.StartsWith("selenium"); }); });

   Assert.AreEqual("selenium - Google Search", driver.Title);
}

In the example above, the driver will try clicking btnSearch until the title of the current page starts with the ‘selenium’ word.

Example 7 – custom click actions. Invoking the method in the following manner:

btnSearch.SmartClick(clicks: ClickAction.JSClick);

will make SmartClick working like SmartClcik() without parameters (see details above) but only using JavaScript function for performing click action instead of native IWebElement.Click() action. Of course, you can pass any other action or a set of actions instead of ClickAction.JSClick.

Example 8.1 – passing a parametrized method as a parameter. The next example shows invoking SmartClick with a function as a parameter which has its own parameters. Assume you have some method in your test class (which matches to CustomFunction delegate signature – bool Method(params object[])). Here it is:

public bool EnterSearchCriteria(params object[] obj)
{
   if (!(obj[0] is IWebElement) || obj[0] == null)
      throw new Exception("Wrong parameter input");
   if (!(obj[1] is string) || string.IsNullOrEmpty((string)obj[1]))
      throw new Exception("Wrong parameter input");
   IWebElement query = (IWebElement)obj[0];
   query.Clear();
   query.SendKeys((string)obj[1]);
   return true;
}

And here is the example of its usage:

public void GoogleSearch()
{
   IWebDriver driver = new FirefoxDriver();
   driver.Navigate().GoToUrl("http://www.google.com");
   IWebElement query, btnSearch;
   WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));

   btnSearch = driver.FindElement(By.Name("btnG"));

   btnSearch.SmartClick(precondition: x => EnterSearchCriteria(driver.FindElement(By.Name("q")), "selenium"));

   wait.Until((d) => { return d.Title.StartsWith("selenium"); });
   Assert.AreEqual("selenium - Google Search", driver.Title);
}

Example 8.2. You can do the same thing with post-conditions as well. Assume you have another method with appropriate signature which waits for the page title to change:

public bool WaitForSearchResult(params object[] obj)
{
   if (!(obj[0] is IWebDriver) || obj[0] == null)
      throw new Exception("Wrong parameter input");
   if (!(obj[1] is string) || string.IsNullOrEmpty((string)obj[1]))
      throw new Exception("Wrong parameter input");
   WebDriverWait wait = new WebDriverWait((IWebDriver)obj[0], TimeSpan.FromSeconds(5));
   return wait.Until((d) => { return d.Title.StartsWith((string)obj[1]); });
}

You can invoke it in this way:

btnSearch.SmartClick(postcondition: x => WaitForSearchResult(driver, "selenium"), clicks: new[] { ClickAction.CursorClick, ClickAction.JSClick });

Example 9. In the example above there are multiple click actions are used in addition. You may combine all the method’s parameters as you wish to make the method working the way you need. For example:

btnSearch.SmartClick(driver, x => EnterSearchCriteria(query, "selenium"), x => { return wait.Until((d) => { return d.Title.StartsWith("selenium"); }); }, 2, 3, ClickAction.LeftMBClick, ClickAction.SendKeyEnter);

P.S. It is likely that you will never use all the method’s features, but actually it’s a good to know that you can.

 

Adjustment of testing environment via Registry

Sometimes  you have to adjust parameters of your testing environment before running your tests. It may be some security settings, turning on or off some plug-ins, settings of browsers or other applications. As an example let’s look at the settings of Internet Explorer browser. Using this browser many automation testers often face with the following error: “Unexpected error launching Internet Explorer. Protected Mode settings are not the same for all zones. Enable Protected Mode must be set to the same value (enabled or disabled) for all zones. (NoSuchDriver)” 12.1

This error occurs when Protected  Mode for different zones are not the same. This is old and known issue which is easy to handle off. To solve the issue you just have to turn on Protected Mode for all zones or turn it off. 12.2

You can do it manually or programmatically using InternetExplorerOptions class from OpenQA.Selenium.IE namespace. Here I took this issue as an example to show you how you can adjust IE setting and many other things changing values of your system registry. Default setting of Internet Explorer browser are not the same for all zones and every time you have set the IE to its default (this is recommended to do before running tests) you have to adjust Protected Mode again. And if you have to do it on remote machine and especially on many machines it becomes boring. Fortunately there is an elegant solution. All these settings (and many other) may be set programmatically. Before starting working Internet Explorer reads its settings from system registry. So, if you put correct values to the registry the browser will be parametrized just as you need. Now a couple of words how to work with system registry. С# has special class named Registry for that, this class is located in  Microsoft.Win32 namespace. There are a lot of examples of using this class in the Internet, so I do not see any sense to describe it here one more time. I just want to show you my own solution. You have to create additional application for working with registry. Create new solution in Visual Studio with a project of Console Application type. 12.3

Give a name for your namespace and class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Win32;

namespace Assign_Environment_Settings
{
    class Program
    {
        static void Main()
        {
        }
    }
}

Add enumerator RegistryBranch to class Programm. It will be used for switching between branches of system registry

internal enum Branches
{
    HKLM,
    HKCU
}

Add struct KeyValueDescr, it will be representing one value for the registry

internal struct KeyValueDescr
{
    internal string key;
    internal string value;
    internal string message;
}

Add struct RegistryEntry, this is one registry entry

internal struct RegistryEntry
{
    internal string path;
    internal KeyValueDescr[] values;
}

And now add a method for setting in a value to the registry

internal static void SetRegistryValue(Branches branch, RegistryEntry[] settings, int delay)
{
    //declaration of an instance of registry key
    RegistryKey key = null;

    //block using is utilized for closing the registry after work and releasing used resources as RegistryKey implements interface IDisposable. Details are here : (http://msdn.microsoft.com/en-us/library/system.idisposable.aspx)
    using (key)
    {
        //looking over all setting that should be set
        foreach (var setting in settings)
        {
            //switching between registry branches.
            switch (branch)
            {
                case Branches.HKLM:
                    key = Registry.LocalMachine;
                    break;
                case Branches.HKCU:
                    key = Registry.CurrentUser;
                    break;
            }
            try
            {
               if (key != null)
               {
                   key = key.OpenSubKey(setting.Path, true);
                   foreach (var v in setting.values)
                   {
                       if (key != null)
                       {
                            try
                            {
                                key.SetValue(v.key, v.value);
                                Console.WriteLine(v.message);
                            }
                            catch (Exception ex)
                           {
                               Console.WriteLine(ex.Message);
                           }
                       }
                      else
                          throw new Exception("Registry branch " + setting.Path + " does not exist or is not available for editing!");
                  }
              }
          }
          catch (Exception ex)
          {
              Console.WriteLine(ex.Message);
           }
           Thread.Sleep(delay);
        }
    }
    Console.WriteLine("Done!");
}

Now before the method will be invoked you should describe necessary settings (in our case this is Protected Mode in IE). Inside Main() method let’s declare and initialize the settings:

RegistryEntry[] ie_settings =
 {
     new RegistryEntry
     {
         path = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\1",
         values = new [] 
         {
             new KeyValueDescr
             {
                 key = "2500",
                 value = "0",
                 message = "Protected Mode for Local Intranet zone: enabled"
             }
         }
     },
     new RegistryEntry
     {
         path = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2",
         values = new [] 
         {
             new KeyValueDescr
             {
                 key = "2500",
                 value = "0",
                 message = "Protected Mode for Trusted Sites zone: enabled"
            }
        }
    },
    new RegistryEntry
    {
        path = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3",
        values = new [] 
        {
            new KeyValueDescr
            {
                key = "2500",
                value = "0",
                message = "Protected Mode for Internet zone: enabled"
            }
        }
    },
    new RegistryEntry
    {
        path = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\4",
        values = new [] 
        {
            new KeyValueDescr
            {
                key = "2500",
                value = "0",
                message = "Protected Mode for Restricted Sites: enabled"
            }
        }
    }
};

Now the method SetRegistryValues may be invoked:

const int delay = 1000;
Console.WriteLine("ENVIRONMENT SETTINGS\r\n");
Console.WriteLine("Starting to assign Internet Explorer settings...");
Console.WriteLine("------------------------------------------------");

SetRegistryValues(Branches.HKCU, ie_settings, delay);

Console.WriteLine("\r\nAll changes done!");
Thread.Sleep(delay * 10);

Now I will explain where the values used above are taken from. To know what values to use you should know what is changing in the registry when you do some actions in the system. The program RegFromApp can help. There are 64 and 32-bit versions of the program. Located here. There are good documentation of the program on the site. You can read it for some details. Here I describe only necessary for current project features. Open the application which you are going to watch for (in my case it is Internet Explorer browser). Select Tools -> Internet Options

12.4

Then Security tab

12.5

Adjust all settings you are about to change in wrong position. For example turn off Protected Mode for all zones. Now launch appropriate version of RegFromApp. In ‘select process to inspect’ window select process of the Internet Explorer – IEXPLORE.EXE.

12.6

Return back to IE and turn on Protected Mode for all zones, click Apply. In RegFromApp window you can see the following values:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\4] “2500”=dword:00000000

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2] “2500”=dword:00000000

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\1] “2500”=dword:00000000

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3] “2500”=dword:00000000 12.7

That’s it. You’ve got the path, key and value for all changed settings of Protected Mode. Later when you start the IE this values will be read by the browser from system registry. Now you can put this values in your RegistrySettings application, recompile the code and to launch it from your automated tests or manually. To run the program automatically invoke the code below right before running your tests. For example:

const int timeoutSec = 20;
var regSettings = new ProcessStartInfo
{
    FileName = "C:\\pathToTheProgram\\RegistrySettings.exe"
};
Process proc = Process.Start(regSettings);
int ticker = 0;
while (proc != null && (!proc.HasExited || ticker < timeoutSec))
{
    Thread.Sleep(1000);
    ticker++;
}

You can do it in a method with attribute [TestFixtureSetUp] or in a parent class of your test classes or via separate script. Playing with RegFromApp you can get a lot of additional information about system registry and create automated adjustments for other programs and applications that use the registry for storing their settings. Note that if current user of your operating system may have limited permissions for editing the registry settings. An attempt to change such values will throw an exception with error message like “Registry branch Softaware\Microsoft\Internet Explorer\Main does not exist or is not available for editing!”. To avoid it your current user should have system administrator permissions. Also sometimes the system firewall or antivirus program may deny you to change the registry or may require additional confirmation. It may be adjusted in settings of appropriate program.