User-oriented Web UI browser tests in Python

Related tags

Testing selene
Overview

Selene - User-oriented Web UI browser tests in Python (Selenide port)

selene tests codecov Free MIT License Project Template

GitHub stats in GitHub views GitHub views per week GitHub clones GitHub clones per week

Join telegram chat https://t.me/selene_py Присоединяйся к чату https://t.me/selene_py_ru

Sign up for a course http://autotest.how/selene Запишись на курс http://autotest.how/selene-ru Учи Selene https://leanpub.com/selene-automation-ru Реєструйся на курс http://autotest.how/selene-uk

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

Versions

  • Latest recommended version to use is 2.0.0a33

    • it's a completely new version of selene, with improved API and speed
    • supports python >= 3.7
    • it's incompatible with 1.x
    • current master branch is pointed to 2.x
    • yet in pre-alpha stage, refining API, improving "migratability", and testing
    • it looks pretty stable, but not fully proven yet
      • mainly tested on production code base of a few users who successfully migrated from 1.x to 2.x
  • Latest version marked as stable is: 1.0.2

    • it is main version used by most selene users during last 2 years
    • it was proven to be stable for production use
    • its sources and corresponding README version can be found at 1.x branch.
    • supports python 2.7, 3.5, 3.6, 3.7

THIS README DESCRIBES THE USAGE OF THE PRE-RELEASE version of Selene. For older docs look at 1.x branch.

Migration guide

GIVEN on 1.0.1:

  • upgrade to python 3.7
  • update selene to 2.0.0aLATEST
    • find&replace the collection.first() method from .first() to .first
    • ensure all conditions like text('foo') are used via be.* or have.* syntax
      • example:
        • find&replace all
          • (text('foo')) to (have.text('foo'))
          • (be.visible) to (be.visible)
        • smarter find&replace (with some manual refactoring)
          • .should(x, timeout=y) to .with_(timeout=y).should(x)
        • and add corresponding imports: from selene import be, have
    • fix another broken imports if available
    • run tests, read deprecation warnings, and refactor to new style recommended in warning messages

Prerequisites

Python >= 3.7

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 pre-release recommended version:

$ poetry add selene --allow-prereleases

WHEN latest stable version:

$ poetry add selene

THEN

$ poetry install

pip

Latest recommended pre-release alpha version:

$ pip install selene --pre

Latest stable version:

$ pip install selene

from sources

GIVEN webdriver and webdriver_manager are already installed

THEN

$ 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

see CHANGELOG.md

Contributing

see CONTRIBUTING.md

Release process

  1. python setup.py bdist_wheel
  2. twine upload dist/*
Comments
  • Pycharm + pytest + selene = INTERNALERROR

    Pycharm + pytest + selene = INTERNALERROR

    Hi all! I get an internal error when i start test via Pycharm 2018.2 and pytest-3.10.0 The first test is success but the second one causes the error. When i do the same via command line - no errors

    code for reproduce:

    from selene.api import *
    
    
    def setup_function(module):
        driver = webdriver.Chrome()
        browser.set_driver(driver)
    
    
    def teardown_function(module):
        browser.quit()
    
    
    def test_selene():
        browser.open_url('http://ya.ru')
        s('#text').set('Python Selene try 1')
        s('[type="submit"]').click()
    
    
    def test_selene_2():
        browser.open_url('http://ya.ru')
        s('#text').set('Python Selene try 2')
        s('[type="submit"]').click()
    

    Traceback in attached file traceback.txt

    Same issue: https://automated-testing.info/t/pycharm-pytest-selene-internalerror/21791/1 http://software-testing.ru/forum/index.php?/topic/36059-python-selene-pytest-pycharm-internalerror-pri-zapuske-testov/

    opened by maxim-zaitsev 29
  • Add custom error message in asserts.

    Add custom error message in asserts.

    Hi, yashaka!

    Is it good idea to add custom error message in asserts, like def should(self, condition,error_message = None, timeout=None), so wait_for will throw this custom message with all 'reason' attributes?

    not sure 
    opened by quarrell 14
  • from selenium to selene

    from selenium to selene

    try

    config.browser_name = 'chrome'
    browser.open_url('http://stifix.pythonanywhere.com/welcome/default/user/login')
    browser.element('#auth_user_email').set_value(new_valid_email)
    browser.element('#auth_user_password').set_value(new_password)
    browser.element('//*[@id="submit_record__row"]/div/input').click()
    if "Log In" in browser:
        print("Log In Failed")
    else:
        print("Log In Success")
    browser.quit()
    

    result just open the page, no words typed, submit element not clicked and no print result 'success' or 'failed'

    question how to achieve it using selene ?

    e.g. in selenium

    browser = webdriver.Chrome()
    browser.get(('http://stifix.pythonanywhere.com/welcome/default/user/login') )
    browser.find_element_by_id('auth_user_email').send_keys(new_valid_email)
    browser.find_element_by_id('auth_user_password').send_keys(new_password)
    browser.find_element_by_xpath('//*[@id="submit_record__row"]/div/input').click()
    if "Log In" in browser.page_source:
        print("Log In Failed")
    else:
        print("Log In Success")
    browser.quit()
    

    opinion syntax is quite different from selene with selenium and splinter in selenium and splinter is quite same, but i appreciate variety, that's why want to learn how it works on selene

    suggestion perhaps can activate github discussion so the question like this can goes there

    thx

    question not a bug 
    opened by sugizo 13
  • alert = driver().switch_to.alert alert.accept() throws NoAlertPresentException

    alert = driver().switch_to.alert alert.accept() throws NoAlertPresentException

    When i use selene and try to accept alert:

    config.browser_name = Browser.CHROME
    
    alert = driver().switch_to.alert
    alert.accept()
    

    I get this error: ......\MobilePages\RecipientPageM.py:23: in choose_recipient_from_list alert.accept() ..........\selene-virt\lib\site-packages\selenium\webdriver\common\alert.py:80: in accept self.driver.execute(Command.ACCEPT_ALERT) ..........\selene-virt\lib\site-packages\selenium\webdriver\remote\webdriver.py:233: in execute self.error_handler.check_response(response) E selenium.common.exceptions.NoAlertPresentException: Message: no alert open E (Session info: chrome=58.0.3029.81) E (Driver info: chromedriver=2.29.461591

    While when i use standart driver it works fine:

    self.driver = webdriver.Chrome(ChromeDriverManager().install())
    
    alert = self.driver.switch_to.alert
    alert.accept()
    
    not sure 
    opened by byakatat 12
  • New driver  instance starts when trying to switch to alert

    New driver instance starts when trying to switch to alert

    Hi all! I am using pre-release version of selene: 1.0.0.a10 I got an error when i was trying to switch to alert. Here is a script to reproduce:

    ` from selene.browser import driver, visit from selene.support.jquery_style_selectors import s from selene import config from selene.browsers import Browser

    config.browser_name = Browser.CHROME

    visit('http://javascript.ru/alert') s('input[value="Запустить"]').click() alert = driver().switch_to.alert # Current driver instance has been closed and new driver instance is starting alert.accept() # so we get an exception at the last step: selenium.common.exceptions.NoAlertPresentException: Message: no alert open `

    bug 
    opened by maxim-zaitsev 9
  • Selene Selenium 3.0 Actions error

    Selene Selenium 3.0 Actions error

    According to new release of GeckoDriver https://github.com/mozilla/geckodriver/releases and new version of selenium python binding == 3.3.0 Selene will fail in SeleneElement.init() method This is due to ActionChains, seems that new version of binding brings key that says if a given browser is w3c compliant.

    During debug I found that to fix this issue we need to init ActrionChains like this:

     self._actions_chains = ActionChains(webdriver._source.driver)
    

    instead of:

    self._actions_chains = ActionChains(webdriver)
    
    help wanted 
    opened by SergeyPirogov 9
  • six library is missing

    six library is missing

    Tried basic example from README.md file.

    Traceback (most recent call last):
      File "login_test.py", line 1, in <module>
        from selene.api import *
      File "/Users/erik/.virtualenvs/ui-tests/lib/python3.6/site-packages/selene/api/__init__.py", line 1, in <module>
        from selene import config, browser, browsers
      File "/Users/erik/.virtualenvs/ui-tests/lib/python3.6/site-packages/selene/browser.py", line 6, in <module>
        import selene.driver
      File "/Users/erik/.virtualenvs/ui-tests/lib/python3.6/site-packages/selene/driver.py", line 11, in <module>
        from selene.elements import SeleneElement, SeleneCollection
      File "/Users/erik/.virtualenvs/ui-tests/lib/python3.6/site-packages/selene/elements.py", line 23, in <module>
        from selene.wait import wait_for
      File "/Users/erik/.virtualenvs/ui-tests/lib/python3.6/site-packages/selene/wait.py", line 1, in <module>
        import six
    ModuleNotFoundError: No module named 'six'
    
    bug 
    opened by aruba8 7
  • No ability to get page url

    No ability to get page url

    For example I want to check page url:

    main_page = LoginPage().open().login("admin","admin")

    main_page.url.should_be("blablabla")

    I didn't found the way in selene to get current page Url. Maybe we need to add such method

    enhancement 
    opened by SergeyPirogov 7
  • If by.xpath contains utf8 symbols and not condition get UnicodeEncodeError: 'ascii'

    If by.xpath contains utf8 symbols and not condition get UnicodeEncodeError: 'ascii'

    Checking for linux geckodriver:v0.14.0 in cache
    Driver found in /home/toxa/.wdm/geckodriver/v0.14.0/geckodriver
    Traceback (most recent call last):
      File "./first_step.py", line 21, in 
        s(by.xpath("//button[contains(.,'Объекты')]")).should_be(be.conditions.Clickable()).click()
      File "/usr/local/lib/python2.7/dist-packages/selene/elements.py", line 305, in should
        _wait_with_screenshot(self._webdriver, self, condition, timeout)
      File "/usr/local/lib/python2.7/dist-packages/selene/elements.py", line 187, in _wait_with_screenshot
        return wait_for(entity, condition, timeout, polling)
      File "/usr/local/lib/python2.7/dist-packages/selene/wait.py", line 17, in wait_for
        reason_string = '{name}: {message}'.format(name=reason.__class__.__name__, message=reason_message)
    UnicodeEncodeError: 'ascii' codec can't encode characters in position 47-53: ordinal not in range(128)
    
    opened by kixiro 6
  • Find elements by class name

    Find elements by class name

    Hey :)

    Iv'e tried to implement element finding by class name and saw that selene doesn't support that natively bys.py There is any reason to this ? if not, can I add the implementation myself ?

    Thanks! Amit.

    opened by amitaz3354 5
  • Document mentioning issue number in commit messega for contributed PR

    Document mentioning issue number in commit messega for contributed PR

    Now we have: image

    Commit your changes (git commit -am 'Add some feature')
    

    but should have something like:

    Commit your changes (`git commit -am "$ISSUE_NUMBER: Add some feature"`, where ISSUE_NUMBER is the number of issue this commit relates to)
    
    

    Additional todos:

    • emphasize that -am is just an example, for sure we should use git add ... if new files were added - reflect this somehow in contribution guide also
    help wanted good first issue 
    opened by yashaka 5
  • Collection.matching(be.present) returns True if empty

    Collection.matching(be.present) returns True if empty

    Following code

    browser.all('.some-random-and-for-sure-is-not-presented-in-a-dom-class').matching(be.present)
    

    returns True but it should be False

    This leads to situations when browser.all('.some-random-and-for-sure-is-not-presented-in-a-dom-class').should(be.present) passes and browser.all('.some-random-and-for-sure-is-not-presented-in-a-dom-class').should(be.absent) fails.

    opened by SanKolts 1
  • Build(deps): Bump setuptools from 65.3.0 to 65.5.1

    Build(deps): Bump setuptools from 65.3.0 to 65.5.1

    Bumps setuptools from 65.3.0 to 65.5.1.

    Changelog

    Sourced from setuptools's changelog.

    v65.5.1

    Misc ^^^^

    • #3638: Drop a test dependency on the mock package, always use :external+python:py:mod:unittest.mock -- by :user:hroncok
    • #3659: Fixed REDoS vector in package_index.

    v65.5.0

    Changes ^^^^^^^

    • #3624: Fixed editable install for multi-module/no-package src-layout projects.
    • #3626: Minor refactorings to support distutils using stdlib logging module.

    Documentation changes ^^^^^^^^^^^^^^^^^^^^^

    • #3419: Updated the example version numbers to be compliant with PEP-440 on the "Specifying Your Project’s Version" page of the user guide.

    Misc ^^^^

    • #3569: Improved information about conflicting entries in the current working directory and editable install (in documentation and as an informational warning).
    • #3576: Updated version of validate_pyproject.

    v65.4.1

    Misc ^^^^

    • #3613: Fixed encoding errors in expand.StaticModule when system default encoding doesn't match expectations for source files.
    • #3617: Merge with pypa/distutils@6852b20 including fix for pypa/distutils#181.

    v65.4.0

    Changes ^^^^^^^

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Build(deps): Bump certifi from 2021.10.8 to 2022.12.7

    Build(deps): Bump certifi from 2021.10.8 to 2022.12.7

    Bumps certifi from 2021.10.8 to 2022.12.7.

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • set(Keys.ESCAPE) or .press_escape is not working when im trying to interact with not interactable element on webpage

    set(Keys.ESCAPE) or .press_escape is not working when im trying to interact with not interactable element on webpage

    Hey! So my problem is that i cant use .press_escape or set(Keys.ESCAPE) in some cases.

    The code is below: browser.open("https://www.dns-shop.ru/") s(by.xpath("//a[text()='ТВ и мультимедиа']")).click() s(by.xpath("//a[@class='subcategory__item ui-link ui-link_blue']")).click() s(by.xpath("//a[@class='subcategory__item ui-link ui-link_blue']")).click() ss(by.xpath("//div[@class='catalog-product__stat']//label[@class='ui-checkbox']")).element(0).click()

    /Problem code below/ s(by.xpath("//button[@class='button-ui button-ui_brand compare-login-modal__login-btn']")).press_escape() or s(by.xpath("//button[@class='button-ui button-ui_brand compare-login-modal__login-btn']")).set(Keys.ESCAPE)

    /Working code/ s(by.xpath("//button[@class='button-ui button-ui_brand compare-login-modal__login-btn']")).send_keys(Keys.ESCAPE) or s(by.xpath("//button[@class='button-ui button-ui_brand compare-login-modal__login-btn']")).type(Keys.ESCAPE)

    opened by vrapsa 1
  • [#457] FIX: Upgrade webdriver-manager version for Apple M1 chips

    [#457] FIX: Upgrade webdriver-manager version for Apple M1 chips

    Google decided to rename the chromedriver file for Apple M1 based chips recently which breaks the URL in webdriver-manager 3.8.3. This issue is fixed in webdriver-manager 3.8.4 and so this dependency should be upgraded.

    opened by MatuMikey 4
  • Consider adding @property last to the Collection class

    Consider adding @property last to the Collection class

    just like we've got this:

    @property def first(self): """ A human-readable alias to .element(0) or [0] """ return self[0]

    could you please consider implementing last ?

    opened by roman-isakov 1
Releases(2.0.0b16)
  • 2.0.0b16(Nov 16, 2022)

  • 2.0.0b14(Oct 6, 2022)

    flatten args in have.texts & co

    NEW

    command.js.set_style_property(name, value)

    from selene.support.shared import browser
    from selene import command
    
    # calling on element
    overlay = browser.element('#overlay')
    overlay.perform(command.js.set_style_property('display', 'none'))
    
    # can be also called on collection of elements:
    ads = browser.all('[id^=google_ads][id$=container__]')
    ads.perform(command.js.set_style_property('display', 'none'))
    

    added conditions: have.values and have.values_containing

    all conditions like have.texts & have.exact_texts – flatten passed lists of texts

    This allows to pass args as lists (even nested) not just as varagrs.

    from selene.support.shared import browser
    from selene import have
    
    """
    # GIVEN html page with:
    <table>
      <tr class="row">
        <td class="cell">A1</td><td class="cell">A2</td>
      </tr>
      <tr class="row">
        <td class="cell">B1</td><td class="cell">B2</td>
      </tr>
    </table>
    """
    
    browser.all('.cell').should(
        have.exact_texts('A1', 'A2', 'B1', 'B2')
    )
    
    browser.all('.cell').should(
        have.exact_texts(['A1', 'A2', 'B1', 'B2'])
    )
    
    browser.all('.cell').should(
        have.exact_texts(('A1', 'A2', 'B1', 'B2'))
    )
    
    browser.all('.cell').should(
        have.exact_texts(
            ('A1', 'A2'),
            ('B1', 'B2'),
        )
    )
    

    removed trimming text on conditions like have.exact_text, have.texts, etc.

    because all string normalization is already done by Selenium Webdriver.

    but added query.text_content to give access to raw element text without space normalization
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b13(Oct 4, 2022)

    Stripped text in conditions and more deprecated removals

    NEW

    have.text, have.exact_text, have.texts and have.exact_texts strip/trim text when matching

    config.window_width and config.window_height can be set separately

    Now, you can set only one axis dimension for the browser, and it will change it on browser.open. Before it would change browser window size only if both width and height were set;)

    access to self.locate() as element or self from the script passed to element.execute_script(script_on_self, *arguments)

    Examples:

    from selene.support.shared import browser
    
    browser.element('[id^=google_ads]').execute_script('element.remove()')
    # OR
    browser.element('[id^=google_ads]').execute_script('self.remove()')
    '''
    # are shortcuts to
    browser.execute_script('arguments[0].remove()', browser.element('[id^=google_ads]')())
    '''
    
    browser.element('input').execute_script('element.value=arguments[0]', 'new value')
    # OR
    browser.element('input').execute_script('self.value=arguments[0]', 'new value')
    '''
    # are shortcuts to
    browser.execute_script('arguments[0].value=arguments[1]', browser.element('input').locate(), 'new value')
    '''
    

    collection.second shortcut to collection[1]

    element.locate() -> WebElement, collection.locate() -> List[WebElement] #284

    ... as more human-readable aliases to element() and collection() correspondingly

    entity.__raw__

    It's a «dangled» property and so consider it an experimental/private feature. For element and collection – it's same as .locate(). For browser it's same as .driver ;)

    Read more on it at this comment to #284

    ... as aliases to element(), collection() correspondingly

    NEW: DEPRECATED:

    element._execute_script(script_on_self, *args)

    ... in favor of .execute_script(script_on_self, *arguments) that uses access to arguments (NOT args!) in the script.

    collection.filtered_by(condition) in favor of collection.by(condition)

    browser.close_current_tab()

    Deprecated because the «tab» term is not relevant for mobile context. Use a browser.close() or browser.driver.close() instead.

    The deprecation mark was removed from the browser.close() correspondingly.

    browser.clear_session_storage() and browser.clear_local_storage()

    Deprecated because of js nature and not-relevance for mobile context; Use browser.perform(command.js.clear_session_storage) and browser.perform(command.js.clear_local_storage) instead

    NEW: BREAKING CHANGES

    arguments inside script passed to element.execute_script(script_on_self, *arguments) starts from 0

    from selene.support.shared import browser
    
    # before this version ...
    browser.element('input').execute_script('arguments[0].value=arguments[1]', 'new value')
    # NOW:
    browser.element('input').execute_script('element.value=arguments[0]', 'new value')
    

    removed earlier deprecated

    • browser.elements(selector) in favor of browser.all(selector)
    • browser.ss(selector) in favor of browser.all(selector)
    • browser.s(selector) in favor of browser.element(selector)
    • element.get_actual_webelement() in favor of element.locate()
    • collection.get_actual_webelements() in favor of collection.locate()

    renamed collection.filtered_by_their(selector, condition) to collection.by_their(selector, condition)

    removed collection.should_each ... #277

    • ... and ability to pass element_condition to collection.should(HERE)
    • Use instead: collection.should(element_condition.each)
      • like in browser.all('.selene-user').should(hava.css_class('cool').each)
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b12(Sep 26, 2022)

    browser.all('.selene-user').should(hava.css_class('cool').each)

    NEW: collection.should(condition.each) #277

    The older style is totally deprecated now:

    • Instead of:
      • collection.should(element_condition) and collection.should_each(element_condition)
    • Use:

    NEW: BREAKING CHANGE: removed SeleneElement, SeleneCollection, SeleneDriver

    use instead:

    import selene
    
    element: selene.Element = ...
    collection: selene.Collection = ...
    browser: selene.Browser = ...
    

    or:

    from selene import Element, Collection, Browser
    
    element: Element = ...
    collection: Collection = ...
    browser: Browser = ...
    
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b11(Sep 24, 2022)

  • 2.0.0b9(Sep 14, 2022)

    New filtering style for collections, a few shortcuts and goodbye to deprecations:)

    NEW: browser.all(selector).by(condition) to filter collection

    from selene.support.shared import browser
    from selene import have
    
    browser.open('https://todomvc.com/examples/emberjs/')
    browser.element('#new-todo').type('a').press_enter()
    browser.element('#new-todo').type('b').press_enter()
    browser.element('#new-todo').type('c').press_enter()
    
    browser.all('#todo-list>li').by(have.text('b')).first.element('.toggle').click()
    
    browser.all('#todo-list>li').by(have.css_class('active')).should(have.texts('a', 'c'))
    browser.all('#todo-list>li').by(have.no.css_class('active')).should(have.texts('b'))
    

    Hence, considering to deprecate:

    • collection.filtered_by(condition) in favor of collection.by(condition)
    • collection.element_by(condition) in favor of collection.by(condition).first

    NEW: collection.even and collection.odd shortcuts

    from selene.support.shared import browser
    from selene import have
    
    browser.open('https://todomvc.com/examples/emberjs/')
    
    browser.element('#new-todo').type('1').press_enter()
    browser.element('#new-todo').type('2').press_enter()
    browser.element('#new-todo').type('3').press_enter()
    
    browser.all('#todo-list>li').even.should(have.texts('2'))
    browser.all('#todo-list>li').odd.should(have.texts('1', '3'))
    

    NEW: defaults for all params of collection.sliced(start, stop, step)

    Now you can achieve more readable collection.sliced(step=2) instead of awkward collection.sliced(None, None, 2)

    Remember that you still can use less readable but more concise collection[::2] ;)

    DEPRECATED:

    • selene.core.entity.SeleneElement
      • you can use selene.core.entity.Element
    • selene.core.entity.SeleneCollection
      • you can use selene.core.entity.Collection
    • selene.core.entity.SeleneDriver
      • you can use selene.core.entity.Browser

    NEW: BREAKING CHANGE: removed deprecated

    • selene.browser module
    • selene.browsers module
    • selene.bys module
    • selene.driver module
    • selene.wait module
    • selene.elements module
    • selene.core.entity.Browser:
      • .quit_driver(self) in favor of .quit(self)
      • .wrap(self, webdriver) in favor of Browser(Config(driver=webdriver))
      • .find(self, css_or_xpath_or_by: Union[str, tuple]) -> Element:
        • in favor of .element(self, selector) -> Element
      • .find_all(self, css_or_xpath_or_by: Union[str, tuple]) -> Collection:
        • in favor of .all(self, selector) -> Collection
      • .find_elements in favor of browser.driver.find_elements
      • .find_element in favor of browser.driver.find_element
    • selene.core.entity.Collection:
      • .should(self, condition, timeout)
        • in favor of selene.core.entity.Collection.should(self, condition) with ability to customize timeout via collection.with_(timeout=...).should(condition)
      • .should_each(self, condition, timeout)
        • in favor of selene.core.entity.Collection.should_each(self, condition) with ability to customize timeout via collection.with_(timeout=...).should_each(condition)
      • .assure*(self, condition) -> Collection
      • .should_*(self, condition) -> Collection
    • selene.core.entity.Element:
      • .should(self, condition, timeout)
        • in favor of selene.core.entity.Element.should(self, condition) with ability to customize timeout via element.with_(timeout=...).should(condition)
      • .assure*(self, condition) -> Element
      • .should_*(self, condition) -> Element
      • .caching(self)
      • .find(self, css_or_xpath_or_by: Union[str, tuple]) -> Element
      • .find_all(self, css_or_xpath_or_by: Union[str, tuple]) -> Collection
      • .parent_element(self) -> Element
        • use .element('..') instead
      • .following_sibling(self) -> Element
        • use .element('./following-sibling::*') instead
      • .first_child(self) -> Element
        • use .element('./*[1]')) instead
      • .scroll_to(self) -> Element
        • use .perform(command.js.scroll_into_view) instead
      • .press_down(self) -> Element
        • use .press(Keys.ARROW_DOWN) instead
      • .find_element(self, by, value)
      • .find_elements(self, by, value)
      • .tag_name(self)
      • .text(self)
      • .attribute(self, name)
      • .js_property(self, name)
      • .value_of_css_property(self, name)
      • .get_attribute(self, name)
      • .get_property(self, name)
      • .is_selected(self)
      • .is_enabled(self)
      • .is_displayed(self)
      • .location(self)
      • .location_once_scrolled_into_view(self)
      • .size(self)
      • .rect(self)
      • .screenshot_as_base64(self)
      • .screenshot_as_png(self)
      • .screenshot(self, filename)
      • .parent(self)
      • .id(self)
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b10(Sep 14, 2022)

    A few bye bye to deprecations from Collection.*

    NEW: BREAKING CHANGE: removed deprecated selene.core.entity.Collection.:

    • caching(self) in favor of cashed(self)
    • all_by(self, condition) -> Collection in favor of by(condition)
    • filter_by(self, condition) -> Collection in favor of by(condition)
    • find_by(self, condition) -> Element
    • size(self) -> int in favor of __len__(self)
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b8(Sep 5, 2022)

    Report to Allure in one shot

    NEW: selene.support._logging.wait_with(context, translations)

    Added selene.support._logging experimental module with «predefined recipe» of wait_decorator for easier logging of Selene waiting commands (yet riskier, cause everything marked as experimental is a subject to change).

    Now, given added allure dependency to your project, you can configure logging Selene commands to Allure report as simply as:

    from selene.support.shared import browser
    from selene import support
    import allure_commons
    
    browser.config._wait_decorator = support._logging.wait_with(
      context=allure_commons._allure.StepContext
    )
    

    ... or implement your own version of StepContext – feel free to use Alure's context manager as example or the one from Selene's browser__config__wait_decorator_with_decorator_from_support_logging_test.py test.

    You also can pass a list of translations to be applied to final message to log, something like:

    from selene.support.shared import browser
    from selene import support
    import allure_commons
    
    browser.config._wait_decorator = support._logging.wait_with(
      context=allure_commons._allure.StepContext,
      translations=(
            ('browser.element', '$'),
            ('browser.all', '$$'),
      )
    )
    

    But check the default value for this parameter, maybe you'll be fine with it;)

    And remember, the majority of selene extensions from the support.* package, including its _logging module – are things you'd better implement on your side to be less dependent to 3rd party helpers;) Feel free to Copy&Paste it into your code and adjust to your needs.

    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b7(Sep 2, 2022)

    Better version of config._wait_decorator

    • BREAKING_CHANGE: change type of config._wait_decorator to access entities, not just commands on them
      • from Callable[[F], F]
      • to Callable[[Wait[E]], Callable[[F], F]]
      • i.e. now it should be not a simple decorator that maps function F to a new F with for example added logging, but it should be «decorator with parameter» or in other words – a «decorator factory» function that based on passed parameter of Wait type will return an actual decorator to be applied to the main logic of waiting inside Wait#for_ method.
      • This change will allow inside the decorator to access entities (browser, element, element-collection), for example, to log them too;)
      • see examples at:
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b6(Aug 31, 2022)

    Opera & Edge for shared.browser, click_by_js and decorating hook for all waits to log commands

    • NEW: added "opera" and "edge" support for shared browser

      • example:

        from selene.support.shared import browser
        
        # browser.config.browser_name = 'opera'
        browser.config.browser_name = 'edge'
        
    • NEW: added config._wait_decorator

      • decorating Wait#for_ method
        • that is used when performing any element command and assertion (i.e. should)
        • hence, can be used to log corresponding commands with waits and integrate with something like allure reporting;)
      • prefixed with underscore, indicating that method is experimental, and can be e.g. renamed, etc.
      • see example at examples/log_all_selene_commands_with_wait.py
    • NEW: added config.click_by_js #420

      • for usage like in:

        from selene.support.shared import browser
        
        # browser.config.click_by_js = True
        # '''
        # if we would want to make all selene clicks to work via JS
        # as part of some CRAZY workaround, or maybe to make tester faster o_O :p
        # (it was a joke, nothing will be much faster :D with click via js)
        # '''
        
        button = browser.element('#btn').with_(click_by_js=True)
        '''
        to make all clicks for element('#btn') to work via js
        as part of some workaround ;)
        '''
        
        button.click()
        ...
        button.click()
        
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b5(Jun 24, 2022)

    browser.all('[id^=google_ads]').perform(command.js.remove)

    • NEW: added command.js.*:
      • remove
      • set_style_display_to_none
      • set_style_display_to_block
      • set_style_visibility_to_hidden
      • set_style_visibility_to_visible Example:
      browser.all('[id^=google_ads]').perform(command.js.remove)
      
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b4(Jun 15, 2022)

    Upgrade Selenium to 4.2.0 and Webdriver-manager to 3.7.0 + FIXES

    • NEW: upgrade selenium to 4.2.0
    • NEW: upgrade webdriver-manager to 3.7.0
      • IMPORTANT:
        • if you used webdriver_manager.utils in imports, like in:
          from webdriver_manager.utils import ChromeType
          
        • then you have to upgrade them to webdriver_manager.core.utils, like in:
          from webdriver_manager.core.utils import ChromeType
          
    • FIX: set_window_size in shared.browser.open
    • FIX: provide correct chrome type for wdm
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b3(May 29, 2022)

  • 2.0.0b2(Mar 29, 2022)

    Allow lambda at config.driver

    • first steps on simplifying the current browser management, yet making it more powerful
      • now you can pass a lambda to browser.config.driver = HERE providing more smart logic of driver management see a dummy example at this test
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0b1(Feb 22, 2022)

    Support Selenium 4

    • added support selenium 4.1 #375
      • the =4.1 version is frozen/hardcoded as dependency
        • without backwards compatibility to selenium 3
          • the newly added service arg have been added to automatic driver management on the selene side
            • yet, if anyone needs backwards compatibility, we can consider implementing it in following patches, feel free to file an issue;)
      • fixed #398
    • Upgrade webdriver-manager 3.5.0 -> 3.5.3 (see changes)
    • removed deprecation
      • from:
        • collection.should_each(element_condition)
          • reason:
            • making collection.should(condition) so smart that it can accept both collection_condition and element_condition might be not a good idea – it violates KISS
            • maybe keeping things simpler with extra method like should_each is better...
            • let's see...
        • element.send_keys(keys)
          • reason:
            • yes, send_keys is low level, but sometimes somebody needs this low level style, because of the nature and context of send_keys usage, like sending keys to hidden fields
            • yet not sure about this decision... let's see...
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0a40(Oct 9, 2021)

  • 2.0.0a39(Jul 26, 2021)

  • 2.0.0a38(May 5, 2021)

  • 2.0.0a37(Apr 24, 2021)

  • 2.0.0a36(Mar 30, 2021)

    Release notes in one word: poetry!

    We moved to poetry for better dependency resolving. But don't worry! It affects only selene development lifecycle and selene contributors.

    • Moved from Pipenv to Poetry as a greater python dependency resolver of 2021 (see #302).
    • Moved to a new release process with Poetry: added bash aliases (see #304).
    • Moved from setup.py and setup.cfg to Poetry's pyproject.toml config-file.
    • Updated README.md Release process with new poetry aliases.
    • Updated CONTRIBUTING.md with black and pylint information.
    Source code(tar.gz)
    Source code(zip)
  • 1.0.2(May 7, 2021)

  • 2.0.0a6(Jan 5, 2020)

  • 2.0.0a1(Dec 28, 2019)

    Complete reincarnation of Selene for python version >= 3.7 :)

    Current limitations

    • no test coverage;

    • do updated docs

      • you can check the only one working test at tests/acceptance/shared_browser/straightforward_style_test.py
      • and use it as a fast intro
      • keep in mind that it describes old style + new style;
      • so you will not see there some guides for newer style; wait for that;)
    • no hooks (and so no screenshots in error messages);

    • no temporal support for 1.0.0 aliases for some methods

      • will be added as deprecated and kept for some time to allow smoother migration
    • old implementation of everything still exists in selene.support.past.*

    Source code(tar.gz)
    Source code(zip)
  • 1.0.1(Dec 28, 2019)

  • 1.0.0a9(Jan 11, 2018)

    releasing 1.0.0a9 with selene.browser.* over deprecated selene.tools.*; config.base_url over deprecated config.app_host; and new selene api entry point as 'from selene.api import *'

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0a13(Jan 11, 2018)

Owner
Iakiv Kramarenko
Iakiv Kramarenko
pytest splinter and selenium integration for anyone interested in browser interaction in tests

Splinter plugin for the pytest runner Install pytest-splinter pip install pytest-splinter Features The plugin provides a set of fixtures to use splin

pytest-dev 238 Nov 14, 2022
Automated tests for OKAY websites in Python (Selenium) - user friendly version

Okay Selenium Testy Aplikace určená k testování produkčních webů společnosti OKAY s.r.o. Závislosti K běhu aplikace je potřeba mít v počítači nainstal

Viktor Bem 0 Oct 1, 2022
Repository for JIDA SNP Browser Web Application: Local Deployment

JIDA JIDA is a web application that retrieves SNP information for a genomic region of interest in Homo sapiens and calculates specific summary statist

null 3 Mar 3, 2022
Let your Python tests travel through time

FreezeGun: Let your Python tests travel through time FreezeGun is a library that allows your Python tests to travel through time by mocking the dateti

Steve Pulec 3.5k Dec 29, 2022
Data-Driven Tests for Python Unittest

DDT (Data-Driven Tests) allows you to multiply one test case by running it with different test data, and make it appear as multiple test cases. Instal

null 424 Nov 28, 2022
The pytest framework makes it easy to write small tests, yet scales to support complex functional testing

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries. An example o

pytest-dev 9.6k Jan 2, 2023
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)

pytest-sugar pytest-sugar is a plugin for pytest that shows failures and errors instantly and shows a progress bar. Requirements You will need the fol

Teemu 963 Dec 28, 2022
:game_die: Pytest plugin to randomly order tests and control random.seed

pytest-randomly Pytest plugin to randomly order tests and control random.seed. Features All of these features are on by default but can be disabled wi

pytest-dev 471 Dec 30, 2022
Selects tests affected by changed files. Continous test runner when used with pytest-watch.

This is a pytest plug-in which automatically selects and re-executes only tests affected by recent changes. How is this possible in dynamic language l

Tibor Arpas 614 Dec 30, 2022
Docker-based integration tests

Docker-based integration tests Description Simple pytest fixtures that help you write integration tests with Docker and docker-compose. Specify all ne

Avast 326 Dec 27, 2022
To automate the generation and validation tests of COSE/CBOR Codes and it's base45/2D Code representations

To automate the generation and validation tests of COSE/CBOR Codes and it's base45/2D Code representations, a lot of data has to be collected to ensure the variance of the tests. This respository was established to collect a lot of different test data and related test cases of different member states in a standardized manner. Each member state can generate a folder in this section.

null 160 Jul 25, 2022
Show surprise when tests are passing

pytest-pikachu pytest-pikachu prints ascii art of Surprised Pikachu when all tests pass. Installation $ pip install pytest-pikachu Usage Pass the --p

Charlie Hornsby 13 Apr 15, 2022
Django-google-optimize is a Django application designed to make running server side Google Optimize A/B tests easy.

Django-google-optimize Django-google-optimize is a Django application designed to make running Google Optimize A/B tests easy. Here is a tutorial on t

Adin Hodovic 39 Oct 25, 2022
Run ISP speed tests and save results

SpeedMon Automatically run periodic internet speed tests and save results to a variety of storage backends. Supported Backends InfluxDB v1 InfluxDB v2

Matthew Carey 9 May 8, 2022
Given some test cases, this program automatically queries the oracle and tests your Cshanty compiler!

The Diviner A complement to The Oracle for compilers class. Given some test cases, this program automatically queries the oracle and tests your compil

Grant Holmes 2 Jan 29, 2022
Statistical tests for the sequential locality of graphs

Statistical tests for the sequential locality of graphs You can assess the statistical significance of the sequential locality of an adjacency matrix

null 2 Nov 23, 2021
Fail tests that take too long to run

GitHub | PyPI | Issues pytest-fail-slow is a pytest plugin for making tests fail that take too long to run. It adds a --fail-slow DURATION command-lin

John T. Wodder II 4 Nov 27, 2022
a wrapper around pytest for executing tests to look for test flakiness and runtime regression

bubblewrap a wrapper around pytest for assessing flakiness and runtime regressions a cs implementations practice project How to Run: First, install de

Anna Nagy 1 Aug 5, 2021
Travel through time in your tests.

time-machine Travel through time in your tests. A quick example: import datetime as dt

Adam Johnson 373 Dec 27, 2022