02.08 — POMBase#
Fichier source :
src/ocarina/pom/base.pyBase abstraite framework-agnostic du Page Object Model. Deux méthodes obligatoires. Aucune mention de Selenium.
Code#
class POMBase(ABC):
@abstractmethod
def verify(self, *, timeout: float | None = None) -> Self:
...
@abstractmethod
def get_current_title(self) -> str:
...Six octets de contrat (les deux signatures). C’est tout.
Pourquoi deux méthodes#
verify(timeout=None) -> Self#
But : prouver qu’on est sur la bonne page. Sinon, lève.
- L’argument
timeoutpermet d’attendre jusqu’à un certain temps que la page se charge (utile pour les SeleniumWebDriverWait). - Le default
Nonelaisse l’implémentation choisir (typiquement, lire depuisget_timeout()qui lit la CLI). - Le retour
Selfpermet le method chaining fluide (MyPage(...).open().verify().click()).
C’est l’unique méthode dont on a vraiment besoin Test-side pour garantir que « je suis bien sur la page que je veux ». Toutes les actions (click_xxx, enter_xxx) sont laissées à la subclass.
get_current_title() -> str#
But : récupérer le titre de la page courante.
Usages :
Logging : on peut écrire
logger.info(f"Current page: {page.get_current_title()}").Détection de page d’erreur dans le hook
on_failuredeact(cf.03-railway/05-create-act-hooks.md). Exempleocarina-example:def failure_hook(pom: TPOM, exc: Exception) -> Fail: title = pom.get_current_title() if title and ERROR_PAGE_REGEX.match(title.strip()): return Fail(error=HttpErrorPageReachedError(f"HTTP error page: {title}")) return Fail(error=exc)
→ Le hook ne sait pas ce que c’est qu’un WebDriver, mais il sait appeler get_current_title(). C’est le bon niveau d’abstraction.
Implémentation côté utilisateur#
@final
class Homepage(SeleniumTitleMixin, POMBase):
def __init__(self, *, driver: WebDriver, url: str = HOMEPAGE_URL) -> None:
self._driver = driver
self._URL = url
def open(self) -> Homepage:
self._driver.get(self._URL)
return self
def verify(self, *, timeout: float | None = None) -> Homepage:
try:
if timeout is None:
timeout = get_timeout()
WebDriverWait(self._driver, timeout).until(ec.title_is("Welcome to my homepage"))
WebDriverWait(self._driver, timeout).until(
ec.text_to_be_present_in_element((By.TAG_NAME, "h1"), "My homepage")
)
except TimeoutException as exc:
raise PageVerificationError from exc
return selfSeleniumTitleMixinfournit l’implémentation deget_current_titlepour ne pas dupliquer (cf.10-infra/05-selenium-adapters.md,mixins.py).verifylèvePageVerificationError, sous-classe d’Exceptionpropre au framework. Permet aux transient_errors de matcher.return selfsystématique : fluent chaining.
Pourquoi framework-agnostic#
# Selenium
class LoginPage(POMBase):
def __init__(self, driver: WebDriver):
self._driver = driver
def verify(self, timeout=None) -> Self:
WebDriverWait(self._driver, timeout or 10).until(
EC.presence_of_element_located((By.ID, "login-form"))
)
return self
# Playwright
class LoginPage(POMBase):
def __init__(self, page: Page):
self._page = page
def verify(self, timeout=None) -> Self:
self._page.wait_for_selector("#login-form", timeout=timeout)
return selfLe PlaywrightTitleMixin n’est plus hypothétique — depuis la 1.1.3 Ocarina le livre dans pom/playwright/, juste à côté de SeleniumTitleMixin. On écrit un PuppeteerTitleMixin de la même façon si besoin.
Self (PEP 673)#
@final
class CorsicamonEnterApiKeyPage(SeleniumTitleMixin, POMBase):
def enter_api_key(self) -> CorsicamonEnterApiKeyPage: # version naïve
...vs
@final
class CorsicamonEnterApiKeyPage(SeleniumTitleMixin, POMBase):
def enter_api_key(self) -> Self: # mieux
...Le Holy Book (chapitre « Premiers pas », section « Retourner self ») le note :
Chaque méthode d’action retourne
self. C’est un choix de design volontaire dans Ocarina, à respecter systématiquement, il permet le chaînage des appels et la composition fluide des scénarios.
verify n’est pas un matcher#
C’est une distinction explicite dans le Holy Book (chapitre sur la composabilité des scénarios, section « match »page_) :
Il n’est pas non plus recommandé de déguiser un
verifyen matcher : ce sont deux outils différents.
| Outil | But | Comportement sur échec |
|---|---|---|
verify | Garantir qu’on est sur la bonne page | Lève (PageVerificationError) |
Matcher (utilisé par match_page) | Choisir une branche conditionnelle | Renvoie False |
__init__#
Convention dans tout l’écosystème :
def __init__(self, *, driver: WebDriver, url: str = DEFAULT_URL) -> None:Le CLAUDE.md d’ocarina-with-ai-example formalise comme convention :
POMs take a single
url: str = <DEFAULT_URL>parameter, defaulted to the constant. Scenarios construct pages with justPage(driver=driver)and passurl=...only to override.