Search code examples
javaseleniumtestngpage-factory

How to run multiple test classes in testng suite with only one web driver instance?


Suite xml file

I'm trying to use @beforeSuite and @AfterSuite to run my test in one browser instance. however it run the first the first test class but fail with null pointer exception when it the second class.

Here is my code below:

LaunchBrowser.java class

public class LaunchBrower {

    protected WebDriver driver;

    public WebDriver getDriver() {
        return driver;
    }

    @Parameters({ "browserType", "appURL" })
    @BeforeSuite
    public void setUp(@Optional String browsertype, @Optional String appURL) {
        System.out.println("Launching google chrome with new profile..");
        driver = getBrowserType(browsertype);
        driver.manage().timeouts().implicitlyWait(60, TimeUnit.SECONDS);
        driver.manage().window().maximize();
        driver.navigate().to(appURL);
    }

    private WebDriver getBrowserType(String browserType) {
        if (driver == null ) {
            if (browserType.equalsIgnoreCase("chrome")) {
                return  new ChromeDriver();
            }
            else if (browserType.equalsIgnoreCase("InternetExplorer")) {
                return new InternetExplorerDriver();
            }
        }
        return driver;
    }


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

}

LoginPageTest class

public class LoginPageTest extends LaunchBrower {

    protected WebDriver driver;
    private LoginPage loginpage;
    private MyProfile profilepage;

    Logger log = Logger.getLogger("Results:");

    @BeforeClass(alwaysRun = true)
    public void setUp() {       
        loginpage = PageFactory.initElements(getDriver(), LoginPage.class);
        //loginpage =  new LoginPage(driver);
        //driver=getDriver();
    }

    @Test(groups = "LoginPage")
    public void verifyLogin() throws InterruptedException {
        //LoginPage login =  new LoginPage(driver);
        System.out.println("Sign In functionality details...");
        //System.out.println("Sign In functionality details seee...");
        Thread.sleep(10000);
        //login.enterUserName("11111111");
        Assert.assertEquals("11111111",loginpage.enterUserName("11111111"));
        log.debug("Correct Username Pass");
        //System.out.println("Correct username...");
        Assert.assertEquals("fe9245db",loginpage.enterPassword("fe9245db"));
        log.debug("Correct Password Pass");
        loginpage.clickOnLogin();
        log.debug("Login Pass");
    }
}

MyProfileTest java class

public class MyProfileTest extends LaunchBrower {

    protected WebDriver driver;
    private MyProfile profilepage;

    @BeforeClass(alwaysRun = true)
    public void setUp() {
        profilepage = PageFactory.initElements(getDriver(), MyProfile.class);
        //driver=getDriver();
    }

    @Test(groups = "Myprofile")
    public void verifyMyprofile() throws InterruptedException {
        System.out.println("My profile test...");
        //MyProfile profile = new MyProfile(driver);
        profilepage.ClickToggleLink();
        profilepage.ClickMyProfile();
    }
}

Solution

  • The problem lies in your test code. @BeforeSuite is designed in TestNG to be invoked only once per <suite>. You are combining that logic along with inheritance and then relying on the @BeforeSuite method to initialize your WebDriver. So it will do it for the first class in your <suite> by from the second class onwards TestNG is not going to be invoking the @BeforeSuite and thus your second class onwards you start seeing the NullPointerException. You should instead consider relying on a ISuiteListener implementation as a listener and then wire it into your test execution.

    Your tests would now start relying on a webdriver that is created in this manner and then work with it.

    Please ensure that you are using TestNG 6.12 or higher (which at this point doesn't exist).

    Here's a full fledged example that shows all of this in action.

    The base class of my test classes look like below

    package com.rationaleemotions.stackoverflow.qn46323434;
    
    import org.openqa.selenium.remote.RemoteWebDriver;
    import org.testng.annotations.Listeners;
    
    @Listeners(BrowserSpawner.class)
    public class MyBaseClass {
        protected void launchPage(String url) {
            RemoteWebDriver driver = BrowserSpawner.getDriver();
            driver.get(url);
            System.err.println("Page Title :" + driver.getTitle());
        }
    }
    

    The test classes that I am using in this example look like below

    package com.rationaleemotions.stackoverflow.qn46323434;
    
    import org.testng.annotations.Test;
    
    public class MyFirstTestCase extends MyBaseClass {
    
        @Test
        public void testGooglePage() {
            launchPage("http://www.google.com");
        }
    
        @Test
        public void testFaceBookPage() {
            launchPage("http://www.facebook.com");
        }
    }
    
    package com.rationaleemotions.stackoverflow.qn46323434;
    
    import org.testng.annotations.Test;
    
    public class MySecondTestCase extends MyBaseClass {
    
        @Test
        public void testHerokkuPage() {
            launchPage("https://the-internet.herokuapp.com/");
        }
    
        @Test
        public void testStackOverFlowPage() {
            launchPage("http://stackoverflow.com/");
        }
    }
    

    The ISuiteListener implementation looks like below

    package com.rationaleemotions.stackoverflow.qn46323434;
    
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.firefox.FirefoxDriver;
    import org.openqa.selenium.remote.RemoteWebDriver;
    import org.testng.ISuite;
    import org.testng.ISuiteListener;
    import org.testng.ITestResult;
    import org.testng.Reporter;
    
    public class BrowserSpawner implements ISuiteListener {
        private static final String DRIVER = "driver";
    
        @Override
        public void onStart(ISuite suite) {
            RemoteWebDriver driver;
            String browserType = suite.getParameter("browserType");
            switch (browserType) {
                case "chrome":
                    driver = new ChromeDriver();
                    break;
                default:
                    driver = new FirefoxDriver();
            }
            suite.setAttribute(DRIVER, driver);
        }
    
        @Override
        public void onFinish(ISuite suite) {
            Object driver = suite.getAttribute(DRIVER);
            if (driver == null) {
                return;
            }
            if (!(driver instanceof RemoteWebDriver)) {
                throw new IllegalStateException("Corrupted WebDriver.");
            }
            ((RemoteWebDriver) driver).quit();
            suite.setAttribute(DRIVER, null);
        }
    
        public static RemoteWebDriver getDriver() {
            ITestResult result = Reporter.getCurrentTestResult();
            if (result == null) {
                throw new UnsupportedOperationException("Please invoke only from within an @Test method");
            }
            Object driver = result.getTestContext().getSuite().getAttribute(DRIVER);
            if (driver == null) {
                throw new IllegalStateException("Unable to find a valid webdriver instance");
            }
            if (! (driver instanceof RemoteWebDriver)) {
                throw new IllegalStateException("Corrupted WebDriver.");
            }
            return (RemoteWebDriver) driver;
        }
    }
    

    The suite xml file that I am using looks like below

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
    <suite name="46323434_Suite" parallel="false" verbose="2">
        <parameter name="browserType" value="chrome"/>
        <test name="46323434_test1">
            <classes>
                <class name="com.rationaleemotions.stackoverflow.qn46323434.MyFirstTestCase"/>
            </classes>
        </test>
        <test name="46323434_test2">
            <classes>
                <class name="com.rationaleemotions.stackoverflow.qn46323434.MySecondTestCase"/>
            </classes>
        </test>
    </suite>
    

    When you run this the output would look like below

    ...
    ... TestNG 6.12 by Cédric Beust (cedric@beust.com)
    ...
    Starting ChromeDriver 2.32.498537 (cb2f855cbc7b82e20387eaf9a43f6b99b6105061) on port 45973
    Only local connections are allowed.
    log4j:WARN No appenders could be found for logger (org.apache.http.client.protocol.RequestAddCookies).
    log4j:WARN Please initialize the log4j system properly.
    Sep 20, 2017 10:36:41 PM org.openqa.selenium.remote.ProtocolHandshake createSession
    INFO: Detected dialect: OSS
    Page Title :Facebook – log in or sign up
    Page Title :Google
    PASSED: testFaceBookPage
    PASSED: testGooglePage
    
    ===============================================
        46323434_test1
        Tests run: 2, Failures: 0, Skips: 0
    ===============================================
    Page Title :The Internet
    Page Title :Stack Overflow - Where Developers Learn, Share, & Build Careers
    PASSED: testHerokkuPage
    PASSED: testStackOverFlowPage
    
    ===============================================
        46323434_test2
        Tests run: 2, Failures: 0, Skips: 0
    ===============================================
    
    ===============================================
    46323434_Suite
    Total tests run: 4, Failures: 0, Skips: 0
    ===============================================
    

    The most important things to remember here are :

    • Your tests are no longer going to be starting and cleaning up the browser. This is now going to be done by your ISuiteListener implementation.
    • Due to the model that is being used (i.e., relying on one browser instance per <suite>) you will never be able to run your tests in parallel. All of your tests should strictly run in sequential manner.
    • You need to ensure that none of your @Test methods either crash the browser (or) cause the browser instance to be cleaned up (this is applicable when you are dealing with a Grid setup wherein you probably endup leaving the browser idle for sometime causing the node to cleanup the browser from the server side). If you cause any of this, then your tests are going to fail with unexpected results.