User-oriented browser tests in Python (Selenide port)
Project description
Selene - User-oriented Web UI browser tests in Python
(Selenide port to Python)
Main features:
- User-oriented API for Selenium Webdriver (code like speak common English)
- Ajax support (Smart implicit waiting and retry mechanism)
- PageObjects support (all elements are lazy-evaluated objects)
- Automatic driver management (no need to install and setup driver for quick local execution)
Selene was inspired by Selenide from Java world.
Tests with Selene can be built either in a simple straightforward "selenide' style or with PageObjects composed from Widgets i.e. reusable element components.
Table of content
Prerequisites
Given pyenv installed, installing needed version of Python is pretty simple:
$ pyenv install 3.7.3
$ pyenv global 3.7.3
$ python -V
Python 3.7.3
Installation
poetry + pyenv (recommended)
GIVEN poetry and pyenv installed ...
AND
$ poetry new my-tests-with-selene
$ cd my-tests-with-selene
$ pyenv local 3.7.3
WHEN latest stable version:
$ poetry add selene
WHEN latest pre-release version:
$ poetry add selene --allow-prereleases
THEN
$ poetry install
pip
Latest stable version:
$ pip install selene
Latest pre-release version:
$ pip install selene --pre
from sources
$ git clone https://github.com/yashaka/selene.git
$ python setup.py install
or using pip:
$ pip install git+https://github.com/yashaka/selene.git
Usage
Quick Start
Simply...
from selene.support.shared import browser
from selene import by, be, have
browser.open('https://google.com/ncr')
browser.element(by.name('q')).should(be.blank)\
.type('selenium').press_enter()
browser.all('.srg .g').should(have.size(10))\
.first.should(have.text('Selenium automates browsers'))
OR with custom setup
from selene.support.shared import browser
from selene import by, be, have
browser.config.browser_name = 'firefox'
browser.config.base_url = 'https://google.com'
browser.config.timeout = 2
# browser.config.* = ...
browser.open('/ncr')
browser.element(by.name('q')).should(be.blank)\
.type('selenium').press_enter()
browser.all('.srg .g').should(have.size(10))\
.first.should(have.text('Selenium automates browsers'))
OR more Selenide from java style:
from selene.support.shared import config, browser
from selene import by, be, have
from selene.support.shared.jquery_style import s, ss
config.browser_name = 'firefox'
config.base_url = 'https://google.com'
config.timeout = 2
# config.* = ...
browser.open('/ncr')
s(by.name('q')).should(be.blank)\
.type('selenium').press_enter()
ss('.srg .g').should(have.size(10))\
.first.should(have.text('Selenium automates browsers'))
Core Api
Given:
from selenium.webdriver import Chrome
AND chromedriver executable available in $PATH
WHEN:
from selene import Browser, Config
browser = Browser(Config(
driver=Chrome(),
base_url='https://google.com',
timeout=2))
AND (uncomment if needed):
# import atexit
# atexit.register(browser.quit)
AND:
browser.open('/ncr')
AND:
# browser.element('//*[@name="q"]')).type('selenium').press_enter()
# OR...
# browser.element('[name=q]')).type('selenium').press_enter()
# OR...
from selene import by
# browser.element(by.name('q')).type('selenium').press_enter()
# OR...for total readability
query = browser.element(by.name('q')) # actual search doesn't start here, the element is "lazy"
# here the actual webelement is found
query.type('selenium').press_enter()
# and here it's located again, i.e. the element is "dynamic"
AND (in case we need to filter collection of items by some condition like visibility):
from selene import be
results = browser.all('.srg .g').filtered_by(be.visible)
THEN: from selene import have
# results.should(have.size(10))
# results.first.should(have.text('Selenium automates browsers'))
# OR...
results.should(have.size(10))\
.first.should(have.text('Selenium automates browsers'))
FINALLY (if not registered "atexit" before):
browser.quit()
Automatic Driver and Browser Management
Instead of:
from selene import Browser, Config
browser = Browser(Config(
driver=Chrome(),
base_url='https://google.com',
timeout=2))
You can simply use the browser and config instance predefined for you in selene.support.shared
module:
from selene.support.shared import browser, config
# ... do the same with browser.*
So you don't need to create you driver instance manually. It will be created for you automatically.
Yet, if you need some special case, like working with remote driver, etc., you can still use shared browser object, while providing driver to it through:
config.driver = my_remote_driver_instance
# or
browser.config.driver = my_remote_driver_instance
Advanced API
Sometimes you might need some extra actions on elements, e.g. for workaround something through js:
from selene import command
browser.element('#not-in-view').perform(command.js.scroll_into_view)
Probably you think that will need something like:
from selene import query
product_text = browser.element('#to-assert-something-non-standard').get(query.text)
price = my_int_from(product_text)
assert price > 100
But usually it's either better to implement your custom condition:
browser.element('#to-assert-something-non-standard').should(have_in_text_the_int_number_more_than(100))
Where the have_in_text_the_int_number_more_than
is your defined custom condition. Such condition-based alternative will be less fragile, because python's assert does not have "implicit waiting", like selene's should ;)
Furthermore, the good test is when you totally control your test data, and instead:
product = browser.element('#to-remember-for-future')
product_text_before = product.get(query.text)
price_before = my_int_from(product_text_before)
# ... do something
product_text_after = product.get(query.text)
price_after = my_int_from(product_text_after)
assert price_after > price_before
Normally, better would be to refactor to something like:
product = browser.element('#to-remember-for-future')
product.should(have.text('100$'))
# ... do something
product.should(have.text('125$'))
You might think you need something like:
from selene import query
if browser.element('#i-might-say-yes-or-no').get(query.text) == 'yes':
# do something...
Or:
from selene import query
if browser.all('.option').get(query.size) >= 2:
# do something...
Maybe one day, you really find a use case:) But for above cases, probably easier would be:
if browser.element('#i-might-say-yes-or-no').wait_until(have.text('yes'):
# do something
# ...
if browser.all('.i-will-appear').wait_until(have.size_greater_than_or_equal(2)):
# do something
Or, by using non-waiting versions, if "you are in a rush:)":
if browser.element('#i-might-say-yes-or-no').matching(have.text('yes'):
# do something
# ...
if browser.all('.i-will-appear').matching(have.size_greater_than_or_equal(2)):
# do something
Tutorials
TBD
More examples
TBD
Changelog
Contributing
Before implementing your ideas, it is recommended first to create a corresponding issue and discuss the plan to be approved;) Also consider first to help with issues marked with help_needed label ;)
-
Clone project git clone https://github.com/yashaka/selene.git
-
Install pipenv via pip install pipenv
-
cd selene
-
pipenv install --dev
-
Add a "feature request" Issue to this project.
-
Discuss its need and possible implementation. And once approved...
-
Fork the project ( https://github.com/[my-github-username]/selene/fork )
-
Create your feature branch (
git checkout -b my-new-feature
) -
Commit your changes (
git commit -am 'Add some feature'
) -
Push to the branch (
git push origin my-new-feature
) -
Create a new Pull Request
Release process
- python setup.py bdist_wheel
- twine upload dist/*
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
File details
Details for the file selene-2.0.0a18-py3-none-any.whl
.
File metadata
- Download URL: selene-2.0.0a18-py3-none-any.whl
- Upload date:
- Size: 64.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/2.0.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.36.1 CPython/3.7.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 010a6074e8d84458cd250b5d65179babd1ee76cd3aa5709b6770c6299719c2ea |
|
MD5 | e0a7f01106db7b59fffaf5d0f1ced53e |
|
BLAKE2b-256 | 24ea457197bb11de9a8d2e84c736c0821f6041c45b69e340054fd101ce3454d5 |