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

При попытке преобразовать BaseTest класс с создания и удаления браузера каждый тест метод в создание и удаление браузера каждый класс появляется NullPointer

junit
java
webdriver
Теги: #<Tag:0x00007f7b70af2f88> #<Tag:0x00007f7b70af2e48> #<Tag:0x00007f7b70af2d08>

(Oleksii Ihnatiuk) #1

Предыстория.
Сейчас браузер создается и удаляется каждый тестовый метод. BaseTest класс выглядел следующим образом (добавил @BeforeClass @AfterClass, чтобы было наглядней) и тесты работали корректно, пробовал запускать тесты параллельно: два потока в двух классах - все отрабатывало корректно.

public abstract class BaseTest {

private static final ThreadLocal<WebDriver> DRIVER_CONTAINER = new ThreadLocal<>();
public static WebDriver getDriver() {
    return DRIVER_CONTAINER.get();
}
protected HomePage openHomePage(){
    getDriver().get(ReadXMLFile.takeConstantFromXML("URL", "Landing Page", "url"));
    return new HomePage();
}
@BeforeClass
public static void BeforeClass(){
    System.out.println(getDriver() +  " Before Class\n");
}
@Before
public void setupDriver(){
    ChromeDriverManager.getInstance().setup();
    DRIVER_CONTAINER.set(new ChromeDriver());
    System.out.println(getDriver() +  " Before\n");
}
@After
public void cleanUp(){
    ofNullable(getDriver()).ifPresent(WebDriver::quit);
    DRIVER_CONTAINER.remove();
    System.out.println(getDriver() +  " After\n");
}
@AfterClass
public static void afterClass(){
    System.out.println(getDriver() +  " After Class\n");
}

}

Вот что вижу в консоле:

null Before Class

Starting ChromeDriver 2.29.461591 (62ebf098771772160f391d75e589dc567915b233) on port 18301
Only local connections are allowed.
квіт. 24, 2017 12:12:44 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: OSS
ChromeDriver: chrome on XP (3aee0cc2089d69a337cec2c04f11e3ca) Before

null After

null After Class

То есть я вижу что все отрабатывает так как и ожидалось: Before Class драйвера еще нет, Before метод драйвер уже есть, After метод и After Class драйвера нет.

После преобразования получается такой класс:

public abstract class BaseTest {

private static final ThreadLocal<WebDriver> DRIVER_CONTAINER = new ThreadLocal<>();
public static WebDriver getDriver() {
    return DRIVER_CONTAINER.get();
}
protected HomePage openHomePage(){
    return new HomePage();
}
@BeforeClass
public static void setupDriver(){
    ChromeDriverManager.getInstance().setup();
    DRIVER_CONTAINER.set(new ChromeDriver());
    System.out.println(getDriver() +  "BeforeClass\n");
}
@Before
public void Before() {
    System.out.println(getDriver() +  "Before\n");
    getDriver().get(ReadXMLFile.takeConstantFromXML("URL", "Landing Page", "url"));
}
@After
public void cleanUp() {
    getDriver().manage().deleteAllCookies();
    System.out.println(getDriver() +  "After\n");
}
@AfterClass
public static void printAfter(){
    ofNullable(getDriver()).ifPresent(WebDriver::quit);
    DRIVER_CONTAINER.remove();
    System.out.println(getDriver() +  "AfterClass\n");
}

}

В консоле:

ChromeDriver: chrome on XP (dc1941851efef3ccae72bc169026de5f)BeforeClass

nullBefore

goToSingleReviewPage(tests.Navigation_TestCase): run 1 failed.
nullBefore

goToSingleReviewPage(tests.Navigation_TestCase): run 2 failed.
nullBefore

goToSingleReviewPage(tests.Navigation_TestCase): run 3 failed.
goToSingleReviewPage(tests.Navigation_TestCase): giving up after 3 failures.

java.lang.NullPointerException
at wrappers.BaseTest.Before(BaseTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

nullAfterClass

Драйвер как и ожидалось создался Before Class, но я не могу понять почему его уже нет в Before метод.

Буду рад любым подсказкам :slight_smile:


(Михаил Братухин) #2

В конструкторе не убиваете драйвер? :sweat_smile:


(Oleksii Ihnatiuk) #3

В Base Test классе нет конструктора.


(Михаил Братухин) #4

А как у вас тесты в абстрактном классе стартуют? У меня даже кнопки нет этой в IDEA.
Вот тут прочел, что можно запускать методы из абстрактного класса, но все равно нужно указать реализацию:
Testing Tip: Run Test Method from Abstract Test Class


(Oleksii Ihnatiuk) #5

У меня тесты находятся в других отдельных классах, которые наследываются​ от этого.


(Михаил Братухин) #6

Так его конструктор сработает раньше, чем before, сразу после статичных вещей и того же beforeClass


(Oleksii Ihnatiuk) #7

Там нет конструктора. Только @Test методы.


(Oleksii Ihnatiuk) #8

Можете посмотреть весь код https://github.com/CrispusDH/HealthOutcomeAutomation


(Михаил Братухин) #9

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


(Sergey Korol) #10

Рекомендую на досуге взглянуть на junit в разрезе масштабирования тестов, контекст аннотаций и т.п. А потом связать это со спецификой работы ThreadLocal контейнера. :wink:


(Oleksii Ihnatiuk) #11

Спасибо. Буду читать. :slight_smile:


(t62) #12

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


(Oleksii Ihnatiuk) #13
  1. Гугл в помощь, он расскажет лучше зачем ThreadLocal;

  2. Обычно метод getDriver() статичный. Это может быть корнем проблем при многопоточном выполнении тестов. Для этого инстанс веб драйвера оборачивается в ThreadLocal. Который делает ссылку на него потокобезопасной.


(Михаил Братухин) #14

А теперь вопрос: что будет с
DRIVER_CONTAINER к которому вы обращаетесь в методе get, если он создан в одном потоке (для beforeClass используется отдельный поток) и вы пытаетесь обратится к нему из другого потока (before, test). :slight_smile:


(Oleksii Ihnatiuk) #15

Cпасибо, понял куда смотреть :slight_smile: Пока не понял как реализовать правда)
Я думал, что поток будет один для всех методов внутри класса.


(Михаил Братухин) #16

В интернетах люди про подобное спрашивают. Где-то может и есть ответ, но я тут вижу “взаимоисключающие” концепции. Может и неправ, т.к. опыта не так уж и много. Вы выносите общую логику в BeforeClass, чтобы инициировать драйвер один раз, а не каждый раз выполнять эту тяжелую операцию. Но при этом еще и хотите запускать тесты в параллель, хотя в коде нигде не видно намеков на подобную работу и что она будет подразумеваться. В итоге драйвер создается в отдельном потоке и пытаетесь с ним работать в другом.

Тут либо исключить желание параллельного запуска, т.к. непонятно как вы тесты хотите в параллель пускать при одном инстансе драйвера, ведь вы его один раз создаете в BeforeClass. Другого нет и чтобы тестам с ним работать - нужно быть в одном потоке, т.е. последовательно.

Но возможно вас выручит вот эта статья и другие схожие темы:
Junit4 running parallel junit classes

Можно попытаться сделать параметризированный параллельный запуск в разных потоках, но создавать драйвер не в BeforeClass, а в конструкторе или еще как-то его передавать. Тогда он будет запускаться для каждого экземпляра. Но могу и ошибаться. Сам еще не пробовал так делать. Если получится - то делитесь решением.
Может быть еще какие-то Rule могут помочь в решении.

Но в любом случае код будет слегка сложнее вашего и тут уже придется чем-то жертвовать. TestNG не рассматривали для тестов?

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

Вот тут еще сравнивают JUnit и TestNG и как раз указывается на проблему параллелизма и статичных методов:
To Test Or Not To Test – TestNG VS. JUnit


(Oleksii Ihnatiuk) #17

Большое спасибо за проделанную работу. Обязательно напишу что получилось. Лично мое мнение, JUnit лучше. Также очень хороший доклад по этому поводу был у Сергея Пирогова


(Oleksii Ihnatiuk) #18

Мы наверное не так друг друга поняли. Я хочу чтобы браузер создавался один раз для класса. А параллельно выполнялись не тестовые методы, а разные класса. Например если у меня есть два класса, то будет активно два окна.