Buenos hellos!
Take a second break from the football and check out what release just rolled in:
Selenide 7.17.0!
We finally shipped the byRole locator!
byRolelocator β find an element by ARIA role- More MCP tools for your AI slave!
- Downloading multiple files at once
- SelenideAppiumElement return type
Selenide.cookies()β a new API for cookieselement()/elements()for Kotlin developers in Appium- Upgraded Selenium from 4.44.0 to 4.46.0
byRole locator β find an element by ARIA role
Lately, “accessibility locators” have become all the rage.
I love the thought that these were originally invented to help blind and visually impaired people and so on. And testers went like: sweet, who cares about the blind, we’ll use this in our tests so we don’t have to hunt for IDs!
It’s often handy in tests to search for elements by role. Like, “find the button” β without caring how that button is actually implemented under the hood.
Now the byRole method lets you find an element by role, or by role and text.
$(byRole("button")).click();
$(byRole("button", "Start refueling")).click();
And the role isn’t only matched via the explicit
role="button"attribute, but also via a tag’s implicit role β if you’ve got an honest-to-goodness<button>or<a href="...">, Selenide will find it too. The list of supported roles is decent:button,link,heading,checkbox,radio,textbox,searchbox,combobox,listbox,option,img,list,listitem,table,row,cell,navigation,dialog,form, and about a dozen more.And the text (i.e.
accessibleNameβ the element’s “accessible name”) is computed in a specific order: firstaria-labelledby, thenaria-label, then a linked<label>, thenalt, then the inner text, and finallytitle.
| Element | Locator |
|---|---|
<button>Refuel</button> | $(byRole("button")).click(); |
<h1>No fuel<h2>,<h6>Station down</h6> | $$(byRole("heading")) |
<div role="alert">No 95 octane!</div> | $(byRole("alert")) |
<button aria-label="We don't fill canisters">Γ</button> | $(byRole("button", "We don't fill canisters")) |
<label>Complaint<input type="text"></label> | $(byRole("textbox", "Complaint")) |
<img src="qr.png"alt="QR code for refueling"/> | $(byRole("img", "QR code for refueling")) |
You can refine the text search too: “full text” or “substring”, “case-sensitive” or “case-insensitive”; “ignore whitespace” or “respect whitespace” β same as in byPlaceholder, byTitle, byAltText:
import static com.codeborne.selenide.TextMatchOptions.*;
import static com.codeborne.selenide.Selectors.byRole;
$(byRole("img", "QR code", partialText()));
$(byRole("img", "qr code for REFUELING", fullText().caseInsensitive()));
$(byRole("img", "QR code for refueling", partialText().ignoreWhitespaces()));
This isn’t a full implementation of the W3C AccName algorithm yet (recursive subtree traversal and hidden elements aren’t there yet), but for 95% of real-world pages it’ll be more than enough.
See issue 3359 and PR 3336.
More MCP tools for your AI slave!
Remember we recently shipped the Selenide MCP server? At first it only had a limited set of tools β just enough to try it out and play around. Now we’ve vibe-coded it nine more tools:
- tab management β
browser_tab_list,browser_tab_select,browser_tab_new,browser_tab_close browser_resizeβ resize the browser windowbrowser_fill_formβ fill a whole batch of fields in one callbrowser_wait_forβ wait for text, for text to disappear, for an element’s state, or just for a plain timeoutbrowser_network_requests/browser_network_requestβ see what the browser’s actually been asking the backend for (requires--proxy-enabledat startup)
Now your AI can see all the tabs on the field, track network requests, and even fill out forms with a single kick instead of poking at every field one by one.
> claude
> /mcp enable selenide
> Dear agent,
> resize the window to mobile size and find out which gas stations have 95-octane fuel.
> Open each gas station in a separate tab.
> Grab the session cookies from all the responses and retry every 5 minutes.
> Don't give up!See PR 3335.
Downloading multiple files at once
Selenide has long had the $.download method with a whole bunch of options, to click a button and download a file.
But here’s the catch: sometimes one click triggers downloading several files at once, and the test wants to check all of them. And sometimes downloading them one at a time just isn’t an option β say, if the link is single-use.
Now there’s a $.downloadFiles method, which clicks and returns several files at once:
List<File> reports = $("#reports").downloadFiles(
files(5).withExtension("zip")
);
assertThat(reports).hasSizeGreaterThanOrEqualTo(5);
Handy if one click in the UI triggers downloading a whole batch of files (reports, archives, whatever), and you need to check that they all arrived and are the right format.
See issue 3261 and PR 3334.
SelenideAppiumElement now honestly returns SelenideAppiumElement
An old thorn in mobile tests’ side, going on two years now: if you called find()/$()/$x() on a
SelenideAppiumElement, it used to hand you back a plain SelenideElement β and all the mobile-specific methods
became unavailable. Like calling up a striker for the national team, and then sending a midfielder onto the pitch instead.
SelenideAppiumElement parent = SelenideAppium.$(By.xpath("//android.view.ViewGroup"));
SelenideAppiumElement child = SelenideAppium.$(By.xpath("//android.widget.TextView"));
// Compilation error: child is `SelenideElement`, not `SelenideAppiumElement`.
Now find(), $(), $x(), findAll(), $$(), $$x() on SelenideAppiumElement return proper Appium elements β
throughout the whole call chain, not just at the first level.
Thanks to Stanislav Vasenkov for starting to dig into this in PR 3362.
See issue 2761 and PR 3373.
Selenide.cookies() β a new API for cookies
Selenide has long had a nice, symmetric API for storages:
sessionStorage().clear();
localStorage().clear();
But cookies never got that same beauty β just the old clunky Selenide.clearBrowserCookies() and checks
via webdriver().shouldHave(cookie("name")).
We fixed that injustice β now cookies live in the same lineup:
cookies().clear();
cookies().add("mouse", "Jerry");
cookies().shouldHave(cookie("cat"));
cookies().shouldHave(cookie("mouse", "Jerry"));
While we were at it, we deprecated the old clearBrowserLocalStorage() and clearBrowserCookies() methods β
use
localStorage().clear(),sessionStorage().clear()andcookies().clear().
Thanks to Stanislav Vasenkov for PR 3361.
See PR 3375 and issue 3357.
element()/elements() for Kotlin developers in Appium
If you write Appium tests in Kotlin, you’ve probably already cursed the $ method β in Kotlin you have to escape
it with backticks, and it looks more like a magic spell than code:
To cool down the burning Kotlin backsides, we added element()/elements() aliases β just like in Selenide.
// before
private val button = SelenideAppium.`$`(CombinedBy.android(...).ios(...))
// after
private val button = SelenideAppium.element(CombinedBy.android(...).ios(...))
Thanks to Gabriel Baldez for the PR!
See issue 3348 and PR 3350.
Upgraded Selenium from 4.44.0 to 4.46.0
And bumped the CDP version from v148 to v150.
Notable changes for us:
- debug logs in Grid when downloading files β now when downloading a file from Selenium Grid, the grid writes debug logs about which files were found and where. Will help those poor souls whose files from the grid mysteriously refuse to download.
- ElectronDriver β native support for little Electron apps!
- publish selenium-devtools-latest artifact Now instead of hardcoding the CDP version in imports everywhere:
import org.openqa.selenium.devtools.v148.browser.Browser;
and forever bumping it across all your tests, you can just write “latest” and forget about it forever:
import org.openqa.selenium.devtools.latest.browser.Browser;
See PR 3347, PR 3374 and the Selenium changelog.
Don’t delay,
update!
We’ll be vibecoding even more soon…
selenide.org
12.07.26
