Вышла Selenide 7.19.0

Вышла Selenide 7.19.0

Залогинь и сохрани!
10.10.26

Добрый вечер!

Голубиной почтой нам доставили Selenide 7.19.0!



Сохранение и восстановление состояния браузера

Многие тесты начинаются с логина. А логин занимает время. Ну знаете, открыть логин-форму, ввести пароль, получить как-то email или SMS и т.д.

Самое лучшее решение - это, конечно, сделать способ быстрого логина без лишних телодвижений. Об этом в былые времена я рассказывал на моём любимом Selenium Camp, например.

Увы, оно доступно не всем. Поэтому для массового автоматизатора теперь есть способ попроще: один раз залогиниться и сохранить состояние браузера в файл, а в каждом последующем тесте его восстанавливать.

Мы постарались сделать в Selenide таксимально короткий API для этого:

open("/dashboard", withBrowserState("build/auth.json")
  .loggedInIf(() -> $("#logout").is(visible, Duration.ofSeconds(2)))
  .orLogin(() -> login("bob", "secret")));

Как это работает:

  1. Если файл build/auth.json есть, Selenide восстанавливает из него куки, localStorage и sessionStorage, открывает страницу и проверяет условие loggedInIf.
  2. Если файла нет или проверка не прошла (протухла кука, перезапустился сервер, отозвали сессию), Selenide чистит старое состояние, вызывает login, сохраняет новое состояние в файл и снова открывает страницу. И ваш браузер готов к работе.
  3. Если проверка не прошла даже сразу после логина, вы получите понятную ошибку (а не бесконечный цикл).

Работает и в параллельных тестах: логинится только один поток, остальные переиспользуют его состояние.

Если логика “залогиниться при необходимости” не нужна, можно просто восстановить состояние:

open("/dashboard", withBrowserState("build/auth.json"));

Если вы лямбды не уважаете, есть и низкоуровневый API. Тупо два метода “сохрани” и “восстанови”:

webdriver().saveBrowserState(Path.of("build/auth.json"));
webdriver().restoreBrowserState(fromFile("build/auth.json"));
BrowserState state = webdriver().getBrowserState();

P.S. Если что, класс WebDriverConditional переименовался в BrowserSession, и метод webdriver() теперь возвращает его. Ваш код скомпилируется без изменений, но библиотеки, скомпилированные со старой версией Selenide, придётся перекомпилировать.

Спасибо jinlxz за идею!

См. issue 3378 и PR 3461.


Эмуляция времени и часового пояса

Как протестировать страницу, которая пишет “Доброе утро” только утром? Или баннер “ваша подписка истекает завтра”? Теперь есть метод clock():

import static com.codeborne.selenide.Selenide.clock;

clock().setTimezone("America/New_York");
clock().setFixedTime(Instant.parse("2025-01-15T14:00:00Z")); // 9:00 утра в Нью-Йорке

open("/dashboard");
$("[data-testid=greeting]").shouldHave(text("Good morning, 9:00 AM"));

clock().reset();
  • setFixedTime замораживает new Date() и Date.now() для вновь загружаемых страниц.
  • Работает в Chrome и Edge (через CDP) и в Firefox (через BiDi).
  • reset() можно спокойно вызывать в @AfterEach для любого браузера.

Спасибо Nigel за идею и suhanrain за реализацию!

См. issue 3370, PR 3431 и PR 3442.


Условия visibleText и exactVisibleText

Условие text() проверяет весь текст элемента, даже если часть его обрезана через CSS (overflow: hidden, text-overflow: ellipsis).

// Пользователь видит:
"Пневмония не…"

// Тест радостно проходит с:
text("Пневмония неизвестного происхождения");

Теперь можно проверить только тот текст, который реально виден на экране:

$(".product-name").shouldHave(visibleText("Пневмония неиз"));
$(".product-name").shouldHave(exactVisibleText("Пневмония неиз…"));

Старые условия text() и exactText() работают как раньше.

Спасибо svasenkov за реализацию!

См. issue 1741 и PR 3366.


HtmlUnit вернулся

Когда-то Selenide поддерживал HtmlUnit из коробки, но мы его выпилили из-за размера. Ну и как будто проект казался мёртвым какое-то время. Теперь он вернулся, но как опциональная зависимость. Если хотите гонять тесты в HtmlUnit, добавьте зависимость:

testImplementation("org.seleniumhq.selenium:htmlunit3-driver:4.48.0")

и поставьте

Configuration.browser = "htmlunit";
// либо -Dselenide.browser=htmlunit

Конечно, HtmlUnit - не настоящий браузер: shadow DOM, лейаут, координаты, hover и т.п. там не работают. Но для простых страниц это может быть быстрый вариант прогнать тесты вообще без браузера.

См. PR 3470.


Capabilities браузера в MCP и CLI

У Selenide MCP и Selenide CLI появился параметр --capability=<name>=<value>. Его можно повторять, а значения, начинающиеся с { или [, парсятся как JSON, так что можно передавать вложенные capabilities:

selenide open --capability='goog:chromeOptions={"args":["--no-sandbox"]}' https://selenide.org
selenide open --capability='selenoid:options={"enableVNC":true}' https://selenide.org

Переданные так аргументы Chrome добавляются к дефолтным аргументам Selenide, а не заменяют их.

См. PR 3459.


Старые временные файлы больше не ломают скачивание

Если скачивание файла падало, браузер мог оставить в папке загрузок временный файл (.crdownload, .tmp, .part). И после этого падали все последующие скачивания с FileNotDownloadedError (это могло быть проблемой, только если вы используете свой вебдрайвер через setWebDriver()).

Теперь Selenide игнорирует временные файлы, которые были в папке до начала скачивания. А если не может почистить папку загрузок (например, на Windows браузер всё ещё держит файл), то просто пишет предупреждение в лог вместо исключения.

Спасибо ilya-corp за репорт!

См. issue 2675 и PR 3457.


Уникальные имена файлов для скриншотов и логов

Некоторые имена файлов (скриншоты, папки загрузок, webdriver.*.log) генерировались только из текущего времени. Поэтому два потока теоретически могли в одну и ту же миллисекунду создать файл с одинаковым именем, и один файл затирал другой.

Теперь в имена файлов добавляется ещё и уникальный счётчик.

См. PR 3469.


Appium: scroll() и swipe() внутри элемента

До сих пор скролл и свайп в Appium всегда выполнялись по всему экрану. Теперь их можно выполнить внутри заданного элемента:

$(By.xpath(".//*[@text='Tabs']")).scroll(down().inside($(By.id("android:id/list"))));
$(By.xpath(".//*[@text='TAB 12']")).swipe(right(10).inside($(By.id("android:id/tabs"))));

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

Спасибо valentin-kudin за реализацию!

См. issue 3445, PR 3446 и PR 3455.


Appium: цепочечные методы возвращают SelenideAppiumElement

Раньше такой код не компилировался, потому что setValue возвращал SelenideElement:

SelenideAppium.$(By.id("amount")).setValue("100").tap();

Теперь 27 цепочечных методов (setValue, val, type, append, press*, click, doubleClick, hover, scroll и т.д.) возвращают SelenideAppiumElement.

Спасибо aquahitt за реализацию!

См. issue 3440 и PR 3441.


Appium: свой класс элемента в SelenideAppiumCollection

Если у вас есть свой интерфейс элемента, наследующий SelenideAppiumElement, теперь можно создать и соответствующую коллекцию: у SelenideAppiumCollection появился protected конструктор, принимающий класс элемента (такой же, какой появился у ElementsCollection в Selenide 7.17.0).

Спасибо aquahitt за реализацию!

См. issue 3438, PR 3439 и PR 3447.


Обновили Appium java-client с 10.1.1 до 11.0.0

В Appium java-client 11 есть ломающие изменения, но Selenide прячет их от вас:

  • SelenideAppiumPageFactory работает с новым FieldDecorator;
  • чтение/запись буфера обмена по-прежнему работает со старыми Appium-серверами (например, на BrowserStack).

См. PR 3467.


Обновили Selenium с 4.49.0 до 4.51.0

  • Support CDP versions: v153, v154, v155

См. PR 3454, PR 3468 и ченджлог селениума.


Хочешь, я добавлю сюда изменённую цитату про касаток и белых медведей или какую-либо ещё лютую дичь? Могу переделать в контексте айти. Или назовём текст “Чумовой релиз”? Выйдет угарно.



Андрей Солнцев

ru.selenide.org

10.10.26