Releases: yashaka/selene
2.0.0rc2
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
isNone
by default- and means "desired requested driver name"
- setting
config.driver_options
usually is enough to guess the driver name,
e.g., just by settingconfig.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
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 isTrue
by default - can be enabled on any explicit or implicit call to
browser.config.driver
,
if setbrowser.config.rebuild_not_alive_driver = True
(that isFalse
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 ofbrowser.get(query.screenshot_saved())
browser.save_page_source
in favor ofbrowser.get(query.page_source_saved())
browser.last_screenshot
in favor ofbrowser.config.last_screenshot
browser.last_page_source
in favor ofbrowser.config.last_page_source
match.browser_has_js_returned
in favor ofmatch.browser_has_script_returned
have.js_returned
in favor ofhave.script_returned
have.js_returned_true(...)
in favor ofhave.script_returned(True, ...)
browser.config.get_or_create_driver
browser.config.reset_driver
- use
selene.browser.config.driver = ...
- use
browser.config.browser_name
in favor ofbrowser.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
- renamed to shared.browser.config._Source.
- 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
2.0.0b16
2.0.0b14
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
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 ofbrowser.all(selector)
browser.ss(selector)
in favor ofbrowser.all(selector)
browser.s(selector)
in favor ofbrowser.element(selector)
element.get_actual_webelement()
in favor ofelement.locate()
collection.get_actual_webelements()
in favor ofcollection.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)
- like in
2.0.0b12
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)
andcollection.should_each(element_condition)
- Use:
collection.should(element_condition.each)
- see more examples at tests/integration/condition_each_test.py
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
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
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 ofcollection.by(condition)
collection.element_by(condition)
in favor ofcollection.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)
- in favor of selene.core.entity.Collection.should(self, 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)
- in favor of selene.core.entity.Collection.should_each(self, condition)
- .assure*(self, condition) -> Collection
- .should_*(self, condition) -> Collection
- .should(self, condition, timeout)
- 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)
- in favor of selene.core.entity.Element.should(self, 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)
- .should(self, condition, timeout)
2.0.0b10
A few bye bye to deprecations from Collection.*
NEW: BREAKING CHANGE: removed deprecated selene.core.entity.Collection.:
caching(self)
in favor ofcashed(self)
all_by(self, condition) -> Collection
in favor ofby(condition)
filter_by(self, condition) -> Collection
in favor ofby(condition)
find_by(self, condition) -> Element
size(self) -> int
in favor of__len__(self)