RyanSchlomer.com

Sr QA Consultant

Wrapping HTML Elements for Better Tests

Posted by:

|

On:

|

Wrapping HTML Elements

In automation testing, particularly for web applications, how you interact with HTML elements is crucial. Wrapping HTML elements—encapsulating them within custom classes or methods—offers several benefits that enhance your test suite’s effectiveness.

Readability and Maintainability

Wrapped elements simplify your code by replacing complex selectors with descriptive method names, making it easier to read and understand. It’s like a car. You don’t have to rebuild the engine every time you drive your car. Just step on the gas and you go. Push the break and you stop. Turn the wheel to turn. Simple. When UI changes occur, updating the selectors becomes a one-time task, significantly improving maintainability.

Reusability and Robustness

Creating wrappers for HTML elements allows you to reuse them across multiple test cases, ensuring consistency and saving time. These wrappers can also include built-in logic for waits or retries, making your tests more robust and less prone to flakiness.

Practical Application

Let’s look at DemoBlaze, a mock website for testing. You notice right away on that website that there is a top menu and a footer section on every page. These are great candidates for creating classes for these elements. The table in the middle with the products is also a great candidate since there are multiple Categories and multiple pages of data in the table. A table is almost always a good candidate to wrap because you can implement your table class to handle all types of tables on your web pages.

The TopMenu class is below. A few things to notice:

  1. This class wraps the “navbarExample” element.
  2. I hard-coded the Id to “navbarExample”, but this id probably won’t change from page to page. If it did, send the Id in the constructor.
  3. Notice how I always get the MenuElement anytime a child element is referenced. This keeps elements from throwing the StaleElementReferenceException exception.
  4. I am using ExpectedConditions.ElementIsVisible before getting the MenuElement. However, this is a good place for retry logic as well. I just didn’t add it in.
  5. Get items by index and not text when interacting with a list of items. This is good practice for verifying items are in the correct order.
  6. GetMenuItemNamesAndIndexes() could be outside the class and in the test itself. It compiles a list of the menu items for verification.
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.WaitHelpers;
using System.Collections.ObjectModel;

namespace demoblaze
{
    public class TopMenu
    {
        private readonly IWebDriver _driver;
        private readonly WebDriverWait _wait;

        public TopMenu(IWebDriver driver)
        {
            _driver = driver;
            _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10));
        }

        public IWebElement MenuElement
        {
            get
            {
                ElementIsVisible(By.Id("navbarExample"));
                return _driver.FindElement(By.Id("navbarExample"));
            }
        }

        public ReadOnlyCollection<IWebElement> MenuItems
        {
            get
            {
                return MenuElement.FindElements(By.TagName("li"));
            }
        }

        public IWebElement GetMenuItemByIndex(int index)
        {
            return MenuItems[index];
        }

        public void ClickMenuItemByIndex(int index)
        {
            GetMenuItemByIndex(index).Click();
        }

        public Dictionary<string, int> GetMenuItemNamesAndIndexes()
        {
            Dictionary<string, int> menuItemNamesAndIndexes = new Dictionary<string, int>();
            var menuItems = MenuItems;
            for (int i = 0; i < menuItems.Count; i++)
            {
                string name = menuItems[i].Text;
                menuItemNamesAndIndexes[name] = i;
            }
            return menuItemNamesAndIndexes;
        }

        public IWebElement ElementIsVisible(By by)
        {
            return _wait.Until(ExpectedConditions.ElementIsVisible(by));
        }
    }
}

Let’s look at how this class would be used in a test.

  1. I haven’t gone over the driver details, but for now, the test maximizes the Edge browser and navigates to DemoBlaze.
  2. Instantiate the top menu.
  3. Get a list of the menu items and most likely verify the list.
  4. Navigate to the Cart page.
  5. Sleep???? Are you kidding me? This is for testing purposes only until I implement my WaitForPageToLoad method.
  6. Navigate to the Home page

Notice that I did not have to get the “navBarExample” element outside the class after the page postback. That’s the benefit of wrapping that HTML element.

[TestMethod]
        public void TestA()
        {
            _driver.Manage().Window.Maximize();
            _driver.Url = "https://www.demoblaze.com/";

            TopMenu topMenu = new TopMenu(_driver);

            Dictionary<string, int> menuItems = topMenu.GetMenuItemNamesAndIndexes();
            //Do something here, like verify the menu items are correct

            topMenu.ClickMenuItemByIndex(3); //Cart

            Thread.Sleep(2000);

            topMenu.ClickMenuItemByIndex(0); //Home
        }

In summary, wrapping HTML elements in your automation tests leads to more readable, maintainable, reusable, and robust test suites. It’s a practice that pays off in long-term efficiency and reliability.