Используя Selenium с TestNg, TestListenerAdapter смешивает тесты, драйвер, похоже, распределяется между тестовыми классами

Я запускаю параллельные тесты с использованием сетки selenium (selenium-server-standalone-2.47.1.jar) с TestNg, запускаемой ant, и с использованием TestListenerAdapter. Скриншоты делаются в методе onTestFailure слушателя. Проблема в том, что слушатель, кажется, не понимает, какой драйвер он должен использовать, и иногда делает снимок экрана неправильного окна браузера или вообще не работает, если драйвер, который, по его мнению, должен использовать, уже завершил работу.

Когда запускаются тесты, методы TestNg @BeforeTest и TestListenerAdapter 'onTestStart' выполняются в одном потоке, но когда тест завершается неудачно, метод onTestFailure TestListenerAdapter запускается в отдельном потоке. Кажется, что нити каким-то образом пересекаются / разделяются, но я не могу понять, почему.

Вот какой-то скелетный код, любая помощь очень ценится.

Базовый тестовый класс:

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);
    }

}

Класс слушателя

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 создает / поставляет драйвер

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();
    }

}

Тесты запускаются через ant.

<suite name="Dev2 for debugging" parallel="tests" thread-count="10">-- tests here </suite>

person JackhammersForWeeks    schedule 01.10.2015    source источник


Ответы (2)


Поработав над этим некоторое время, я пришел к выводу, что есть две вещи, которые, похоже, очень помогли. 1) удалите слушателя и сделайте все скриншоты в @AfterMethod. 2) Переместите методы @ Before / After Method / Test в дочерние классы, но просто вызовите методы в родительском для выполнения всей работы. Еще я заметил, что для №2 TestNG должен запускать родительские методы '@Before', а затем дочерние методы '@Before'; а затем в конце запустите дочерние методы '@After', а затем родительские методы '@After'. Я провел серию простых тестов и обнаружил, что все методы до и после не выполняются, поэтому в тех немногих случаях, когда я использовал методы @Before и @After как в родительском, так и в дочернем, я консолидировал. Кажется, теперь все работает намного лучше, драйвер не запутывается, а скриншоты прикрепляются к правильному браузеру / тесту.

person JackhammersForWeeks    schedule 07.10.2015

Попробуйте использовать ThreadLocal для RemoteWebDriver, чтобы он мог обрабатывать параллельные запуски:

public class DriverUtils {
    private static ThreadLocal<RemoteWebDriver> driver = new ThreadLocal<~>();
    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.set(new RemoteWebDriver(new URL(hostUrl), caps));
        browserVersion = getDriver().getCapabilities().getVersion();
    }

    public RemoteWebDriver getDriver() {
        return driver.get();
    }

    public AmBrowser getBrowser() {
        return browser;
    }

    public String getBrowserVersion() {
        return browserVersion;
    }


    public void quitDriver() {
        getDriver().quit();
    }

    public void deleteCookies(){
        getDriver().manage().deleteAllCookies();
    }
}
person Shane    schedule 02.10.2015
comment
Спасибо, я попробовал ваше предложение, но, похоже, это не помогло. Похоже, что метод '@BeforeTest' (beforeTest) запускает несколько тестовых классов в одном потоке, и это приводит к хаосу. Когда я зарегистрировал поток в '@BeforeTest' (beforeTest) для 3 тестовых классов, казалось, что он запускается только с использованием двух потоков ... TRACE: Запуск beforeTest для AcceptanceTests.ErrorReportingTests в потоке: 1151820366 TRACE: Запуск beforeTest для AcceptanceTests.PopupTests в потоке: 1227297304 TRACE: Запуск до теста для AcceptanceTests.CalendarPageTests в потоке: 1227297304 - person JackhammersForWeeks; 02.10.2015
comment
В дополнение к изменению ThreadLocal, которое определенно необходимо для параллельных запусков с TestNg, попробуйте сделать RemoteWebDriver в BaseClassUtils неокончательным (защищенный драйвер RemoteWebDriver;) и посмотрите, поможет ли это. - person Shane; 02.10.2015
comment
Пробовал, спасибо, но такая же проблема. Проблема, похоже, в том, что метод beforeTest '@BeforeTest' запускает два тестовых класса в одном потоке. Как только два класса работают в одном потоке, я чувствую себя подавленным, никакое количество чего-либо не будет разделять драйверы. Я не могу понять, почему тестовые классы не запускаются в отдельных потоках. - person JackhammersForWeeks; 03.10.2015
comment
Это определенно странно. К сожалению, я не могу предложить больше предложений, не увидев примера класса, расширяющего BaseClassTests. Эти дочерние классы должны иметь свои собственные аннотированные методы BeforeTest и BeforeMethod, которые зависят от методов BaseClassTests. Я знаю, что документация TestNg не так хороша, но есть пример . - person Shane; 03.10.2015
comment
В моем случае BaseTestClass делает все, что необходимо в методах BeforeTest и BeforeMethod, поэтому они мне не нужны в дочерних классах. В тестовом коде нет ничего особенного, он просто использует объект AutomationUtils ('au'), созданный в BaseClassTests. Очень странно, что два тестовых класса запускаются в одном потоке. - person JackhammersForWeeks; 03.10.2015
comment
Вы упомянули, что запускаете это в Selenium Grid. Я не знаю, используете ли вы сетку локально или на удаленном сервере, но удостоверились ли вы, что у вас может быть более одного экземпляра браузера, работающего в этой сетке? Например, если у вас есть три теста, настроенных в testng.xml, и два из них настроены на выполнение двух в chrome, они, скорее всего, будут использовать один и тот же поток, если сетка не может запускать более одного экземпляра chrome. - person Shane; 05.10.2015