Привет!
9 день рождественского календаря Selenide.
Расскажу о том, что сильнее всего волнует общественность.
Почему статики запретили в 5.0.0, а потом снова разрешили?
Короткий ответ: запретили случайно. Но правильно сделали.
А теперь подробнее.
Коротко о хранении вебдрайверов в селениде
Селенид изначально хранил вебдрайверы в ThreadLocal.
Это позволяет запускать тесты параллельно: в разных потоках - разные вебдрайверы. Код выглядит примерно так:
class WebDriverRunner {
private static final ThreadLocal<WebDriver> webdriver = new ThreadLocal<>();
private static final ThreadLocal<SelenideProxyServer> proxy = new ThreadLocal<>();
}
Скажем, метод $("a").click()
работает примерно так:
WebDriverRunner.webdriver.get().findElement("a").click();
Коротко про SelenideDriver
Но ThreadLocal накладывает ограничение: ты не можешь использовать в одном потоке два вебдрайвера (а также в двух потоках - один и тот же вебдрайвер, но до этого мы пока не дошли).
Это мы и собирались решить в Selenide 5.0.0. Мы сделали специальный класс SelenideDriver
, позволяющий использовать два
вебдрайвера в одном тесте:
SelenideDriver browser1 = new SelenideDriver();
SelenideDriver browser2 = new SelenideDriver();
browser1.open("https://google.com");
browser2.open("http://yandex.ru");
browser1.$(h1).shouldHave(text("Google"));
browser2.$(h1).shouldHave(text("Yandeks"));
Для этого пришлось сделать большой рефакторинг, так чтобы внутри самого селенида нигде не использовались старые добрые
статические методы open()
, $
и $$
. Везде, где нужен вебдрайвер, селенид теперь использовал параметр SelenideDriver
.
Да-да, пришлось его прокидывать во все методы как параметр.
И вот тут главный вопрос
А что делать со старыми добрыми статическими методами open()
, $
и $$
? Они должны откуда-то взять инстанс SelenideDriver
и передать
его дальше в кишки селенида. Откуда его взять?
Selenide 5.0.0: Статикам наносят удар
На тот момент этот вопрос решили так (как оказалось, это решение сильно повлияло на ход дел).
В класс WebDriverRunner
добавили третий ThreadLocal:
private static final ThreadLocal<SelenideDriver> selenideDriver = new ThreadLocal<>();
Сам SelenideDriver
- это, грубо говоря, очень простой класс с двумя полями WebDriver
и SelenideProxyServer
.
И в целом всё хорошо работало и решало поставленную задачу.
Чего я не мог предусмотреть на тот момент - это того, что многие люди определяли веб-элементы в статических полях:
public class MyPageObject {
public static SelenideElement fname = $("#fname");
public static SelenideElement lname = $("#lname");
}
Да ещё и переоткрывали браузер между тестами.
Важный нюанс:
в Selenide 5.0.0 мы сделали ещё одно улучшение.
Раньше селенид автоматически открывал новый браузер, если его ещё нет, что часто вызывало недоумение, ибо браузер
иногда открывался в очень неожиданные моменты (например, при попытке залогировать ошибку, произошедшую из-за закрэшившегося браузера).
Конечно, это происходило из-за плохого кода тестов, но мы же создали селенид, чтобы помогать людям, верно?
Поэтому с версии 5.0.0 селенид стал чётко говорить: “Браузера нет, работать не могу. Вызови сначала метод open()
, тогда поговорим.”
И что же свалилось?
Совпадение этих двух факторов привело к следующей проблеме:
- Человек создаёт статическую переменную
static SelenideElement fname = $("#fname")
. - Этот
fname
запоминает, с каким инстансомSelenideDriver
он был создан. - Человек в конце теста закрывает браузер
- В следующем тесте человек открывает новый браузер и снова обращается к статическому полю
fname
. - А
fname
обращается к своемуSelenideDriver
, с которым он был изначально создан. - И кидает ошибку, потому что тот браузер уже закрылся.
Больше года - с сентября 2018 до октября 2019 - я пытался объяснить всем, что статические переменные - это зло, не надо их использовать.
Сделал даже специально доклад “Антистатик”.
Но в итоге сдался из тех соображений, что слишком много людей, которым пришлось бы переписывать свои тесты.
Даже если они согласны насчёт злостности статических переменных, переписать всё - это зачастую неподъёмная задача.
Мы же создали селенид, чтобы помогать людям, верно?
Selenide 5.4.0: Статики победили
Как в итоге решили эту проблему?
Довольно просто. Мы заменили ThreadLocal<SelenideDriver>
на статическую переменную (да, вы не ослышались :))
private static final SelenideDriver = new ThreadLocalSelenideDriver();
Теперь наш “статический” SelenideDriver
- один на всех. Он никогда не закрывается. Все статические поля, созданные с
ним, будут жить вечно. Но он и не хранит в себе поля WebDriver
и SelenideProxyServer
. Он каждый берёт их из
тредлокалов WebDriverRunner
.
P.S.
Поэтому в версии 5.4.0 пропал метод WebDriverRunner.getSelenideDriver()
.
Я очень удивился, что многие люди уже успели его использовать. Люди, я вас не понимаю! Как вы умудряетесь всё так неправильно использовать?
Ок, я не подумав, сделал его публичным. Моя ошибка. Но он не упомянут ни в каких пресс-релизах, ни в какой документации.
Как могла кому-то прийти в голову идея его использовать? Как эта магия вообще происходит?
Что теперь?
Мы снова вернули возможность объявлять SelenideElement
в статических полях.
Но пожалуйста, не злоупотребляйте этим.
Я всё ещё это не одобряю. :)
ru.selenide.org
09.12.19