Using Selenium with TestNg, TestListenerAdapter is getting tests mixed, the driver seems to be getting shared between test classes
I am running parallel tests using selenium (selenium-server-standalone-2.47.1.jar) grid with TestNg, kicked off by ant, and using a TestListenerAdapter. Screenshots are taken in the listener's 'onTestFailure' method. The problem is that the listener seems to get crossed up about which driver it should be using, and sometimes takes a screenshot of the wrong browser window, or fails altogether if the driver that it thinks it should be using has already quit.
When the tests start, TestNg's @BeforeTest and the TestListenerAdapter's 'onTestStart' methods are running on the same thread, but when the test fails, the TestListenerAdapter's 'onTestFailure' method appears to be running on a separate thread. It seems like the threads are getting crossed/shared somehow, but I can't figure out why.
Here is some skeleton code, any assistance greatly appreciated.
Base Test Class:
public class baseClassTests{
protected AutomationUtils au;
protected DriverUtils du;
@BeforeTest(alwaysRun = true)
@Parameters({ "selenium.OS", "selenium.browser" })
public void beforeTest(String OS, String browser) {
//these call simple private methods to know where to set up the driver
String port = getPort(OS, browser);
String host = getHost(OS);
//make a driver utility object here, this makes a driver
du = new DriverUtils(browser, host, port);
//pass this driver utility object to another class of utilities
//this 'AutomationUtils' class gets a RemoteWebDriver ('driver') by calling driver=du.getDriver();
//the 'AutomationUtils' class is then the one that does all of the 'driver.findBy...' etc
au = new AutomationUtils(du);
}
@BeforeMethod(alwaysRun = true)
public void beforeMethod(Method m, ITestResult tr) {
du.deleteCookies();
testNgTestName = m.getName();
print("Method: "+testNgTestName + " Thread: "+Thread.currentThread().hashCode());
//set the attribute of the ITestResult object so we can use the same object in the listener
tr.setAttribute("du", du);
tr.setAttribute("au", au);
}
}
Listener class
public class AmSimpleTestListener extends TestListenerAdapter {
private DriverUtils driveU;
private AutomationUtils AutoU;
private RemoteWebDriver driver;
private RemoteWebDriver augmentedDriver;
private String methodName;
private String browser;
private String browserVersion;
String testClass;
@Override
public void onTestStart(ITestResult tr) {
//pick up the correct driver utility object from the test class/method we are in
driveU = (DriverUtils) tr.getAttribute("du");
AutoU = (AutomationUtils) tr.getAttribute("au");
driver = du.getDriver();
augmentedDriver = (RemoteWebDriver) new Augmenter().augment(driver);
methodName = tr.getName();
testClass=tr.getTestClass(); //sort of, I actually parse it up a bit
browser = driveU.getBrowser();
browserVersion = driveU.getBrowserVersion();
print("Method: "+methodName + " Thread: "+Thread.currentThread().hashCode());
}
@Override
public void onTestFailure(ITestResult tr) {
print("Method: "+tr.getName() + " Thread: "+Thread.currentThread().hashCode());
try{
writeScreenshotFile();
}
catch (Exception e){
Out.error("Unable to take screen shot");
e.printStackTrace();
}
}
private String writeScreenshotFile() {
if (driver != null && driver.getSessionId() != null) {
File scrShot = ((TakesScreenshot) augmentedDriver).getScreenshotAs(OutputType.FILE);
File localPathToScreenShot = new File("/path/to/base/directory/"+testClass+"/"+methodName+".png");
try {
FileUtils.copyFile(scrShot, localPathToScreenShot);
} catch (Exception e) {
Out.error("Couldn't write screenshot to file");
}
return localPathToScreenShot.getAbsolutePath();
}
return "Could not get path.";
}
}
DriverUtils class makes/supplies the driver
public class DriverUtils {
private RemoteWebDriver driver;
private int timeout;
private String browserVersion;
private String browser
private DesiredCapabilities caps;
public DriverUtils(String browser, String host, String port) {
String hostUrl = "http://" + host + ":" + port + "/wd/hub";
this.browser=browser;
//do some stuff here to set capabilties
driver = new RemoteWebDriver(new URL(hostUrl), caps);
browserVersion = driver.getCapabilities().getVersion();
}
public RemoteWebDriver getDriver() {
return driver;
}
public AmBrowser getBrowser() {
return browser;
}
public String getBrowserVersion() {
return browserVersion;
}
public void quitDriver() {
driver.quit();
}
public void deleteCookies(){
driver.manage().deleteAllCookies();
}
}
public class AutomationUtils extends BaseClassUtils {
public AutomationUtils(DriverUtils driverUtils) {
//pass it up to the base class utils (this is different than base class tests, above)
//do this so the driver can be accessed by other utility classes as well
super(driverUtils);
}
//All sorts of methods here to find elements, login, blah blah everything that is done with a driver object
}
public class BaseClassUtils { //this is a different class than BaseClassTests
//make the driver a protected object so all utility classes can access as nec.
protected final RemoteWebDriver driver;
public BaseClassUtils(DriverUtils driverUtils) {
driver = driverUtils.getDriver();
}
}
Tests are run via ant.
<suite name="Dev2 for debugging" parallel="tests" thread-count="10">-- tests here </suite>
After tinkering this for a while, I came to the conclusion that there were two things that seemed to help tremendously.
- eliminate the listener, and take all screenshots in the
@AfterMethod
- Move the
@Before/After Method/Test methods
into the child classes, but simply call methods in the parent to do all the work.
Another thing I noticed is that for #2, TestNG is supposed to run the parent @Before
methods then the child @Before
methods; and then at the end run the child '@After' methods and then the parent @After
methods.
I ran a series of simple tests, I found that all before/after methods were not being run, so for the few cases where I was using @Before
and @After
methods in both parent and child, I consolidated.
Things seem to run much better now, the driver does not get confused, and screenshots are being attached to the correct browser/test.