Dark side of a Software Development Engineer in Test (SDET)

A lot of people have an opinion that a software development engineer in test is a more difficult, more responsible, more skilled position than that of an automation QA engineer who is actually just a ‘tester’. It’s because the SDET is almost a software developer: he deeply knows programming languages, design patterns, algorithms. He can create a high-quality, maintainable, and performant code. He creates tools for automated testing, writes his own frameworks – such kind of work is more complicated and, therefore, should be better-paying. That’s why many automation test engineers like to name themselves an SDET, alluding to their high skills and value.

Sure, everything said above is true, if and only if the SDET also has a deep knowledge of software testing methodologies and APPLIES it to his work. Unfortunately, in real life you can see the following situation:

There are two QA teams on a project – manual testers and automation testers (who proudly consider themselves SDETs). The manual team creates a set of test cases and passes them to the auto-team for automation. The automation team develops its own framework, converts the test cases to automated ones, writes test scripts, and runs them.

As you can see, there are two different roles: 1 – the team of high skilled manual testers who write test cases and are responsible for the product quality, 2 – high skilled developers who convert manual test cases to automated scripts. Obviously, the manual test cases and the programming code will be much qualitative in terms of an architecture and design.

But, there are also some cons:

When the automation team receives manual test cases as an input and produces automated scripts as an output only, it means that the team is not responsible for the product quality. It’s because they cannot be responsible for the product test coverage, test strategy, or test design quality – they do not do this. And it also means that the AUTO-TEAM is the only one in the project who IS NOT RESPONSIBLE FOR THE PRODUCT QUALITY!!! And therefore it is not motivated to give the product of high quality. Their goal is tons of test reports marked ‘passed’.

Simple example. Assume there is a defect found in the production branch.

Customer: why has it happened and who is responsible for that?
Manual QA: I created a fine test case that covers everything you need, including the found defect. Then I passed the test case to the automation team. They converted the test case and ran it.
Automation QA: I covered all the requirements and all are reports are green (all the tests are passed).
Customer: But there is a defect!
Manual QA: If you run my test cases manually, you will catch the defect.
Automation QA: Our tools cannot catch such kind of bugs and It’s not my fault that the manual QA did not foresee that and did not provide a more suitable scenario. All my scripts exactly repeat the test cases, every single step.

To avoid a problem similar to the one described above, the SDET must have and apply his/her knowledge of manual software testing. He must be at the heart of the software development process as the customer’s advocate. In addition, he must be responsible for the product quality. So, any SDET must have a very deep knowledge of manual testing and preferably some working experience in this position. Sure, now he does pure automation and writes the code like a developer but he also must take part in the development of the test strategy, create his own test plans and test cases and be responsible for his part of the work.

If you hear someone calling himself an SDET, whereas his function is limited to the development of testing tools, you should know that this person must have come to the software testing from the development field (or always dreamt to be a developer but lacks qualification) and has an insufficient knowledge of software testing and reluctance to study it.

So, make your own conclusions but note that everything written above is only my personal opinion.

Read More

Smart click (part 1)

When using the Selenium Webdriver tool for testing web-based applications, I faced a problem: sometimes the common Click() method does not work for specific elements. Or, for example, it may work fine in the FF browser and fails to click in Internet Explorer. After a little investigation, I noticed that instead of the Click() method I can use methods from the Actions class, or I can click elements with JavaScript. But I also discovered that I have to use one action for FF and another for IE – often an action worked with one browser but failed with the other. I had to add extra logic for specific elements and it began spreading some mess in my code.

Then a couple of months later “funny” things started to happen regularly. Guys from Mozilla Corp. or Microsoft delivered a new version of their browser. The old version of Selenium Webdriver didn’t support this new browser, so I had to upgrade it to the latest one. But oops… most of my specific clicks turned out to work conversely. Or did not work at all, and I had to return to the common Click() method. Chaos in my code was about to come.

That’s why I started looking for a method that would work consistently with any browser, webdriver, or elements and would also give me some additional helpful features like events logging with screenshots, handling exceptions, coded-behind verification, etc. Finally, I realized I had to invent it myself.

I asked myself what I do when I click a web element (assume a button) in a browser manually and came up with the following list:

  1. I determine what the button I am going to click.
  2. I am sure if I can click the button. I check if it is present on the page, is displayed, is enabled for a click, etc. In other words, I verify conditions for a successful click.
  3. Then I click the button.
  4. I verify that click has been successful, that some changes has occurred – expected or not – even if I’ve got an error, it means that the click itself has happened. So, I verify conditions after the click.

I do all the thing from the list above unconsciously. So does everyone else.

So I decided to write my own version of the Click() method reproducing all my manual actions for clicking a button in real life. Here is the recipe:

If you have separate classes for different types of web elements in your testing framework, such as buttons or links, you can add the method Click() to this class(es) and use a private IWebElement instance defined inside the class for interactions. For example:

public class WebButton
{    
    private IWebElement element;
    public WebButton(/*constructor parameters*/) 
    {
        element = //instance of IWebElement according to constructor parameters
        //bla-bla
    }

    public void Click(/*possible parameters*/)
    {
        //logic for clicking the element in a smart way
    }
}

If you use an ordinary IWebElement interface for describing web elements, you can create an extension method for the interface. Like this:

public static class Extensions
{
    public static void Click(this IWebElement buttonToClick, /*possible parameters*/)
    {
        //logic for smart click on buttonToClick
    } 
}

You will be able to invoke the methods in your tests in this way:

//1st case
WebButton button = new WebButton(/*constructor parameters*/);
button.Click();

//2nd case
IWebElement button = driver.FindElement(By.Id("buttonID"));
button.Click();

 

 

Read More

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.

 

 

Read More