Skip to content

#

This is the latest development version

Some features documented on this page may not yet be available in the published stable version.

Module overview#

This module contains a set of advanced functionality that can be used in addition to the standard Selene method like entity.locate() when you need to acquire some information from a Selene entity. Functions defined in this module are called "queries", because they get and return some information about the entity, unlike advanced commands that perform some actions and return the entity itself. Thus, an advanced queries are defined outside the entity class, here in this module, and given named as advanced_query then can be executed on entity via entity.get(advanced_query).

The idiomatic way to use advanced queries is to import the whole module:

from selene import browser, have, query  # ❗️over from selene.core.query import text

# GIVEN
...
current_price = browser.element('#cart-price').get(query.text)  # ⬅️ used via module

# WHEN
browser.all('.product').element_by(have.text('Apple')).element('#add-to-cart').click()

# THEN
browser.element('#cart-price').should(
    have.exact_text(str(float(current_price) + 1.99))
)

Thus, you don't need to remember all available queries, you just import the module and select the one you need from the list of suggestions among query.*.

Why do we need a separate module for queries, why not element.text?#

– Because the test in previous example that asserts final price based on original price – is not a perfect test. A good test case is a "case with strictly defined preconditions", where we know for sure what will happen, after what, and with exact expected results. Therefore, in the example above, we should know in-forward the final price of the cart, there is no need to "store original price" and then "calculate final". And here, in Selene, we follow the principle: "A good design is one that makes it easy to do the right thing and hard to do the wrong thing".

That's why all "queries" were intentionally removed from entities and moved to the separate module, so that it's harder to use them on regular basis. As by default, we allways should try to implement "the perfect test" that implies we know in forward the exact state of the system on each test step. Then we don't need to "get from entity" and "store" intermediate results, like "current price", and once we do assertion, we already have expected conditions:

from selene import browser, have  # ❗️ have.* are expected conditions;)

...
# THEN
browser.element('#cart-price').should(have.exact_text('11.99'))

Expected conditions like have.exact_text or be.visible are better than using "assert + get query":

from selene import browser

...
# THEN
assert browser.element('#cart-price').text == '11.99'

– because such "simple assertion" does now support smart implicit waiting, that is crucial for stable and fast tests of modern web applications that are dynamic in nature.

That's why there is no easy syntax like element.text in Selene. If you really need to get some information from the entity (sometimes you really do need it, in case of some limitations on the project, etc.), at least, you have to think twice to use available but less handy syntax in Selene – like element.get(query.text).

Tip

Yet you can always extend Selene entities with your own queries built in.

Why and how to implement custom queries?#

The list of advanced queries in this module is far from exhaustive, and there is no goal to make it complete, because in many cases, the end user will need his own list of custom queries specific to his application context. But this list can be a good starting point for such custom queries. Taking the latter into account we try to keep implementation of the queries in this module – as simple as possible, so that the end user can easily understand them and use as examples to implement own custom queries. That's why we avoid following DRY principle here, and prefer pure selenium code over reusing already implemented in Selene helpers.

The pattern to implement a custom query is same as described for Selene custom commands.

Here are just a simple example:

# Full path can be: my_tests_project/extensions/selene/command.py

from selene.core.query import *
from selene.core.wait import Query
from selene import Browser


def __get_browser_logs(browser: Browser):
    return browser.driver.get_log('browser')


logs: Query[Browser, list] = Query('browser logs', __get_browser_logs)

– to be used as

from selene import browser,
from my_project_root.extensions.selene import query

browser.open('https://todomvc-emberjs-app.autotest.how/')
print(browser.get(query.logs))
...

And here are a few implementation examples of already available queries in Selene:

def attribute(name: str) -> Query[Element, str]:
    def fn(element: Element):
        return element.locate().get_attribute(name)

    return Query(f'attribute {name}', fn)


inner_html = attribute('innerHTML')
text_content = attribute('textContent')
value = attribute('value')

tag: Query[Element, str] = Query('tag name', lambda element: element.locate().tag_name)

– As you can see, it all comes simply to define a function or lambda on entity object and wrap it into Query😇.

For more examples of how to build your own custom queries see the actual implementation of Selene's queries in this module.

The actual list of queries ↙️#

current_tab: Query[Browser, str] = Query('current tab (window handle)', lambda browser: browser.driver.current_window_handle) module-attribute #

inner_html = attribute('innerHTML') module-attribute #

inner_htmls = attributes('innerHTML') module-attribute #

internal_id: Query[Element, Any] = Query('internal id', lambda element: element.locate().id) module-attribute #

location: Query[Element, Dict[str, int]] = Query('location', lambda element: element.locate().location) module-attribute #

location_once_scrolled_into_view: Query[Element, Dict[str, int]] = Query('location once scrolled into view', lambda element: element.locate().location_once_scrolled_into_view) module-attribute #

next_tab: Query[Browser, str] = Query('next tab', __next_tab_fn) module-attribute #

outer_html = attribute('outerHTML') module-attribute #

outer_htmls = attributes('outerHTML') module-attribute #

page_source: Query[Browser, str] = Query('page source', lambda browser: browser.driver.page_source) module-attribute #

previous_tab: Query[Browser, str] = Query('previous tab', __previous_tab_fn) module-attribute #

rect: Query[Element, Dict[str, Any]] = Query('rect', lambda element: element.locate().rect) module-attribute #

screenshot_as_base64: Query[Element, Any] = Query('screenshot as base64', lambda element: element.locate().screenshot_as_base64) module-attribute #

screenshot_as_png: Query[Element, Any] = Query('screenshot as png', lambda element: element.locate().screenshot_as_png) module-attribute #

size: Query[Element | Collection | Browser, dict | int] = Query('size', lambda entity: entity.driver.get_window_size() if isinstance(entity, Browser) else entity.locate().size if isinstance(entity, Element) else len(entity.locate()) if isinstance(entity, Collection) else typing.cast(Browser, entity).driver.get_window_size()) module-attribute #

tabs: Query[Browser, List[str]] = Query('tabs', lambda browser: browser.driver.window_handles) module-attribute #

tabs_number: Query[Browser, int] = Query('tabs number', lambda browser: len(browser.driver.window_handles)) module-attribute #

tag: Query[Element, str] = Query('tag name', lambda element: element.locate().tag_name) module-attribute #

tags: Query[Collection, List[str]] = Query('tag names', lambda collection: [element.tag_name for element in collection.locate()]) module-attribute #

text: Query[Element, str] = Query('text', lambda element: element.locate().text) module-attribute #

normalized text of element

texts: Query[Collection, List[str]] = Query('texts', lambda collection: [element.text for element in collection.locate()]) module-attribute #

list of normalized texts of all elements in collection

title: Query[Browser, str] = Query('title', lambda browser: browser.driver.title) module-attribute #

url: Query[Browser, str] = Query('url', lambda browser: browser.driver.current_url) module-attribute #

value = attribute('value') module-attribute #

values = attributes('value') module-attribute #

visible_texts: Query[Collection, List[str]] = Query('visible texts', lambda collection: [element.text for element in collection.locate() if element.is_displayed()]) module-attribute #

list of normalized texts of all visible elements in collection

js #

shadow_root: Query[Element, Element] = Query('shadow root', lambda element: Element(Locator(f'{element}: shadow root', lambda: element.config.driver.execute_script('return arguments[0].shadowRoot', element.locate())), element.config)) class-attribute instance-attribute #

A lazy query that actually builds an Element entity based on element's shadow root acquired via JavaScript.

shadow_roots: Query[Collection, Collection] = Query('shadow roots', lambda collection: Collection(Locator(f'{collection}: shadow roots', lambda: collection.config.driver.execute_script('return [...arguments[0]].map(arg => arg.shadowRoot)', collection.locate())), collection.config)) class-attribute instance-attribute #

A lazy query that actually builds a Collection entity based on elements' shadow roots acquired via JavaScript.

attribute(name) #

A query that gets the value of the attribute of the element

Parameters:

  • name (str) –

    name of the attribute

attributes(name) #

A query that gets the values of the attribute of all elements in the collection

Parameters:

  • name (str) –

    name of the attribute

css_properties(name) #

A query that gets the values of the css properties of all elements in the collection

Parameters:

  • name (str) –

    name of the attribute

css_property(name) #

js_property(name) #

native_properties(name) #

A query that gets the values of the native properties of all elements in the collection

Parameters:

  • name (str) –

    name of the attribute

native_property(name) #

page_source_saved(path=None) #

screenshot(filename) #

screenshot_saved(path=None) #

tab(index) #