<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tests-Internes on Ocarina — précis</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/series/tests-internes/</link>
    <description>Recent content in Tests-Internes 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/tests-internes/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>04.01 — Stratégie « dehors comme un utilisateur »</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/01-strategy/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/01-strategy/</guid>
      <description>&lt;h1 id=&#34;0401stratégie-dehors-comme-un-utilisateur&#34;&gt;04.01 — Stratégie « &lt;em&gt;dehors comme un utilisateur&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#0401strat%c3%a9gie-dehors-comme-un-utilisateur&#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;C&amp;rsquo;est la posture documentée dès le &lt;code&gt;conftest.py&lt;/code&gt; des tests scénarios. Aucun test ne triche en regardant les internes.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;testsscenariosconftestpy&#34;&gt;&lt;code&gt;tests/scenarios/conftest.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#testsscenariosconftestpy&#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;Composant&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&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;FakeDriver&lt;/code&gt; (dataclass)&lt;/td&gt;&#xA;          &lt;td&gt;Driver minimaliste : &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;disposed&lt;/code&gt;. Aucune méthode Selenium.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_built_driver()&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Factory &lt;code&gt;(FakeDriver, dispose)&lt;/code&gt;, soit la signature attendue par &lt;code&gt;WebDriversPool&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_pool(max_size=2)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;WebDriversPool[FakeDriver]&lt;/code&gt; prêt à l&amp;rsquo;emploi.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;RecordingPOM(POMBase)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;POM qui consigne ses appels et peut être configuré pour lever.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;acting(pom, step)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ActionSuccess[RecordingPOM]&lt;/code&gt; complet (failure+success en no-op).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;scenario_of(&amp;quot;ok&amp;quot;, &amp;quot;ok2&amp;quot;)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestScenario[FakeDriver]&lt;/code&gt; à N steps.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;failing_scenario(step=&amp;quot;boom&amp;quot;, exc=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Scénario à 1 step qui lève.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_test(name, scenario=..., test_id=..., skipped=False)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Test[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_suite(name, tests, pool=..., transient_errors=..., max_retries_per_test=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestSuite[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_campaign(name, suites, max_workers=1)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestCampaign[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;make_cycle(name=&amp;quot;cycle&amp;quot;, campaigns=..., smoke=..., mode=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestCycle[FakeDriver]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;run_chain(runner)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Helper qui exécute un &lt;code&gt;ChainRunner&lt;/code&gt; et retourne le &lt;code&gt;ActionChain&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;fakedriver&#34;&gt;&lt;code&gt;FakeDriver&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#fakedriver&#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;@dataclass&#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;FakeDriver&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    title: &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:#f1fa8c&#34;&gt;&amp;#34;fake&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    disposed: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est &lt;strong&gt;tout&lt;/strong&gt;.&#xA;&lt;strong&gt;Le cœur d&amp;rsquo;Ocarina n&amp;rsquo;appelle aucune API Selenium&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.02 — Cram tests (prysk)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/02-cram-prysk/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/02-cram-prysk/</guid>
      <description>&lt;h1 id=&#34;0402cram-tests-prysk&#34;&gt;04.02 — Cram tests (&lt;code&gt;prysk&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0402cram-tests-prysk&#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;Tests CLI au format &lt;em&gt;cram&lt;/em&gt; : un fichier &lt;code&gt;.t&lt;/code&gt; contient des commandes shell et leur sortie attendue. Outil utilisé : &lt;code&gt;prysk&lt;/code&gt; (réécriture moderne de &lt;code&gt;cram&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;fichier-t&#34;&gt;Fichier &lt;code&gt;.t&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#fichier-t&#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;Valid args produce a deterministic parsed config&#xA;&#xA;  $ touch driver&#xA;  $ &amp;#34;$PYTHON&amp;#34; &amp;#34;$TESTDIR/_demo_cli.py&amp;#34; --browser firefox --driver-path driver --workers 3 --wait-timeout 20 --logger terminal&#xA;  browser=firefox&#xA;  driver_path=driver&#xA;  headless=True&#xA;  workers=3&#xA;  wait_timeout=20&#xA;  logger=terminal&#xA;  only=()&#xA;  exclude=()&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&#xA;&lt;li&gt;Première ligne : titre (commentaire pour l&amp;rsquo;humain).&lt;/li&gt;&#xA;&lt;li&gt;Ligne vide.&lt;/li&gt;&#xA;&lt;li&gt;Lignes commençant par &lt;code&gt; $&lt;/code&gt; : commande shell exécutée.&lt;/li&gt;&#xA;&lt;li&gt;Lignes suivantes (indent 2) : sortie attendue.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Si la sortie réelle diffère : le test fail.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.03 — Tests scénarios (pytest &#43; allure)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/03-pytest-scenarios/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/03-pytest-scenarios/</guid>
      <description>&lt;h1 id=&#34;0403tests-scénarios-pytest--allure&#34;&gt;04.03 — Tests scénarios (pytest + allure)&lt;a class=&#34;anchor&#34; href=&#34;#0403tests-sc%c3%a9narios-pytest--allure&#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;15 fichiers &lt;code&gt;test_*.py&lt;/code&gt; du dossier &lt;code&gt;tests/scenarios/&lt;/code&gt; couvrent le DSL, l&amp;rsquo;orchestration et l&amp;rsquo;acteur Playwright. Tests dynamiques.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#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;tests/scenarios/&#xA;├── conftest.py                              # FakeDriver, RecordingPOM, make_*&#xA;├── test_railway_and_action_chain.py         # create_act, drive_page, Result, ChainRunner&#xA;├── test_match_page.py                       # match_page / when (premières / dernières branches, exceptions)&#xA;├── test_validate.py                         # validate, assert_that, otherwise, then, chain_validations&#xA;├── test_invariants_properties.py            # hypothesis (cf. 06-hypothesis-properties)&#xA;├── test_drivers_pool.py                     # WebDriversPool (acquire, warmup, shutdown, semaphore leaks)&#xA;├── test_screenshotter.py                    # Screenshotter (single shot, burst, healthcheck, threadsafety)&#xA;├── test_driver_builder.py                   # DriverBuilder (profile tmp dir, dispose)&#xA;├── test_watcher.py                          # Watcher (start, stop, callback errors swallowed, dedup cache)&#xA;├── test_playwright_driver_actor.py          # acteur PlaywrightDriver (sync_playwright mocké, sans navigateur)&#xA;├── test_playwright_adapter.py               # smoke navigateur réel (skip si pas de Chromium installé)&#xA;├── test_test_suite.py                       # TestSuite (parallel, saturation, only/exclude, transient_errors)&#xA;├── test_test_cycle_and_bootstrap.py         # TestCycle, mode fail-fast vs wait-for-all, bootstrap&#xA;├── test_loggers_and_reports.py              # PrintLogger, FileLogger, pretty_print, JSON&#xA;├── test_docx_tests_proofs.py                # generate_docx_proof (parse logs, insert images)&#xA;└── test_env.py                              # smoke test : versions, Python 3.14+&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;exemple-de-test-interne&#34;&gt;Exemple de test interne&lt;a class=&#34;anchor&#34; href=&#34;#exemple-de-test-interne&#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;@allure.epic(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Railway / action chain&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.feature(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Action chain&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.tag(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;act&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;handlers&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;happy-path&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.severity(allure&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;severity_level&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;CRITICAL)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.label(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;layer&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;unit&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@allure.title(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;A successful act reports success; the success handler fires once&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:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_success_act_fires_success_handler&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;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pom &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; RecordingPOM()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    calls &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;0&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;    chain &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;        create_act(pom, &lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; p: p&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;step(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;click&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:#ff79c6&#34;&gt;.&lt;/span&gt;failure(&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; _: calls&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__setitem__&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;, calls[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&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;success(&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: calls&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__setitem__&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;, calls[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;] &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&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;execute()&#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;    &lt;span style=&#34;color:#ff79c6&#34;&gt;assert&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;is_ok()&#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;assert&lt;/span&gt; pom&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;calls &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;click&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:#ff79c6&#34;&gt;assert&lt;/span&gt; calls &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; {&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;success&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;failure&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;0&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;Assertion&lt;/th&gt;&#xA;          &lt;th&gt;Cible&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;chain.is_ok()&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;API publique&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pom.calls == [&amp;quot;click&amp;quot;]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;comportement observable du POM (recording)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;calls == {&amp;quot;success&amp;quot;: 1, &amp;quot;failure&amp;quot;: 0}&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;bon handler appelé&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;annotations-allure&#34;&gt;Annotations Allure&lt;a class=&#34;anchor&#34; href=&#34;#annotations-allure&#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;Catégorie&lt;/th&gt;&#xA;          &lt;th&gt;Valeurs typiques&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;epic&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;Railway / action chain&amp;rdquo;, &amp;ldquo;Invariants&amp;rdquo;, &amp;ldquo;Orchestration&amp;rdquo;, &amp;ldquo;Watcher&amp;rdquo;, &amp;ldquo;Drivers pool&amp;rdquo;, &amp;ldquo;Reports&amp;rdquo;, &amp;ldquo;Match page&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;Action chain&amp;rdquo;, &amp;ldquo;Validate&amp;rdquo;, &amp;ldquo;TestSuite&amp;rdquo;, &amp;ldquo;TestCycle&amp;rdquo;, &amp;ldquo;Bootstrap&amp;rdquo;, &amp;ldquo;Pretty print&amp;rdquo;, &amp;ldquo;JSON&amp;rdquo;, &amp;ldquo;DOCX proofs&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tag&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;act&amp;rdquo;, &amp;ldquo;handlers&amp;rdquo;, &amp;ldquo;happy-path&amp;rdquo;, &amp;ldquo;error-handling&amp;rdquo;, &amp;ldquo;retry&amp;rdquo;, &amp;ldquo;transient&amp;rdquo;, &amp;ldquo;smoke&amp;rdquo;, &amp;ldquo;skip&amp;rdquo;, &amp;ldquo;saturation&amp;rdquo;, &amp;ldquo;parallel&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;severity&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;CRITICAL &amp;gt; NORMAL &amp;gt; MINOR &amp;gt; TRIVIAL&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;label(&amp;quot;layer&amp;quot;, ...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;unit&amp;rdquo;, &amp;ldquo;integration&amp;rdquo;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;test_test_suitepy&#34;&gt;&lt;code&gt;test_test_suite.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#test_test_suitepy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Tests passent en mode séquentiel (&lt;code&gt;max_workers=1&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Tests passent en mode parallèle (&lt;code&gt;max_workers=N&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Filtrage &lt;code&gt;--only&lt;/code&gt; / &lt;code&gt;--exclude&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Mutex &lt;code&gt;--only&lt;/code&gt; + &lt;code&gt;--exclude&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Saturation : tests clonés avec &lt;code&gt;[COPY N]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Retries sur transient errors.&lt;/li&gt;&#xA;&lt;li&gt;Setup failure → SKIPPED si tous les attempts ratent dès le setup.&lt;/li&gt;&#xA;&lt;li&gt;Test &lt;code&gt;skipped=True&lt;/code&gt; s&amp;rsquo;arrête directement.&lt;/li&gt;&#xA;&lt;li&gt;Garde-fous (noms uniques, IDs uniques).&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;take_screenshot&lt;/code&gt; appelé sur fail si &lt;code&gt;autoscreen_on_fail&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Comptage exact des tentatives sur &lt;code&gt;max_retries&lt;/code&gt; (un test couvre le bord de la boucle).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;test_match_pagepy&#34;&gt;&lt;code&gt;test_match_page.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#test_match_pagepy&#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;Cas&lt;/th&gt;&#xA;          &lt;th&gt;Assertion&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Première branche matche → exécutée&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;chain.is_ok()&lt;/code&gt;, branches suivantes non évaluées&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Deuxième branche matche → exécutée&lt;/td&gt;&#xA;          &lt;td&gt;id.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Aucune branche ne matche&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;NoMatchingBranchError&lt;/code&gt; dans le &lt;code&gt;Fail&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Branche matche, mais son &lt;code&gt;then&lt;/code&gt; fail&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Fail&lt;/code&gt; propagé&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;condition&lt;/code&gt; lève une &lt;code&gt;Exception&lt;/code&gt; ordinaire&lt;/td&gt;&#xA;          &lt;td&gt;Traité comme &lt;code&gt;False&lt;/code&gt;, branches suivantes essayées&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;condition&lt;/code&gt; lève une exception du tuple &lt;code&gt;raised_exceptions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Re-raise&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Branche &lt;code&gt;then=[]&lt;/code&gt; matche&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Ok(None)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page&lt;/code&gt; au milieu d&amp;rsquo;un &lt;code&gt;test_chain&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Court-circuite si fail&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;test_drivers_poolpy&#34;&gt;&lt;code&gt;test_drivers_pool.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#test_drivers_poolpy&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Note : utilisation d&amp;rsquo;un &lt;code&gt;FakeDriverFactory&lt;/code&gt; qui peut être configuré pour lever / bloquer / dormir, et qui compte ses appels.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.04 — Tests du typage statique (pytest-mypy-plugins)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/04-mypy-plugins-types/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/04-mypy-plugins-types/</guid>
      <description>&lt;h1 id=&#34;0404tests-du-typage-statique-pytest-mypy-plugins&#34;&gt;04.04 — Tests du &lt;em&gt;typage statique&lt;/em&gt; (&lt;code&gt;pytest-mypy-plugins&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0404tests-du-typage-statique-pytest-mypy-plugins&#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;Cinq fichiers &lt;code&gt;*test_types.yml&lt;/code&gt; qui testent &lt;em&gt;le comportement du checker&lt;/em&gt;. C&amp;rsquo;est une dimension de tests &lt;strong&gt;unique&lt;/strong&gt; à Ocarina dans son genre : on s&amp;rsquo;assure que ce qui &lt;em&gt;devrait&lt;/em&gt; être une erreur &lt;em&gt;mypy&lt;/em&gt; en est bien une.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#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;tests/&#xA;├── dsl/&#xA;│   ├── invariants/test_types.yml&#xA;│   └── testing_with_railway/test_types.yml&#xA;└── opinionated/&#xA;    ├── cli/test_types.yml&#xA;    ├── infra/test_types.yml&#xA;    └── plugins/reports/...   (snapshots — pas YAML)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;yaml&#34;&gt;YAML&lt;a class=&#34;anchor&#34; href=&#34;#yaml&#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-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;case&lt;/span&gt;: compatible_predicate_types&#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;description&lt;/span&gt;: Predicates should accept values of compatible types&#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;main&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#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;    from ocarina.dsl.invariants.validate import validate&#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;    from ocarina.dsl.invariants.assertions import is_email, is_positive&#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;&#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;    validate(1234).assert_that(is_positive)&#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;    validate(&amp;#34;a@a.com&amp;#34;).assert_that(is_email)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sans &lt;code&gt;out:&lt;/code&gt; → on attend que &lt;code&gt;mypy&lt;/code&gt; passe &lt;strong&gt;sans erreur&lt;/strong&gt; sur le code donné.&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.05 — Snapshot testing (syrupy)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/05-syrupy-snapshots/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/05-syrupy-snapshots/</guid>
      <description>&lt;h1 id=&#34;0405snapshot-testing-syrupy&#34;&gt;04.05 — Snapshot testing (&lt;code&gt;syrupy&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0405snapshot-testing-syrupy&#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;&lt;code&gt;syrupy&lt;/code&gt; est un plugin pytest qui sérialise la sortie d&amp;rsquo;un test dans un fichier &lt;code&gt;.ambr&lt;/code&gt; et compare aux runs suivants. Utilisé pour la sortie de &lt;code&gt;pretty_print_results&lt;/code&gt; et &lt;code&gt;results_to_json&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;ambr&#34;&gt;&lt;code&gt;.ambr&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ambr&#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;tests/opinionated/plugins/reports/__snapshots__/&#xA;├── test_pretty_print_results.ambr&#xA;└── test_results_to_json.ambr&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Généré et géré par &lt;code&gt;syrupy&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;exemple&#34;&gt;Exemple&lt;a class=&#34;anchor&#34; href=&#34;#exemple&#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:#6272a4&#34;&gt;# tests/opinionated/plugins/reports/test_pretty_print_results.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:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_pretty_print_results_renders_campaign_suite_test&lt;/span&gt;(snapshot, capsys):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    results &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;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dashboard&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;Login happy paths&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;Login - without OTP&amp;#34;&lt;/span&gt;: (Ok(&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;), &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login_no_otp&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;Login - with OTP&amp;#34;&lt;/span&gt;: (Fail(error&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;RuntimeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;OTP missed&amp;#34;&lt;/span&gt;)), &lt;span style=&#34;color:#bd93f9&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login_otp&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;Skipped one&amp;#34;&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:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;skipped_one&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;        },&#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;    pretty_print_results(results, with_colors&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    out &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; capsys&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;readouterr()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;out&#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;assert&lt;/span&gt; out &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; snapshot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;code&gt;snapshot&lt;/code&gt; (fixture de &lt;code&gt;syrupy&lt;/code&gt;) :&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.06 — Property-based testing (hypothesis)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/06-hypothesis-properties/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/06-hypothesis-properties/</guid>
      <description>&lt;h1 id=&#34;0406property-based-testing-hypothesis&#34;&gt;04.06 — Property-based testing (&lt;code&gt;hypothesis&lt;/code&gt;)&lt;a class=&#34;anchor&#34; href=&#34;#0406property-based-testing-hypothesis&#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;Un seul fichier (&lt;code&gt;test_invariants_properties.py&lt;/code&gt;), mais le pattern est intéressant : on &lt;strong&gt;génère&lt;/strong&gt; des inputs et on vérifie des &lt;strong&gt;propriétés universelles&lt;/strong&gt; des prédicats d&amp;rsquo;invariants.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-concept&#34;&gt;Le concept&lt;a class=&#34;anchor&#34; href=&#34;#le-concept&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;hypothesis&lt;/code&gt; génère automatiquement des &lt;em&gt;cas de test&lt;/em&gt; qui satisfont des contraintes données, puis vérifie qu&amp;rsquo;une propriété est vraie pour tous les cas générés. En cas d&amp;rsquo;échec, &lt;em&gt;hypothesis&lt;/em&gt; fait du &lt;strong&gt;shrinking&lt;/strong&gt; pour trouver le &lt;strong&gt;plus petit contre-exemple&lt;/strong&gt;.&lt;/p&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;from&lt;/span&gt; hypothesis &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; given, strategies &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; st&#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;@given(st&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;integers())&#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_is_positive_passes_on_positive&lt;/span&gt;(value):&#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;if&lt;/span&gt; value &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        is_positive(value)             &lt;span style=&#34;color:#6272a4&#34;&gt;# ne doit pas lever&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;else&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;with&lt;/span&gt; pytest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raises(InvariantViolationError):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            is_positive(value)         &lt;span style=&#34;color:#6272a4&#34;&gt;# doit lever&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;→ &lt;code&gt;hypothesis&lt;/code&gt; génère des entiers (typiquement 100 par défaut), vérifie la propriété pour chacun. Si une exception &lt;em&gt;inattendue&lt;/em&gt; surgit, c&amp;rsquo;est un fail. Le shrinker trouve le plus petit cas qui fail (&lt;code&gt;0&lt;/code&gt; ? &lt;code&gt;-1&lt;/code&gt; ? &lt;code&gt;MIN_INT&lt;/code&gt; ?).&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.07 — Politique de couverture : ce qui est testé, ce qui ne l&#39;est pas, pourquoi</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/07-coverage-policy/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/07-coverage-policy/</guid>
      <description>&lt;h1 id=&#34;0407politique-de-couverture-ce-qui-est-testé-ce-qui-ne-lest-pas-pourquoi&#34;&gt;04.07 — Politique de couverture : ce qui est testé, ce qui ne l&amp;rsquo;est pas, pourquoi&lt;a class=&#34;anchor&#34; href=&#34;#0407politique-de-couverture-ce-qui-est-test%c3%a9-ce-qui-ne-lest-pas-pourquoi&#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;La couverture par ligne est &lt;strong&gt;explicitement scopée&lt;/strong&gt; au DSL pur et à l&amp;rsquo;infra agnostique. Le reste est testé par d&amp;rsquo;autres moyens (cram, types, snapshots, e2e) ou hors scope (shapes inertes).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pyprojecttomltoolcoveragerun&#34;&gt;&lt;code&gt;pyproject.toml#tool.coverage.run&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pyprojecttomltoolcoveragerun&#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.coverage.run]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;omit = [&#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;*/__init__.py&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;*/tests/*&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;*/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;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;src/ocarina/custom_errors/*&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;src/ocarina/custom_types/*&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;src/ocarina/ports/*&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;src/ocarina/opinionated/loggers/custom_types/*&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;src/**/*singleton.py&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;src/**/consts/**&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:#6272a4&#34;&gt;# Selenium adapter layer — exercised only against a real browser.&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;src/ocarina/infra/selenium/*&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;src/ocarina/dsl/testing/selenium/*&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;src/ocarina/opinionated/cli/selenium/*&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;src/ocarina/pom/selenium/muted.py&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:#6272a4&#34;&gt;# Playwright adapter layer — exercised only against a real browser.&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;src/ocarina/infra/playwright/*&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;src/ocarina/dsl/testing/playwright/*&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;src/ocarina/opinionated/cli/playwright/*&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;src/ocarina/pom/playwright/*&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;src/ocarina/opinionated/cli/phantoms.py&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:#6272a4&#34;&gt;# Opinionated loggers — only file_logger.py stays in scope.&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;src/ocarina/opinionated/loggers/create_matching_logger.py&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;src/ocarina/opinionated/loggers/muted_logger.py&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;src/ocarina/opinionated/loggers/print_logger.py&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;src/ocarina/opinionated/loggers/print_and_file_logger.py&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;src/ocarina/opinionated/loggers/utils/*&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:#6272a4&#34;&gt;# Traversed by cram tests&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;src/ocarina/opinionated/cli/store.py&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;src/ocarina/opinionated/cli/builder.py&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;h2 id=&#34;catégories&#34;&gt;Catégories&lt;a class=&#34;anchor&#34; href=&#34;#cat%c3%a9gories&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-exclusions-évidentes&#34;&gt;1. Exclusions évidentes&lt;a class=&#34;anchor&#34; href=&#34;#1-exclusions-%c3%a9videntes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Pattern&lt;/th&gt;&#xA;          &lt;th&gt;Justification&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;*/__init__.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Modules vides (Python 3 n&amp;rsquo;en a souvent pas besoin)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;*/tests/*&lt;/code&gt;, &lt;code&gt;*/test_*.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Les tests ne se testent pas eux-mêmes&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;2-types-errors-ports-shapes&#34;&gt;2. Types / errors / ports / shapes&lt;a class=&#34;anchor&#34; href=&#34;#2-types-errors-ports-shapes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Chemin&lt;/th&gt;&#xA;          &lt;th&gt;Justification&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;src/ocarina/custom_types/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Juste des &lt;code&gt;type X = ...&lt;/code&gt; et frozen dataclasses. Pas de logique runtime.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_errors/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Juste des sous-classes d&amp;rsquo;Exception. Pas de logique.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/ports/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;ABCs et Protocols. Pas de comportement.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/opinionated/loggers/custom_types/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;id.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/**/consts/**&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Constantes (&lt;code&gt;LOGGERS_CHOICES = (...)&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/**/*singleton.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wrappers Singleton triviaux.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;3-couches-selenium-et-playwright-e2e-seulement&#34;&gt;3. Couches Selenium et Playwright (e2e seulement)&lt;a class=&#34;anchor&#34; href=&#34;#3-couches-selenium-et-playwright-e2e-seulement&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Chemin&lt;/th&gt;&#xA;          &lt;th&gt;Justification&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;src/ocarina/infra/selenium/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Code Selenium réel (&lt;code&gt;Chrome()&lt;/code&gt;, &lt;code&gt;Firefox()&lt;/code&gt;). Ne s&amp;rsquo;exécute qu&amp;rsquo;avec un vrai navigateur.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/dsl/testing/selenium/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_test&lt;/code&gt;, &lt;code&gt;create_selenium_watcher&lt;/code&gt; (factory triviales sur &lt;code&gt;Test&lt;/code&gt; / &lt;code&gt;Watcher&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/opinionated/cli/selenium/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_*_cli_store&lt;/code&gt; (lit &lt;code&gt;platform.system&lt;/code&gt;, instancie).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/pom/selenium/muted.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;MutedPOM&lt;/code&gt; utilitaire (presque vide).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/infra/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Code Playwright réel + l&amp;rsquo;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/06-playwright-actor/&#34;&gt;acteur &lt;code&gt;PlaywrightDriver&lt;/code&gt;&lt;/a&gt;. Le test de bout en bout ne se prouve qu&amp;rsquo;avec un vrai navigateur.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/dsl/testing/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_playwright_test&lt;/code&gt;, &lt;code&gt;create_playwright_watcher&lt;/code&gt; (mêmes factory triviales).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/opinionated/cli/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_playwright_cli_store&lt;/code&gt; (CLI Playwright).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/pom/playwright/*&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;MutedPlaywrightPOM&lt;/code&gt; + &lt;code&gt;mixins&lt;/code&gt; (Null Object, presque vide).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Ces fichiers sont &lt;strong&gt;couverts&lt;/strong&gt; par :&lt;/p&gt;</description>
    </item>
    <item>
      <title>04.08 — Allure &#43; action composite allure-history &#43; déploiement GH Pages</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/08-allure-history/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/04-internal-tests/08-allure-history/</guid>
      <description>&lt;h1 id=&#34;0408allure--action-composite-allure-history--déploiement-gh-pages&#34;&gt;04.08 — Allure + action composite &lt;code&gt;allure-history&lt;/code&gt; + déploiement GH Pages&lt;a class=&#34;anchor&#34; href=&#34;#0408allure--action-composite-allure-history--d%c3%a9ploiement-gh-pages&#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;Le rapport Allure est généré à chaque CI, &lt;em&gt;historisé&lt;/em&gt; sur une branche Git dédiée, puis &lt;em&gt;publié&lt;/em&gt; sur GitHub Pages. Live URL : &lt;a href=&#34;https://mojo-molotov.github.io/ocarina/allure-report/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://mojo-molotov.github.io/ocarina/allure-report/&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pipeline&#34;&gt;Pipeline&lt;a class=&#34;anchor&#34; href=&#34;#pipeline&#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;│ main_ci.yml (push/PR main, ou dispatch)                │&#xA;└──────────────────┬─────────────────────────────────────┘&#xA;                   ▼&#xA;   ┌───────────────────────────────────┐&#xA;   │ job : test                        │&#xA;   │  - matrix (ubuntu / windows × py) │&#xA;   │  - make test (cram + pytest)      │&#xA;   │  - upload-artifact allure-results │&#xA;   └───────────────┬───────────────────┘&#xA;                   ▼&#xA;   ┌───────────────────────────────────┐&#xA;   │ job : allure-history              │&#xA;   │  - download all allure-results    │&#xA;   │  - action composite locale :      │&#xA;   │       allure-history/action.yml   │&#xA;   │  - upload-artifact allure-report  │&#xA;   └───────────────┬───────────────────┘&#xA;                   ▼&#xA;   ┌───────────────────────────────────┐&#xA;   │ job : deploy                      │&#xA;   │  - download allure-report         │&#xA;   │  - prepare pages-root/            │&#xA;   │  - actions/upload-pages-artifact  │&#xA;   │  - actions/deploy-pages           │&#xA;   └───────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;allure-history&#34;&gt;&lt;code&gt;allure-history&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#allure-history&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;.github/actions/allure-history/action.yml&lt;/code&gt; : action composite &lt;strong&gt;locale&lt;/strong&gt; au repo (&lt;code&gt;uses: ./.github/actions/allure-history&lt;/code&gt;).&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
