Released Selenide 7.19.0

Released Selenide 7.19.0

Log in and preserve!
10.10.26

Good evening!

A carrier pigeon just delivered us Selenide 7.19.0!



Saving and restoring browser state

Many tests start with a login. And the login takes time. You know: open the login form, enter the password, somehow get an email or SMS, etc.

The best solution, of course, is to build a quick way to log in without all that hassle. I talked about it back in the day at my beloved Selenium Camp, for example.

Alas, not everyone has that option. So for the mass test automation engineer, there’s now a simpler way: log in once and save the browser state to a file, then restore it in every subsequent test.

We tried to make the shortest possible API for this in Selenide:

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

How it works:

  1. If the file build/auth.json exists, Selenide restores cookies, localStorage and sessionStorage from it, opens the page and checks the loggedInIf condition.
  2. If the file is missing or the check fails (cookie expired, server restarted, session revoked), Selenide clears the stale state, calls login, saves the new state to the file and opens the page again. And your browser is ready to go.
  3. If the check fails even right after login, you get a clear error (instead of an endless loop).

It works in parallel tests too: only one thread logs in, the others reuse its state.

If you don’t need the “log in if needed” logic, you can just restore the state:

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

If you don’t respect lambdas, there’s a low-level API too. Just two plain methods, “save” and “restore”:

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

P.S. Just in case: class WebDriverConditional was renamed to BrowserSession, and method webdriver() now returns it. Your code will compile without changes, but libraries compiled against an older Selenide version will need to be recompiled.

Thanks to jinlxz for the idea!

See issue 3378 and PR 3461.


Time and timezone emulation

How do you test a page that says “Good morning” only in the morning? Or a banner “your subscription expires tomorrow”? Now there is a method clock():

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

clock().setTimezone("America/New_York");
clock().setFixedTime(Instant.parse("2025-01-15T14:00:00Z")); // 9:00 AM in New York

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

clock().reset();
  • setFixedTime freezes new Date() and Date.now() for newly loaded pages.
  • Works in Chrome and Edge (via CDP) and in Firefox (via BiDi).
  • reset() is safe to call in @AfterEach for any browser.

Thanks to Nigel for the idea and suhanrain for the implementation!

See issue 3370, PR 3431 and PR 3442.


Conditions visibleText and exactVisibleText

Condition text() checks the whole text of an element, even if part of it is cut off with CSS (overflow: hidden, text-overflow: ellipsis).

// The user sees:
"Pneumonia of unkn…"

// The test happily passes with:
text("Pneumonia of unknown origin");

Now you can check only the text that is actually visible on the screen:

$(".product-name").shouldHave(visibleText("Pneumonia of unkn"));
$(".product-name").shouldHave(exactVisibleText("Pneumonia of unkn…"));

The existing conditions text() and exactText() work as before.

Thanks to svasenkov for the implementation!

See issue 1741 and PR 3366.


HtmlUnit is back

Once upon a time, Selenide supported HtmlUnit out of the box, but we dropped it because of its size. Plus, the project seemed dead for a while. Now it’s back, but as an optional dependency. If you want to run tests in HtmlUnit, add the dependency:

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

and set

Configuration.browser = "htmlunit";
// or -Dselenide.browser=htmlunit

Of course, HtmlUnit is not a real browser: shadow DOM, layout, coordinates, hover etc. don’t work there. But for simple pages it can be a fast way to run tests without any browser at all.

See PR 3470.


Browser capabilities in MCP and CLI

Selenide MCP and Selenide CLI got a new parameter --capability=<name>=<value>. It can be repeated, and values starting with { or [ are parsed as JSON, so you can pass nested capabilities:

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

Chrome arguments passed this way are added to Selenide’s default arguments rather than replacing them.

See PR 3459.


Stale temporary files no longer break downloads

If a file download failed, the browser could leave a temporary file (.crdownload, .tmp, .part) in the downloads folder. And after that, all subsequent downloads failed with FileNotDownloadedError (it could be a problem only if you use your own webdriver via setWebDriver()).

Now Selenide ignores temporary files that were in the folder before the download started. And if it can’t clean up the downloads folder (e.g. on Windows the browser still holds a file), it just logs a warning instead of throwing an exception.

Thanks to ilya-corp for the report!

See issue 2675 and PR 3457.


Unique file names for screenshots and logs

Some file names (screenshots, downloads folders, webdriver.*.log) were generated from the current time only. So two threads could theoretically create a file with the same name within the same millisecond, and one file would overwrite the other.

Now a unique counter is added to the file names as well.

See PR 3469.


Appium: scroll() and swipe() inside an element

Until now, scroll and swipe in Appium were always performed on the whole screen. Now you can perform them inside a given element:

$(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"))));

If the container extends partially beyond the screen, the gesture is performed only in its visible part.

Thanks to valentin-kudin for the implementation!

See issue 3445, PR 3446 and PR 3455.


Appium: chainable methods return SelenideAppiumElement

Previously, this code didn’t compile, because setValue returned SelenideElement:

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

Now 27 chainable methods (setValue, val, type, append, press*, click, doubleClick, hover, scroll, etc.) return SelenideAppiumElement.

Thanks to aquahitt for the implementation!

See issue 3440 and PR 3441.


Appium: custom element class in SelenideAppiumCollection

If you have your own element interface extending SelenideAppiumElement, you can now create a matching collection too: SelenideAppiumCollection got a protected constructor accepting the element class (the same one ElementsCollection got in Selenide 7.17.0).

Thanks to aquahitt for the implementation!

See issue 3438, PR 3439 and PR 3447.


Upgraded Appium java-client from 10.1.1 to 11.0.0

Appium java-client 11 has some breaking changes, but Selenide hides them from you:

  • SelenideAppiumPageFactory works with the new FieldDecorator;
  • reading/writing the clipboard still works with older Appium servers (e.g. on BrowserStack).

See PR 3467.


Upgraded Selenium from 4.49.0 to 4.51.0

  • Supported CDP versions: v153, v154, v155

See PR 3454, PR 3468 and the Selenium changelog.


Want me to add a tweaked quote about killer whales and polar bears here, or some other wild nonsense? I can rework it for an IT context. Or should we title it “One Plague of a Release”? It’ll go viral.



Andrei Solntsev

selenide.org

10.10.26