In BaseTest.java I have a method to initialize an object of the type AppiumDriver. In LogInTest.java I'm creating an instance of BaseTest class and calling the initializeDriver method. And again in LoginPage.java I'm creating another instance of the BaseTest class but I'm not calling the initializeDriver method. Since in LoginPage.java I'm only creating an instance of BaseTest class and not calling initializeDriver method, isn't the driver object associated BaseTest instance of LoginPage.java supposed to be null?
The program works as my teacher explained, but I just don't understand how it works as in LoginPage.java base.getDriver() should be null as I never called the initializeDriver method there.
BaseTest.java
package com.qa;
import java.io.File;
import java.io.FileInputStream;
import java.net.URL;
import java.util.Properties;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Parameters;
import com.utils.TestUtils;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import io.appium.java_client.remote.MobileCapabilityType;
public class BaseTest {
public BaseTest() {
}
protected static AppiumDriver driver;
protected static Properties prop;
protected FileInputStream fis;
protected String dir;
public void setDriver(AppiumDriver driver) {
this.driver=driver;
}
public AppiumDriver getDriver() {
return driver;
}
public void initializeDriver(String platformName,String platformVersion, String deviceName) {
try {
dir=System.getProperty("user.dir");
fis=new FileInputStream(new File(dir+File.separator+"src"+File.separator+"main"+File.separator+"java"+File.separator+"com"+File.separator+"qa"+File.separator+"config.properties"));
prop=new Properties();
prop.load(fis);
DesiredCapabilities cap=new DesiredCapabilities();
cap.setCapability(MobileCapabilityType.PLATFORM_NAME, platformName);
cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, platformVersion);
cap.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName);
cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, prop.getProperty("UIAutomator2"));
//cap.setCapability(MobileCapabilityType.UDID, "50");
String appURL=dir+File.separator+prop.getProperty("appLocation");
cap.setCapability(MobileCapabilityType.APP, appURL); // if you want to install and launch a new app
cap.setCapability("autoGrantPermissions", true);
//cap.setCapability(MobileCapabilityType.BROWSER_NAME, "Chrome");
//cap.setCapability("chromedriverExecutable", "C:\\chromedriver-mobile_browser\\chromedriver.exe");
//cap.setCapability("newCommandTimeout", 900); //how long
cap.setCapability("appPackage", prop.getProperty("androidAppPackage"));
cap.setCapability("appActivity", prop.getProperty("androidAppActivity"));
cap.setCapability("autoAcceptAlerts", "true"); // to accept all alerts
//cap.setCapability("avdLaunchTimeout", "300000");
cap.setCapability("unlockType", "pin");
cap.setCapability("unlockKey", "74159");
URL url=new URL(prop.getProperty("appiumURL"));
driver=new AndroidDriver(url,cap);
String sessionID=driver.getSessionId().toString();
}
catch(Exception e) {
e.printStackTrace();
}
}
public void waitForVisibility(MobileElement e) {
WebDriverWait wait=new WebDriverWait(driver,TestUtils.WAIT);
wait.until(ExpectedConditions.visibilityOf(e));
}
public void click(MobileElement e) {
waitForVisibility(e);
e.click();
}
public void sendKeys(MobileElement e, String keys) {
waitForVisibility(e);
e.sendKeys(keys);
}
public String getAttribute(MobileElement e, String attribute) {
waitForVisibility(e);
return e.getAttribute(attribute);
}
public void quitDriver() {
driver.quit();
}
}
LogInTest.java
package com.qa.test;
import org.testng.annotations.Test;
import com.qa.BaseTest;
import com.qa.pages.LoginPage;
import com.qa.pages.ProductsPage;
import io.appium.java_client.AppiumDriver;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import java.lang.reflect.Method;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
public class LogInTest{
LoginPage logInPage;
ProductsPage productsPage;
BaseTest base;
AppiumDriver driver;
@Parameters({"platformName","platformVersion","deviceName"})
@BeforeClass
public void beforeClass(String platformName,String platformVersion,String deviceName) {
base=new BaseTest();
base.initializeDriver(platformName, platformVersion, deviceName);
}
@AfterClass
public void afterClass() {
base.quitDriver();
}
@BeforeMethod
public void beforeMethod(Method m) {
logInPage=new LoginPage();
productsPage=new ProductsPage();
System.out.println("Current test being executed "+m.getName());
}
@Test
public void invalidUserName() {
logInPage.enterUsername("Incorrect user");
logInPage.enterPassword("secret_sauce");
logInPage.clickLoginBtn();
String expectedMsg="Username and password do not match any user in this service.";
String errMsg=logInPage.getErrTxt();
System.out.println("Error message is "+ errMsg);
Assert.assertEquals(expectedMsg, errMsg);
}
@Test
public void invalidPassword() {
logInPage.enterUsername("standard_user");
logInPage.enterPassword("Incorrect Password");
logInPage.clickLoginBtn();
String expectedMsg="Username and password do not match any user in this service.";
String errMsg=logInPage.getErrTxt();
System.out.println("Error message is "+ errMsg);
Assert.assertEquals(expectedMsg, errMsg);
}
@Test
public void validCredentials() {
logInPage.enterUsername("problem_user");
logInPage.enterPassword("secret_sauce");
logInPage.clickLoginBtn();
String title="PRODUCTS";
String actTitle=productsPage.getTitle();
System.out.println("Actual title is "+ actTitle);
Assert.assertEquals(actTitle, title);
}
}
LoginPage.java
package com.qa.pages;
import org.openqa.selenium.support.PageFactory;
import com.qa.BaseTest;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
public class LoginPage {
BaseTest base;
public LoginPage() {
base=new BaseTest();
PageFactory.initElements(new AppiumFieldDecorator(base.getDriver()), this);
}
@AndroidFindBy(accessibility="test-Username")
private MobileElement userNameTxtFld;
@AndroidFindBy(accessibility="test-Password")
private MobileElement passWordTxtFld;
@AndroidFindBy(accessibility="test-LOGIN")
private MobileElement logInBtn;
@AndroidFindBy(xpath="//android.view.ViewGroup[@content-desc=\"test-Error message\"]/android.widget.TextView")
private MobileElement errMsg;
public LoginPage enterUsername(String userName) {
base.sendKeys(userNameTxtFld,userName);
return this;
}
public LoginPage enterPassword(String passWord) {
base.sendKeys(passWordTxtFld,passWord);
return this;
}
public String getErrTxt() {
return base.getAttribute(errMsg,"text");
}
public ProductsPage clickLoginBtn() {
base.click(logInBtn);
return new ProductsPage();
}
}
There are a couple of things you need to understand. The first thing has to do with the keyword static
. I am only going to touch on information related to this issue.
Then a class has fields marked at static
, that means that every instance of that class share the exact variable. Let look at your BaseTest
class:
public class BaseTest {
public BaseTest() {}
protected static AppiumDriver driver; // shared variable
...
protected String dir; // not shared
public void setDriver(AppiumDriver driver) {
this.driver=driver;
}
... // more stuff in the class
}
If class A and class B both create an instance of BaseTest
and one sets AppiumDriver
, the second class doesn't have to set a driver if it also creates its own instance of BaseTest
. Likewise, if class B changes the instance of AppiumDriver
, it also changes the AppiumDriver
object held by the BaseTest
object created by class A. This is the effect of marking a field (variable) as static.
The second thing has to do with order of execution. Remember, if both Class A and B create an instance of BaseTest
once one sets AppiumDriver
it sets it for both. HOWEVER, what if the class that doesn't set the driver attempts to use it first? In that case, you will get a NullPointerException
without a doubt. That said, as @rzwitserloot mentioned in his or her post, order of execution is controlled by the annotation @BeforeClass
. So your professor is right because,
BaseTest
has a method that gets invoked before any test gets executed and this method sets the AppiumDriver
object, andAppiumDriver
object is static, which means every instance of BaseTest
share the same driver object.