Custom matchers¶
Custom matchers let you extend expect() with your own assertion methods that integrate with the retry loop and negation.
@extend decorator¶
Register a custom matcher function under a name. The function receives the assertion's target as its first argument and must return a (bool, Any) tuple — the bool indicates pass/fail, and the Any is the actual value for error reporting.
from selenium_expect import extend
@extend("to_be_in_viewport")
def check_in_viewport(element):
driver = element.parent
script = """
var rect = arguments[0].getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
"""
in_viewport = driver.execute_script(script, element)
return (in_viewport, "in viewport" if in_viewport else "out of viewport")
Usage¶
Once registered, the matcher is available on any expect() call:
expect(element).to_be_in_viewport()
expect(element).not_.to_be_in_viewport()
expect(element).to_be_in_viewport(timeout=5, polling=0.2)
Custom matchers work with:
expect(element)—ExpectElementexpect(driver)—ExpectDriverexpect(driver, by=By.ID, value="foo")—LocatorExpect(re-finds on each poll)expect(items)—ExpectListexpect(alert)—ExpectAlertexpect(select)—ExpectSelectexpect(shadow_root)—ExpectShadow
merge_expects()¶
Combine custom matchers from multiple modules into the registry:
from selenium_expect import merge_expects
import my_project.matchers
import my_project.custom_assertions
merge_expects(my_project.matchers, my_project.custom_assertions)
You can also pass importable strings:
Matcher signature¶
target: The object passed toexpect()(e.g.WebElement,WebDriver).*args, **kwargs: Any additional arguments from the call site (excludingtimeoutandpolling, which are consumed by the retry loop).- Return:
(bool, Any)whereboolisTrueif the condition passed.
Example: custom text matcher¶
from selenium_expect import extend
@extend("to_have_trimmed_text")
def check_trimmed_text(element, expected: str):
actual = element.text.strip()
return (actual == expected, actual)
# Usage
expect(element).to_have_trimmed_text("Hello, World!")
More examples¶
Custom matcher with arguments¶
from selenium_expect import extend
@extend("to_have_css_color")
def check_css_color(element, color: str):
"""Assert element has a specific CSS color property."""
actual = element.value_of_css_property("color")
return (actual == color, actual)
# Usage
expect(element).to_have_css_color("rgba(255, 0, 0, 1)")
expect(element).not_.to_have_css_color("rgba(0, 0, 0, 1)")
Custom matcher for driver¶
from selenium_expect import extend
@extend("to_have_scroll_position")
def check_scroll_position(driver, position: int):
"""Assert the page is scrolled to a specific Y position."""
actual = driver.execute_script("return window.scrollY")
return (actual == position, actual)
# Usage
expect(driver).to_have_scroll_position(0)
expect(driver).not_.to_have_scroll_position(500)
Custom matcher with timeout and polling¶
@extend("to_have_data_loaded")
def check_data_loaded(element):
"""Assert element has data-loaded='true' attribute."""
actual = element.get_attribute("data-loaded")
return (actual == "true", actual)
# Custom timeout and polling work automatically
expect(element).to_have_data_loaded(timeout=15, polling=0.5)
Organizing matchers in a module¶
# my_project/matchers.py
from selenium_expect import extend
@extend("to_be_in_viewport")
def check_in_viewport(element):
driver = element.parent
script = """
var rect = arguments[0].getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
"""
in_viewport = driver.execute_script(script, element)
return (in_viewport, "in viewport" if in_viewport else "out of viewport")
@extend("to_have_data_loaded")
def check_data_loaded(element):
actual = element.get_attribute("data-loaded")
return (actual == "true", actual)
@extend("to_have_no_console_errors")
def check_no_console_errors(driver):
errors = driver.execute_script(
"return window.__consoleErrors || []"
)
return (len(errors) == 0, errors)
```python
my_project/test_something.py¶
from selenium_expect import expect, merge_expects import my_project.matchers
Register all matchers from the module¶
merge_expects(my_project.matchers)
Use them¶
expect(element).to_be_in_viewport() expect(element).to_have_data_loaded() expect(driver).to_have_no_console_errors()