selene

User-oriented Web UI browser tests in Python

MIT License

Downloads
27.5K
Stars
666
Committers
45

Bot releases are hidden (Show)

selene - 2.0.0rc9 Latest Release

Published by yashaka 8 months ago

Click with Offset & Better command.select_all

Click with offsets

As simple as that:

from selene import browser, command

...

browser.element('#point1').click(xoffset=-5, yoffset=5)  # relative from center
browser.element('#point1').click()  # still works as before (clicking at center)
# with js too:
browser.element('#point1').perform(command.js.click(xoffset=-5, yoffset=5))
browser.element('#point1').perform(command.js.click())  # also works
browser.element('#point1').perform(command.js.click)  # still works as before
# or:
browser.element('#point1').with_(click_by_js=True).click(xoffset=-5, yoffset=5)

Smarter command.select_all

Seems like the send_keys(Keys.COMMAND + 'a' + Keys.NULL) receipe has stopped working since some Selenium version...
So we update the command.select_all implementation to be based on ActionChains, and also work both on browser and element. Here go two examples that demonstrate the new behavior:

when called on element:

page.opened_with_body('<input id="text-field" value="text"></input>')

browser.element('#text-field').perform(command.select_all).type('reset')

browser.element('#text-field').should(have.value('reset'))

when called on browser:

page.opened_with_body('<input id="text-field" value="text"></input>')

browser.element('#text-field').click()  # <- MANDATORY to make the input focused

browser.perform(command.select_all)
browser.element('#text-field').type('reset')

browser.element('#text-field').should(have.value('reset'))

qualname support in context of rendering conditions in error messages

Allows to simplify custom conditions implementation to something like:

class have:
    @staticmethod
    def attribute(entity):
        if entity.attribute is None:
            raise AssertionError('attribute is None')

Since the have.attribute staticmethod will already have __qualname__ defined and equal to 'have.attribute', that will result in same rendering of the condition name in error messages on failed waiting (entity.wait.for_(condition)) or assertion (via entity.should(condition)).

selene - 2.0.0rc8

Published by yashaka 8 months ago

Nicer logging of "reason" in error messages

– by removed stacktrace in processing of timeout exception at wait.py (thanks to @jacekziembla)

selene - 2.0.0rc7

Published by yashaka 9 months ago

Experimental browser._actions

browser._actions is an instance of experimental _Actions class – an alternative implementation of ActionChains from Selenium...

So you can use:

from selene import browser
from selene.support.shared.jquery_style import s

browser._actions.move_to(s('#point1')).pause(1).click_and_hold(s('#point1')).pause(1).move_by_offset(0, 5).move_to(s('#point2')).pause(1).release().perform()

instead of something like:

from selene import browser
from selene.support.shared.jquery_style import s
from selenium.webdriver.common.action_chains import ActionChains

ActionChains(browser.driver).move_to_element(s('#point1').locate()).pause(1).click_and_hold(s('#point1').locate()).pause(1).move_by_offset(0, 5).move_to_element(s('#point2').locate()).pause(1).release().perform()

or actually even instead of this:

from selene import browser, be
from selene.support.shared.jquery_style import s
from selenium.webdriver.common.action_chains import ActionChains

ActionChains(browser.driver).move_to_element(s('#point1').should(be.in_dom).locate()).pause(1).click_and_hold(s('#point1').should(be.in_dom).locate()).pause(1).move_by_offset(0, 5).move_to_element(s('#point2').should(be.in_dom).locate()).pause(1).release().perform()

Here are advantages of Selene's _actions over Selenium's ActionChains:

  • the code is more concise
  • you can pass Selene's elements to it, instead of Selenium's webelements
  • adding new command to the chain automatically includes automatic waiting for element to be in DOM
  • if some error happens inside .perform – it will be automatically retried in context of common Selene's implicit waiting logic

Here are some open points regarding this implementation and why this feature is marked as experimental:

  • the implicit waiting are yet not same powerful as in other Selene's commands
    • error messages are less readable, too low level
    • not sure if retry logic inside .perform is needed at all... can hardly imagine any failure there that can be fixed by retrying
  • not sure how will it work with Appium drivers...

Some inner refactoring...

  • moved Browser class from selene.core.entity.py to selene.core._browser.py
    (yet the module is named as experimental, yet the safest way to import Browser is from selene import Browser that is unchanged!)
selene - 2.0.0rc6

Published by yashaka 9 months ago

Goodbye to python 3.7 and webdriver-manager 👋🏻

  • drop py3.7 support + upgrade selenium>=4.12.0
  • drop webdriver-manager in favor of Selenium Manager
selene - 2.0.0rc5

Published by yashaka 9 months ago

Drag & drop in advanced commands

when Selenium can interact with simple draggable controls:

  • browser.element('#volume-slider-thumb').perform(command.drag_and_drop_to(browser.element('#volume-up')))
  • when for some reason, for example because of not loaded page yet, you have to retry dragging until we can asset that element actually was moved to the new location:
    • browser.element('#volume-slider-thumb').perform(command.drag_and_drop_to(browser.element('#volume-up'), _assert_location_changed=True))
      the _assert_location_changed=True is marked as experimental by _ prefix,
      so it may be renamed or removed in future releases.
  • browser.element('#volume-slider-thumb').perform(command.drag_and_drop_by_offset(x=-10, y=0))

when Selenium can not interact with simple draggable controls:

  • browser.element('#volume-slider-thumb').perform(command.js.drag_and_drop_to(browser.element('#volume-up')))

when there is no input element with type file, and you need to simulate the "drop file" by JS:

  • browser.element('#drag-file-here-to-upload').perform(command.js.drop_file('/path/to/file'))

Find more examples at these tests:

selene - 2.0.0rc4

Published by yashaka about 1 year ago

Unfreeze version of typing-extensions to >=4.6.1 to support pydantic v2.0

selene - 2.0.0rc3post3

Published by yashaka about 1 year ago

Improves wdm patch to find chromedrivers also for macs with intel processors.

selene - 2.0.0rc3post2

Published by yashaka about 1 year ago

Prepare Selene to work with wdm > 3.8.6

Hence, 4.0.0 should be kind of supported now... But Selene's tests, if executed on macOS arm64 – are very unstable with chromedriver downloaded by wdm 4.0.0 :(, failing with error:

selenium.common.exceptions.WebDriverException: Message: Service /Users/yashaka/.wdm/drivers/chromedriver/mac64/115.0.5790.114/chromedriver-mac-arm64/chromedriver unexpectedly exited. Status code was: -9

That's why we still freeze wdm to 3.8.6, but on your own risk you can try 4.0.0.

selene - 2.0.0rc3post1

Published by yashaka about 1 year ago

Fixes patch from rc3 to download latest chromedriver if google did not publish matched chromedriver for latest Chrome version.

webdriver-manager is still frozen to 3.8.6, though there are already 4.0.

Reminder for MacOS users

Remember that on MacOS you probably have either to install Chrome for Testing or specify browser location manually via:

from selene import browser
from selenium import webdriver

browser.config.driver_options = webdriver.ChromeOptions()
browser.config.driver_options.binary_location = (
    '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
)
browser.open('https://www.ecosia.org/')

See more in 2.0.0rc3 release notes.

selene - 2.0.0rc3

Published by yashaka about 1 year ago

HOTFIX webdriver_manager after changes in google chromedrivers APIs

Fixes #536 wdm issue by patching wdm of 3.8.6 version to workaround the following error:

ValueError: There is no such driver by url https://chromedriver.storage.googleapis.com/LATEST_RELEASE_115.0.5790

This hotfix is really hot:), so might break something. Use it on your own risk.
If something went wrong, roll back to 2.0.0rc2.

If you don't use Selene, feel free to copy the patch, adapt it for your liking and use to fix wdm at your context.

In Selene we also froze webdriver_manager version to 3.8.6, so it will not be updated automatically and our hotfix will not be broken :D. Let's see how it goes further... One day we hope to remove hotfix and unfreeze webdriver_manager version.

Should work for new versions of Chrome from v115 out of the box.

If you use webdriver_manager on your own, you can do the following trick to patch it with the fix:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.core.utils import ChromeType

from selene import support

chrome_driver = webdriver.Chrome(
    service=Service(
        support._extensions.webdriver_manager.patch._to_find_chromedrivers_from_115(
            ChromeDriverManager(chrome_type=ChromeType.GOOGLE)
        ).install()
    )
)

Notice underscore prefixes in module and patch function names at _extensions.webdriver_manager.patch._to_find_chromedrivers_from_115. Use it on your own risk, as it is marked as private and experimental;).

Remember that currently on macOS the fix itself might not be enough, for Chrome versions less than 117, you probably will have to install Chrome for Testing browser instead of Chrome and fix it with xattr -cr 'Google Chrome for Testing.app' command. An alternative to installing Chrome for Testing, can be setting binary location manually via:

from selene import browser
from selenium import webdriver

browser.config.driver_options = webdriver.ChromeOptions()
browser.config.driver_options.binary_location = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
selene - 2.0.0rc2

Published by yashaka over 1 year ago

Smarter guessing of which driver to build

Driver is guessed by config.driver_options too

Before:

  • config.driver_name was 'chrome' by default

Now:

  • config.driver_name is None by default
    • and means "desired requested driver name"
  • setting config.driver_options usually is enough to guess the driver name,
    e.g., just by setting config.driver_options = FirefoxOptions()
    you already tell Selene to build Firefox driver.

config.driver_service

Just in case you want, e.g. to use own driver executable like:

from selene import browser
from selenium.webdriver.chrome.service import Service
from selenium import webdriver

browser.config.driver_service = Service('/path/to/my/chromedriver')
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
browser.config.driver_options = chrome_options

command.select_all to simulate ctrl+a or cmd+a on mac

from selene import browser, by, have, command

browser.open('https://www.ecosia.org/')

browser.element(by.name('q')).type('selene').should(have.value('selene'))

browser.element(by.name('q')).perform(command.select_all).type('github yashaka selene')
browser.element(by.name('q')).should(have.value('github yashaka selene'))

Probably might be useful for cases where normal element.set_value(text), while based on webelement.clear(); webelement.send_keys(text), - does not work, in most cases because of some events handled on clear().

command._long_press

More relevant to the mobile case. Might work for web too, but not tested fully for web, not covered with tests. That's why is still marked with _ as experimental.

selene - 2.0.0rc1

Published by yashaka over 1 year ago

New Config with Remote and Appium support out of the box

Changes

Any custom driver will now be automatically quit at exit

Any driver instance passed to browser.config.driver will be automatically quit at exit, unless browser.config.hold_driver_at_exit = True, that is False by default

Manually set driver can be still automatically rebuilt on next call to browser.open(url)

... if happened to be not alive, e.g. after quit or crash. This was relevant in the past, but not for manually set drivers. Not it works for all cases by default, including manually set driver by browser.config.driver = my_driver_instance. To disable this behavior set browser.config._reset_not_alive_driver_on_get_url = False (currently this option is still marked as experimental with _ prefix, it may be renamed till final 2.0 release).

Once automatic rebuild is disabled, you can schedule rebuild on next access to driver by setting browser.config.driver = ... (besides ellipsis, setting to None also works). This is actually what is done inside browser.open(url) if browser.config._reset_not_alive_driver_on_get_url = True and driver is not alive.

There is another "rebuild" option in config that is disabled by default: browser.config.rebuild_not_alive_driver. It is used to rebuild driver on any next access to it, if it is not alive. This is different from browser.config._reset_not_alive_driver_on_get_url that resets driver (scheduling to be rebuilt) only on next call to browser.open(url). Take into account that enabling this option may leed to slower tests when running on remote drivers, because it will check if driver is alive on any access to it, not only on browser.open(url).

«browser» term is deprecated in a lot of places

... except Browser class itself, of course (but this might be changed somewhere in 3.0🙃)

For example, config.browser_name is deprecated in favor of config.driver_name. Main reason – «browser» term is not relevant to mobile testing, where in a lot of cases we test user actions in app, not browser.

New

from selene import browser

– to be used instead of from selene.support.shared import browser.

No difference between Config and SharedConfig anymore. The new, completely refactored, Config is now used everywhere and allows to customize browser instance in a more convenient way.

Adds ability to use browser.with_(**config_options_to_override) to create new browser instance, for example:

from selene import browser

chrome = browser
firefox = browser.with_(driver_name='firefox')
edge = browser.with_(driver_name='edge')
...
# customizing all browsers at once:
browser.config.timeout = 10

as alternative to:

from selene import Browser, Config

chrome = Browser(Config())
firefox = Browser(Config(driver_name='firefox'))
edge = Browser(Config(driver_name='edge'))

...

# customizing all browsers:
chrome.config.timeout = 10
firefox.config.timeout = 10
edge.config.timeout = 10

browser.config.driver_options + browser.config.driver_remote_url

Finally, you can delegate building driver to config manager by passing driver_options and driver_remote_url to it:

import dotenv
from selenium import webdriver
from selene import browser, have


def test_complete_task():
  options = webdriver.ChromeOptions()
  options.browser_version = '100.0'
  options.set_capability(
    'selenoid:options',
    {
      'screenResolution': '1920x1080x24',
      'enableVNC': True,
      'enableVideo': True,
      'enableLog': True,
    },
  )
  browser.config.driver_options = options  # <- 🥳
  project_config = dotenv.dotenv_values()
  browser.config.driver_remote_url = (  # <- 🎉🎉🎉
    f'https://{project_config["LOGIN"]}:{project_config["PASSWORD"]}@'
    f'selenoid.autotests.cloud/wd/hub'
  )

  browser.open('http://todomvc.com/examples/emberjs/')
  browser.should(have.title_containing('TodoMVC'))

  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').should(have.exact_texts('a', 'b', 'c'))

browser.open() without args

Will just open driver or do nothing if driver is already opened.

Can also load page from browser.config.base_url if it is set and additional experimental browser.config._get_base_url_on_open_with_no_args = True option is set (that is False by default).

Automatic driver rebuilding still happens on browser.open, but...

but can be configured as follows:

  • can be disabled by setting browser.config.__reset_not_alive_driver_on_get_url = False,
    that is True by default
  • can be enabled on any explicit or implicit call to browser.config.driver,
    if set browser.config.rebuild_not_alive_driver = True (that is False by default)

Appium support out of the box:)

Yet you have to install it manually. But given installed via pip install Appium-Python-Client or something like poetry add Appium-Python-Client, running tests on mobile devices is as easy as...

Running locally against Appium server:
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selene import browser, have

android_options = UiAutomator2Options()
android_options.new_command_timeout = 60
android_options.app = 'wikipedia-alpha-universal-release.apk'
android_options.app_wait_activity = 'org.wikipedia.*'
browser.config.driver_options = android_options
# # Possible, but not needed, because will be used by default:
# browser.config.driver_remote_url = 'http://127.0.0.1:4723/wd/hub'

by_id = lambda id: (AppiumBy.ID, f'org.wikipedia.alpha:id/{id}')

# GIVEN
browser.open()
browser.element(by_id('fragment_onboarding_skip_button')).click()

# WHEN
browser.element((AppiumBy.ACCESSIBILITY_ID, 'Search Wikipedia')).click()
browser.element(by_id('search_src_text')).type('Appium')

# THEN
browser.all(by_id('page_list_item_title')).should(
  have.size_greater_than(0)
)
Running remotely against Browserstack server:
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selene import browser, have

options = UiAutomator2Options()
options.app = 'bs://c700ce60cf13ae8ed97705a55b8e022f13c5827c'
options.set_capability(
  'bstack:options',
  {
    'deviceName': 'Google Pixel 7',
    'userName': 'adminadminovych_qzqzqz',
    'accessKey': 'qzqzqzqzqzqzqzqzqzqz',
  },
)
browser.config.driver_options = options
browser.config.driver_remote_url = 'http://hub.browserstack.com/wd/hub'

by_id = lambda id: (AppiumBy.ID, f'org.wikipedia.alpha:id/{id}')

# GIVEN
browser.open()  # not needed, but to explicitly force appium to open app

# WHEN
browser.element((AppiumBy.ACCESSIBILITY_ID, 'Search Wikipedia')).click()
browser.element(by_id('search_src_text')).type('Appium')

# THEN
browser.all(by_id('page_list_item_title')).should(
  have.size_greater_than(0)
)

A lot of other local, remote and mobile test examples at...

https://github.com/yashaka/selene/tree/master/examples

autocomplete for entity.with_(HERE)

Other

Deprecated

  • browser.save_screenshot in favor of browser.get(query.screenshot_saved())
  • browser.save_page_source in favor of browser.get(query.page_source_saved())
  • browser.last_screenshot in favor of browser.config.last_screenshot
  • browser.last_page_source in favor of browser.config.last_page_source
  • match.browser_has_js_returned in favor of match.browser_has_script_returned
  • have.js_returned in favor of have.script_returned
  • have.js_returned_true(...) in favor of have.script_returned(True, ...)
  • browser.config.get_or_create_driver
  • browser.config.reset_driver
    • use selene.browser.config.driver = ...
  • browser.config.browser_name in favor of browser.config.driver_name

Removed

  • from selene.support.shared import SharedConfig, SharedBrowser
  • from selene.core.entity import BrowserCondition, ElementCondition, CollectionCondition

Removed deprecated

  • shared.browser.config.desired_capabilities
  • shared.browser.config.start_maximized
  • shared.browser.config.start_maximized
  • shared.browser.config.cash_elements
  • shared.browser.config.quit_driver
  • shared.browser.latest_page_source
  • shared.browser.quit_driver
  • shared.browser.set_driver
  • shared.browser.open_url
  • shared.browser.elements
  • shared.browser.wait_to
  • shared.browser.title
  • shared.browser.take_screenshot
  • jquery_style_selectors

Removed not deprecated

  • shared.browser.config.Source
    • renamed to shared.browser.config._Source.
      Currently, is used nowhere in Selene
  • shared.browser.config.set_driver (getter and setter)
  • shared.browser.config.counter
    • use shared.browser.config._counter instead, and better – not use it;)
  • shared.browser.config.generate_filename
    • use shared.browser.config._generate_filename instead, and better – not use it;)
selene - 2.0.0b17

Published by yashaka over 1 year ago

Selenium gets free and browser comes to selene.* imports

  • update selenium and weaken dependency to >=4.4.3
  • from selene import browser ;)
selene - 2.0.0b16

Published by yashaka almost 2 years ago

wdm 3.8.3 -> 3.8.5 with fix for chromedriver for m1 macs

selene - 2.0.0b14

Published by yashaka about 2 years ago

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
selene - 2.0.0b13

Published by yashaka about 2 years ago

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)
selene - 2.0.0b12

Published by yashaka about 2 years ago

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 = ...
selene - 2.0.0b11

Published by yashaka about 2 years ago

Upgraded selenium to 4.4.3 & webdriver-manager to 3.8.3

BREAKING CHANGE: removed 'opera' support for shared.browser.config.browser_name

see reasons at:

selene - 2.0.0b10

Published by yashaka about 2 years ago

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)
selene - 2.0.0b9

Published by yashaka about 2 years ago

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)
Package Rankings
Top 3.45% on Pypi.org
Badges
Extracted from project README
tests codecov MIT License Downloads Project Template Code style: black Join telegram chat https://t.me/selene_py Присоединяйся к чату https://t.me/selene_py_ru Sign up for a course https://autotest.how/sdet-start Запишись на курс https://autotest.how/sdet-start-ru Реєструйся на курс https://autotest.how/sdet-start-uk
Related Projects