Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

Не отробатывает метод onTestFailure(ITestResult result) TestListenerAdapter TestNG


(heartwilltell) #1

Ситуация - есть лиснер extends TestListenerAdapter, в котором драйвер получаю из контекста и использую его для скриншота по фейлу теста.

@Override
    public void onTestFailure(ITestResult result) {
        driver = (WebDriver) result.getTestContext().getAttribute("driver");
        screenshoter = new Screenshoter(driver);
        screenshoter.takeShot();
    }

Есть базовый класс от которого наследуются все тестовые классы и там в afterClass() вызывается метод который убивает драйвер.
Так вот, если фейлится последний тест, то метод onTestFailure не может отработать и хватает нул поинтер так как драйвера уже нету, его убило афтеркласом, соответственно в контексте пусто.

Что посоветуете?


(Александр Таранков) #2

Не уверен, что @AfterClass - это правильная аннотация для прибивания драйвера. Я её не использовал, но из описания похоже, что метод, помеченный этой аннотацией, будет вызываться после последнего теста в каждом классе. Вряд ли это ожидаемое поведение?


(Sergey Korol) #3

Покажите, как сетите / убиваете драйвер + сам exception. @AfterClass должен вызываться после onTestFailure. Ради интереса, подключите логирование и посмотрите последовательность вызовов.

Кстати, какого рода фейл возникает? Assert или от драйвера? Как обрабатываете?


(heartwilltell) #4

Ну в данном случает я паралелю тесты именно по классам, потому посчитал логичным убивать драйвер в афтерклас методе


(heartwilltell) #5

Это класс отвечающий за создание драйвера

public class WebDriverStorage {

    private static final Logger log = LoggerFactory.getLogger(WebDriverStorage.class);

    private WebDriver driver;
    private String DRIVER_SYS_PROPERTY = BuildPropertyGetter.DRIVER.getValue();
    private String HUB_SYS_PROPERTY = BuildPropertyGetter.GRID_HUB.getValue();
    private Map<WebDriver, String> storage = new HashMap<>();

    public WebDriverStorage() {
        log.debug("WebDriverStorage is created for " + Thread.currentThread().getName() + " " + WebDriverStorage.
    public WebDriver getDriver() {
        return driverFactoryMechanism(createCapabilities(), getHub());
    }

    public WebDriver getDriver(DriverModification modification) {
        return driverFactoryMechanism(createModifiedCapabilities(modification), getHub());
    }

    public WebDriver getCurrentDriver() {
        return driver;
    }

    public void dissmissDriver(WebDriver driver) {
        if (storage.get(driver) == null) {
            throw new Error("Driver is not owned by the factory: " + driver);
        }
        if (driver != this.driver) {
            throw new Error("Driver does not belong to the current thread: " + driver);
        }
        driver.quit();
        storage.remove(driver);
    }

    public void dissmissAllThreadDrivers() {
        log.debug("Before removal storage contains such drivers: " + storage);
        String threadKey = Thread.currentThread().getName() + "-" + Thread.currentThread().getId()+ "-StorageKey-";
            new HashSet<String>(storage.values()).stream().filter(value -> value.contains(threadKey)).forEach(value -> {
                storage.keySet().stream().filter(dr -> storage.get(dr).equals(value)).forEach(WebDriver::quit);
                storage.values().remove(value);
            });
        log.debug("Now storage contains such drivers: " + storage);
    }

    public void dismissAllDrivers() {
        log.debug("Before removal storage contains such drivers: " + storage);
        if (storage != null) {
                for (WebDriver driver : new HashSet<WebDriver>(storage.keySet())) {
                    driver.quit();
                    storage.remove(driver);
                }
        }
        log.debug("Now storage contains such drivers: " + storage);
    }

    public void setDefaultTimeOut(int pageLoadTimeout, int implicitlyWait, int setScriptTimeout) {
        driver.manage().timeouts().implicitlyWait(implicitlyWait, TimeUnit.SECONDS);
        driver.manage().timeouts().pageLoadTimeout(pageLoadTimeout, TimeUnit.SECONDS);
        driver.manage().timeouts().setScriptTimeout(setScriptTimeout, TimeUnit.SECONDS);
    }

    private DesiredCapabilities createCapabilities() {
        DesiredCapabilities capabilities = null;
        if (DRIVER_SYS_PROPERTY != null) {
            if (DRIVER_SYS_PROPERTY.equals(DriverType.FIREFOX.getValue()))
                capabilities = DesiredCapabilities.firefox();
            if (DRIVER_SYS_PROPERTY.equals(DriverType.CHROME.getValue()))
                capabilities = DesiredCapabilities.chrome();
            if (DRIVER_SYS_PROPERTY.equals(DriverType.SAFARI.getValue()))
                capabilities = DesiredCapabilities.safari();
            if (DRIVER_SYS_PROPERTY.equals(DriverType.OPERA.getValue()))
                capabilities = DesiredCapabilities.operaBlink();
            if (DRIVER_SYS_PROPERTY.equals(DriverType.IEXPLORER.getValue()))
                capabilities = DesiredCapabilities.internetExplorer();
            if (DRIVER_SYS_PROPERTY.equals(DriverType.HTMLUNIT_WITH_JS.getValue()))
                capabilities = DesiredCapabilities.htmlUnitWithJs();
            if (DRIVER_SYS_PROPERTY.equals(DriverType.PHANTOMJS.getValue()))
                capabilities = DesiredCapabilities.phantomjs();
        } else {
            capabilities = DesiredCapabilities.firefox();
        }
        return capabilities;
    }

    private Capabilities createModifiedCapabilities(DriverModification modification) {
        DesiredCapabilities capabilities = createCapabilities();
        String browserType = capabilities.getBrowserName();
        switch (modification) {
            case BROWSERMOBPROXY:
                BMProxy bmProxy = BMProxy.INSTANCE;
                bmProxy.init();
                capabilities.setCapability(CapabilityType.PROXY, bmProxy.getProxy());
            case USERAGENT: 
                if (browserType.equals(BrowserType.FIREFOX)) {
                    FirefoxProfile firefoxProfile = new FirefoxProfile();
                    firefoxProfile.setPreference("general.useragent.override", "userAgent");
                    capabilities.setCapability(FirefoxDriver.PROFILE, firefoxProfile);
                }
                else if (browserType.equals(BrowserType.CHROME)) {
                    ChromeOptions chromeOptions = new ChromeOptions();
                    chromeOptions.addArguments("--user-agent=" + "userAgent");
                    capabilities.setCapability(ChromeOptions.CAPABILITY, chromeOptions);
                }
        }
        return capabilities;
    }


    private WebDriver driverFactoryMechanism(Capabilities capabilities, URL hub) {
        String newKey = createKey(capabilities, hub);
        if (driver == null) {
            createDriver(capabilities, hub);
        } else {
            String key = storage.get(driver);
            if (key == null) {
                createDriver(capabilities, hub);
            } else {
                if (!newKey.equals(key)) {
                    dissmissDriver(driver);
                    createDriver(capabilities, hub);
                } else {
                    try {
                        driver.getCurrentUrl();
                    } catch (Throwable throwable) {
                        createDriver(capabilities, hub);
                    }
                }
            }
        }
        return driver;
    }

    private void createDriver(Capabilities capabilities, URL hub) {
        String newKey = createKey(capabilities, hub);
        if (hub == null) {
            driver = createDriverLocal(capabilities);
            storage.put(driver, newKey);
        } else {
            driver = createDriverRemote(capabilities, hub);
            storage.put(driver, newKey);
        }
    }

    private WebDriver createDriverLocal(Capabilities capabilities) {
        String browserType = capabilities.getBrowserName();
        if (browserType.equals(BrowserType.FIREFOX))
            driver = new FirefoxDriver(capabilities);
        if (browserType.equals(BrowserType.IE))
            driver = new InternetExplorerDriver(capabilities);
        if (browserType.equals(BrowserType.CHROME))
            driver = new ChromeDriver(capabilities);
        if (browserType.equals(BrowserType.SAFARI))
            driver = new SafariDriver(capabilities);
        if (browserType.equals(BrowserType.PHANTOMJS))
            driver = new PhantomJSDriver(capabilities);
        if (browserType.equals(BrowserType.HTMLUNIT))
            driver = new HtmlUnitDriver(capabilities);
        if (browserType.equals(BrowserType.OPERA_BLINK))
            driver = new OperaDriver(capabilities);
        setDefaultTimeOut(15, 15, 15);
        return driver;
    }

    private WebDriver createDriverRemote(Capabilities capabilities, URL hub) {
        driver = new RemoteWebDriver(hub, capabilities);
        setDefaultTimeOut(15, 15, 15);
        return driver;
    }


    private URL getHub() {
        if (HUB_SYS_PROPERTY != null) {
            try {
                return new URL(HUB_SYS_PROPERTY);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private String createKey(Capabilities capabilities, URL hub) {
        String createdKey = Thread.currentThread().getName() + "-" + Thread.currentThread().getId()+ "-StorageKey-" + capabilities.toString() + ":" + hub;
        log.debug("Key created: " + createdKey);
        return createdKey;
    }
}

Это базовый тестовый класс

public abstract class BaseTest {

    private static final Logger log = LoggerFactory.getLogger(BaseTest.class);

    protected WebDriver driver;
    protected PropertyStorage propertyStorage = PropertyStorage.getInstance();
    protected Screenshoter screenshoter;
    protected WebDriverStorage webDriverStorage = new WebDriverStorage();

    @BeforeSuite(alwaysRun = true)
    public void beforeSuite() {
        log.debug("");
    }

    @BeforeTest
    public void beforeTest() {
        log.debug("");
    }

    @BeforeClass(alwaysRun = true)
    public void beforeClass() {
        log.debug("");
        driver = webDriverStorage.getDriver();
        webDriverStorage.setDefaultTimeOut(15, 15, 15);
    }

    @BeforeMethod(alwaysRun = true)
    public void beforeMethod(ITestContext context) {
        driver = webDriverStorage.getCurrentDriver();
        context.setAttribute("driver", driver);
        driver.manage().deleteAllCookies();
    }

    @AfterMethod(alwaysRun = true)
    public void afterMethod() {
        log.debug("");
    }


    @AfterClass(alwaysRun = true)
    public void afterClass() {
        log.debug("");
        webDriverStorage.dissmissAllThreadDrivers();
    }

    @AfterTest
    public void afterTest() {
        log.debug("");
    }

    @AfterSuite(alwaysRun = true)
    public void afterSuite() {
        log.debug("");
        webDriverStorage.dismissAllDrivers();
    }

}

(Александр Таранков) #6

Ну и баги в TestNG тоже никто не отменял :slight_smile: Посмотри, можешь уже есть такой. Если нет, потом новый заведешь :slight_smile:


(Sergey Korol) #7

Я бы рекомендовал начать с ревайза выбранного подхода:

  • Слишком много лишних переприсваиваний.
  • Локальные сторэджи / мапы для единственного драйвера. Я так понимаю, что идея заключалась как раз таки в общем хранилище драйверов для многопоточной среды?
  • @BeforeClass/Method не согласованы. Зачем вам вообще инстанс драйвера на уровне BaseTest, если вы его используете для точечных локальных действий? Или же зачем дергать геттер из стореджа, если у вас уже есть созданный инстанс в @BeforeClass?
  • Инстанс сетится в контекст перед каждым методом, хотя драйвер создается для уровня класса.
  • Зачем хранить инстанс драйвера на уровне слушателя, который дергается миллион раз во время экзекьюшена?

Потенциальная проблема может крыться где угодно, т.к. уж очень много переплетений.

П.С. К слову, какую версию testng используете?


(heartwilltell) #8

Использую 6.8.21


(heartwilltell) #9

Да идея заключалась в едином классе хранилище драйверов для всех потоков.
В целом я запускаю драйвер для класса, классов у меня много, в них есть некоторые тесты, которые требуют подхатчить капабилити браузера, например драйвер с проксей, или драйвер с другим юзер агентом, или другим профайлом, если тест этого требует то я делаю необходимый драйвер прямо в нем, путем запроса в хранилище драйвера с модификациями. Как вы видели используется фабрика, драйвер из которой отдается по аргументу энуму. И дальше тесты продолжаются с тем же драйвером.


(Sergey Korol) #10

Вся проблема в том, что ваше хранилище существует сугубо внутри своего потока, оно не общее для всех. То же касается и мапы. TestNG запустит ваши классы в параллели в отдельных потоках, которые создадут свои собственные инстансы сторэджа с единственным драйвером внутри.


(heartwilltell) #11

Нужно написать синглтон и обернуть драйвер в Threadlocal или какие ваши советы?


(Sergey Korol) #12

Статический сторэдж + мапа с поддержкой конкаренси. HashMap не катит для многопоточного read | write аксеса.

П.С. Посмотрите, как реализован WebDriverRunner (его сторедж) + сам контейнер с мапой внутри.