User-oriented Web UI browser tests in Python
Selene is a concise and powerful library for writing browser UI tests in Python. It was built as a Pythonic port of the popular Selenide project from the Java world.
Selene helps developers write readable and maintainable tests that “speak” in common English, making them easier to understand and share across teams.
Selene’s core strength is its user-oriented API, which abstracts the complexity of working with Selenium WebDriver. Tests can be written using simple, expressive syntax and support features like lazy-evaluated elements, automatic retry mechanisms for smarter implicit waiting for Ajax-like loading. With built-in PageObject support, it enables the reusability of web elements through Widgets.
By.*
tuple-like locators, allowing also to create reusable elements for different components.ActionChains
.Selene currently supports two major versions:
Latest Recommended Pre-Release Version (v2.0.0rc9):
3.8+
>=4.12.0
Stable Version (v1.0.2):
2.7, 3.5, 3.6, 3.7
<4.0.0
For migration from v1.x to v2.x, follow the migration guide.
From 1.0.2
to 2.0.0rc<LATEST>
:
2.0.0rc<LATEST>
collection.first()
method from .first()
to .first
text('foo')
use the be.*
or have.*
syntaxExamples of potential refactoring during migration:
(text('foo'))
to (have.text('foo'))
(visible)
to (be.visible)
.should(x, timeout=y)
to .with_(timeout=y).should(x)
.should_not(be.*)
to .should(be.not_.*)
or .should(be.*.not_)
.should_not(have.*)
to .should(have.no.*)
or .should(have.*.not_)
.should_each(condition)
to .should(condition.each)
from selene import be, have
Python 3.8+ is required.
Given pyenv is installed, installing the needed version of Python is simple:
$ pyenv install 3.8.13
$ pyenv global 3.8.13
$ python -V
Python 3.8.13
Ensure poetry and pyenv are installed, then:
poetry new my-tests-with-selene
cd my-tests-with-selene
pyenv local 3.8.13
poetry add selene --allow-prereleases # for pre-release version
poetry install
For the pre-release version (recommended for new projects):
pip install selene --pre
For the latest stable version:
pip install selene
If you prefer to install Selene directly from the source code:
git clone https://github.com/yashaka/selene.git
cd selene
python setup.py install
Or using poetry:
poetry add git+https://github.com/yashaka/selene.git
Or using pip:
pip install git+https://github.com/yashaka/selene.git
Automate a simple Google search using Selene:
from selene import browser, be, have
browser.open('https://google.com/ncr')
browser.element('[name=q]').should(be.blank)\
.type('selenium').press_enter()
browser.all('#rso>div').should(have.size_greater_than(5))\
.first.should(have.text('Selenium automates browsers'))
# not mandatory, because will be closed automatically:
# browser.quit()
Selene provides an intuitive API for interacting with web elements using modules like be
, have
or by
.
Lazy and Dynamic Elements: Selene elements are lazy and dynamic, meaning they are located each time an action is performed. This ensures interaction with the most up-to-date element.
Here is a basic element interaction:
from selene import browser, by, be
# because elements are "lazy",
# you can store them in variable:
search_box = browser.element(by.name('q'))
# – even before the actual page will be loaded:
browser.open('https://google.com/ncr')
search_box.should(be.blank).type('Selenium').press_enter()
Choosing the correct element locators is crucial for reliable tests. Here are some tips:
Inspect the Element: Right-click on the web element and select Inspect to view its HTML in the browser’s developer tools.
Use Unique Attributes: Look for unique attributes like id, name, or custom attributes to use in your selectors. Best practice is to negotiate with developers on using unique data-*
attributes specifically for testing needs, like data-test-id
.
Construct CSS or XPath Selectors: Build selectors that uniquely identify elements. For example, using conciser css-selectors #elementId
, .className
, or [name="q"]
, or using XPath for things that CSS can’t handle: //*[text()="Submit"]/..
. Selene will automatically detect whether you provide a CSS or XPath selector.
Utilize Selene’s Selector Helpers (optional): If you need most human-readable code, you can use by.name('q')
, by.text('Submit')
and other by.*
helpers. Notice, that someone would prefer raw css selector like [name=q]
over by.name('q')
for the purpose of KISS principle.
Break down long selectors into smaller parts for better readability and maintainability: If to find an element you have a complex selector like in: browser.element('//*[@role="row" and contains(.,"Jon")]//*[@data-field="select-row"]')
, decomposing it utilizing Selene’s filtering collections API to browser.all('[role=row]').element_by(have.text('Jon')).element('[data-field=select-row]')
will make it easier to understand and maintain, because if something changes in the structure of the page, and your test fails, you will see exactly where it fails among “decomposed parts” of the selector, while in case of longer XPath selector you will see only that it fails somewhere in the middle of the long selector with the following need to double-check each potential reason of failure.
Create custom selector strategy (optional): Imagine your frontend developers follow best practices and use data-test-id
attributes for all elements that need to be covered in tests, and there is a followed naming convention for such element to be snake_case_OR-kebab-case-words. With Selene’s browser.config.selector_to_by_strategy
option you can define a custom selector strategy to automatically detect all such "snake_kebab-words"
passed as selectors and transform them into [data-test-id="snake_kebab-words"]
locators. Then an example like browser.all('[data-testid=result]').first.element('[data-testid=result-title-a]').click()
can be simplified to browser.all('result').first.element('result-title-a').click()
. See How to simplify search by Test IDs? guide for more details.
❗This feature will be available in the next release of Selene.️
Selene can automatically manage the browser driver for you, but you can customize it if needed.
from selene import browser
browser.config.base_url = 'https://google.com'
browser.config.timeout = 2
browser.open('/ncr')
from selenium import webdriver
from selene import browser
options = webdriver.ChromeOptions()
options.add_argument('--headless')
# Add other options as needed
# Selene will automatically detect the browser type (Chrome, Firefox, etc.)
# based on the options passed
browser.config.driver_options = options
browser.config.driver_remote_url = 'http://localhost:4444/wd/hub'
browser.config.base_url = 'https://google.com'
browser.config.timeout = 2
browser.open('/ncr')
from selenium import webdriver
from selene import browser
options = webdriver.ChromeOptions()
options.add_argument('--headless')
# Add other options as needed
browser.config.driver = webdriver.Remote(
command_executor='http://localhost:4444/wd/hub',
options=options
)
browser.config.base_url = 'https://google.com'
browser.config.timeout = 2
browser.open('/ncr')
Use Readable and Stable Selectors: Aim for selectors that are unique, expressive and minimally coupled with DOM structure to make your tests more maintainable.
Encapsulate Reusable Components: Utilize the Page Object Model to encapsulate elements and actions, promoting code reuse.
Leverage Built-in Waiting: Selene automatically waits for elements to be in the desired state, reducing the need for explicit waits.
Explore the API: Dive into Selene’s source code or documentation to understand the available methods and configurations better.
We welcome contributions to Selene! Here’s how you can get involved:
For more details, refer to our contribution guide.
See the Changelog for more details about recent updates.