WebDriverWait и ThreadLocal-переменные
Думаю, многие знают, что WebDriver предоставляет два механизма ожиданий — неявные и явные.
При использовании неявных ожиданий функции поиска элементов (findElement и findElements) не возвращают результат до тех пор, пока не найдутся нужные элементы, либо пока не истечёт установленный таймаут. Это ожидание реализовано на стороне браузера, поэтому WebDriver никак не может его прервать, ему остаётся только ждать либо положительного, либо отрицательного результата поиска.
Явные ожидания реализуются при помощи класса WebDriverWait, в который можно передать некоторую функцию-условие. Эта функция будет периодически вызываться до тех пор, пока либо не вернётся "хороший" результат, либо не истечёт время, в течение которого должно проверяться условие.
А что будет, если использовать оба механизма ожидания одновременно, при этом установить время неявного ожидания, скажем, на 5 минут, а потом использовать явное ожидание условия "findElements возвращает непустой список элементов" и указать в нём время ожидания 1 минута?
Оказывается, неявные ожидания побеждают явные. Так как функция findElement заблокируется на 5 минут, класс WebDriverWait не сможет завершить работу через 1 минуту, он будет честно ждать 5 минут, пока функция findElement не вернёт результат. Потому что WebDriverWait вызывает проверяемое условие в текущем потоке, тем самым блокируя его.
А что если функция-условие вообще заблокируется навсегда и не вернёт никакой результат? Тогда и WebDriverWait тоже зависнет навсегда.
Примерно год назад нам пришёл пулл-реквест, который решал эту проблему:
https://github.com/SeleniumHQ/selenium/pull/7692Для устранения блокировок было предложено запускать проверяемые условия в отдельном потоке, так что основной поток не блокируется и может прекратить ожидание ровно в запланированное время.
И всё было бы хорошо, но после выпуска версии 4.0-alpha-5 с этим улучшенным механизмом ожиданий мы получили целую серию баг-репортов, в которых сообщалось, что явные ожидания перестали работать вообще!
Оказывается, многие люди хранят ссылку на драйвер в виде ThreadLocal-переменной, то есть с каждым потоком ассоциируется свой экземпляр драйвера. Это делается якобы для того, чтобы тесты было проще распараллеливать. И если такая ThreadLocal-переменная используется для получения ссылки на драйвер в условии ожидания, а условие проверяется в отдельном потоке — оно не может получить доступ к драйверу!
Вообще-то драйвер передаётся в условие в виде параметра, так что обращаться к ThreadLocal-переменной не нужно вообще. Тот факт, что люди этот параметр игнорируют и вместо этого достают ссылку на драйвер из ThreadLocal-переменной, оказался для нас сюрпризом. Это же явный признак плохого дизайна тестов именно с точки зрения их распараллеливания!
Но... Спустя несколько месяцев и кучку баг-репортов мы решили это изменение откатить и вернуться к старой блокирующейся реализации. Обратная совместимость важнее.
Возможно, в будущем рядом с классом WebDriverWait появится похожий на него класс WebDriverWaitAsync, который будет проверять условия в отдельном потоке. А пока признаем, что любители ThreadLocal-переменных победили.