I’m trying to integrate Spring Dependency Injection into our Appium test automation framework such that I can inject instance of WebDriver into any PageObject automatically. This has worked well so far except the fact that PageFactory.initElements(new AppiumFieldDecorator(driver),pageObjectClassInstance);
does not decorate elements with the locator annotations. Instead it simply decorates them with id as the element variable name, which I guess is the fallback strategy.
I get an instance of WebDriver using a WebDriverRunner class in the following manner:
package com.org.core;
import com.org.listener.ElementListener;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.events.EventFiringWebDriverFactory;
import io.appium.java_client.events.api.Listener;
import io.appium.java_client.ios.IOSDriver;
import io.github.bonigarcia.wdm.WebDriverManager;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class WebDriverRunner {
private WebDriver driver;
private static final Logger LOG = LoggerFactory.getLogger(WebDriverRunner.class);
public void initWebDriver(TestConfiguration configuration) throws MalformedURLException {
LOG.info("Initialising web driver");
if (configuration.isWeb()) {
WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
if (configuration.isHeadless()) {
options.addArguments("--headless");
}
driver = new ChromeDriver(options);
driver.navigate().to(configuration.getBaseUrl());
} else {
String appiumUrl = System.getProperty("appium_url");
if (System.getProperty("platform_name").equals("android")) {
driver = new AndroidDriver(new URL(appiumUrl), configuration.getCapabilities());
} else {
driver = new IOSDriver(new URL(appiumUrl), configuration.getCapabilities());
}
}
// Adding event listeners
List<Listener> listeners = new ArrayList<>();
listeners.add(new ElementListener());
driver = EventFiringWebDriverFactory.getEventFiringWebDriver(driver, listeners);
}
public WebDriver getWebDriver() {
return driver;
}
}
This is my Spring Context class:
package com.org.core;
import com.org.listener.ElementListener;
import com.org.registry.PageObjectRegistry;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.events.EventFiringWebDriverFactory;
import io.appium.java_client.events.api.Listener;
import io.appium.java_client.ios.IOSDriver;
import io.github.bonigarcia.wdm.WebDriverManager;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
@ComponentScan(basePackages = "com.org")
public class SpringContext {
private static final Logger LOG = LoggerFactory.getLogger(SpringContext.class);
@Autowired
public TestConfiguration configuration;
@Autowired
public PageObjectRegistry pageObjectRegistry;
@Autowired
public WebDriverRunner driverRunner;
@Bean @Lazy(true)
public WebDriver getDriver() {
return driverRunner.getWebDriver();
}
}
I have a PageObjectRegistry class which returns instances of pageobjects based on platform the tests are running on (iOS, Android etc). It has a method named get() which returns an instance of a PageObject. Please note that it is in this method that I perform PageFactory.initElements();
package com.org.registry;
import com.org.core.Platform;
import com.org.pages.BasePage;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import org.apache.commons.collections4.map.LinkedMap;
import org.apache.commons.collections4.map.MultiKeyMap;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* Registry for page objects
*
* <p>The registry holds a map of page objects by page and platform. E.g. get(LoginPage.class,
* Platform.ANDROID) will return the Android page object for the login page.
*
* <p>The registry can be initialised one page object at a time ({@link #register(Class)}) or by
* scanning certain packages on the classpath ({@link #initialiseFromClasspath(String[])}).
*/
@Component
public class PageObjectRegistry implements ApplicationContextAware {
/**
* Fetch an instance of a page object class from the registry.
*
* @param <T> type of page object
* @param clazz type to look for
* @param platform platform to look for
* @param driver {@link WebDriver}
* @return instance of the corresponding page object class
*/
public <T extends BasePage> T get(Class<T> clazz, Platform platform, WebDriver driver) {
Class<T> registeredClass = (Class) registry.get(clazz, platform);
if (registeredClass == null) {
LOG.warn(
"Could not find a page object for key {}|{}. Returning null",
clazz.getName(),
platform.name());
return null;
}
LOG.info(
"Returning instance of {} for key {}|{}",
registeredClass.getName(),
clazz.getName(),
platform.name());
String className = registeredClass.getName();
String beanName = className.substring(className.lastIndexOf(".")+1);
char[] chArr = beanName.toCharArray();
chArr[0] = Character.toLowerCase(chArr[0]);
T page = (T) applicationContext.getBean(new String(chArr));
PageFactory.initElements(new AppiumFieldDecorator(driver),page);
return page;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Now, wherever I need to use an instance of WebDriver, I have autowired the driver variable like this:
@Autowired @Lazy
protected WebDriver driver;
Let’s say there is a PageObject class LoginPage:
@Component
public class LoginPage extends BasePage {
@iOSFindBy(accessibility = "login_page_username_text_field")
@AndroidFindBy(
uiAutomator = "new UiSelector().className(\"android.widget.EditText\").instance(0)")
@FindBy(id = "USER_NAME")
private WebElement usernameField;
@iOSFindBy(accessibility = "login_page_password_text_field")
@AndroidFindBy(
uiAutomator = "new UiSelector().className(\"android.widget.EditText\").instance(1)")
@FindBy(id = "PASSWORD")
private WebElement passwordField;
@iOSFindBy(accessibility = "login_page_login_button")
@AndroidFindBy(id = appPackage + ":id/login_page_login_button")
@FindBy(xpath = "//span[text()='Log in']")
private WebElement loginButton;
@iOSXCUITFindBy(iOSNsPredicate = "name == 'done'")
@AndroidFindBy(id = appPackage + ":id/action_signin")
private WebElement reccomendationDone;
@iOSFindBy(accessibility = "Allow")
private WebElement allowNotification;
@iOSFindBy(accessibility = "Notify me of chats")
private WebElement receiveAlertsChat;
/** Login with username and password. */
public HomePage login(String username, String password) throws InterruptedException {
//code for the method here
return page(HomePage.class);
}
}
Problem:
Now whenever the get()
method in PageObjectRegistry is called to fetch PageObject instance, it fetches an instance of a PageObject but PageFactory.initElements()
does not decorate the webelements in the page with the locators in the @FindBy
annotation. Instead it gets decorated with variable name as the id
. I am not sure why. Could someone please help. Many Thanks!