{{announcement.body}}
{{announcement.title}}

Full-Stack Test Automation Frameworks — API Usability, Part 2

DZone 's Guide to

Full-Stack Test Automation Frameworks — API Usability, Part 2

In this article, we continue to discuss API usability in full-stack test automation frameworks and framework features that are beneficial for testing it.

· DevOps Zone ·
Free Resource

In the last article from the series, we talked about API usability, one of the must-have features of full-stack test automation frameworks. Here, I am going to show you how API usability goes hand-in-hand with extensibility features. You will see examples of how to create additional element locators and waits. Additionally, we are going to talk about typified elements for accelerating tests development and making tests more readable.

NOTE: You cannot just copy-paste or use most of the examples directly unless you use the Bellatrix framework. However, you can get lots of ideas on how you can make your test automation framework easier to use, extensible, and customizable.

Add New Find Locators

In the last article, I showed you how element locators could be improved in 5th-generation test automation frameworks. As mentioned, API usability should come with the flexibility of extending the framework. Part of this is being able to add new locators, which is an almost impossible job in vanilla WebDriver ("vanilla" means examples that use only standard WebDriver code without anything else).

To skip code duplication and complex locators, you may need to add a custom find locator. It is relatively easy to do with Bellatrix.

Here is a sample implementation of locator for finding all elements starting with ID. First, we need to create the By locator.

public class ByIdEndingWith : By
{
    public ByIdEndingWith(string value)
        : base(value)
    {
    }
    public override OpenQA.Selenium.By Convert() 
    {
        return OpenQA.Selenium.By.CssSelector($"[id^='{Value}']");
    }
}

In the Convert method, we use standard WebDriver By locator. In this case, we implement our requirement through a little CSS.

To ease the usage of the locator, we need to create an extension method of ElementCreateService.

public static TElement CreateByIdStartingWith<TElement>(this ElementCreateService repository, string idStarting)
    where TElement : Element
{
    repository.Create<TElement, ByIdStartingWith>(new ByIdStartingWith(idStarting));
}

This is everything after that you can use your new locator as it was originally part of Bellatrix.

Add New Element Wait Methods

In the last article, we talked how difficult can be to wait for elements with vanilla WebDriver. I showed you how we elegantly could solve this problem with ToBe extension wait methods. Here, I will continue with the subject by showing you how to add custom wait methods.

Imagine that you want to wait for an element to have a specific style.

First, create a new class and inherit BaseUntil class.

public class UntilHasStyle : BaseUntil
{
    private readonly string _elementStyle;
    public UntilHasStyle(string elementStyle, int? timeoutInterval = null, int? sleepInterval = null)
        : base(timeoutInterval, sleepInterval)
    {
        _elementStyle = elementStyle;
    }
    public override void WaitUntil<TBy>(TBy by)
    {
       WaitUntil(ElementHasStyle(WrappedWebDriver, by), TimeoutInterval, SleepInterval);
    }
    private Func<IWebDriver, bool> ElementHasStyle<TBy>(ISearchContext searchContext, TBy by)
        where TBy : By
     {
        return driver =>
        {
            try
            {
                var element = FindElement(searchContext, by);
                return element != null && element.GetAttribute("style").Equals(_elementStyle);
            }
            catch (StaleElementReferenceException)
            {
                return false;
            }
        };
     }    
}

The important part is located in the ElementHasStyle function. There we find the element and check the current value in the style attribute. The internal WaitUntil will wait until the value changes in the specified time.

The next and final step is to create an extension method for all UI elements.

public static TElementType ToHasStyle<TElementType>(
    this TElementType element,
    string style, 
    int? timeoutInterval = null,
    int? sleepInterval = null)
    where TElementType : Element
{
    var until = new UntilHasStyle(style, timeoutInterval, sleepInterval);
    element.EnsureState(until);
    return element;
}

After UntilHasStyle is created is important to be passed to element’s EnsureState method.

From now on the method is available as it was originally part of the framework.

Typified Elements

Since WebDriver is not a test framework, it is written in most general manner. All web elements share common IWebElement interface, containing the same methods and properties.

For example, you use the SendKeys method to type text in a text field, clicking a button using Enter key or uploading a file. Or why do you need the Click method for DIV or text field?

Vanilla WebDriver Example

IWebElement agreeCheckBox = driver.FindElement(By.Id("agreeChB"));
agreeCheckBox.Click();
IWebElement firstNameTextField = driver.FindElement(By.Id("firstName"));
firstNameTextField.SendKeys("John");
IWebElement avatarUpload = driver.FindElement(By.Id("uploadAvatar"));
avatarUpload.SendKeys("pathTomyAvatar.jpg");
IWebElement saveBtn = driver.FindElement(By.Id("saveBtn"));
agreeCheckBox.SendKeys(Keys.Enter);

One more thing, if you don’t use suffixes in the names of your variables or properties, you won’t know what type of elements they are. In case you want to perform specific verifications, this might be important. For example, anchor web elements cannot be disabled, but buttons can.

Bellatrix offers over 30 strongly typed web controls such as anchor, button, text field, checkbox, etc. Each specific control contains well-named actions and properties that are related to it.

Bellatrix Example

CheckBox agreeCheckBox = App.ElementCreateService.CreateById<CheckBox>("agreeChB");
agreeCheckBox.Check();
TextField firstNameTextField = App.ElementCreateService.CreateById<TextField>("firstName");
firstNameTextField.SetText("John");
InputFile avatarUpload = App.ElementCreateService.CreateById<InputFile>("uploadAvatar");
avatarUpload.Upload("pathTomyAvatar.jpg");
Button saveBtn = App.ElementCreateService.CreateById<Button>("saveBtn");
saveBtn.ClickByEnter();

While reading the code, you can quickly find what is the type of the elements since it is the first thing you see. Moreover, the specific action methods and properties make the code more self-explanatory.

Instead of using Selenium.Support package for selecting elements, we created a separate web control for the job.

Bellatrix Example

Select billingCountry = App.ElementCreateService.CreateById<Select>("billing_country");
billingCountry.SelectByText("Bulgaria");

Here is the same example written with vanilla WebDriver. First, you need to install an additional package: Selenium.Support.

Vanilla WebDriver Example

IWebDriver driver;
IWebElement billingCountry = driver.FindElement(By.Id("billing_country"));
var billingCountrySelectElement = new SelectElement(billingCountry);
billingCountrySelectElement.SelectByText("Bulgaria");

We bring new useful methods to default controls, such as Focus and Hover.

Bellatrix Example

var confirmBtn = App.ElementCreateService.CreateById<Button>("confirm");
confirmBtn.Hover();
confirmBtn.Focus();

To hover an element directly with WebDriver, you can use the following code.

Vanilla WebDriver Example

IWebDriver driver;
IJavaScriptExecutor jsDriver = (IJavaScriptExecutor)driver;
jsDriver.ExecuteScript("arguments[0].onmouseover();", element);

It is not very user-friendly, don't you think? Nor readable.

See a similar example of how to focus an element with vanilla WebDriver:

Vanilla WebDriver Example

IWebDriver driver;
IJavaScriptExecutor jsDriver = (IJavaScriptExecutor)driver;
jsDriver.ExecuteScript("arguments[0].focus();", element);

The most important attributes of each web control are included and their assertion alternatives.

Bellatrix Example

var confirmBtn = App.ElementCreateService.CreateById<Button>("confirm");
confirmBtn.EnsureAccessKeyIs(5);
var productQuantity = App.ElementCreateService.CreateById<Number>("qt_number");
productQuantity.EnsureMaxIs(5);


Automating HTML 5 Web Controls

Test automation of framework API usability can be further enhanced through the introduction of HTML 5 web controls. These controls cannot be automated out-of-the-box with vanilla WebDriver.

Bellatrix Example

Phone billingPhone = App.ElementCreateService.CreateById<Phone>("billing_phone");
billingPhone.SetPhone("+00359894646464");
Color carColor = App.ElementCreateService.CreateById<Color>("car_color");
carColor.SetColor("#f00030");
Time timeElement = App.ElementCreateService.CreateById<Time>("time");
timeElement.SetTime(12, 12);

To set time in HTML 5 controls using WebDriver, you can write something like below.

Vanilla WebDriver Example

void SetTime(int hours, int minutes)
{
    IWebDriver driver;
    IJavaScriptExecutor jsDriver = (IJavaScriptExecutor)driver;
    jsDriver.ExecuteScript("arguments[0].setAttribute(value, arguments[1]);", element, $"{hours}:{minutes}:00");
}


Summary

I showed you how API usability goes hand-in-hand with API extensibility. You learned how you can add new find locators and element wait methods. Also, we talked about typified elements and how they can significantly improve the API user-friendliness and readability of your tests, hiding low-level unreadable vanilla WebDriver code. In the next article, I will show you how you can improve the readability of your tests though BDD logging using more extensibility features.

Topics:
devops ,automated testing ,test automation ,tutorial ,usability testing ,api

Published at DZone with permission of Anton Angelov , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}