Skip to content

Releases: yashaka/selene

2.0.0rc2

13 Apr 16:52
Compare
Choose a tag to compare

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.

2.0.0rc1

11 Apr 02:13
Compare
Choose a tag to compare

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;)

2.0.0b17

17 Feb 13:01
Compare
Choose a tag to compare

Selenium gets free and browser comes to selene.* imports

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

2.0.0b16

16 Nov 20:36
Compare
Choose a tag to compare

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

2.0.0b14

06 Oct 12:17
Compare
Choose a tag to compare

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

2.0.0b13

04 Oct 16:04
Compare
Choose a tag to compare

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)

2.0.0b12

26 Sep 11:53
Compare
Choose a tag to compare

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 = ...

2.0.0b11

24 Sep 14:27
Compare
Choose a tag to compare

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:

2.0.0b9

14 Sep 16:43
Compare
Choose a tag to compare

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)

2.0.0b10

14 Sep 16:58
Compare
Choose a tag to compare

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)