#
Config
#
A one cross-cutting-concern-like object to group all options
that might influence Selene behavior depending on context.
For example, config.timeout
is used in all "waiting" logic
of Selene commands. And config.base_url
is used
in browser.open(relative_url)
command.
As option, the driver instance is also considered. Moreover, this config is not just config, but fully manages the driver lifecycle. Actually, the "driver manager" is a part of this config.
While surfing through all available options, pay attention to terminology:
- all options that have a
driver
word in their name are related to driver management, and they are connected in a specific way:)- read more on this under
config.with_
doc section;)
- read more on this under
- all options that have a
strategy
word in their name directly influence the driver lifecycle in context of driver management. -
all options that are prefixed with
_
are considered "experimental" (their naming can be changed in the future, or even an option can be removed)
Examples:
Here's how you can build a driver with the instance of this config:
>>> from selene import Config
>>> config = Config()
>>> driver = config.driver # new instance, built on 1st access to `driver`
>>> assert driver.name == 'chrome'
Or pre-configuring the firefox driver:
>>> from selene import Config
>>> config = Config(driver_name='firefox')
>>> driver = config.driver
>>> assert driver.name == 'firefox'
Or post-configuring the firefox driver:
>>> from selene import Config
>>> config = Config()
>>> config.driver_name = 'firefox'
>>> driver = config.driver
>>> assert driver.name == 'firefox'
Selene has already predefined shared instance of Config, so you can economize on lines of code;)...
>>> from selene.support.shared import config
>>> config.driver_name = 'firefox'
>>> driver = config.driver
>>> assert driver.name == 'firefox'
Same shared Config instance is available as browser.config:
>>> from selene import browser
>>> browser.config.driver_name = 'firefox'
>>> driver = browser.config.driver
>>> assert driver.name == 'firefox'
There is an alternative style of customizing config.
The config.option_name = value
is known in programming
as "imperative programming" style. When you are creating
a new Config from scratch, you are actually using
a "declarative programming" style:
>>> from selene import Config
>>> my_config = Config(driver_name='firefox')
>>> driver = my_config.driver
>>> assert driver.name == 'firefox'
Here is an alternative declarative style of customizing new config by copying existing:
>>> from selene import browser
>>> my_config = browser.config.with_(driver_name='firefox')
>>> driver = my_config.driver
>>> assert driver.name == 'firefox'
>>> # AND...
>>> assert driver is not browser.config.driver # ;)
>>> assert browser.config.driver.name == 'chrome'
As you can see Selene config is closely related to the browser. Moreover, the same type of "declarative config copying" happens implicitly, when you apply "copying" to browser:
>>> from selene import browser
>>> second_browser = browser.with_(driver_name='firefox')
>>> assert second_browser.config.driver.name == 'firefox'
>>> # AND...
>>> assert second_browser.config.driver is not browser.config.driver # ;)
>>> assert browser.config.driver.name == 'chrome'
Moreover, if you need only a driver, you can have it
via browser.driver
shortcut, thus, completely hiding the config:
>>> from selene import browser
>>> second_browser = browser.with_(driver_name='firefox')
>>> assert second_browser.driver.name == 'firefox'
>>> # AND...
>>> assert second_browser.driver is not browser.driver # ;)
>>> assert browser.driver.name == 'chrome'
- such shortcut exists only for the
driver
option of config, not for other options liketimeout
orbase_url
. More nuances ofbrowser
behavior find in its docs;).
And here are some more examples of customizing config for common test automation use cases...
Scenario: "Run locally in headless Chrome"
>>> from selene import browser
>>> from selenium import webdriver
>>>
>>> options = webdriver.ChromeOptions()
>>> options.add_argument('--headless')
>>> # additional options:
>>> options.add_argument('--no-sandbox')
>>> options.add_argument('--disable-gpu')
>>> options.add_argument('--disable-notifications')
>>> options.add_argument('--disable-extensions')
>>> options.add_argument('--disable-infobars')
>>> options.add_argument('--enable-automation')
>>> options.add_argument('--disable-dev-shm-usage')
>>> options.add_argument('--disable-setuid-sandbox')
>>> browser.config.driver_options = options
Scenario: "Run remotely on Selenoid"
>>> import os
>>> from selene import browser
>>> from selenium import webdriver
>>>
>>> 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
>>> browser.config.driver_remote_url = (
>>> f'https://{os.getenv("LOGIN")}:{os.getenv("PASSWORD")}@'
>>> f'selenoid.autotests.cloud/wd/hub'
>>> )
Scenario: "Run remotely on BrowserStack in iOS Safari"
>>> import os
>>> from selene import browser
>>> from selenium.webdriver.common.options import ArgOptions
>>>
>>> options = ArgOptions()
>>> options.set_capability(
>>> 'bstack:options',
>>> {
>>> 'deviceName': 'iPhone 14 Pro Max',
>>> # 'browserName': 'safari', # default for iPhone
>>> 'userName': os.getenv('BROWSERSTACK_USERNAME'),
>>> 'accessKey': os.getenv('BROWSERSTACK_ACCESS_KEY'),
>>> },
>>> )
>>> browser.config.driver_options = options
>>> browser.config.driver_remote_url = 'http://hub.browserstack.com/wd/hub'
Scenario: "Run locally in Android Chrome"
>>> from selene import browser
>>> from appium.options.common import AppiumOptions
>>>
>>> mobile_options = AppiumOptions()
>>> mobile_options.new_command_timeout = 60
>>> # Mandatory, also tells Selene to build Appium driver:
>>> mobile_options.platform_name = 'android'
>>> mobile_options.set_capability('browserName', 'chrome')
>>>
>>> browser.config.driver_options = mobile_options
>>> # Not mandatory, because it is the default value:
>>> # browser.config.driver_remote_url = 'http://127.0.0.1:4723/wd/hub'
Scenario: "Run locally in Android App"
>>> from selene import browser
>>> from appium.options.android import UiAutomator2Options
>>>
>>> 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
Scenario: "Run remotely in Android App on BrowserStack"
>>> import os
>>> from selene import browser
>>> from appium.options.android import UiAutomator2Options
>>>
>>> options = UiAutomator2Options()
>>> options.app = 'bs://c700ce60cf13ae8ed97705a55b8e022f13c5827c'
>>> options.set_capability(
>>> 'bstack:options',
>>> {
>>> 'deviceName': 'Google Pixel 7',
>>> 'userName': os.getenv('BROWSERSTACK_USERNAME'),
>>> 'accessKey': os.getenv('BROWSERSTACK_ACCESS_KEY'),
>>> },
>>> )
>>> browser.config.driver_options = options
>>> browser.config.driver_remote_url = 'http://hub.browserstack.com/wd/hub'
By having config options that influences Selene behavior,
like config.timeout
and config.base_url
,
– together with complete "driver management",
we definitely break SRP principle... In the name of Good:D. Kind of;).
All this makes it far from being a simple options data class...
– yet kept as one "class for everything" to keep things easier to use,
especially taking into account some historical reasons of Selene design,
that was influenced a lot by the Selenide from Java world.
As a result sometimes options are not consistent with each other,
when we speak about different contexts of their usage.
For example, this same config,
once customized with config.driver_options = UiAutomator2Options()
,
will result in mobile driver built, but then all other web-related options,
for example, a config.base_url
will be not relevant.
Some of them will be ignored, while some of them,
for example js-related, like config.set_value_by_js
,
will break the code execution (JavaScript does not work in mobile apps).
In an ideal world, we would have to split this config into several ones,
starting BaseConfig and continuing with WebConfig, MobileConfig, etc.
Yet, we have what we have. This complicates things a bit,
especially for us, contributors of Selene,
but makes easier for newbies in a lot of "harder" cases,
like customizing same shared browser instance for multi-platform test runs,
when we have one test that works for all platforms.
Thus, we allow to do "harder" tasks easier for "less experienced" users.
Again, such "easiness" does not mean "simplicity" for us, contributors,
and also for advanced Selene users,
who want to customize things in a lot of ways
and have them easier to support on a long run.
But for now, let's keep it as is, considered as a trade-off.
build_driver_strategy: Callable[[Config], WebDriver] = _build_local_driver_by_name_or_remote_by_url_and_options
class-attribute
instance-attribute
#
A factory to build a driver instance based on this config instance.
The driver built with this factory will be available via config.driver
.
Hence, you can't use config.driver
directly inside this factory,
because it may lead to recursion.
The default factory builds:
- either a local driver by value specified in
config.driver_name
- or a local driver by browserName capability specified in
config.driver_options
- or remote driver by value specified in
config.driver_remote_url
- or mobile driver according to
config.driver_options
capabilities
_schedule_driver_teardown_strategy: Callable[[Config, Callable[[], WebDriver]], None] = lambda config, get_driver: atexit.register(lambda: config._teardown_driver_strategy(config)(get_driver()))
class-attribute
instance-attribute
#
Defines when drier will be teardown. Is supposed to use config._teardown_driver_strategy under the hood.
By default, it's registered as an atexit handler.
_teardown_driver_strategy: Callable[[Config, WebDriver], None] = lambda config: lambda driver: driver.quit() if not config.hold_driver_at_exit and config._is_driver_set_strategy(driver) and config._is_driver_alive_strategy(driver) else None
class-attribute
instance-attribute
#
Defines how driver will be teardown.
By default it quits the driver if it's alive and not asked to be held at exit
via config.hold_driver_at_exit
.
_is_driver_set_strategy: Callable[[WebDriver], bool] = lambda driver: driver is not ... and driver is not None
class-attribute
instance-attribute
#
Defines how to check if driver is set, and so defines how to "unset" or "reset" it.
_is_driver_alive_strategy: Callable[[WebDriver], bool] = lambda driver: driver.service.process is not None and driver.service.process.poll() is None if hasattr(driver, 'service') else on_error_return_false(lambda: driver.window_handles is not None)
class-attribute
instance-attribute
#
Defines the logic of checking driver for being alive.
Is supposed to be used in context of triggering automatic driver rebuild, depending on context.
driver_options: Optional[BaseOptions] = None
class-attribute
instance-attribute
#
Individual browser options to be used on building a driver.
Examples:
Can be used instead of config.driver_name
to tell Selene
which driver to build, e.g. just specifying
>>> from selene import browser
>>> from selenium import webdriver
>>>
>>> browser.config.driver_options = webdriver.FirefoxOptions()`
– will tell Selene to build a Firefox driver.
But usually you want something more specific, like specifying to run a browser in headless more:
driver_service: Optional[Service] = None
class-attribute
instance-attribute
#
Service instance for managing the starting and stopping of the driver. Might be useful, for example, for customizing driver executable path, if you want to use a custom driver executable instead of the one, downloaded by Selenium Manager automatically.
driver_remote_url: Optional[str] = None
class-attribute
instance-attribute
#
A URL to be used as remote server address to instantiate a RemoteConnection to be used by RemoteWebDriver to connect to the remote server.
Also known as command_executor
,
when passing on init: driver = remote.WebDriver(command_executor=HERE)
.
Currently we name it and type hint as URL,
but if you pass a RemoteConnection object,
it will work same way as in Selenium WebDriver.
driver_name: Optional[str] = None
class-attribute
instance-attribute
#
A desired name of the driver to build by config.build_driver_strategy
.
If not set (i.e. set to None, that is a current default value), the 'chrome' driver will be used by default.
It is ignored by default config.build_driver_strategy
if config.driver_remote_url
is set.
If you are going to provide your desired driver options
via config.driver_options
,
then Selene will try to guess the corresponding driver name
based on the options you provided. I.e. no need to provide both:
It's enough to provide only the options:
GIVEN set to any of: 'chrome', 'firefox', 'edge',
AND config.driver is left unset (default value is ...),
THEN default config.build_driver_strategy will automatically install drivers
AND build webdriver instance for you
AND this config will store the instance in config.driver
_override_driver_with_all_driver_like_options: bool = True
class-attribute
instance-attribute
#
Controls whether driver will be deep copied
with config.driver_name
, config.driver_remote_url
,
and so for any other config.*driver*
option.
when customizing config via config.with_(**options_to_override)
.
Examples:
Building 2 drivers with implicit deep copy of driver storage:
>>> chrome_config = Config(
>>> driver_name='chrome',
>>> timeout=10.0,
>>> base_url='https://autotest.how',
>>> )
>>> chrome = chrome_config.driver
>>> firefox_config = chrome_config.with_(driver_name='firefox')
>>> firefox = firefox_config.driver
>>> assert firefox is not chrome
Building 2 drivers with explicit deep copy of driver storage [1]:
>>> chrome_config = Config(
>>> driver_name='chrome',
>>> timeout=10.0,
>>> base_url='https://autotest.how',
>>> _override_driver_with_all_driver_like_options=False,
>>> )
>>> chrome = chrome_config.driver
>>> firefox_config = chrome_config.with_(driver_name='firefox', driver=...)
>>> firefox = firefox_config.driver
>>> assert firefox is not chrome
Building 2 drivers with explicit deep copy of driver storage [2]:
>>> chrome_config = Config(
>>> driver_name='chrome',
>>> timeout=10.0,
>>> base_url='https://autotest.how',
>>> )
>>> chrome_config._override_driver_with_all_driver_like_options = False
>>> chrome = chrome_config.driver
>>> firefox_config = chrome_config.with_(name='firefox', driver=...)
>>> firefox = firefox_config.driver
>>> assert firefox is not chrome
Building 1 driver because driver storage was not copied:
>>> chrome_config = Config(
>>> driver_name='chrome',
>>> timeout=10.0,
>>> base_url='https://autotest.how',
>>> )
>>> chrome_config._override_driver_with_all_driver_like_options = False
>>> chrome = chrome_config.driver
>>> firefox_config = chrome_config.with_(name='firefox')
>>> firefox = firefox_config.driver
>>> assert firefox is chrome # o_O ;)
hold_driver_at_exit: bool = False
class-attribute
instance-attribute
#
Controls whether driver will be automatically quit at process exit or not.
Will not take much effect on Chrome for 4.5.0 < selenium versions <= 4.8.3 < ?.?.?, Because for some reason, Selenium of such versions kills driver by himself, regardless of what Selene thinks about it:D
_driver_get_url_strategy: Callable[[Config], Callable[[Optional[str]], None]] = _maybe_reset_driver_then_tune_window_and_get_with_base_url
class-attribute
instance-attribute
#
Defines how to get url with driver depending on other options, like config.base_url.
Is used inside browser.open(relative_or_absolute_url)
,
and defines its behavior correspondingly.
_reset_not_alive_driver_on_get_url: bool = True
class-attribute
instance-attribute
#
Controls whether driver should be automatically reset and, thus,
forced to be rebuilt, if it was noticed as not alive (e.g. after quit or crash)
on next call to config.driver.get(url)
(via config._driver_get_url_strategy
).
Does not work if config.driver
was set manually to Callable[[], WebDriver]
.
Is a more "week" option than config.rebuild_not_alive_driver
,
that is disabled by default,
that forces to rebuild driver on any next access.
rebuild_not_alive_driver: bool = False
class-attribute
instance-attribute
#
Controls whether driver should be automatically rebuilt when on next call to config.driver it was noticed as not alive (e.g. after quit or crash).
May slow down your tests if running against remote Selenium server, e.g. Grid or selenoid, because of additional request to check if driver is alive per each driver action.
Does not work if config.driver
was set manually to Callable[[], WebDriver]
.
Is a more "strong" option than config._reset_not_alive_driver_on_get_url
,
(enabled by default), that schedules rebuilding driver
on next access only inside "get url" logic.
driver: WebDriver = _ManagedDriverDescriptor(default=...)
class-attribute
instance-attribute
#
A driver instance with lifecycle managed by this config special options depending on their values and customization of this attribute.
Once driver-definition-related options are set
(like config.driver_options
, config.driver_remote_url
),
the driver will be built on first access to this attribute.
Thus, to build the driver with Selene,
you simply call config.driver
for the first time,
and usually the simplest way to access it
– is through either browser.config.driver
or even browser.driver
shortcut.
Moreover, usually you don't do this explicitly,
but rather use browser.open(url)
to build a driver and open a page.
Scenarios
GIVEN unset, i.e. equals to default ...
or None
,
WHEN accessed first time (e.g. via config.driver)
THEN it will be set to the instance built by config.build_driver_strategy
.
GIVEN set manually to an existing driver instance,
like: config.driver = Chrome()
THEN it will be reused as it is on any next access
WHEN reset to ...
OR None
THEN will be rebuilt by config.build_driver_strategy
GIVEN set manually to an existing driver instance (not callable),
like: config.driver = Chrome()
AND at some point of time the driver is not alive
like crashed or quit
AND config._reset_not_alive_driver_on_get_url
is set to True
,
that is default
WHEN driver.get(url) is requested under the hood
like at browser.open(url)
THEN config.driver will be reset to ...
AND thus will be rebuilt by config.build_driver_strategy
GIVEN set manually to a callable that returns WebDriver instance
(currently marked with FutureWarning, so might be deprecated)
WHEN accessed fist time
AND any next time
THEN will call the callable and return the result
GIVEN unset or set manually to not callable
AND config.hold_driver_at_exit
is set to False
(that is default)
WHEN the process exits
THEN driver will be quit.
timeout: float = 4
class-attribute
instance-attribute
#
A default timeout for all Selene waiting that happens under the hood of the majority of Selene commands and assertions.
poll_during_waits: int = 100
class-attribute
instance-attribute
#
A fake option, not currently used in Selene waiting:)
base_url: str = ''
class-attribute
instance-attribute
#
A base url to be used when opening a page with relative url.
Examples:
Instead of duplicating the same base url in all your tests:
>>> from selene import browser
>>> browser.open('https://mywebapp.xyz/signin')
>>> ...
>>> browser.open('https://mywebapp.xyz/signup')
>>> ...
>>> browser.open('https://mywebapp.xyz/profile')
>>> ...
You can set it once in your config and then just use relative urls:
_get_base_url_on_open_with_no_args: bool = False
class-attribute
instance-attribute
#
A flag to indicate whether to use config.base_url
when opening a page with browser.open()
command without arguments.
If you call browser.open()
without arguments,
it will just force the driver to be built and real browser to be opened.
Even if you have set config.base_url
, to reuse it in your tests on browser.open
,
you have to specify the url explicitly, like browser.open('/')
or at least browser.open('')
.
But there are cases where you would like to load base url on browser.open()
,
for example in context of cross-platform testing. Then you use this option;)
See example at
https://github.com/yashaka/selene/blob/master/examples/run_cross_platform
window_width: Optional[int] = None
class-attribute
instance-attribute
#
If set, will be used to set the window width on next call to browser.open(url)
.
window_height: Optional[int] = None
class-attribute
instance-attribute
#
If set, will be used to set the window height on next call
to browser.open(url)
.
log_outer_html_on_failure: bool = False
class-attribute
instance-attribute
#
If set to True, will log outer html of the element on failure of any Selene command.
Is disabled by default, because:
- it might add too much of noise to the logs
- will not work on mobile app tests because under the hood - uses JavaScript
set_value_by_js: bool = False
class-attribute
instance-attribute
#
A flag to indicate whether to use JavaScript to set value of an element
on element.set_value(value)
for purposes of speeding up the test execution,
or as a workaround when default selenium-based implementation does not work.
type_by_js: bool = False
class-attribute
instance-attribute
#
A flag to indicate whether to use JavaScript to type text to an element
on element.type(text)
for purposes of speeding up the test execution,
or as a workaround when default selenium-based implementation does not work.
click_by_js: bool = False
class-attribute
instance-attribute
#
A flag to indicate whether to use JavaScript to click on element
via element.click()
, usually, as a workaround,
when default selenium-based implementation does not work.
drag_and_drop_by_js: bool = False
class-attribute
instance-attribute
#
A flag to indicate whether to use JavaScript to drag and drop a web
element via source_element.drag_and_drop_to(target_element)
, usually,
as a workaround, when default selenium-based implementation does not work.
wait_for_no_overlap_found_by_js: bool = False
class-attribute
instance-attribute
#
A flag to indicate whether to use JavaScript to detect overlapping elements
and wait for them to disappear, when calling commands like element.type(text)
.
It is needed because Selenium does not support overlapping elements detection
on any command except click
. Hence, when you call click
on an element,
and there is some overlay on top of it
(e.g. for the sake of indicating "loading in progress"),
that is going to disappear after some time,
then Selenium will detect such overlap,
that tells Selene to wait for it to disappear.
But for any other command (double_click, context_click, type, etc.)
Selenium will not and so Selene will not wait.
Hence, if you want to wait in such cases, turn on this option.
Just keep in mind, that it will work only for web tests, not mobile.
_match_only_visible_elements_texts: bool = True
class-attribute
instance-attribute
#
A flag to indicate whether to filter out all elements for visibility
on applying Selene conditions that match elements collection texts
(usually in Selene assertions vis should
method calls).
_match_only_visible_elements_size: bool = False
class-attribute
instance-attribute
#
A flag to indicate whether to filter out all elements for visibility
on applying Selene conditions that match elements collection size
(usually in Selene assertions vis should
method calls).
It is set to False by default for backward compatibility reasons.
_placeholders_to_match_elements: Dict[Literal['exactly_one', 'zero_or_one', 'one_or_more', 'zero_or_more'], Any] = cast(dict, MappingProxyType({}))
class-attribute
instance-attribute
#
A dict of default placeholders to be used among values passed to Selene
collection conditions like have._texts_like(*values)
. Such values then can
be considered as a list globbing pattern, where a defined placeholder will
match the corresponding to placeholder type number of ANY elements.
The default list globbing placeholders are:
[{...}]
matches zero or one item of any text in the list{...}
matches exactly one item of any text in the list...
matches one or more items of any text in the list[...]
matches zero or more items of any text in the list
Thus, using this option you can redefine them. Assuming, you don't like, that
...
matches "one or more" and want it to match "exactly one" instead, then
you can set the following defaults:
from selene import browser
...
# GIVEN default placeholders
browser.all('.number0-9').should(have._texts_like([{...}], 0, {...}, 2, ...))
# WHEN
browser.config._placeholders_to_match_elements = {
'zero_or_one': '[...]',
'exactly_one': '...',
'one_or_more': '(...,)',
'zero_or_more': '[(...,)]',
}
# THEN
browser.all('.number0-9').should(have._texts_like([...], 0, ..., 2, (...,)))
All globbing placeholders can be mixed in the same list of expected item values in any order. *
selector_to_by_strategy: Callable[[str], Tuple[str, str]] = lambda selector: (By.XPATH, selector) if selector.startswith('/') or selector.startswith('./') or selector.startswith('..') or selector.startswith('(') or selector.startswith('*/') else (By.CSS_SELECTOR, selector)
class-attribute
instance-attribute
#
A strategy to convert a selector string to a Selenium By type of selector, that is a 2-dimension tuple of selector type and selector value.
Can be useful to define custom selectors to be used on building common Selene
entities like browser.element(selector)
or browser.all(selector)
.
You can find a simple example of such strategy definition in the default value
of this option. Here goes a smarter example of building a custom strategy on top
of the default one, that will automatically convert a "one word" selector string
to the [data-testid=<WORD>]
css selector:
# tests/conftest.py
import re
import pytest
import selene
from selene.common.helpers import _HTML_TAGS
@pytest.fixture(scope='function', autouse=True)
def browser_management():
selene.browser.config.selector_to_by_strategy = lambda selector: (
# wrap into default strategy
selene.browser.config.selector_to_by_strategy(
# detected testid
f'[data-testid={selector}]'
if re.match(
# word_with_dashes_underscores_or_numbers
r'^[a-zA-Z_\d\-]+$',
selector,
)
and selector not in _HTML_TAGS
else selector
)
)
yield
selene.browser.quit()
_wait_decorator: Callable[[Wait[E]], Callable[[F], F]] = lambda w: lambda f: f
class-attribute
instance-attribute
#
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;)
Yet prefixed with underscore, indicating that method is experimental, and so can be renamed or change its type signature, etc.
The default value of this option just does nothing.
See example of implementing and setting a custom wait decorator at https://github.com/yashaka/selene/blob/master/examples/log_all_selene_commands_with_wait.py
Examples:
And here is how you can use some predefined wait decorators in Selene, for example, to configure logging Selene commands to Allure report:
_disable_wait_decorator_on_get_query: bool = True
class-attribute
instance-attribute
#
A flag controlling whether to disable wait decorator on calls like entity.get(query.*), turned on by default, because queries usually serve as auxiliary methods to get some intermediate information, and not influence significantly the test flow, that is usually reported and logged via wait decorator. Hence, by default, we disable wait decoration for queries, removing redundant noise from potential logging and speeding up the test execution for some specific cases like working with nested Frames (for more see warning at _FrameContext.element).
hook_wait_failure: Optional[Callable[[TimeoutException], Exception]] = None
class-attribute
instance-attribute
#
A handler for all exceptions, thrown on failed waiting for timeout. Should process the original exception and rethrow it or the modified one.
reports_folder: Optional[str] = os.path.join(os.path.expanduser('~'), '.selene', 'screenshots', str(round(time.time() * 1000)))
class-attribute
instance-attribute
#
A folder to save screenshots and page sources on failure.
save_screenshot_on_failure: bool = True
class-attribute
instance-attribute
#
A flag to indicate whether to save screenshot on failure or not. If saved, will be also logged to the console on failure.
save_page_source_on_failure: bool = True
class-attribute
instance-attribute
#
A flag to indicate whether to save page source on failure or not. If saved, will be also logged to the console on failure.
_counter: itertools.count = itertools.count(start=int(round(time.time() * 1000)))
class-attribute
instance-attribute
#
A counter, currently used for incrementing screenshot and page source names
_build_wait_strategy: Callable[[Config], Callable[[E], Wait[E]]] = lambda config: lambda entity: Wait(entity, at_most=config.timeout, or_fail_with=config._inject_screenshot_and_page_source_pre_hooks(config.hook_wait_failure), _decorator=config._wait_decorator)
class-attribute
instance-attribute
#
A strategy for building a Wait object based on other config options
like config.timeout
, config.hook_wait_failure
, config._wait_decorator
, etc.
with_(**options_to_override)
#
Returns (Config): A new config with overridden options that were specified as arguments.
All other config options will be shallow-copied
from the current config.
Those other options that are of immutable types,
like `int` - will be also copied by reference,
i.e. in a truly shallow way.
Parameters:
-
**options_to_override
(Any
, default:{}
) –options to override in the new config.
Technically "override" here means: "deep copy option storage and update its value to the specified one". All other option storages will be: "shallow copied from the current config".
If
driver_name
is amongoptions_to_override
, anddriver
is not among them, andself._override_driver_with_all_driver_like_options
is True, thendriver
will be implicitly added to the options to override, i.e.with_(driver_name='firefox')
will be equivalent towith_(driver_name='firefox', driver=...)
. The latter gives a readable and concise shortcut to spawn more than one browser:from selene import Config config = Config(timeout=10.0, base_url='https://autotest.how') chrome = config.driver # chrome is default browser firefox_config = config.with_(driver_name='firefox') firefox = firefox_config.driver edge_config = config.with_(driver_name='edge') edge = edge_config.driver
Same logic applies to
remote_url
, and all other config.driver options.
_format_path_as_uri(path)
#
Converts a local file path to a URI that can be clicked in most editors and browsers.
_save_screenshot_strategy#
Defines a strategy for saving a screenshot.
The default strategy saves a screenshot to a file,
and stores the path to config.last_screenshot
.
_save_screenshot_strategy: Callable[
[Config, Optional[str]], Any
] = lambda config, path=None: fp.thread(
path,
lambda path: (
config._generate_filename(suffix='.png') if path is None else path
),
lambda path: (
os.path.join(path, f'{next(config._counter)}.png')
if path and not path.lower().endswith('.png')
else path
),
fp.do(
fp.pipe(
os.path.dirname,
lambda folder: (
os.makedirs(folder)
if folder and not os.path.exists(folder)
else ...
),
)
),
fp.do(
lambda path: (
warnings.warn(
'name used for saved screenshot does not match file '
'type. It should end with an `.png` extension',
UserWarning,
)
if not path.lower().endswith('.png')
else ...
)
),
lambda path: (path if config.driver.get_screenshot_as_file(path) else None),
fp.do(
lambda path: setattr(config, 'last_screenshot', path)
),
)
_save_page_source_strategy#
Defines a strategy for saving a page source on failure.
The default strategy saves a page_source to a file,
and stores the path to config.last_page_source
.
_save_page_source_strategy: Callable[
[Config, Optional[str]], Any
] = lambda config, path=None: fp.thread(
path,
lambda path: (
config._generate_filename(suffix='.html') if path is None else path
),
lambda path: (
os.path.join(path, f'{next(config._counter)}.html')
if path and not path.lower().endswith('.html')
else path
),
fp.do(
fp.pipe(
os.path.dirname,
lambda folder: (
os.makedirs(folder)
if folder and not os.path.exists(folder)
else ...
),
)
),
fp.do(
lambda path: (
warnings.warn(
'name used for saved page source does not match file '
'type. It should end with an `.html` extension',
UserWarning,
)
if not path.lower().endswith('.html')
else ...
)
),
lambda path: (path, config.driver.page_source),
fp.do(lambda path_and_source: fp.write_silently(*path_and_source)),
lambda path_and_source: path_and_source[0],
fp.do(
lambda path: setattr(config, 'last_page_source', path)
),
)