How can I store WebElement in json or property so that I can change it easily

Viewed 1942
@FindBy(xpath="//button[@class='submitButton']")
WebElement submit;

I have used this type of code in testing framework using POM.

I want to store this Xpath in property file and want assign only key to it.

eg :

@FindBy(xpath=config.getsubmit())
WebElement submit;

Is there any way to store WebElement or make WebElement repository?

4 Answers

You cannot do this with the plain vanilla flavor of PageObjects and PageFactory.

You would need to do some customization after which you can get this done.

To get this done, you would basically need the following

  • A customized implementation of org.openqa.selenium.support.pagefactory.ElementLocator so that you can have it honour calls to findElement and findElements using the provided WebDriver instance. The default implementation just queries from the annotations.
  • A customized implementation of org.openqa.selenium.support.pagefactory.ElementLocatorFactory so that it can produce your customized ElementLocator instances instead of the default ones.
  • A custom annotation parser so that you can parse the custom annotations that you have added to the WebElement objects by extending org.openqa.selenium.support.pagefactory.AbstractAnnotations
  • Last but not the least, a custom annotation that captures some meta data which can be used to read the locator from a JSON file for a given web element

With all these you can do the following

  • Define your custom annotation such that it lets you identify a locator uniquely from a file.
  • Define a custom annotation parser which would read your custom annotation's attributes and construct a By object
  • Define a custom ElementLocator such that it uses the custom annotation parser to find the elements.
  • Define a custom ElementLocatorFactory such that it produces instances of the custom ElementLocator.

I wrote up this entire concept as a blog sometime back. Please take a look at it here

To get a complete understanding of how PageFactory works take a look at this blog that I created.

Someone took my blog post as a starting point and have built a library also which can do all this for you. You can consume it from here https://github.com/shchukax/search-with

For the sake of completeness, I am including all the code snippets from my blog

The json would look like this

[
  {
    "pageName": "HomePage",
    "name": "abTesting",
    "locateUsing": "xpath",
    "locator": "//a[contains(@href,'abtest')]"
  },
  {
    "pageName": "HomePage",
    "name": "checkBox",
    "locateUsing": "xpath",
    "locator": "//a[contains(@href,'checkboxes')]"
  },
  {
    "pageName": "CheckboxPage",
    "name": "checkBox1",
    "locateUsing": "xpath",
    "locator": "//input[@type='checkbox'][1]"
  },
  {
    "pageName": "CheckboxPage",
    "name": "checkBox2",
    "locateUsing": "xpath",
    "locator": "//input[@type='checkbox'][2]"
  }
]

The custom annotation would look like this

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.FIELD)
public @interface SearchWith {
    String inPage() default "";

    String locatorsFile() default "";

    String name() default "";
}

Custom ElementLocator implementation

import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.AbstractAnnotations;
import org.openqa.selenium.support.pagefactory.ElementLocator;

import java.util.List;

public class FileBasedElementLocator implements ElementLocator {

    private final SearchContext searchContext;
    private final boolean shouldCache;
    private final By by;
    private WebElement cachedElement;
    private List<WebElement> cachedElementList;


    public FileBasedElementLocator(SearchContext searchContext, AbstractAnnotations annotations) {
        this.searchContext = searchContext;
        this.shouldCache = annotations.isLookupCached();
        this.by = annotations.buildBy();
    }

    @Override
    public WebElement findElement() {
        if (cachedElement != null && shouldCache) {
            return cachedElement;
        }

        WebElement element = searchContext.findElement(by);
        if (shouldCache) {
            cachedElement = element;
        }

        return element;

    }

    @Override
    public List<WebElement> findElements() {
        if (cachedElementList != null && shouldCache) {
            return cachedElementList;
        }

        List<WebElement> elements = searchContext.findElements(by);
        if (shouldCache) {
            cachedElementList = elements;
        }

        return elements;
    }
}

Custom element locator factory would look like below

import org.openqa.selenium.SearchContext;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;

import java.lang.reflect.Field;

public class FileBasedElementLocatorFactory implements ElementLocatorFactory {
    private final SearchContext searchContext;

    public FileBasedElementLocatorFactory(SearchContext searchContext) {
        this.searchContext = searchContext;
    }

    @Override
    public ElementLocator createLocator(Field field) {
        return new FileBasedElementLocator(searchContext, new CustomAnnotations(field));
    }
}

Custom implementation of AbstractAnnotations

import com.google.common.base.Preconditions;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.openqa.selenium.By;
import org.openqa.selenium.support.CacheLookup;
import org.openqa.selenium.support.pagefactory.AbstractAnnotations;
import organized.chaos.annotations.SearchWith;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.util.Iterator;

class CustomAnnotations extends AbstractAnnotations {
    private final Field field;

    CustomAnnotations(Field field) {
        this.field = field;
    }

    @Override
    public By buildBy() {
        SearchWith search = field.getAnnotation(SearchWith.class);
        Preconditions.checkArgument(search != null, "Failed to locate the annotation @SearchWith");
        String elementName = search.name();
        String pageName = search.inPage();
        String locatorsFile = search.locatorsFile();
        Preconditions
            .checkArgument(isNotNullAndEmpty(elementName), "Element name is not found.");
        Preconditions.checkArgument(isNotNullAndEmpty(pageName), "Page name is missing.");
        Preconditions.checkArgument(isNotNullAndEmpty(locatorsFile), "Locators File name not provided");
        File file = new File(locatorsFile);
        Preconditions.checkArgument(file.exists(), "Unable to locate " + locatorsFile);
        try {
            JsonArray array = new JsonParser().parse(new FileReader(file)).getAsJsonArray();
            Iterator&amp;lt;JsonElement&amp;gt; iterator = array.iterator();
            JsonObject foundObject = null;
            while (iterator.hasNext()) {
                JsonObject object = iterator.next().getAsJsonObject();
                if (pageName.equalsIgnoreCase(object.get("pageName").getAsString()) &&
                    elementName.equalsIgnoreCase(object.get("name").getAsString())) {
                    foundObject = object;
                    break;
                }
            }
            Preconditions.checkState(foundObject != null, "No entry found for the page [" + pageName + "] in the "
                + "locators file [" + locatorsFile + "]");
            String locateUsing = foundObject.get("locateUsing").getAsString();
            if (! ("xpath".equalsIgnoreCase(locateUsing))) {
                throw new UnsupportedOperationException("Currently " + locateUsing + " is NOT supported. Only xPaths "
                    + "are supported");
            }

            String locator = foundObject.get("locator").getAsString();
            Preconditions.checkArgument(isNotNullAndEmpty(locator), "Locator cannot be null (or) empty.");
            return new By.ByXPath(locator);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public boolean isLookupCached() {
        return (field.getAnnotation(CacheLookup.class) != null);
    }

    private boolean isNotNullAndEmpty(String arg) {
        return ((arg != null) && (! arg.trim().isEmpty()));
    }
}

Here's how the page object class can look like

import org.openqa.selenium.WebElement;
import organized.chaos.annotations.SearchWith;

public class HomePage {
    public static final String PAGE = "HomePage";
    @SearchWith (inPage = HomePage.PAGE, locatorsFile = "src/main/resources/locators.json", name = "abTesting")
    private WebElement abTestingLink = null;

    @SearchWith (inPage = HomePage.PAGE, locatorsFile = "src/main/resources/locators.json", name = "checkBox")
    private WebElement checkBoxLink = null;

    public HomePage() {
    }

    public CheckBoxPage navigateToCheckBoxPage() {
        checkBoxLink.click();
        return new CheckBoxPage();
    }
}

Another page class

import org.openqa.selenium.WebElement;
import organized.chaos.annotations.SearchWith;

public class CheckBoxPage {
    private static final String PAGE = "CheckBoxPage";

    @SearchWith (inPage = CheckBoxPage.PAGE, locatorsFile = "src/main/resources/locators.json", name = "checkBox1")
    private WebElement checkBoxOne;

    @SearchWith (inPage = CheckBoxPage.PAGE, locatorsFile = "src/main/resources/locators.json", name = "checkBox2")
    private WebElement checkBoxTwo;

    public void unCheckCheckBoxTwo() {
        if (checkBoxTwo.isSelected()) {
            checkBoxTwo.click();
        }
    }

    public boolean isCheckBoxTwoUnchecked() {
        return (! checkBoxTwo.isSelected());
    }
}

And here's a test class that consumes this

import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import organized.chaos.pages.CheckBoxPage;
import organized.chaos.pages.HomePage;
import organized.chaos.support.FileBasedElementLocatorFactory;

public class AlteredPageFactoryDemo {

    private RemoteWebDriver driver;

    @BeforeClass
    public void setup() {
        driver = new ChromeDriver();
    }

    @AfterClass
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    public void testMethod() {
        driver.get("https://the-internet.herokuapp.com/");
        HomePage homePage = new HomePage();
        ElementLocatorFactory factory = new FileBasedElementLocatorFactory(driver);
        PageFactory.initElements(factory, homePage);
        CheckBoxPage checkboxPage = homePage.navigateToCheckBoxPage();
        PageFactory.initElements(factory, checkboxPage);
        checkboxPage.unCheckCheckBoxTwo();
        Assert.assertTrue(checkboxPage.isCheckBoxTwoUnchecked());
    }
}

You can't pass values to annotations determinated at runtime, only constants like final fields or types are allowed. See the spec for details. This is because annotations are evaluated at compile time. Their value is possibly retained in the class file.

Though you can find elements programmatically:

WebElement element = driver.findElement(By.xpath(config.getsubmit()));

However, this requires you to have a reference to the WebDriver instance.

Vicky,

You can create a WebElement repository by creating using POJO model.

Public class WebElementRepo 
{
static WebElement01 = null;

// then create getter setter for all these webelements

public static void setWebElement01(WebElement WE)
{
WebElement01 = WE;
}

public static WebElement getWebElement01(WebElement WE)
{
return WebElement01;
}

}

When ever u need to save a web element to the repository just pass the webelement to the WebElementRepo.setWebElement() and use WebElementRepo.getWebElement() to retrieve the webelement.

@FindBy accepts final String

static final String xpath;
xpath=config.getsubmit(); 
@FindBy(xpath)
WebElement submit;
Related