User-oriented Web UI browser tests in Python

Related tags

Testingselene
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 ^^^^

    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
Generates realistic traffic for load testing tile servers

Generates realistic traffic for load testing tile servers. Useful for: Measuring throughput, latency and concurrency of your tile serving stack. Ident

Brandon Liu 23 Dec 05, 2022
LuluTest is a Python framework for creating automated browser tests.

LuluTest LuluTest is an open source browser automation framework using Python and Selenium. It is relatively lightweight in that it mostly provides wr

Erik Whiting 14 Sep 26, 2022
Minimal example of how to use pytest with automated 'devops' style automated test runs

Pytest python example with automated testing This is a minimal viable example of pytest with an automated run of tests for every push/merge into the m

Karma Computing 2 Jan 02, 2022
Pytest support for asyncio.

pytest-asyncio: pytest support for asyncio pytest-asyncio is an Apache2 licensed library, written in Python, for testing asyncio code with pytest. asy

pytest-dev 1.1k Jan 02, 2023
A suite of benchmarks for CPU and GPU performance of the most popular high-performance libraries for Python :rocket:

A suite of benchmarks for CPU and GPU performance of the most popular high-performance libraries for Python :rocket:

Dion Häfner 255 Jan 04, 2023
Parameterized testing with any Python test framework

Parameterized testing with any Python test framework Parameterized testing in Python sucks. parameterized fixes that. For everything. Parameterized te

David Wolever 714 Dec 21, 2022
frwk_51pwn is an open-sourced remote vulnerability testing and proof-of-concept development framework

frwk_51pwn Legal Disclaimer Usage of frwk_51pwn for attacking targets without prior mutual consent is illegal. frwk_51pwn is for security testing purp

51pwn 4 Apr 24, 2022
Python script to automatically download from Zippyshare

Zippyshare downloader and Links Extractor Python script to automatically download from Zippyshare using Selenium package and Internet Download Manager

Daksh Khurana 2 Oct 31, 2022
Test python asyncio-based code with ease.

aiounittest Info The aiounittest is a helper library to ease of your pain (and boilerplate), when writing a test of the asynchronous code (asyncio). Y

Krzysztof Warunek 55 Oct 30, 2022
Data App Performance Tests

Data App Performance Tests My hypothesis is that The different architectures of

Marc Skov Madsen 6 Dec 14, 2022
Testing Calculations in Python, using OOP (Object-Oriented Programming)

Testing Calculations in Python, using OOP (Object-Oriented Programming) Create environment with venv python3 -m venv venv Activate environment . venv

William Koller 1 Nov 11, 2021
A twitter bot that simply replies with a beautiful screenshot of the tweet, powered by poet.so

Poet this! Replies with a beautiful screenshot of the tweet, powered by poet.so Installation git clone https://github.com/dhravya/poet-this.git cd po

Dhravya Shah 30 Dec 04, 2022
API mocking with Python.

apyr apyr (all lowercase) is a simple & easy to use mock API server. It's great for front-end development when your API is not ready, or when you are

Umut Seven 55 Nov 25, 2022
DUCKSPLOIT - Windows Hacking FrameWork using Reverse Shell

Ducksploit Install Ducksploit Hacker setup raspberry pico Download https://githu

2 Jan 31, 2022
🐍 Material for PyData Global 2021 Presentation: Effective Testing for Machine Learning Projects

Effective Testing for Machine Learning Projects Code for PyData Global 2021 Presentation by @edublancas. Slides available here. The project is develop

Eduardo Blancas 73 Nov 06, 2022
Selenium Page Object Model with Python

Page-object-model (POM) is a pattern that you can apply it to develop efficient automation framework.

Mohammad Ifran Uddin 1 Nov 29, 2021
A Proof of concept of a modern python CLI with click, pydantic, rich and anyio

httpcli This project is a proof of concept of a modern python networking cli which can be simple and easy to maintain using some of the best packages

Kevin Tewouda 17 Nov 15, 2022
A Simple Unit Test Matcher Library for Python 3

pychoir - Python Test Matchers for humans Super duper low cognitive overhead matching for Python developers reading or writing tests. Implemented in p

Antti Kajander 15 Sep 14, 2022
reCaptchaBypasser For Bypass Any reCaptcha For Selenium Python

reCaptchaBypasser ' Usage : from selenium import webdriver from reCaptchaBypasser import reCaptchaScraper import time driver = webdriver.chrome(execu

Dr.Linux 8 Dec 17, 2022
Pymox - open source mock object framework for Python

Pymox is an open source mock object framework for Python. First Steps Installation Tutorial Documentation http://pymox.readthedocs.io/en/latest/index.

Ivan Rocha 7 Feb 02, 2022