Library / SDK
yashaka/selene avatar
yashaka/selene

Selene and the cost of the Selenide API in Python

User-oriented Web UI browser tests in Python

733 stars180 forksPythonMIT

At a glance

What is it?
Selene is a Python port of the Java Selenide library that wraps Selenium WebDriver in a user-oriented API with lazy elements, implicit retry and a larger set of conditions. Its current state is awkward: the recommended line is still a 2.0 release candidate while the 1.x branch is pinned to Python versions long out of support.
Who is it for?
Selene suits a Python team that wants Selenide's readability without leaving Python, particularly one already living with Selenium 4 and annoyed by hand-rolled wait helpers, and it suits mobile testing through Appium since the examples cover Android and iOS locally and on BrowserStack.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A port of Selenide, which decides the whole shape of the API

Selene describes itself as a concise and powerful library for writing browser UI tests in Python, and the origin is stated plainly: it was built as a Pythonic port of the popular Selenide project from the Java world.

That lineage explains the design better than any feature list. Selenide's premise is that a test should describe what a user sees, not how to drive a browser, so the same phrasing, lazy elements and matchers arrive in Python with the same names. The stated goal is tests that speak in common English and can be shared across teams.

Underneath, it is still Selenium. The dependency list in pyproject.toml is selenium>=4.12.0 alongside pyperclip and typing-extensions, so the library layers its API on the WebDriver protocol rather than replacing it. That is the key architectural fact for an engineer: Selene is a vocabulary layer over Selenium, and anything Selenium cannot do, Selene cannot do either.

Two supported lines, and the awkward gap between them

The versions section is the most consequential part of the documentation for anyone deciding whether to adopt the library.

The recommended line is 2.0.0rc9, requiring Python 3.10+ and Selenium 4.12.0 or newer. It is described as introducing an improved API and better performance, and as recommended for new projects. The guide is also candid about its state: it is in alpha or beta stage, the API is still being refined, and the only risk is API changes, with some commands in the process of deprecation and renaming. It notes that most active Selene users already upgraded to the 2.0 alpha or beta and have been using it in production during the last two years.

The stable line is v1.0.2, which supports Python 2.7, 3.5, 3.6 and 3.7 and Selenium below 4.0.0. It is offered only for projects that need compatibility with older Python versions.

So the stable option is not really stable in the modern sense, since every Python version it supports has been out of common use for years, and the recommended option is a release candidate. The releases list shows 2.0.0rc10 published on 2026-05-20, which is newer than the rc9 named in the guide, so the documentation itself lags behind the tags. The last push to the repository was on 2026-09-09.

The migration from 1.x to 2.x is a search and replace, with traps

The migration guide is unusually concrete, and it is worth reading twice before committing to the 2.0 line.

The mechanical part: upgrade to Python 3.10+, update selene to the 2.0 release candidate, replace the collection.first() method with the .first property, and make sure all conditions such as text('foo') use the be.* or have.* syntax. Other deprecated methods need updating as needed.

The part that needs judgement is the conditional rewriting. The guide splits it into a plain find and replace of (text('foo')) to (have.text('foo')) and (visible) to (be.visible), then a set of smarter substitutions that need manual refactoring: .should(x, timeout=y) becomes .with_(timeout=y).should(x), .should_not(be.*) becomes .should(be.not_.*) or .should(be.*.not_), .should_not(have.*) becomes .should(have.no.*) or .should(have.*.not_), and .should_each(condition) becomes .should(condition.each).

The imports change with it, from selene import be, have. The last group is where a blind find and replace breaks a suite, because the negated and each forms have more than one valid spelling.

Lazy elements and widgets are what make PageObject work

The PageObject support is described through lazy-evaluated elements. PageObjects compose elements over selectors or By.* tuple-like locators, and the result is reusable elements for different components, which the guide calls Widgets.

Laziness is the mechanism that makes this work. An element reference is not resolved when it is declared but when it is used, so a PageObject can be constructed at import time and still resolve against whichever page is open when the assertion fires. The cost is that a bad locator fails at use rather than at definition, which moves the error away from the code that named the element.

The library adds a list of predefined commands on elements to cut down on ActionChains composition, out of the box support for frames and shadow root, and flexible filtering of collections so locators can be composed instead of expressed as one complex XPath. The claimed payoff of that last one is not just shorter selectors but more detailed error messages, because a filtered collection can report which filter failed rather than that an XPath found nothing.

The examples folder shows how far this extends: a select_from_table.py file, custom_conditions.py, an extend_selene_conditions__framework example, and separate directories for building a custom condition and for logging every Selene command with its wait.

Driver management covers local, headless, remote and mobile

Automatic driver management is listed as handling browser driver setup and teardown for quick local executions. Beyond that, Selene supports local browsers, headless setups and cloud services such as Selenium Grid, in a similar way to Selenium, by providing the corresponding driver options.

The examples directory is where this becomes verifiable, and the naming is unusually informative. There are directories for running locally in default Chrome, in Firefox via set_config_driver_options, in Firefox via set_config_name, and in Safari via set_config_driver, plus run_local_in_chrome_with_options_for_headless for the headless case, and remote directories for Selenium Grid via selenoid, BrowserStack with web Chrome, BrowserStack with iOS Safari, and BrowserStack with Android Chrome.

Mobile is a first-class case rather than an afterthought. There are run_local_android_app_with_options_for_appium, run_local_android_chrome_with_options_for_appium, run_cross_platform, run_cross_platform_android_ios, and run_cross_platform_with_fixture_and_custom_location_strategy. The guide notes that while Selene works with Appium mobile and desktop drivers, some options in the config are platform-specific.

Timeouts can be set at four different levels

The customization paragraph in the feature list is worth reading closely, because it answers a question that comes up in every test framework: who gets to decide how long to wait.

The guide says the same options, for example timeout, can be customised via the same API at four levels: globally, on a specific browser instance, on a specific element or collection of elements stored in a variable, and for a specific action or assertion on an element. It also mentions customisable selector templates, which is how a project changes how a locator is written without rewriting every call site.

That granularity is what makes the library usable in a large suite. A global default handles the common case, while a single element that loads slowly can carry its own timeout without a fixture or a context manager around it. The trade-off is that a test can end up with four different timeout values in play, and understanding why a wait is long requires knowing which level won.

Advanced users are the stated audience for this part of the API, which suggests the default surface is meant to stay small.

Getting Python and Selene installed for a new project

Python 3.10 or newer is the requirement, and the guide shows the pyenv route:

bash
$ pyenv install 3.10.16
$ pyenv global 3.10.16
$ python -V
Python 3.10.16

The recommended installation path is Poetry plus Pyenv, and it starts by creating a project rather than installing into an existing one:

bash
poetry new my-tests-with-selene
cd my-tests-with-selene
pyenv local 3.10.16
poetry add selene --allow-prereleases  # for pre-release version
poetry install

The --allow-prereleases flag is required because the recommended line is a release candidate, so a plain poetry add selene would pull the stable 1.0.2 instead and land you on Python versions that predate the current ecosystem. A Pip installation path is also documented for the pre-release version.

Two details about the packaging are worth knowing. The project is distributed as the selene package on PyPI, with the py.typed marker shipped so type checkers pick up the inline annotations, and the project guide links to separate template repositories for web tests and for mobile cross-platform tests, which are a faster starting point than assembling a project from scratch.

The conditions vocabulary is where the readability comes from

The extended list of expected conditions is described as much bigger than raw Selenium allows, with the aim of more readable and concise assertions. The namespace split introduced in version 2 makes that explicit, with be for state conditions such as visible and have for content conditions such as text.

The migration examples give the shape of the vocabulary: have.text('foo') for text content and be.visible for presence, each of which can be negated with the not_ form, and collections can be asserted with the each modifier rather than a separate should_each method. The be and have split is a real readability gain, since it makes the difference between an element being in a state and an element having a property visible in the call itself.

The custom condition example directories show that the vocabulary is meant to be extended, with custom_conditions.py and an extend_selene_conditions__framework example, so a team can define its own matcher for a domain-specific condition rather than falling back to a raw assertion on a Selenium element.

A small, well-documented package with a long release history

Selene is MIT licensed, authored by Iakiv Kramarenko, and its package metadata is unusually complete. The classifier list covers Python 3.10 through 3.14, the development status is Beta, and the keywords name selenide, selenium, pageobject, widget and wrapper, which is a fair description of the design in one line. The project requires Python 3.10 or newer and lists its home page, repository, documentation and changelog URLs.

The package layout in pyproject.toml splits the selene package into api, common, core and support, with support further split into shared and conditions, and it ships a py.typed marker so type checkers read the inline annotations.

The repository carries the parts a project of this size usually has: mkdocs.yml for the documentation site, examples/, tests/, tools/, poetry.lock, a .codecov.yml, a .coveragerc, a .pylint-disabled-rules file and CHANGELOG.md. The project guide points to the hosted documentation at yashaka.github.io/selene, and badges link to a Telegram chat in English and Russian, a web test template and a mobile cross-platform test template, and the black code style.

The licence question is straightforward. MIT permits use in commercial projects and modification without imposing copyleft on the code that uses the library, which matters more for a test framework than for a runtime dependency, since test code ships far less often.

Editorial conclusion

Selene suits a Python team that wants Selenide's readability without leaving Python, particularly one already living with Selenium 4 and annoyed by hand-rolled wait helpers, and it suits mobile testing through Appium since the examples cover Android and iOS locally and on BrowserStack. It does not suit a team that needs a stable, non-prerelease dependency today, because the recommended line is 2.0.0rc9 while the newest tag is 2.0.0rc10, and it does not suit a project still on Selenium below 4.12.0, since that is the stated floor. Before adopting it, read the migration guide, because the v1 to v2 change moves .first to a property, shifts conditions into the be and have namespaces, and replaces .should(x, timeout=y) with .with_(timeout=y).should(x).

Frequently asked questions

What is Selene and how is it different from Selenium?

Selene is a Python port of the Java Selenide library that sits on top of Selenium WebDriver, which it depends on at version 4.12.0 or newer. It adds a user-oriented API with lazy elements, implicit waiting, a larger set of conditions and PageObject widgets, without replacing Selenium itself.

Which version of Selene should I use?

The guide recommends the 2.0.0rc9 pre-release line for new projects, requiring Python 3.10+ and Selenium 4.12.0+. The stable v1.0.2 supports Python 2.7, 3.5, 3.6 and 3.7 with Selenium below 4.0.0, and is only for older Python compatibility. The releases list shows 2.0.0rc10 as the newest tag, published on 2026-05-20.

How do I migrate a Selene 1.x test suite to 2.x?

Upgrade to Python 3.10+, install the 2.0 release candidate, change .first() to the .first property, move conditions into the be.* and have.* namespaces, and replace .should(x, timeout=y) with .with_(timeout=y).should(x). The negated and each forms have more than one valid spelling, so those need manual refactoring.

Does Selene work with mobile browsers and Appium?

Yes. Selene works with Appium mobile and desktop drivers, and the examples folder covers local Android apps, local Android Chrome, cross-platform runs for Android and iOS, and remote runs on BrowserStack for iOS Safari and Android Chrome. Some config options are platform-specific.

How do I set a timeout in Selene?

The same option can be set at four levels: globally, on a specific browser instance, on a specific element or collection stored in a variable, and for a specific action or assertion on an element. Selector templates are also customisable for projects that want a different locator style.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. yashaka/selene on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yashaka-selene.svg)](https://hysenlabs.com/projects/yashaka-selene)

Was this useful?