<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ocarina on Ocarina — précis</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/series/ocarina/</link>
    <description>Recent content in Ocarina on Ocarina — précis</description>
    <generator>Hugo</generator>
    <language>fr-FR</language>
    <lastBuildDate>Thu, 04 Jun 2026 21:25:43 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/fr/series/ocarina/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>02.01 — Identité technique d&#39;Ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/01-identity/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/01-identity/</guid>
      <description>&lt;h1 id=&#34;0201identité-technique-docarina&#34;&gt;02.01 — Identité technique d&amp;rsquo;Ocarina&lt;a class=&#34;anchor&#34; href=&#34;#0201identit%c3%a9-technique-docarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;pyprojecttoml&#34;&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttoml&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[project]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;name = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ocarina&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;version = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1.1.10&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;description = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Websites test framework for Igor&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;requires-python = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;gt;=3.14&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;authors = [{ name=&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Igor Casanova&amp;#34;&lt;/span&gt;, email=&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;[REDACTED]&amp;#34;&lt;/span&gt; }]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;license = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;MIT&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;license-files = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;LICEN[CS]E*&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;readme = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;README.md&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dependencies = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;python-docx&amp;gt;=1.2.0&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;version = &amp;quot;1.1.10&amp;quot;&lt;/code&gt;&lt;/strong&gt; : le projet est en &lt;em&gt;stable 1.x&lt;/em&gt;, pas en pré-version.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;requires-python = &amp;quot;&amp;gt;=3.14&amp;quot;&lt;/code&gt;&lt;/strong&gt; : le typage générique PEP 695 est utilisé partout.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;dependencies = [&amp;quot;python-docx&amp;gt;=1.2.0&amp;quot;]&lt;/code&gt;&lt;/strong&gt; : &lt;strong&gt;une seule&lt;/strong&gt; dépendance d&amp;rsquo;exécution. Tout le reste est dans &lt;code&gt;dev&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;license = &amp;quot;MIT&amp;quot;&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;description = &amp;quot;Websites test framework for Igor&amp;quot;&lt;/code&gt;&lt;/strong&gt; : pas « &lt;em&gt;for everyone&lt;/em&gt; », pas « &lt;em&gt;for humans&lt;/em&gt; », explicitement « &lt;em&gt;for Igor&lt;/em&gt; ». Cohérent avec &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/05-political-stance/&#34;&gt;&lt;code&gt;../01-philosophy/05-political-stance.md&lt;/code&gt;&lt;/a&gt; : « &lt;em&gt;C&amp;rsquo;est ma voiture.&lt;/em&gt; »&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;dépendances-dev&#34;&gt;Dépendances &lt;code&gt;dev&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#d%c3%a9pendances-dev&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[dependency-groups]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dev = [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ruff&amp;gt;=0.15.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;mypy&amp;gt;=1.17.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;mypy-extensions&amp;gt;=1.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;typing-extensions&amp;gt;=4.14.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pytest&amp;gt;=9.0.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pytest-cov&amp;gt;=7.0.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hypothesis&amp;gt;=6.151.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pytest-mypy-plugins&amp;gt;=3.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;allure-pytest&amp;gt;=2.15.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;allure-python-commons&amp;gt;=2.15.3&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pre-commit&amp;gt;=4.5.1&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;syrupy&amp;gt;=5.1.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;selenium&amp;gt;=4.40.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;playwright&amp;gt;=1.60.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twine&amp;gt;=6.2.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;build&amp;gt;=1.4.2&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;prysk&amp;gt;=0.20.0&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Outil&lt;/th&gt;&#xA;          &lt;th&gt;Rôle dans Ocarina&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Linter + formatter (remplace flake8 + isort + black). &lt;code&gt;select = [&amp;quot;ALL&amp;quot;]&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;mypy&lt;/code&gt; (+ extensions)&lt;/td&gt;&#xA;          &lt;td&gt;Type-checker. &lt;code&gt;strict = true&lt;/code&gt; (cf. &lt;code&gt;mypy.ini&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pytest&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Runner des tests unitaires (le framework est lui-même testé).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pytest-cov&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Couverture de tests (du framework lui-même). Configurée dans &lt;code&gt;pyproject.toml#tool.coverage&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;hypothesis&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Property-based testing (PBT)&lt;/em&gt; → &lt;code&gt;test_invariants_properties.py&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pytest-mypy-plugins&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tests &lt;strong&gt;statiques&lt;/strong&gt; de typage (vérifie les erreurs &lt;em&gt;mypy&lt;/em&gt; attendues et tests d&amp;rsquo;inférence de types avec &lt;code&gt;reveal_type&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;allure-pytest&lt;/code&gt; / &lt;code&gt;allure-python-commons&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Rapport Allure (déployé sur GH Pages).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hooks Git locaux (ruff-format).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;syrupy&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Snapshot testing&lt;/em&gt; (sortie de &lt;code&gt;pretty_print_results&lt;/code&gt;, &lt;code&gt;results_to_json&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Présent en &lt;code&gt;dev&lt;/code&gt; parce qu&amp;rsquo;Ocarina &lt;strong&gt;ne dépend pas&lt;/strong&gt; de Selenium pour fonctionner, livre juste un &lt;em&gt;adapter&lt;/em&gt; Selenium pour être immédiatement opérationnel.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;playwright&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Même logique : un second &lt;em&gt;adapter&lt;/em&gt; livré (depuis la &lt;code&gt;1.1.3&lt;/code&gt;). Ocarina pilote désormais &lt;strong&gt;Selenium et Playwright&lt;/strong&gt; out of the box, derrière les mêmes ports.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;twine&lt;/code&gt; / &lt;code&gt;build&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Publication PyPI.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;prysk&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tests CLI au format &lt;em&gt;cram&lt;/em&gt; (&lt;code&gt;.t&lt;/code&gt;). Successeur de &lt;code&gt;cram&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;mypyini&#34;&gt;&lt;code&gt;mypy.ini&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mypyini&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[mypy]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;python_version&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;3.14&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;strict&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# * ... Allow missing annotations (type inference is cool)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;disallow_incomplete_defs&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# * ... Allow missing annotations (type inference is cool)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;disallow_untyped_defs&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;strict = true&lt;/code&gt;&lt;/strong&gt; : active &lt;code&gt;--warn-redundant-casts&lt;/code&gt;, &lt;code&gt;--warn-unused-ignores&lt;/code&gt;, &lt;code&gt;--no-implicit-optional&lt;/code&gt;, &lt;code&gt;--check-untyped-defs&lt;/code&gt;, etc.&lt;/li&gt;&#xA;&lt;li&gt;Deux exceptions : &lt;code&gt;disallow_incomplete_defs&lt;/code&gt; et &lt;code&gt;disallow_untyped_defs&lt;/code&gt; désactivés, car l&amp;rsquo;auteur estime que l&amp;rsquo;&lt;strong&gt;inférence&lt;/strong&gt; de type de &lt;em&gt;mypy&lt;/em&gt; est suffisante quand on n&amp;rsquo;a pas besoin d&amp;rsquo;expliciter. Toutes les annotations explicites sont là où elles &lt;strong&gt;comptent&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;pyprojecttomltoolruff&#34;&gt;&lt;code&gt;pyproject.toml#tool.ruff&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolruff&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tool.ruff.lint]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;select = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ignore = [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ANN002&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ANN003&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ANN201&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;TRY003&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;C901&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;D203&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#6272a4&#34;&gt;# conflit avec D211&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;D213&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#6272a4&#34;&gt;# conflit avec D212&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;COM812&amp;#34;&lt;/span&gt;,  &lt;span style=&#34;color:#6272a4&#34;&gt;# conflit avec le formatter ruff (auto-fix)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[tool.ruff]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;exclude = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/.venv/**&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/bin/**&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/__init__.py&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;**/__bypass_linter__&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;select = [&amp;quot;ALL&amp;quot;]&lt;/code&gt;&lt;/strong&gt; : toutes les règles de &lt;em&gt;ruff&lt;/em&gt; sont activées (~800).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Ignore list courte&lt;/strong&gt; : six règles seulement, toutes justifiées.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;B008&lt;/code&gt; NE doit JAMAIS être ignoré&lt;/strong&gt; (« &lt;em&gt;&lt;code&gt;# &amp;quot;B008&amp;quot;,  # * ... NEVER ignore this rule without knowing very well what you are doing: https://docs.astral.sh/ruff/rules/function-call-in-default-argument/&lt;/code&gt;&lt;/em&gt; »). C&amp;rsquo;est une note pour ne pas répéter une erreur passée.&lt;/li&gt;&#xA;&lt;li&gt;**&lt;code&gt;**/**bypass_linter**&lt;/code&gt;** : convention de répertoire pour héberger du code explicitement non-linté (utile pour des modules expérimentaux internes).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;pyprojecttomltoolpytestini_options&#34;&gt;&lt;code&gt;pyproject.toml#tool.pytest.ini_options&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolpytestini_options&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;testpaths = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tests&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;python_files = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;test_*.py&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;norecursedirs = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.*&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;__pycache__&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli = &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli_level = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli_format = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;%(asctime)s [%(levelname)s] %(name)s: %(message)s&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;log_cli_date_format = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;%Y-%m-%d %H:%M:%S&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;addopts = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov=src&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-branch&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-report=term-missing&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-report=html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;--cov-report=xml:coverage.xml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Réglage&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;log_cli = true&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tous les logs des tests apparaissent en CLI (utile pour les scénarios qui passent par &lt;code&gt;ILogger&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--cov=src&lt;/code&gt; + &lt;code&gt;--cov-branch&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Couverture par branche, pas seulement par ligne.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--cov-report=html&lt;/code&gt; + &lt;code&gt;xml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Trois sorties : terminal, HTML local, XML pour ingestion CI.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;pyprojecttomltoolcoverage&#34;&gt;&lt;code&gt;pyproject.toml#tool.coverage&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolcoverage&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Voir le détail ici : &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/07-coverage-policy/&#34;&gt;&lt;code&gt;../04-internal-tests/07-coverage-policy.md&lt;/code&gt;&lt;/a&gt;. En bref : tout ce qui est &lt;em&gt;shape&lt;/em&gt; (custom_types, ports, errors), ce qui requiert un navigateur réel (adapters Selenium), et ce qui est traversé par les cram tests (cli/store, cli/builder), est explicitement omis. Pour ne pas &lt;strong&gt;fausser&lt;/strong&gt; les métriques.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.02 — Arborescence du module ocarina</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/02-module-tree/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/02-module-tree/</guid>
      <description>&lt;h1 id=&#34;0202arborescence-du-module-ocarina&#34;&gt;02.02 — Arborescence du module &lt;code&gt;ocarina&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0202arborescence-du-module-ocarina&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;Le module Python source contient &lt;strong&gt;134 fichiers &lt;code&gt;.py&lt;/code&gt;&lt;/strong&gt; répartis en quatre couches conceptuelles.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;src/ocarina/&#xA;├── __init__.py                              # vide — pas de &amp;#34;facade&amp;#34; globale, on importe précisément&#xA;├── py.typed                                 # PEP 561 : déclare le package comme &amp;#34;typed&amp;#34;&#xA;│&#xA;├── railway/                                 # Couche 1 — primitives FP&#xA;│   ├── __init__.py&#xA;│   └── result.py                            # Ok[T] / Fail / Result[T] / is_ok / is_fail&#xA;│&#xA;├── custom_types/                            # Couche 2 — types et alias&#xA;│   ├── __init__.py&#xA;│   ├── effect.py                            # type Effect = Callable[[], None]&#xA;│   ├── thunk.py                             # type Thunk[T] = Callable[[], T]&#xA;│   ├── tpom.py                              # TypeVar TPOM bound POMBase&#xA;│   ├── supports_write.py                    # Protocol SupportsWrite[T], reflète type non exposé par lib standard Python&#xA;│   ├── built_web_driver.py                  # type BuiltWebDriver[Driver] = tuple[Driver, Effect]&#xA;│   ├── scenario.py                          # Scenario[Driver] (frozen dataclass)&#xA;│   ├── test_components.py                   # TestChain / TestSetup / TestTeardown / TestWatchers&#xA;│   ├── test_runner.py                       # TestRunner[Driver]&#xA;│   ├── oc_test.py                           # TestName / TestScenario / TestScenarioFragment&#xA;│   ├── oc_test_layers.py                    # TestResult / TestSuiteResult / TestCampaignResults / TestCycleResults&#xA;│   ├── selenium/&#xA;│   │   ├── built_web_driver.py              # BuiltSeleniumWebDriver&#xA;│   │   ├── oc_test_scenario.py&#xA;│   │   ├── supported_browsers.py            # type SupportedSeleniumBrowser = Literal[&amp;#34;chrome&amp;#34;, &amp;#34;firefox&amp;#34;, &amp;#34;edge&amp;#34;, &amp;#34;safari&amp;#34;]&#xA;│   │   └── web_drivers_pool.py              # type SeleniumWebDriversPool = WebDriversPool[WebDriver]&#xA;│   └── playwright/&#xA;│       ├── built_web_driver.py              # type BuiltPlaywrightDriver = BuiltWebDriver[PlaywrightDriver]&#xA;│       ├── oc_test_scenario.py              # PlaywrightTestScenario / PlaywrightTestScenarioFragment&#xA;│       ├── supported_browsers.py            # type SupportedPlaywrightBrowser = Literal[&amp;#34;chromium&amp;#34;, &amp;#34;firefox&amp;#34;, &amp;#34;webkit&amp;#34;]&#xA;│       └── web_drivers_pool.py              # type PlaywrightDriversPool = WebDriversPool[PlaywrightDriver]&#xA;│&#xA;├── custom_errors/                           # Couche 2 : erreurs&#xA;│   ├── __init__.py&#xA;│   └── test_framework/&#xA;│       ├── __init__.py&#xA;│       ├── no_matching_branch.py            # NoMatchingBranchError&#xA;│       ├── pages.py                         # PageVerificationError&#xA;│       ├── driver_died.py                   # DriverDiedError&#xA;│       └── campaigns.py&#xA;│&#xA;├── custom_invariants/                       # Couche 2 : invariants prêts-à-l&amp;#39;emploi&#xA;│   ├── __init__.py&#xA;│   └── testing/&#xA;│       ├── __init__.py&#xA;│       ├── workers.py                       # validate_workers_amount&#xA;│       ├── oc_test_runners_ids.py           # validate_test_runners_ids&#xA;│       ├── oc_test_runners_names.py         # validate_test_runners_names (unique + valid filename)&#xA;│       ├── oc_test_suites_names.py&#xA;│       ├── oc_test_campaigns_names.py&#xA;│       └── oc_test_cycles_names.py&#xA;│&#xA;├── ports/                                   # Couche 3 : ports (interfaces)&#xA;│   ├── __init__.py&#xA;│   ├── ilogger.py                           # ILogger (ABC)&#xA;│   └── itake_screenshot.py                  # ITakeScreenshot[Driver] (Protocol)&#xA;│&#xA;├── pom/                                     # Couche 3 : Page Object Model&#xA;│   ├── __init__.py&#xA;│   ├── base.py                              # POMBase (ABC : verify + get_current_title)&#xA;│   ├── selenium/&#xA;│   │   ├── __init__.py&#xA;│   │   └── muted.py                         # MutedPOM utilitaire&#xA;│   └── playwright/&#xA;│       ├── __init__.py&#xA;│       └── muted.py                         # MutedPlaywrightPOM utilitaire&#xA;│&#xA;├── aggregates/                              # Couche 3 : agrégats de résultats&#xA;│   ├── __init__.py&#xA;│   └── tests_layers.py                      # is_test_result_ok / _fail / _skipped (TypeGuard)&#xA;│&#xA;├── dsl/                                     # Couche 4 : DSL Ocarina&#xA;│   ├── __init__.py&#xA;│   ├── invariants/&#xA;│   │   ├── __init__.py&#xA;│   │   ├── validate.py                      # entry-point validate(), BusinessInvariantValidator, FrameworkInvariantValidator&#xA;│   │   ├── assertions.py                    # is_str / is_email / is_positive / is_in / has_unique_elements / each / ...&#xA;│   │   ├── errors.py                        # InvariantViolationError / DuplicatesError / AggregateInvariantViolationError&#xA;│   │   └── internals/&#xA;│   │       ├── __init__.py&#xA;│   │       └── validation_chain.py          # ValidationStartBlock / ValidationAssertBlock / _ValidationChain / _any_of / chain_validations&#xA;│   ├── testing/&#xA;│   │   ├── __init__.py&#xA;│   │   ├── oc_test.py                       # class Test[Driver]&#xA;│   │   ├── oc_test_suite.py                 # class TestSuite[Driver]&#xA;│   │   ├── oc_test_campaign.py              # class TestCampaign[Driver] + campaign_has_failed&#xA;│   │   ├── oc_test_cycle.py                 # class TestCycle[Driver] + type Mode + has_test_cycle_failed&#xA;│   │   ├── filter_tests_by_ids.py&#xA;│   │   ├── watcher.py                       # class Watcher[Driver]&#xA;│   │   ├── internals/&#xA;│   │   │   ├── __init__.py&#xA;│   │   │   ├── test_executor.py             # class TestExecutor[Driver] + ExecutionOutcome (frozen, slots)&#xA;│   │   │   └── test_flow.py                 # class TestFlow[Driver]&#xA;│   │   ├── selenium/&#xA;│   │   │   ├── __init__.py&#xA;│   │   │   ├── create_test.py               # create_selenium_test(...)&#xA;│   │   │   └── create_watcher.py            # create_selenium_watcher(...) + type SeleniumWatcher&#xA;│   │   └── playwright/&#xA;│   │       ├── __init__.py&#xA;│   │       ├── create_test.py               # create_playwright_test(...)&#xA;│   │       └── create_watcher.py            # create_playwright_watcher(...) + type PlaywrightWatcher&#xA;│   └── testing_with_railway/&#xA;│       ├── __init__.py&#xA;│       ├── chain_actions.py                 # class ChainRunner[T] + chain_actions&#xA;│       ├── match_page.py                    # When + _match_page_builder + create_match_page&#xA;│       ├── constructors/&#xA;│       │   ├── __init__.py&#xA;│       │   └── create_act.py                # primitive de bas niveau (à wrapper côté projet)&#xA;│       └── internals/&#xA;│           ├── __init__.py&#xA;│           └── action_chain.py              # ActionStart → ActionFailure → ActionSuccess → ActionChain&#xA;│                                            # + Neutral{Start,Failure,Success}  (rail d&amp;#39;échec)&#xA;│&#xA;├── infra/                                   # Couche 5 : infrastructure&#xA;│   ├── __init__.py&#xA;│   ├── drivers_pool.py                      # class WebDriversPool[Driver] + WarmupTimeoutError&#xA;│   ├── driver_builder.py                    # class DriverBuilder[Driver]&#xA;│   ├── screenshotter.py                     # class Screenshotter[TDriver] + ScreenshotterConfig&#xA;│   ├── act_counter.py                       # class ActCounter (interface)&#xA;│   ├── selenium/&#xA;│   │   ├── __init__.py&#xA;│   │   ├── create_driver.py                 # _build_firefox / _build_chrome / _build_edge / _build_safari&#xA;│   │   ├── create_drivers_pool.py           # create_selenium_drivers_pool&#xA;│   │   ├── create_screenshotter.py          # create_selenium_screenshotter&#xA;│   │   ├── driver_healthcheck.py            # driver_healthcheck (ping driver.title)&#xA;│   │   └── mixins.py                        # SeleniumTitleMixin (détrompeur de typage)&#xA;│   └── playwright/&#xA;│       ├── __init__.py&#xA;│       ├── create_driver.py                 # create_playwright_driver (chromium / firefox / webkit)&#xA;│       ├── create_drivers_pool.py           # create_playwright_drivers_pool&#xA;│       ├── create_screenshotter.py          # create_playwright_screenshotter + _playwright_save_full_page&#xA;│       ├── driver.py                        # class PlaywrightDriver (wrapper sync)&#xA;│       ├── driver_healthcheck.py            # playwright_driver_healthcheck (ping driver.title)&#xA;│       └── mixins.py                        # PlaywrightTitleMixin (détrompeur de typage)&#xA;│&#xA;└── opinionated/                             # Couche 6 : opt-in, tout ce qui est &amp;#34;joli mais remplaçable&amp;#34;&#xA;    ├── __init__.py&#xA;    ├── consts/loggers_choices.py            # LOGGERS_CHOICES = (&amp;#34;terminal&amp;#34;, &amp;#34;file&amp;#34;, &amp;#34;terminal+file&amp;#34;, &amp;#34;muted&amp;#34;)&#xA;    ├── infra/&#xA;    │   ├── __init__.py&#xA;    │   └── act_counter.py                   # ThreadsBasedActCounter (compteur thread-local)&#xA;    ├── cli/&#xA;    │   ├── __init__.py&#xA;    │   ├── builder.py                       # CliBuilder + CliArg + _SilentArgumentParser&#xA;    │   ├── store.py                         # CliStore[TKeys] + _CliField[T] + field(...)&#xA;    │   ├── phantoms.py                      # phantom_validate (no-op predicate)&#xA;    │   ├── selenium/&#xA;    │   │   ├── __init__.py&#xA;    │   │   ├── cli_store_singleton.py       # SeleniumCliStoreSingleton (push / get)&#xA;    │   │   └── create_cli_store.py          # create_selenium_{auto,win,macos,linux}_cli_store&#xA;    │   └── playwright/&#xA;    │       ├── __init__.py&#xA;    │       ├── cli_store_singleton.py       # PlaywrightCliStoreSingleton (push / get)&#xA;    │       └── create_cli_store.py          # create_playwright_{,auto_}cli_store&#xA;    ├── dsl/&#xA;    │   ├── __init__.py&#xA;    │   └── drive_page.py                    # drive_page = chain_actions, alias sémantique&#xA;    ├── launcher/&#xA;    │   ├── __init__.py&#xA;    │   └── bootstrap.py                     # bootstrap(...) + run_plugins(*plugins, exceptions_logger)&#xA;    ├── loggers/&#xA;    │   ├── __init__.py&#xA;    │   ├── create_matching_logger.py        # create_matching_logger(&amp;#34;terminal&amp;#34;|&amp;#34;file&amp;#34;|...) + get_default_log_dir&#xA;    │   ├── print_logger.py                  # PrintLogger (ANSI)&#xA;    │   ├── file_logger.py                   # FileLogger (taxonomie -&amp;gt; arbre de fichiers)&#xA;    │   ├── print_and_file_logger.py&#xA;    │   ├── muted_logger.py&#xA;    │   ├── custom_types/supported_loggers.py&#xA;    │   └── utils/&#xA;    │       ├── __init__.py&#xA;    │       └── format_metadata_str.py       # format_utc_date_metadata_str / format_current_thread_metadata_str / concat_metadata&#xA;    └── plugins/&#xA;        ├── __init__.py&#xA;        └── reports/&#xA;            ├── __init__.py&#xA;            ├── pretty_print_results.py      # rapport ANSI hiérarchique&#xA;            ├── results_to_json.py           # générateur JSON&#xA;            ├── docx_tests_proofs.py         # générateur DOCX (consomme l&amp;#39;arbre de logs)&#xA;            └── timing.py                    # context manager `with timing(prefix=&amp;#34;Duration:&amp;#34;)`&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;schéma-en-couches&#34;&gt;Schéma en couches&lt;a class=&#34;anchor&#34; href=&#34;#sch%c3%a9ma-en-couches&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────────┐&#xA;│ USER PROJECT (e.g. ocarina-example, ocarina-with-ai-example)             │&#xA;│  ┌────────────────────┐  ┌────────────────────┐  ┌─────────────────────┐ │&#xA;│  │ pages/ (POMBase)   │  │ scenarios/ (Test)  │  │ adapters/ (act, ...)│ │&#xA;│  └────────────────────┘  └────────────────────┘  └─────────────────────┘ │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │ imports&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 6 — opinionated/  (CLI, loggers, plugins, bootstrap)              │&#xA;│   opt-in. Peut être remplacée intégralement.                             │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 5 — infra/  (pool, builder, screenshotter, adapters Selenium)     │&#xA;│   I/O. Pas de logique DSL.                                               │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 4 — dsl/  (testing, invariants, testing_with_railway)             │&#xA;│   DSL pur, sans I/O. Toute la grammaire.                                 │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 3 — ports/  +  pom/  +  aggregates/                               │&#xA;│   Interfaces et abstractions.                                            │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 2 — custom_types/  +  custom_errors/  +  custom_invariants/       │&#xA;│   Shapes, alias, types, exceptions, invariants prêts-à-l&amp;#39;emploi.       │&#xA;└─────────────────────────────────┬────────────────────────────────────────┘&#xA;                                  │&#xA;┌─────────────────────────────────▼────────────────────────────────────────┐&#xA;│ COUCHE 1 — railway/  (Result, Ok, Fail, is_ok, is_fail)                  │&#xA;│   La primitive fonctionnelle. Aucune dépendance.                         │&#xA;└──────────────────────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cette &lt;strong&gt;stratification stricte&lt;/strong&gt; garantit l&amp;rsquo;absence de &lt;em&gt;cycles d&amp;rsquo;import&lt;/em&gt; et permet de remplacer chaque couche supérieure sans toucher aux couches inférieures :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.06 — Scenario[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/06-scenario/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/06-scenario/</guid>
      <description>&lt;h1 id=&#34;0206scenariodriver&#34;&gt;02.06 — &lt;code&gt;Scenario[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0206scenariodriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/scenario.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/custom_types/scenario.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;dataclass&#34;&gt;Dataclass&lt;a class=&#34;anchor&#34; href=&#34;#dataclass&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Scenario&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_chain: TestChain&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setup: TestSetup &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    teardown: TestTeardown &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    watchers: TestWatchers[Driver] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/custom_types/test_components.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestChain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Sequence[ChainRunner[Any]]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestSetup &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestTeardown &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestWatchers[Driver] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Sequence[Watcher[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;cycle-de-vie-per-attempt&#34;&gt;Cycle de vie (per attempt)&lt;a class=&#34;anchor&#34; href=&#34;#cycle-de-vie-per-attempt&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. setup()           — optionnel, Effect libre (DB, API, …)&#xA;       → lève     :  skip de test_chain, jump à teardown,&#xA;                     return Outcome(setup_failed=True, should_retry=True)&#xA;       → ok       :  continue à test_chain&#xA;&#xA;2. test_chain        — la chaîne réelle (Sequence[ChainRunner])&#xA;       (les watchers tournent pendant ce temps)&#xA;&#xA;3. teardown()        — optionnel, toujours exécuté&#xA;       → lève     :  log warning + ignore (n&amp;#39;affecte pas le verdict)&#xA;&#xA;Si TOUTES les tentatives lèvent au setup :&#xA;    → test marqué SKIPPED (pas FAILED)&#xA;    → log warning « setup keeps failing »&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;setup-et-teardown&#34;&gt;&lt;code&gt;setup&lt;/code&gt; et &lt;code&gt;teardown&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#setup-et-teardown&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;setup&lt;/code&gt; et &lt;code&gt;teardown&lt;/code&gt; sont &lt;strong&gt;driver-free et injectionless by design.&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.07 — Watcher[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/07-watcher/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/07-watcher/</guid>
      <description>&lt;h1 id=&#34;0207watcherdriver&#34;&gt;02.07 — &lt;code&gt;Watcher[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0207watcherdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/watcher.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/watcher.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Observateur parallélisé qui tourne en &lt;em&gt;daemon thread&lt;/em&gt; aux côtés de la &lt;code&gt;test_chain&lt;/code&gt;. Conçu pour détecter les &lt;strong&gt;frictions imprévisibles et sans impact direct sur le scénario&lt;/strong&gt; qui se produisent &lt;em&gt;pendant&lt;/em&gt; que le scénario tourne : toasts d&amp;rsquo;erreur aléatoires, validations parasites, popups inattendus.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-problème&#34;&gt;Le problème&lt;a class=&#34;anchor&#34; href=&#34;#le-probl%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Citation du Holy Book (&lt;em&gt;handling-flakiness&lt;/em&gt;) :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Plus surprenant encore : des applications affichant des toasts d&amp;rsquo;erreur sans raison apparente, ou des formulaires signalant des erreurs de validation sur des saisies pourtant correctes, sans pour autant bloquer le parcours.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.08 — POMBase</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/08-pom-base/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/08-pom-base/</guid>
      <description>&lt;h1 id=&#34;0208pombase&#34;&gt;02.08 — &lt;code&gt;POMBase&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0208pombase&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/pom/base.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/pom/base.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Base abstraite &lt;strong&gt;framework-agnostic&lt;/strong&gt; du Page Object Model. Deux méthodes obligatoires. Aucune mention de Selenium.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;POMBase&lt;/span&gt;(ABC):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;verify&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, timeout: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Self:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get_current_title&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Six octets de contrat (les deux signatures). C&amp;rsquo;est tout.&lt;/p&gt;&#xA;&lt;h2 id=&#34;pourquoi-deux-méthodes&#34;&gt;Pourquoi deux méthodes&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-deux-m%c3%a9thodes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;verifytimeoutnone---self&#34;&gt;&lt;code&gt;verify(timeout=None) -&amp;gt; Self&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#verifytimeoutnone---self&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;But : &lt;strong&gt;prouver qu&amp;rsquo;on est sur la bonne page&lt;/strong&gt;. Sinon, lève.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;L&amp;rsquo;argument &lt;code&gt;timeout&lt;/code&gt; permet d&amp;rsquo;attendre &lt;em&gt;jusqu&amp;rsquo;à un certain temps&lt;/em&gt; que la page se charge (utile pour les Selenium &lt;code&gt;WebDriverWait&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Le default &lt;code&gt;None&lt;/code&gt; laisse l&amp;rsquo;implémentation choisir (typiquement, lire depuis &lt;code&gt;get_timeout()&lt;/code&gt; qui lit la CLI).&lt;/li&gt;&#xA;&lt;li&gt;Le retour &lt;code&gt;Self&lt;/code&gt; permet le &lt;em&gt;method chaining&lt;/em&gt; fluide (&lt;code&gt;MyPage(...).open().verify().click()&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;C&amp;rsquo;est l&amp;rsquo;unique méthode dont on a vraiment besoin Test-side pour garantir que « &lt;em&gt;je suis bien sur la page que je veux&lt;/em&gt; ». Toutes les actions (&lt;code&gt;click_xxx&lt;/code&gt;, &lt;code&gt;enter_xxx&lt;/code&gt;) sont laissées à la subclass.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.09 — Ports : ILogger, ITakeScreenshot</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/09-ports/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/09-ports/</guid>
      <description>&lt;h1 id=&#34;0209ports-ilogger-itakescreenshot&#34;&gt;02.09 — Ports : &lt;code&gt;ILogger&lt;/code&gt;, &lt;code&gt;ITakeScreenshot&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0209ports-ilogger-itakescreenshot&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Dossier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/ports&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/ports/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Deux ports seulement. C&amp;rsquo;est tout. Le reste (&lt;code&gt;WebDriversPool&lt;/code&gt;, &lt;code&gt;Screenshotter&lt;/code&gt;, etc.) vit dans &lt;code&gt;infra/&lt;/code&gt;, pas dans &lt;code&gt;ports/&lt;/code&gt;. La distinction est claire : un &lt;em&gt;port&lt;/em&gt; est une abstraction au-dessus de laquelle vit le DSL ; une &lt;em&gt;infra&lt;/em&gt; est l&amp;rsquo;implémentation des &lt;em&gt;adapters&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;ilogger&#34;&gt;&lt;code&gt;ILogger&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ilogger&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ILogger&lt;/span&gt;(ABC):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;set_prefix&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, prefix_thunk: Thunk[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Self: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;set_domain_taxonomy&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, taxonomy: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;tuple&lt;/span&gt;[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Self: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;raw&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt;, stream: SupportsWrite[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;critical&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;warning&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;info&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;debug&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_name&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;success&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;exception&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;args, exc: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;kwargs) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @abstractmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;cleanup&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Méthode&lt;/th&gt;&#xA;          &lt;th&gt;Sémantique&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;set_prefix(thunk)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Préfixe &lt;strong&gt;paresseux&lt;/strong&gt; appliqué à chaque log (pour timestamps, threads…). Retourne &lt;code&gt;Self&lt;/code&gt; pour chaining.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;set_domain_taxonomy(taxonomy)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Définit la hiérarchie de domaine (&lt;code&gt;(cycle, campaign, suite, test)&lt;/code&gt;). Crucial pour le &lt;code&gt;FileLogger&lt;/code&gt; qui crée une arborescence de fichiers.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;raw(*args, stream=None)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Écriture brute, sans format (utilisé par les plugins de rapport).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;critical / error / warning / info / debug&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveaux standards. Chaque méthode accepte &lt;code&gt;exc=&lt;/code&gt; pour passer une exception et la formatter.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_name(msg)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveau custom : annonce le test en cours (&lt;code&gt;logger.test_name(test.name)&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;success(msg)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Niveau custom : assertion réussie (utilisé par les &lt;code&gt;.success(...)&lt;/code&gt; handlers).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;exception(msg, exc=)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Logue une exception avec traceback complet.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;cleanup()&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fin de vie : flush + close (pour le &lt;code&gt;FileLogger&lt;/code&gt;). Recycle le fichier de log entre les retries.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;trois-choses-qui-ne-sont-pas-dans-ilogger&#34;&gt;Trois choses qui ne sont &lt;em&gt;pas&lt;/em&gt; dans &lt;code&gt;ILogger&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#trois-choses-qui-ne-sont-pas-dans-ilogger&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de &lt;code&gt;set_level()&lt;/code&gt;&lt;/strong&gt; : le niveau est géré par l&amp;rsquo;implémentation (ex. &lt;code&gt;MutedLogger&lt;/code&gt; filtre tout).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de &lt;code&gt;add_handler()&lt;/code&gt;&lt;/strong&gt; : pas d&amp;rsquo;API à la &lt;code&gt;logging.Logger&lt;/code&gt; du stdlib. La composition se fait par instanciation de loggers différents (&lt;code&gt;PrintAndFileLogger&lt;/code&gt; wrappe les deux).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de &lt;code&gt;child(name)&lt;/code&gt;&lt;/strong&gt; : la taxonomy est passée &lt;em&gt;en bloc&lt;/em&gt; avec &lt;code&gt;set_domain_taxonomy&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;set_prefixthunk&#34;&gt;&lt;code&gt;set_prefix(thunk)&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#set_prefixthunk&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;set_prefix(&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;[&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;datetime&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;now()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;isoformat()&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;]&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si on passait une &lt;code&gt;str&lt;/code&gt;, ce serait calculé &lt;em&gt;au moment du &lt;code&gt;set_prefix&lt;/code&gt;&lt;/em&gt;, donc figé. Avec un &lt;code&gt;Thunk&lt;/code&gt;, le préfixe est calculé &lt;em&gt;à chaque appel de log&lt;/em&gt;. Idéal pour les timestamps.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.12 — Custom types &amp; custom errors</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/12-custom-types-errors/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/12-custom-types-errors/</guid>
      <description>&lt;h1 id=&#34;0212custom-types--custom-errors&#34;&gt;02.12 — Custom types &amp;amp; custom errors&lt;a class=&#34;anchor&#34; href=&#34;#0212custom-types--custom-errors&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Couche &lt;em&gt;shape&lt;/em&gt; d&amp;rsquo;Ocarina. Aucune logique : juste des types, des alias, des exceptions, des protocols. C&amp;rsquo;est ce qui rend le DSL typé sans logique runtime additionnelle.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;custom_types&#34;&gt;&lt;code&gt;custom_types/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#custom_types&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Fichier&lt;/th&gt;&#xA;          &lt;th&gt;Contenu&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;effect.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type Effect = Callable[[], None]&lt;/code&gt; + &lt;code&gt;type Effects = tuple[Effect, ...]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;thunk.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type Thunk[T] = Callable[[], T]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tpom.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TypeVar TPOM bound POMBase&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;supports_write.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Protocol SupportsWrite[T]&lt;/code&gt; (&lt;code&gt;write(s: T) -&amp;gt; Any&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;built_web_driver.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type BuiltWebDriver[Driver] = tuple[Driver, Effect]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;scenario.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Scenario[Driver]&lt;/code&gt; (frozen dataclass)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_components.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestChain&lt;/code&gt;, &lt;code&gt;TestSetup&lt;/code&gt;, &lt;code&gt;TestTeardown&lt;/code&gt;, &lt;code&gt;TestWatchers[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_runner.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestRunner[Driver]&lt;/code&gt; (frozen dataclass)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;oc_test.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestName&lt;/code&gt;, &lt;code&gt;TestScenario[Driver]&lt;/code&gt;, &lt;code&gt;TestScenarioFragment[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;oc_test_layers.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestId&lt;/code&gt;, &lt;code&gt;TestResult&lt;/code&gt;, &lt;code&gt;TestSuiteResult&lt;/code&gt;, &lt;code&gt;TestSuiteResults&lt;/code&gt;, &lt;code&gt;TestCampaignResults&lt;/code&gt;, &lt;code&gt;TestCycleResults&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/built_web_driver.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;BuiltSeleniumWebDriver = BuiltWebDriver[WebDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/oc_test_scenario.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;SeleniumTestScenario = TestScenario[WebDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/supported_browsers.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type SupportedSeleniumBrowser = Literal[&amp;quot;chrome&amp;quot;, &amp;quot;firefox&amp;quot;, &amp;quot;edge&amp;quot;, &amp;quot;safari&amp;quot;]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;selenium/web_drivers_pool.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;type SeleniumWebDriversPool = WebDriversPool[WebDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Tous ces fichiers sont &lt;em&gt;excluded de la couverture&lt;/em&gt; (&lt;code&gt;pyproject.toml#tool.coverage&lt;/code&gt;) :&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
