wabot/tests/unit/test_browser.py

160 lines
5.4 KiB
Python

from types import SimpleNamespace
from unittest.mock import MagicMock
import pytest
from selenium.common.exceptions import WebDriverException
from wabot.browser import Browser
from wabot.pacing import HumanPacing, NoPacing
from wabot.page import Page
class Login(Page):
elements = {"username": ("el", ("id", "username"))}
def do_login(self):
return "logged-in"
def explode(self):
raise WebDriverException("browser gone")
class Unverifiable(Page):
def verify(self):
return False
@pytest.fixture
def bot():
return Browser(MagicMock(name="driver"), pacing=NoPacing())
class TestConstruction:
def test_good_flag_starts_true(self, bot):
assert bot.good is True
def test_default_pacing_is_human(self):
assert isinstance(Browser(MagicMock()).pacing, HumanPacing)
class TestSetPage:
def test_sets_and_returns_true(self, bot):
assert bot.set_page(Login) is True
assert isinstance(bot.page, Login)
def test_failed_verify_refuses_switch(self, bot):
bot.set_page(Login)
assert bot.set_page(Unverifiable) is False
assert isinstance(bot.page, Login) # unchanged
class TestDelegation:
def test_getattr_delegates_to_page(self, bot):
bot.set_page(Login)
assert bot.do_login() == "logged-in"
def test_getitem_delegates_to_page(self, bot):
bot.set_page(Login)
el = bot["username"]
assert el.name == "username"
def test_getattr_without_page_raises(self, bot):
with pytest.raises(AttributeError):
_ = bot.do_login
def test_direct_delegation_does_not_flip_good(self, bot):
# only perform() traps+flips; direct bot.x() is the raw path
bot.set_page(Login)
with pytest.raises(WebDriverException):
bot.explode()
assert bot.good is True
def test_getitem_without_page_raises_friendly(self, bot):
with pytest.raises(RuntimeError, match="no current page"):
bot["username"]
def test_repr_shows_page_and_good(self, bot):
assert "page=None" in repr(bot) and "good=True" in repr(bot)
bot.set_page(Login)
assert "page=Login" in repr(bot)
class TestPerform:
def test_returns_method_result(self, bot):
bot.set_page(Login)
assert bot.perform("do_login") == "logged-in"
def test_missing_method_returns_none(self, bot):
bot.set_page(Login)
assert bot.perform("nope") is None
def test_webdriver_exception_flips_good(self, bot):
bot.set_page(Login)
assert bot.perform("explode") is None
assert bot.good is False
def test_broken_state_refuses_actions_until_reset(self, bot):
bot.set_page(Login)
bot.perform("explode")
assert bot.perform("do_login") is None # refused
assert bot.do_login() is None # __getattr__ path also refused
bot.reset()
assert bot.perform("do_login") == "logged-in"
def test_broken_state_attribute_read_returns_noop_callable(self, bot):
# documents the footgun: while broken, an attribute read yields the
# no-op refusal callable, not the underlying value
bot.set_page(Login)
bot.perform("explode") # flips good=False
assert callable(bot.anything)
class TestQuit:
def test_quit_quits_driver_and_removes_session(self):
driver, store = MagicMock(), MagicMock()
store.get.return_value = SimpleNamespace(service_pid=None) # external: nothing to stop
bot = Browser(driver, session_name="s1", store=store)
bot.quit()
driver.quit.assert_called_once_with()
store.remove.assert_called_once_with("s1")
def test_quit_without_session_only_quits(self):
driver = MagicMock()
Browser(driver).quit()
driver.quit.assert_called_once_with()
def test_quit_stops_managed_service(self, monkeypatch):
import sys
# NOTE(Task 12 wiring fix): `import wabot.browser as browser_mod` would
# resolve to `wabot.browser` the FUNCTION (the public API added in
# Task 12 shadows the submodule attribute of the same name on the
# `wabot` package once `wabot/__init__.py` defines `def browser(...)`).
# sys.modules keeps the real submodule reachable regardless of what
# the package's own `browser` attribute is rebound to.
browser_mod = sys.modules["wabot.browser"]
stopped = []
monkeypatch.setattr(browser_mod, "stop_service", lambda pid: stopped.append(pid))
driver, store = MagicMock(), MagicMock()
store.get.return_value = SimpleNamespace(service_pid=4321) # managed session
bot = Browser(driver, session_name="s1", store=store)
bot.quit()
assert stopped == [4321]
store.remove.assert_called_once_with("s1")
def test_quit_removes_record_even_if_driver_quit_raises(self, monkeypatch):
import sys
# see NOTE in test_quit_stops_managed_service above
browser_mod = sys.modules["wabot.browser"]
monkeypatch.setattr(browser_mod, "stop_service", lambda pid: None)
driver, store = MagicMock(), MagicMock()
driver.quit.side_effect = WebDriverException("boom")
store.get.return_value = SimpleNamespace(service_pid=None)
bot = Browser(driver, session_name="s1", store=store)
with pytest.raises(WebDriverException):
bot.quit()
store.remove.assert_called_once_with("s1") # finally block still ran