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

Помогите разобраться с инициализацией драйвера при использовании ThreadLocal RemoteWebDriver

threadlocal
dataprovider
selenium
testng
java
Теги: #<Tag:0x00007f7b60977240> #<Tag:0x00007f7b609770d8> #<Tag:0x00007f7b60976f98> #<Tag:0x00007f7b60976e58> #<Tag:0x00007f7b60976d18>

(Soft_World) #1

Падают тесты в TestNG с использованием нескольких браузеров одновременно, принято решение попробовать обернуть в ThreadLocal. Помогите разобраться с чем его едят и как правильно использовать RemoteWebDriver get Driver() {return Driver.get()}.

И так, мой базовый класс:

 public class RemoteTestBase {

   private static final String SAUCE_ACCESS_KEY = System.getenv("SAUCE_ACCESS_KEY");
   private static final String SAUCE_USERNAME = System.getenv("SAUCE_USERNAME");
   public ThreadLocal<RemoteWebDriver> driver = new ThreadLocal<>();

  @BeforeMethod

  @DataProvider(name = "browsers", parallel = true)
public static Object[][] sauceBrowserDataProvider(Method testMethod) throws JSONException {

String browsersJSONArrayString  = System.getenv("SAUCE_ONDEMAND_BROWSERS");

JSONArray browsersJSONArrayObj = new JSONArray(browsersJSONArrayString);

 Object[][] browserObjArray = new Object[browsersJSONArrayObj.length()][3];
 for (int i=0; i < browsersJSONArrayObj.length(); i++) {
  JSONObject browserObj = (JSONObject)browsersJSONArrayObj.getJSONObject(i);
  browserObjArray[i] = new Object[]{browserObj.getString("browser"), 
  browserObj.getString("browser-version"), browserObj.getString("os")}; }
  return browserObjArray; }

  void createRemoteDriver(String browser, String version, String os, String methodName) 
  throws Exception     {

 DesiredCapabilities capabilities = new DesiredCapabilities();
 Class<? extends RemoteTestBase> SLclass = this.getClass();
 capabilities.setCapability("browserName", browser);
 if (version != null) {
 capabilities.setCapability("browser-version", version);
 }
 capabilities.setCapability("platform", os);
 capabilities.setCapability("name", SLclass.getSimpleName());
 capabilities.setCapability("tunnelIdentifier", "***");
 URL url = new URL("http://" + SAUCE_USERNAME + ":" + SAUCE_ACCESS_KEY+         
 "@ondemand.saucelabs.com:80/wd/hub"))

RemoteWebDriver rwd = new RemoteWebDriver(url, capabilities);
driver.set(rwd);

randomuser = new RandomDataSelect();
configRead = new ConfigFileReader();
propertyRead = new PropertyLoader();
baseUrl = propertyRead.getProperty("site.url");

 getURL();
 }

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

 private void getURL() {
 getDriver().get(baseURL);}

 @AfterMethod(description = "Throw the test execution results into saucelabs")
 public void tearDown(ITestResult result) {
String texts = "auce:job-result=" + (result.isSuccess() ? "passed" :    "failed"));

getDriver().quit();  }

В каждом пейдж классе инициализирован драйвер таким образом:

 public class LoginObjects {
   private Webdriver driver;
   
   @FindBy(how=How.ID, using="aaa")
   public WebElement ElementOne;

  //какой-нибудь метод, после он передастся в основной тест:

  public void LoginAs() {
  WebDriverWait wait = new WebDriverWait(driver, 40)
  ElementOne.click()}

 // прописан конструктор:
  public LoginObjects(WebDriver driver){
  this.driver = driver;
  PageFactory.initElements(driver, this)}

Каждый такой Пейдж с объектами прописан в классе Аппликейшн:

public class Application {
private WebDriver driver;
public Application(WebDriver driver)
{  this.driver=driver;   }

public LoginObjects loginobjects() {
return new LoginObjects(driver) }

Не исключаю, что данный подход не самый целесообразный, любые советы как не использовать инициализацию браузера в каждом классе будут полезны, но вопрос в другом.

Каким образом вызывать/инициализировать WebDriver driver, когда в базовом классе используется RemoteWebDriver get Driver() ?? Естественно, если оставить все как есть то NullPointerException не заставить себя ждать.

Напоследок образец самого тестового класса, расширяющего базовый:

public class Login extends RemoteTestBase {

  @Test (dataProvider = "browsers")
  public void LoginTest(String browser, String version, String os, Method method) throws Exception {

  this.createRemoteDriver(browser, version, os, method.getName());

  Application app = new Application (driver);

  app.loginobjects().login_as();  }

Проект построен на TestNG.

Буду безмерно благодарна за помощь


(Ruslan Semerenko) #2

Попробуйте для начала почитать документацию https://docs.oracle.com/javase/10/docs/api/java/lang/ThreadLocal.html
Думаю, после этого вопросы должны отпасть сами собой.
Если кратко, то ThreadLocal переменные должны быть static полями. Каждый поток, при обращении к такому полю, будет получать свое значение.
Ок, поле может быть не static, если объект с таким полем шарится между несколькими потоками, но это совсем странный кейс.


(Soft_World) #3

Главный же вопрос что делать с инициализацией драйвера


(Ruslan Semerenko) #4

Если параллелизация настроена по классам, то ваш тестовый пример не нуждается в ThreadLocal, так как поле driver не статическое и в каждом потоке будет иметь свое значение.
Если параллелизация по методам, то не нужно разделять создание и получение драйвера во времени, а делать это в одном месте, типа

protected WebDriver getDriver() {
    WebDriver _driver = driver.get();
    if (_driver == null) {
        _driver = new FirefoxDriver();
        driver.set(_driver);
    }
    return _driver;
}

(Soft_World) #5

В моем случае нет никакого драйвер гет Фф. И параметры браузеров никак не указываются в коде, они передаются с Дженкинса. Вторй вопрос о не использовании thread. Была попытка но несмотря на то что, создаётся для каждого теста новый thread id, оба браузера (тест ранается сразу в 2-х браузерах) используют один инстанс драйвера. Поэтому решили попробовать threadlocal. Но загвоздка как теперь переименовать драйвер в Пейдж классах, и везде где он вызывается


(Soft_World) #6

Вопрос решён: нужно добавить строчку в тестовый класс: Webdriver driver = this.getDriver();