Code & QA

Clickable WebElement class

In the Mapping project there is abstract class WebElement which is parent for all classes that represent web elements. All web elements are different but they may be divided to two main groups:

Elements which may be clicked (Clickable) – buttons, links, checkboxes, and images as links, menu items, drop-down list items and so on.

Elements which may not be clicked (non-Clickable). Sure, you can click on them but it does not arise any event and nothing will happen. It may be textboxes, pictures, labels, containers, etc.

For those elements which are clickable I created additional parental class ClickableWebElement. It is derived from WebElement and provides additional functionality common for all web elements that may be clicked.

/// <summary>

/// Represents web element which may be clicked or dragged on

/// </summary>

public abstract class ClicableWebElement : WebElement

{

//Constructors are empty, parent class constructors are used

protected ClicableWebElement(IWebDriver driver, string name, IWebElement element) : base(driver, name, element) { }

protected ClicableWebElement(IWebDriver driver, string name, int? waitTimeout = null, params ElementLocator[] locators) : base(driver, name, waitTimeout, locators) { }

//if True indicates that after clicking on the element all the page and web elements variables become invalid and must be recalculated for further usage. It usually occurs when after clicking the element new web page is loaded

internal bool InvalidatePageOnClick = true;

//delegates to a function without input parameters and return type Boolean.

//Assumed that if an action must be performed right before a clicking on the element you can describe the action in an anonymous method or assign an existent method which comply with given signature to this variable. If the variable is not null assigned method will be invoked right before click action

internal Func<bool> BeforeClickAction = null;

//Same as variable above but assumed that it stores logic for verification that clicking action on the element is successfully performed. You can assign your own logic to it or default functionality will be used. More about it in the description of Click() method

internal Func<bool> ClickValidationAction = null;

//Same as BeforeClickAction but this variable can store a method that neither receives input parameter nor returns a value. Invoked when clicking action is already performed

internal Action AfterClickAction = null;

internal Func<bool> BeforeDragNDropAction = null;

internal Func<bool> DragNDropValidationAction = null;

internal Action AfterDragNDropAction = null;

//keeps the array of enumerators which determine how Click() method is checking that click action is done

internal ClickValidator[] ClickValidator { get; set; }

//keeps the array of enumerators which determine how click actions is performed on the element in Click method

internal ClickAction[] ClickAction { get; set; }

public void Click() { }

public void DragNDrop(IWebElement target)

{

DragNDrop(target, 0, 0);

}

public void DragNDrop(WebElement target)

{

DragNDrop(target.Element, 0, 0);

}

public void DragNDrop(int x, int y)

{

DragNDrop(null, x, y);

}

private void DragNDrop(IWebElement target, int x, int y) { }

}

Click() and DragNDrop()

Below you can find my implementation of Click() method for performing click actions over different kind of web elements. I added some comments to the code in the most interesting blocks. More detailed description of the method and how the things work you can find by links

http://learnseleniumtesting.com/upgradingclick-superclick/

http://learnseleniumtesting.com/smartclickpart2/

http://learnseleniumtesting.com/smartclickpart3/

/// <summary>

/// Performs click action over web element

/// </summary>

public void Click()

{

//if the element is not found on the page there is nothing to click and any further action is unsencible

if (!IsFound)

{

Report.AddError(“Clicking ” + GetType() + ” ” + Name, GetType() + ” ” + Name + ” is displayed”, GetType() + ” ” + Name + ” is null”, Driver.TakeScreenshot(GetType() + ” ” + Name + ” is null”));

return;

}

try

{

//if a method is assigned to this delegate the method is invoked

if(BeforeClickAction != null)

BeforeClickAction.Invoke();

string pageHashCode = string.Empty;

int numOfWindows = 0;

var currentWindowHandles = Driver.WindowHandles;

string pageTitle = string.Empty;

string pageUrl = string.Empty;

//if no criteria is provided for checking that click action has been performed, the following criteria are set by default

if (ClickValidator == null)

ClickValidator = new[] { Mapping.ClickValidator.AlertDisplayed, Mapping.ClickValidator.NumberOfWindowsChanged, Mapping.ClickValidator.PageHashCodeChanged, Mapping.ClickValidator.PageTitleChanged, Mapping.ClickValidator.PageUrlChanged };

//when ClickValidator is initialized with default or custom values this block fill previously declared variables with appropriate values (if necessary). The values will be stored remembered for further comparison. Actually they keep state of a page(s) before clicking the element

foreach (var v in ClickValidator)

{

if (v == Mapping.ClickValidator.PageHashCodeChanged)

pageHashCode = Driver.GetPageHashCode();

else if (v == Mapping.ClickValidator.NumberOfWindowsChanged)

numOfWindows = Driver.WindowHandles.Count;

else if (v == Mapping.ClickValidator.PageTitleChanged)

pageTitle = Driver.Title;

else if (v == Mapping.ClickValidator.PageUrlChanged)

pageUrl = Driver.Url;

}

//you can assign to this delegate any other method but if it hasn’t been done the following anonymous method will be used for verification that some changes occurred on a page after click has been done. The method is based on those ClickValidator enumerators initialized above

if (ClickValidationAction == null)

ClickValidationAction = delegate

{

foreach (var clickValidator in ClickValidator)

{

switch (clickValidator)

{

case Mapping.ClickValidator.AlertDisplayed:

if (Driver.IsAlertPresent())

{

Report.AddInfo(“Alert message is displayed”, Driver.TakeScreenshot(“Alert displayed”));

return true;

}

break;

case Mapping.ClickValidator.NumberOfWindowsChanged:

if (numOfWindows != Driver.WindowHandles.Count)

{

var windowsHandles = Driver.WindowHandles.Except(currentWindowHandles) as IList<string> ?? Driver.WindowHandles.Except(currentWindowHandles).ToList();

string h = !windowsHandles.Any() ? Driver.WindowHandles.Last() : windowsHandles.Last();

Driver.SwitchTo().Window(h);

Report.AddInfo(“Number of opened windows is changed. The driver is switched to the last opened window”, string.Empty, Driver.TakeScreenshot(“Driver is switched to another tab”));

return true;

}

break;

case Mapping.ClickValidator.PageHashCodeChanged:

if (pageHashCode != Driver.GetPageHashCode())

{

Report.AddInfo(“Hash code of the page has changed”);

return true;

}

break;

case Mapping.ClickValidator.PageTitleChanged:

if (pageTitle != Driver.Title)

{

Report.AddInfo(“Title of the page has changed”);

return true;

}

break;

case Mapping.ClickValidator.PageUrlChanged:

if (pageUrl != Driver.Url)

{

Report.AddInfo(“Url address of the page has changed”);

return true;

}

break;

}

}

return false;

};

//if none of click actions is specified the following click methods will be applied to the element by default

if (ClickAction == null)

ClickAction = new[] { Mapping.ClickAction.Click, Mapping.ClickAction.MouseLbClick };

//Performing click action

foreach(var click in ClickAction)

{

Report.AddInfo(string.Format(“Clicking {0} {1}. ClickAction is {2}”, GetType().Name, Name, click));

IMouse mouse;

ILocatable signinloc;

var builder = new Actions(Driver);

switch (click)

{

case Mapping.ClickAction.Click:

Element.Click();

break;

case Mapping.ClickAction.DoubleClick:

builder.DoubleClick().Build().Perform();

break;

case Mapping.ClickAction.Hover:

mouse = ((IHasInputDevices) Driver).Mouse;

signinloc = (ILocatable) Element;

mouse.MouseMove(signinloc.Coordinates);

break;

case Mapping.ClickAction.JsClick:

((IJavaScriptExecutor) Driver).ExecuteScript(“arguments[0].click()”, Element);

break;

case Mapping.ClickAction.MouseLbClick:

mouse = ((IHasInputDevices) Driver).Mouse;

signinloc = (ILocatable) Element;

mouse.MouseMove(signinloc.Coordinates);

mouse.Click(signinloc.Coordinates);

break;

case Mapping.ClickAction.MouseRbClick:

builder.MoveToElement(Element).ContextClick(Element);

builder.Build().Perform();

break;

case Mapping.ClickAction.SendKeyEnter:

Element.SendKeys(Keys.Enter);

break;

case Mapping.ClickAction.SendKeyReturn:

Element.SendKeys(Keys.Return);

break;

case Mapping.ClickAction.SendKeySpacebar:

Element.SendKeys(Keys.Space);

break;

}

Thread.Sleep(Globals.timeoutBetweenClicks);

if (ClickValidationAction.Invoke())

{

if(InvalidatePageOnClick)

WebApplication.IsValid = false;

break;

}

}

if (AfterClickAction != null)

AfterClickAction.Invoke();

Report.AddInfo(“Click action is performed successfully.”, string.Empty, Driver.TakeScreenshot(“Clicking ” + GetType().Name + ” ” + Name));

}

catch (Exception ex)

{

Report.AddError(“Clicking ” + GetType().Name + ” ” + Name, “Click action is performed”, ex.Message, Driver.TakeScreenshot(“Clicking ” + GetType().Name + ” ” + Name));

}

}

DragNDrop() method is similar to Click(). But only instead of clicking on the element the method moves it to specified position on a page. The method has a couple of overloaded versions for different sets of input parameters. Though actually, all the versions of the method invoke the same extended private version of it. Here it is:

The method moves the element from its current position to new place on a page. It may be done in two ways – dragging the element over another web element or moving the element to specified point on the screen. Therefore, the method takes as input parameters an instance of IWebElement – target element for dragging and two coordinates X and Y – target point for dragging.

/// <summary>

/// moves the element from its current position to new place on a page

/// </summary>

/// <param name=”target”>instance of IWebElement over which current element must be placed</param>

/// <param name=”x”>X coordinate of new position</param>

/// <param name=”y”>Y coordinate of new position</param>

private void DragNDrop(IWebElement target, int x, int y)

{

//if source element is not found there is nothing to drag and drop

if (!IsFound)

{

Report.AddError(MethodBase.GetCurrentMethod().Name + ” error: failed to find web element”, Driver.TakeScreenshot(GetType() + ” ” + Name + ” is null”));

return;

}

try

{

//if any method is assigned to this delegate the method is invoked

if (BeforeDragNDropAction != null)

BeforeDragNDropAction.Invoke();

//these three delegates are assigned with three anonymous method for performing drag’n’drop action on web element

//method 1 moves source web element on specified distance by x and y axis. The distance is calculated as target point minus current element’s position by axis.

Action method1 = () =>

{

var builder = new Actions(Driver);

builder.ClickAndHold(Element).DragAndDropToOffset(Element, x – Element.Location.X, y – Element.Location.Y).Build().Perform();

};

//method 2 moves sources element to specified coordinates using mouse cursor instance

Action method2 = () =>

{

var mouse = ((IHasInputDevices)Driver).Mouse;

var sourceElement = (ILocatable)Element;

var targetElement = (ILocatable)target;

mouse.MouseMove(sourceElement.Coordinates);

Thread.Sleep(500);

mouse.MouseDown(sourceElement.Coordinates);

mouse.MouseMove(targetElement.Coordinates);

Thread.Sleep(100);

mouse.MouseUp(targetElement.Coordinates);

};

//method 3 moves sources element over target element and then releases it

Action method3 = () =>

{

var builder = new Actions(Driver);

builder.ClickAndHold(Element).MoveToElement(target);

builder.Release(target).Build().Perform();

};

//one of the methods above is invoked in accordance method input parameters

if (target != null)

{

Report.AddInfo(“Trying to drag and drop ” + GetType() + Name + ” to coordinates ” + target.Location.X + “, ” + target.Location.Y + “. Attempt #1”);

method3.Invoke();

if (!DragNDropValidationAction.Invoke())

{

Report.AddInfo(“Trying to drag and drop ” + GetType() + Name + ” to coordinates ” + target.Location.X + “, ” + target.Location.Y + “. Attempt #2”);

method2.Invoke();

if (!DragNDropValidationAction.Invoke())

{

Report.AddWarning(“Failed to drag and drop ” + GetType() + Name + ” to coordinates ” + target.Location.X + “, ” + target.Location.Y, GetType().Name + ” ” + Name + ” is moved to target coordinates”, “DragNDropValidatinAction method returned false”, Driver.TakeScreenshot(“DragNDrop ” + GetType().Name + “_” + Name));

return;

}

}

}

else

{

Report.AddInfo(“Trying to drag and drop ” + GetType() + Name + ” to coordinates ” + x + “, ” + y + “. Attempt #1”);

method1.Invoke();

if (!DragNDropValidationAction.Invoke())

{

Report.AddWarning(“Failed to drag and drop ” + GetType() + Name + ” to coordinates ” + x + “, ” + y, GetType().Name + ” ” + Name + ” is moved to target coordinates”, “DragNDropValidatinAction method returned false”, Driver.TakeScreenshot(“DragNDrop ” + GetType().Name + ” ” + Name));

return;

}

}

if (AfterDragNDropAction != null)

AfterDragNDropAction.Invoke();

}

catch (Exception ex)

{

Report.AddWarning(“Failed to drag and drop ” + GetType() + Name, “Drag and drop action is performed”, ex.Message);

}

}

Both methods, Click and DragNDrop are pretty slow because they are performing a lot of additional actions which do basic actions more robust. For example, Click() invokes pre- and post-click operations, it also is doing number of verifications, performs different click actions, records logs. You can remove some functionality from the methods to make them quicker but keep in mind that cutting parts of methods’ functionality makes them less stable.

[row]
[column lg=”4″ md=”12″ sm=”12″ xs=”12″ ]
More extension methods [/column]
[column lg=”4″ md=”12″ sm=”12″ xs=”12″ ]
Table Of Content
[/column]
[column lg=”4″ md=”12″ sm=”12″ xs=”12″ ]
Classes of pages. IsLoaded property
[/column]
[/row]

Leave a Reply