Skip to content

Releases: yashaka/selene

2.0.0rc9

06 Mar 19:05
Compare
Choose a tag to compare

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

2.0.0rc8

13 Feb 09:21
Compare
Choose a tag to compare

Nicer logging of "reason" in error messages

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

2.0.0rc7

25 Jan 19:34
Compare
Choose a tag to compare

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

2.0.0rc6

25 Jan 14:31
Compare
Choose a tag to compare

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

2.0.0rc5

22 Jan 17:24
Compare
Choose a tag to compare

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:

2.0.0rc4

02 Aug 21:27
Compare
Choose a tag to compare

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

2.0.0rc3post3

29 Jul 16:54
Compare
Choose a tag to compare

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

2.0.0rc3post2

27 Jul 16:52
Compare
Choose a tag to compare

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.

2.0.0rc3post1

27 Jul 14:33
Compare
Choose a tag to compare

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.

2.0.0rc3

21 Jul 20:27
Compare
Choose a tag to compare

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'