feat: Page interaction helpers (paced clicks, forms, alerts, waits)

This commit is contained in:
Mathew Sir Guest the best
2026-07-07 23:01:09 -06:00
parent fbb7d4114a
commit c99b9509f4
3 changed files with 287 additions and 2 deletions
+103 -1
View File
@@ -2,8 +2,9 @@ from unittest.mock import MagicMock
from selenium.common.exceptions import NoSuchElementException
import wabot.page as page_mod
from wabot.fields import CheckField, NullField, SelectField, TextField
from wabot.pacing import NoPacing
from wabot.pacing import HumanPacing, NoPacing
from wabot.page import Page
@@ -121,3 +122,104 @@ class TestGetProxy:
class TestVerify:
def test_default_verify_is_true(self):
assert BasePage(make_browser()).verify() is True
class PlainPage(Page):
elements = {}
def make_element(width=100, height=30, displayed=True, enabled=True):
el = MagicMock(name="element")
el.size = {"width": width, "height": height}
el.is_displayed.return_value = displayed
el.is_enabled.return_value = enabled
return el
class TestClick:
def test_refuses_null_element(self):
assert PlainPage(make_browser()).click(None) is False
assert PlainPage(make_browser()).click(NullField(name="x")) is False
def test_plain_click_when_pacing_gives_no_offset(self):
page = PlainPage(make_browser()) # NoPacing -> click_offset None
el = make_element()
assert page.click(el) is True
el.click.assert_called_once_with()
def test_offset_click_uses_action_chains(self, monkeypatch):
browser = make_browser()
browser.pacing = HumanPacing()
chains = MagicMock(name="ActionChains")
monkeypatch.setattr(page_mod, "ActionChains", chains)
page = PlainPage(browser)
el = make_element(width=100, height=40)
assert page.click(el) is True
chains.assert_called_once_with(browser.driver)
args = chains.return_value.move_to_element_with_offset.call_args.args
assert args[0] is el
# offsets are center-relative and must stay inside the element
assert -49 <= args[1] <= 49 and -19 <= args[2] <= 19
chains.return_value.move_to_element_with_offset.return_value.click.return_value.perform.assert_called_once()
def test_never_clickable_element_fails(self):
page = PlainPage(make_browser())
el = make_element(displayed=False)
assert page.click(el, clickable_timeout=0.3) is False
el.click.assert_not_called()
class TestFormHelpers:
def test_set_el_value_clears_types_and_verifies(self):
page = PlainPage(make_browser())
el = make_element()
el.get_attribute.return_value = "mathew"
assert page.set_el_value(el, "mathew") is True
el.clear.assert_called_once_with()
el.send_keys.assert_called_once_with("mathew")
def test_set_el_value_returns_false_on_mismatch(self):
page = PlainPage(make_browser())
el = make_element()
el.get_attribute.return_value = "wrong"
assert page.set_el_value(el, "mathew") is False
def test_set_el_value_none_just_clears(self):
page = PlainPage(make_browser())
el = make_element()
assert page.set_el_value(el, None) is True
el.clear.assert_called_once_with()
el.send_keys.assert_not_called()
def test_get_el_value_and_text_handle_null(self):
page = PlainPage(make_browser())
assert page.get_el_value(None) is None
assert page.get_el_text(None) is None
def test_set_checkbox_clicks_only_when_state_differs(self):
page = PlainPage(make_browser())
el = make_element()
el.is_selected.return_value = False
page.set_checkbox(el, True)
el.click.assert_called_once()
el.click.reset_mock()
el.is_selected.return_value = True
page.set_checkbox(el, True)
el.click.assert_not_called()
class TestStaleness:
def test_fresh_element_is_not_stale(self):
page = PlainPage(make_browser())
assert page.is_element_stale(make_element()) is False
def test_stale_element_is_detected(self):
from selenium.common.exceptions import StaleElementReferenceException
class StaleEl: # NOT a MagicMock: mutating type(mock) would poison every mock
@property
def tag_name(self):
raise StaleElementReferenceException("stale")
page = PlainPage(make_browser())
assert page.is_element_stale(StaleEl()) is True