mirror of
https://git.zavage.net/Zavage-Software/wabot.git
synced 2026-07-21 13:06:08 -06:00
226 lines
7.9 KiB
Python
226 lines
7.9 KiB
Python
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 HumanPacing, NoPacing
|
|
from wabot.page import Page
|
|
|
|
|
|
class BasePage(Page):
|
|
elements = {
|
|
"username": ("el", ("id", "username")),
|
|
"state": ("select", ("id", "state")),
|
|
"agree": ("checkbox", ("id", "agree")),
|
|
"rows": ("els", ("css selector", "tr.row")),
|
|
}
|
|
|
|
|
|
class ChildPage(BasePage):
|
|
elements = {
|
|
"username": ("el", ("id", "user_name_v2")), # override
|
|
"extra": ("el", ("id", "extra")),
|
|
}
|
|
|
|
|
|
def make_browser():
|
|
browser = MagicMock(name="browser")
|
|
browser.pacing = NoPacing()
|
|
return browser
|
|
|
|
|
|
class TestElementResolution:
|
|
def test_own_elements_found(self):
|
|
page = BasePage(make_browser())
|
|
assert page.find_element_locators("username") == ("el", ("id", "username"))
|
|
|
|
def test_mro_walk_inherits_parent_elements(self):
|
|
page = ChildPage(make_browser())
|
|
assert page.find_element_locators("state") == ("select", ("id", "state"))
|
|
assert page.find_element_locators("extra") == ("el", ("id", "extra"))
|
|
|
|
def test_child_overrides_parent(self):
|
|
page = ChildPage(make_browser())
|
|
assert page.find_element_locators("username") == ("el", ("id", "user_name_v2"))
|
|
|
|
def test_missing_key_returns_none(self):
|
|
assert BasePage(make_browser()).find_element_locators("nope") is None
|
|
|
|
def test_empty_override_still_inherits_ancestors(self):
|
|
class Grandchild(ChildPage):
|
|
elements = {} # declares nothing; must still resolve ancestor keys
|
|
|
|
page = Grandchild(make_browser())
|
|
assert page.find_element_locators("state") == ("select", ("id", "state"))
|
|
assert page.find_element_locators("extra") == ("el", ("id", "extra"))
|
|
|
|
|
|
class TestGetProxy:
|
|
def test_typed_dispatch(self):
|
|
page = BasePage(make_browser())
|
|
assert isinstance(page["username"], TextField)
|
|
assert isinstance(page["agree"], CheckField)
|
|
|
|
def test_select_dispatch(self, monkeypatch):
|
|
import wabot.fields as fields_mod
|
|
|
|
monkeypatch.setattr(fields_mod, "Select", MagicMock())
|
|
page = BasePage(make_browser())
|
|
assert isinstance(page["state"], SelectField)
|
|
|
|
def test_els_returns_find_elements_result(self):
|
|
browser = make_browser()
|
|
page = BasePage(browser)
|
|
result = page["rows"]
|
|
browser.driver.find_elements.assert_called_once_with(
|
|
by="css selector", value="tr.row"
|
|
)
|
|
assert result is browser.driver.find_elements.return_value
|
|
|
|
def test_unknown_key_returns_falsy_nullfield(self):
|
|
el = BasePage(make_browser())["nope"]
|
|
assert isinstance(el, NullField)
|
|
assert not el
|
|
|
|
def test_element_not_on_page_returns_nullfield(self):
|
|
browser = make_browser()
|
|
browser.driver.find_element.side_effect = NoSuchElementException("gone")
|
|
el = BasePage(browser)["username"]
|
|
assert isinstance(el, NullField)
|
|
|
|
def test_custom_field_class_dispatch(self):
|
|
class MyField(TextField):
|
|
pass
|
|
|
|
class CustomPage(Page):
|
|
elements = {"thing": (MyField, ("id", "thing"))}
|
|
|
|
assert isinstance(CustomPage(make_browser())["thing"], MyField)
|
|
|
|
def test_malformed_locator_missing_accessors_returns_nullfield(self):
|
|
class BadPage(Page):
|
|
elements = {"x": ("el",)} # missing the accessors half
|
|
|
|
el = BadPage(make_browser())["x"]
|
|
assert isinstance(el, NullField)
|
|
assert not el
|
|
|
|
def test_malformed_accessors_arity_returns_nullfield(self):
|
|
class BadPage(Page):
|
|
elements = {"x": ("els", ("id",))} # accessors not a (by, value) pair
|
|
|
|
assert isinstance(BadPage(make_browser())["x"], NullField)
|
|
|
|
def test_unknown_type_returns_nullfield(self):
|
|
class BadPage(Page):
|
|
elements = {"x": ("selct", ("id", "x"))} # typo'd type string
|
|
|
|
assert isinstance(BadPage(make_browser())["x"], NullField)
|
|
|
|
|
|
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
|