Good evening!
A carrier pigeon just delivered us Selenide 7.19.0!
- Saving and restoring browser state
- Time and timezone emulation
- Conditions
visibleTextandexactVisibleText - HtmlUnit is back
- Browser capabilities in MCP and CLI
- Stale temporary files no longer break downloads
- Unique file names for screenshots and logs
- Appium:
scroll()andswipe()inside an element - Appium: chainable methods return
SelenideAppiumElement - Appium: custom element class in
SelenideAppiumCollection - Upgraded Appium java-client from 10.1.1 to 11.0.0
- Upgraded Selenium from 4.49.0 to 4.51.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:
- If the file
build/auth.jsonexists, Selenide restores cookies,localStorageandsessionStoragefrom it, opens the page and checks theloggedInIfcondition. - 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. - 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();
setFixedTimefreezesnew Date()andDate.now()for newly loaded pages.- Works in Chrome and Edge (via CDP) and in Firefox (via BiDi).
reset()is safe to call in@AfterEachfor 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:
SelenideAppiumPageFactoryworks with the newFieldDecorator;- 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.
selenide.org
10.10.26
