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&lt;JsonElement&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());
}
}