I am using Selenium to develop automated test cases, some of which involve filling out forms with dozens of input fields. At the moment, I am following the Page Object Model/Page Factory design pattern, meaning that my page classes involve declaring all of the necessary elements using the @FindBy annotation and include methods for interacting with each of these elements.

However, after actually writing the page classes for some of these tests, I am wondering if it might be more efficient for me to use an Excel file to store locator information instead.

i.e. Instead of creating a page class where I declare 20 WebElements and write 20 methods for interacting with these elements, I use an Excel file containing the locator information for these elements, and write one for loop iterating across all the columns to locate an element and apply an action to it. What are the pros and cons of using this kind of method instead? I asked a question earlier about performance discrepancies between the two, but now am wondering about the amount of code that actually needs to be written, readability, etc.

up vote 2 down vote accepted

I use an extremely similar design idea, however, I'm using the node.js selenium-webdriver library, and I store the page object data in JSON files.

So some of this may be less applicable to your case, however, I can speak with accuracy to the general concept and how it's worked for my team.

Pros:

  • Adding new, similar elements is easy
  • May open maintenance to less technical members of the team
  • If you do not frequently add new types of UI elements, ongoing maintenance is very limited, and code is very easily shared.

Cons:

  • Adding new types of elements requires a lot more glue to support new ideas about selection and interaction
  • If you end up hacking a solution for a one-off case, you suffer a dramatic impact to test readability
  • You will likely eventually discover some weird edge case that forces you to re-think how you structured your code and/or your page object schema altogether, and that will probably catch you by surprise.
  • The complex bits of your code become centralized in a handful of locations. This is sometimes a pro, and sometimes not, but it hurts pretty bad when it bites you.

I would recommend this method strongly if:

  • You have an old project with a well defined product
  • You have less technical testers interested in helping out with browser automation tests
  • You have an application that very rarely introduces new UI concepts.
  • The overwhelming majority of your features are new variations on things your app has done before, this method works quite well.

This could be a bad idea if:

  • You have a newer project
  • You have a project that focuses strongly on a wide variety of element interactions
  • You frequently break ground on newer, more complicated UI features
  • Your product is less clearly defined

As an aside, I'd probably recommend steering away from XLS/XLSX files as the source for this kind of thing because they don't provide very nice diffs in version control, which will eventually become an issue. CSV, or some other plain-text format works much better.

Overall, I've had a great deal of success with this method, but that is contingent entirely upon the fact that my project checks every single box in the 'recommend strongly' field.

When my app does introduce more complex UI interactions, which happens rarely, test authorship grinds to an immediate halt while I try to sort out how to add support for new interactions, and the length of time that ends up taking is heavily related to how quickly development settles on a design for the new widget.

Overall, the negative side effects haven't had a strong impact on our release velocity (due mostly to the fact that falling back to bespoke interactions for a handful of elements isn't that hard, it just looks ugly), but they can absolutely ruin the accuracy of my estimates.

If you're on a young project, I'd steer clear of this sort of idea until the dust settles a bit.

If you're on an especially dynamic project - Don't do it.

  • Thank you for the detailed reply. Due to the nature of my project I am leaning toward storing page object data in a .csv or similar file. However, I am unsure how/if this method would be compatible with the POM design pattern, which I have been using thus far. Because the purpose of each page class is to store web element locator info, would there even be a purpose to creating page classes if all of this data is to be stored elsewhere? – A. K Nov 14 at 14:05
  • 1
    @A.K - No, you end up basically making a wrapper class over a hashmap associating some kind of key to an object containing a locator string, and some kind of 'type' identifier to define what methods are available for interaction. Your glue code is going to be more complex than mine - JS made this very easy, I imagine more plumbing is required for a concrete type system, but it's definitely feasible. – Iron Gremlin Nov 14 at 16:44

On using @Findby annotation, I suggest you go with object repository where all the web-element address is located all at one place. It's easier and maintainable.

For a small/medium project, try to store the web-element address in a property/(.ini) file, where you can mention web-element locator type (like xpath, id, classname etc,) and web-element locator value (like //input[@id='username']

Eg.: Login_page_btn_username = xpath:>://input[@id='username']

############################### Login PAGE ###################################################################
[LOGINPAGE]

LP_txtbx_username = id:>:login-email

LP_txtbx_password = xpath:>://input[@id='login-password']

LP_btn_submit = xpath:>://input[@id='login-submit']

LP_txt_invalid_login = xpath:>://strong[contains(text(),'There were one or more errors in your submission. ')]

And you can create a customized function to retrieve locator type and value by calling function, and use it whenever the driver is performing a click, send_keys() operations.

from selenium.webdriver.common.by import By
import configparser

def locator(web_element_name):
    try:
        global web_element
        config = configparser.ConfigParser()
        config.read("/home/vinod/automation/learn/Utilities/obj_repo.ini") # ini file path
        for section in config.sections():
            for lines in config.items(section):
                if lines[0].lower() == web_element_name.lower():
                    web_element = lines[1]

        locator_type = web_element.split(':>:')[0]
        locator_value = web_element.split(':>:')[1]

        if locator_type.strip().lower() == "id":
            return [By.ID, locator_value]
        elif locator_type.strip().lower() == "xpath":
            return [By.XPATH, locator_value]
        elif locator_type.strip().lower() == "linktext":
            return [By.LINK_TEXT, locator_value]
        elif locator_type.strip().lower() == "partiallinktext":
            return [By.PARTIAL_LINK_TEXT, locator_value]
        elif locator_type.strip().lower() == 'name':
            return [By.NAME, locator_value]
        elif locator_type.strip().lower() == "tagname":
            return [By.TAG_NAME, locator_value]
        elif locator_type.strip().lower() == "claasname":
            return [By.CLASS_NAME, locator_value]
        elif locator_type.strip().lower() == 'css':
            return [By.CSS_SELECTOR, locator_value]

    except Exception as e:
        print("Method locator | Exp Desc: ", e)
        assert False

And create a test base class to perform selenium action as click, send_keys all in once class.

from selenium import webdriver
from Utilities.obj_property import locator

def activate_driver(browser_name):
    try:
        if browser_name.strip().lower() == 'chrome':
            driver = webdriver.Chrome()
            print("Chrome WebDriver is initialized.")
            return driver
    except Exception as e:
        print("activate_driver | Exp Desc: ", e)
        assert False
def open_url(driver, url):
    try:
        driver.get(url)
        print("Application URL: " + url + " is successfully opened.")
    except Exception as e:
        print("open_url | Exp Desc: ", e)
        assert False
def maximize_window(driver):
    try:
        driver.maximize_window()
        print("Browser window is now maixmized.")
    except Exception as e:
        print("maximize_window | Exp Desc: ", e)
        assert False
def click(driver, web_ele):
    try:
        driver.find_element(locator(web_ele)[0], locator(web_ele)[1]).click()
        print("Clicked on weblement " + web_ele + " successfully.")
    except Exception as e:
        print("click | Exp Desc: ", e)
        assert False
def send_keys(driver, web_ele, value):
    try:
        driver.find_element(locator(web_ele)[0], locator(web_ele)[1]).clear()
        driver.find_element(locator(web_ele)[0], locator(web_ele)[1]).send_keys(value)
        print("value " + value + " is successfully inputed into the " + web_ele + " field.")
    except Exception as e:
        print("send_keys | Exp Desc: ", e)
        assert False

def get_text(driver, web_ele):
    try:
        return driver.find_element(locator(web_ele)[0], locator(web_ele)[1]).text
    except Exception as e:
        print("get_text | Exp Desc: ", e)

As we are following page object model, create a model for login page (in your case) it would filling out form.

from Utilities import test_base
import time

def login(driver, url, username, password):
    try:
        test_base.open_url(driver, url)
        test_base.maximize_window(driver)
        test_base.send_keys(driver, "LP_txtbx_username", username)
        test_base.send_keys(driver, "LP_txtbx_password", password)
        test_base.click(driver, "LP_btn_submit")
        time.sleep(5)

        if driver.current_url == 'https://www.linkedin.com/feed/':
            return True
        else:
            test_base.get_text(driver, "LP_txt_invalid_login") == "There were one or more errors in your submission. "
            return False
    except Exception as e:
        print("login | Exp Desc: ", e)

finally execute test cases (here i'm using pytest)

from Utilities import test_base
from POM import login_page
import pytest

BROWSER = 'Chrome'
URL = "https://www.linkedin.com/"

@pytest.fixture(scope='function')
def driver():
    return test_base.activate_driver(BROWSER)

def test_001_login_with_right_credential(driver):
    assert login_page.login(driver, URL, "right_email_id", "right_password") is True


def test_002_login_with_incorrect_credential(driver):
    assert login_page.login(driver, URL, "right_email_id", "incorrect_password") is False

Run the command: pytest test_execute.py

Output would look something like this. enter image description here

More detailed step by step process, check out this article - https://www.linkedin.com/pulse/how-build-simple-object-repository-selenium-webdriver-kouthal

if you are coding in java, I don't it should be a problem, just understand the workflow and implement in Java. Also, check out this link - http://toolsqa.com/selenium-webdriver/object-repository-in-selenium/ which uses Java.

Hope this helps.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.