In my test class, I have DesiredCapabilities set up for Appium test. In that class, I initialised my BasePage class holding pagefactory elements. When I run the test, it works as expected.
Now, I tried to be a bit more creative by moving my DediredCapabilities into a separate class, CapacityManager. In my test class, I called the method holding the DesiredCapabilities from the CapacityManager. The method call was successful, my app was launched, but the pagefactory elements were no longer working. I am not sure why.
When I run the test, I get a nullPointerExceptio on the first mobile element and this makes me suspect that a pagefactory or driver initialisation problem.
The working test class looks like this:
public class LoginTest {
private final BaseUtil baseUtil = new BaseUtil();
private static BasePage basePage;
public static AndroidDriver<AndroidElement> driver;
public static File classpathRoot;
public void startApp() throws MalformedURLException {
classpathRoot = new File(System.getProperty("user.dir"));
File appDir = new File(classpathRoot, "");
File app = new File(appDir, baseUtil.getMyApp());
DesiredCapabilities cap = new DesiredCapabilities();
cap.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, "11.0");
cap.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
cap.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
URL url = new URL("http://localhost:4723/wd/hub");
driver = new AndroidDriver<>(url, cap);
basePage = PageFactory.initElements(driver, BasePage.class); //pagefactory initialised
}
public void login(String sheetname, int rowNumber) throws InterruptedException, IOException, InvalidFormatException {
FeatureUtils featureUtils = new FeatureUtils();
File excelDir = new File(classpathRoot, "");
File exceldoc = new File(excelDir, baseUtil.getUsersExcelDoc());
List<Map<String, String>> testData = featureUtils.getData(exceldoc.getAbsolutePath(), sheetname);
String username = testData.get(rowNumber).get("username");
String password = testData.get(rowNumber).get("password");
basePage.yesIAgreeButton.click(); //first element clicks successfully
This is the BasePage class holding the pagefactory elements:
public class BasePage {
private final WebDriver driver;
public BasePage(WebDriver driver) { //constructor
this.driver = driver;
}
@FindBy(id = "com.test")
public WebElement yesIAgreeButton;
With the two classes above, the LoginTest class works as expected.
Now, I removed the DesiredCapabilities from the test class and put them in a new class, CapacityManager:
public class CapacityManager {
public static AndroidDriver<AndroidElement> driver;
public static File classpathRoot;
private final BaseUtil baseUtil = new BaseUtil();
private static BasePage basePage;
public DesiredCapabilities appDesiredCapabilities() throws MalformedURLException {
DesiredCapabilities desiredCapabilities = null;
classpathRoot = new File(System.getProperty("user.dir"));
File appDir = new File(classpathRoot, "");
File app = new File(appDir, baseUtil.getMyApp());
DesiredCapabilities cap = new DesiredCapabilities();
cap.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, "11.0");
cap.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
cap.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
URL url = new URL("http://localhost:4723/wd/hub");
driver = new AndroidDriver<>(url, cap);
basePage = PageFactory.initElements(driver, BasePage.class); //pagefactory initialisation
return desiredCapabilities;
}
}
I then called the DesiredCapabilities from the test class, like this:
public class LoginTest {
private final BaseUtil baseUtil = new BaseUtil();
private static BasePage basePage;
public static AndroidDriver<AndroidElement> driver;
public static File classpathRoot;
CapacityManager capacityManager = new CapacityManager();
public void startApp() throws MalformedURLException {
capacityManager.appDesiredCapabilities(); //method call. App launches successfully.
}
public void login(String sheetname, int rowNumber) throws InterruptedException, IOException, InvalidFormatException {
FeatureUtils featureUtils = new FeatureUtils();
File excelDir = new File(classpathRoot, "");
File exceldoc = new File(excelDir, baseUtil.getUsersExcelDoc());
List<Map<String, String>> testData = featureUtils.getData(exceldoc.getAbsolutePath(), sheetname);
String username = testData.get(rowNumber).get("username");
String password = testData.get(rowNumber).get("password");
basePage.yesIAgreeButton.click(); //getting nullpointer error on this line. Looks like an issue with the pagefactory elements initialisation. basePage is null.
driver.pressKey(new KeyEvent(AndroidKey.TAB));
The
basePageinside ofCapacityManagerand the one insideLoginTestare two different objects instances and the one insideLoginTestwas never initialized. All you need to do is something like this:Or... since
basePageisstatic, make it public and call it staticallyCapacityManager.basePage. That said, I don't think any of thosestaticfields should bestatic.UPDATE
The class
CapacityManageris poorly constructed. For starters, it has private static fields that are never used for anything after being initialized. I won't fix everything, but I would fix what pertains to this post. ReplaceCapacityManagerwith my version and update to fix the issue with the other fields.Without changing anything else in the
LoginTestclass (I assume it works "correctly"), simply get the BasePage by calling the getter method:UPDATE #2
Typically, when you create a factory to provide instances of a class, you hide the constructor of that class. When you have public constructors of a class, sometimes is better not to have a factory. Why? To avoid confusion. Of course, there are plenty of exceptions to what I just mentioned. Here, we have one of those confusing implementations.
You have a
BasePagewith a public constructor that takes aWebDriveras an argument. You also havePageFactory.initElements(driver, BasePage.class)that returns an instance ofBasePage. How should I create an instance ofBasePage? When should the factory be used? When should I call the contructor directly?Considering that
PageFactoryis a proven class provided by the Selenium library, it is reasonable to believe thatBasePageis ill contructed and/or the use of the@FindByannotation is incorrect. None of those I can fix for you, but I can suggest what I believeBasePageshould look like. Here you go:The previous code has been updated after this second update.