<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Selenium on Ocarina — précis</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/tags/selenium/</link>
    <description>Recent content in Selenium on Ocarina — précis</description>
    <generator>Hugo</generator>
    <language>fr-FR</language>
    <lastBuildDate>Mon, 01 Jun 2026 19:25:20 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/fr/tags/selenium/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>02.05.01 — Test[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/01-test/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/05-orchestration/01-test/</guid>
      <description>&lt;h1 id=&#34;020501testdriver&#34;&gt;02.05.01 — &lt;code&gt;Test[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020501testdriver&#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/oc_test.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/oc_test.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;signature&#34;&gt;Signature&lt;a class=&#34;anchor&#34; href=&#34;#signature&#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;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test&lt;/span&gt;[Driver]:&#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;__init__&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;font-style:italic&#34;&gt;self&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;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: TestName,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_id: &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;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_scenario: TestScenario[Driver],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pre_test_scenarios_fragments: Sequence[TestScenarioFragment[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; &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;        post_test_scenarios_fragments: Sequence[TestScenarioFragment[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; &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;        skipped: &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;,&#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;-&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;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;is&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;            test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; name&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; name&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;test_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test_id&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;_test_scenario &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test_scenario&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;_pre_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; pre_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;or&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;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_post_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; post_test_scenarios_fragments &lt;span style=&#34;color:#ff79c6&#34;&gt;or&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;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_skipped &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; skipped&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Six paramètres :&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Paramètre&lt;/th&gt;&#xA;          &lt;th&gt;Type&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;name&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestName&lt;/code&gt; (&lt;code&gt;str&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Label humain ; apparaît dans le rapport, et &lt;strong&gt;devient le nom du fichier de log&lt;/strong&gt; (donc soumis à &lt;code&gt;is_valid_filename&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_id&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Identifiant stable pour &lt;code&gt;--only&lt;/code&gt; / &lt;code&gt;--exclude&lt;/code&gt;. Si absent : prend &lt;code&gt;name&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_scenario&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestScenario[Driver]&lt;/code&gt; (alias = &lt;code&gt;Callable[[Driver, ILogger], Scenario[Driver]]&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;Factory&lt;/em&gt; qui construit le &lt;code&gt;Scenario&lt;/code&gt; au moment de l&amp;rsquo;exécution.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pre_test_scenarios_fragments&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sequence[TestScenarioFragment[Driver]] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fonctions &lt;code&gt;(driver, logger) -&amp;gt; TestChain&lt;/code&gt; exécutées &lt;strong&gt;avant&lt;/strong&gt; le scénario principal.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;post_test_scenarios_fragments&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sequence[TestScenarioFragment[Driver]] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fonctions &lt;code&gt;(driver, logger) -&amp;gt; TestChain&lt;/code&gt; exécutées &lt;strong&gt;après&lt;/strong&gt; le scénario principal.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;skipped&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Si &lt;code&gt;True&lt;/code&gt;, le test est enregistré mais pas exécuté.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;1-final&#34;&gt;1. &lt;code&gt;@final&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-final&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Pas d&amp;rsquo;héritage utilisateur. Si l&amp;rsquo;on veut un test « &lt;em&gt;spécial&lt;/em&gt; », on &lt;em&gt;compose&lt;/em&gt; via les fragments ou via un scénario, on ne sous-classe pas.&lt;/p&gt;</description>
    </item>
    <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>02.10.02 — DriverBuilder[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/02-driver-builder/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/02-driver-builder/</guid>
      <description>&lt;h1 id=&#34;021002driverbuilderdriver&#34;&gt;02.10.02 — &lt;code&gt;DriverBuilder[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021002driverbuilderdriver&#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/infra/driver_builder.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/driver_builder.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Encapsule la gestion du &lt;strong&gt;profil&lt;/strong&gt; (souvent une copie tmp d&amp;rsquo;un répertoire utilisateur) et produit la paire &lt;code&gt;(driver, dispose)&lt;/code&gt; attendue par la pool.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pourquoi-un-builder&#34;&gt;Pourquoi un builder ?&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-un-builder&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Quand on construit un driver Selenium avec un &lt;em&gt;profil&lt;/em&gt;, il faut :&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Copier le profil&lt;/strong&gt; dans un dossier temporaire (sinon Firefox/Chrome locked sur le profil original).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lancer le driver&lt;/strong&gt; en pointant vers le dossier temporaire.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;À la fin&lt;/strong&gt; : &lt;code&gt;driver.quit()&lt;/code&gt; &lt;em&gt;puis&lt;/em&gt; supprimer le dossier temporaire.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;code&gt;DriverBuilder&lt;/code&gt; factorise ces trois étapes :&lt;/p&gt;</description>
    </item>
    <item>
      <title>00.03 — Flux d&#39;exécution global d&#39;une campagne e2e</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/03-global-execution-flow/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/00-big-picture/03-global-execution-flow/</guid>
      <description>&lt;h1 id=&#34;0003flux-dexécution-global-dune-campagne-e2e&#34;&gt;00.03 — Flux d&amp;rsquo;exécution global d&amp;rsquo;une campagne e2e&lt;a class=&#34;anchor&#34; href=&#34;#0003flux-dex%c3%a9cution-global-dune-campagne-e2e&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;p&gt;Un &lt;code&gt;python -u src/main.py …&lt;/code&gt; lancé sur l&amp;rsquo;une des deux suites&#xA;(&lt;code&gt;ocarina-example&lt;/code&gt; ou &lt;code&gt;ocarina-with-ai-example&lt;/code&gt;) déclenche cette chaîne :&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;USER  ────────────────────────────────────────────────────────────────────&#xA;       python -u src/main.py --browser firefox --workers 3 [...]&#xA;              │&#xA;              ▼&#xA;       (1) parse CLI&#xA;           CliStoreSingleton.push(create_selenium_auto_cli_store())&#xA;              │&#xA;              ▼&#xA;       (2) build pool&#xA;           create_selenium_drivers_pool(max_size=N)&#xA;              │&#xA;              ▼&#xA;       (3) warm-up dépendances externes&#xA;           - Redis (ocarina-example)&#xA;           - Heroku dyno (ai-example, via curl --retry 6)&#xA;              │&#xA;              ▼&#xA;       (4) bootstrap(&#xA;              test_cycle  = create_e2e_test_cycle(drivers_pool),&#xA;              run_plugins = lambda results: run_plugins(&#xA;                                generate_docx_proof, generate_json_results,&#xA;                                exceptions_logger=...),&#xA;              post_exec   = pretty_print_results + sys.exit(1) si fail&#xA;           )&#xA;              │&#xA;OCARINA  ─────┼──────────────────────────────────────────────────────────&#xA;              │&#xA;              ▼&#xA;       (5) TestCycle.run_all (saturate_workers=True)&#xA;              ├─ smoke_tests_campaigns      [mode : fail-fast | wait-for-all]&#xA;              │     └─ TestCampaign.run_all&#xA;              │           └─ TestSuite.run (max_workers, saturate_workers)&#xA;              │                 └─ ThreadPoolExecutor → TestFlow.run&#xA;              │                       └─ pool.acquire() → TestExecutor.execute&#xA;              │                             ├─ setup()                  (optionnel)&#xA;              │                             ├─ watchers.start()         (daemon threads)&#xA;              │                             ├─ chain_runners            (DSL Railway)&#xA;              │                             ├─ watchers.stop()&#xA;              │                             └─ teardown()               (toujours)&#xA;              ├─ campaigns (main)&#xA;              │  (skippées si un smoke a fail)&#xA;              │&#xA;SUT   ────────┼────────────────────────────────────────────────────────────&#xA;              │&#xA;              ▼&#xA;       Selenium WebDriver ⇄ navigateur réel&#xA;       parfois : OTP HTTP GET + Redis (côté ocarina-example)&#xA;              │&#xA;              ▼&#xA;       (6) run_plugins(results)&#xA;              ├─ generate_docx_proof  (lit l&amp;#39;arbre de logs, fabrique des .docx)&#xA;              ├─ generate_json_results (sérialise results en .json)&#xA;              ├─ d&amp;#39;autres si déclarés (parallélisés via ThreadPoolExecutor)&#xA;              │&#xA;              ▼&#xA;       (6&amp;#39;) post_exec(results)&#xA;              ├─ pretty_print_results (ANSI, hiérarchique)&#xA;              └─ has_test_cycle_failed → sys.exit(1) le cas échéant&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;détail-des-étapes&#34;&gt;Détail des étapes&lt;a class=&#34;anchor&#34; href=&#34;#d%c3%a9tail-des-%c3%a9tapes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-parse-cli&#34;&gt;(1) Parse CLI&lt;a class=&#34;anchor&#34; href=&#34;#1-parse-cli&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;create_selenium_auto_cli_store()&lt;/code&gt; détecte l&amp;rsquo;OS via &lt;code&gt;platform.system()&lt;/code&gt; :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.03 — Screenshotter[TDriver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/03-screenshotter/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/03-screenshotter/</guid>
      <description>&lt;h1 id=&#34;021003screenshottertdriver&#34;&gt;02.10.03 — &lt;code&gt;Screenshotter[TDriver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#021003screenshottertdriver&#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/infra/screenshotter.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/screenshotter.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Utilitaire de capture d&amp;rsquo;écran générique, &lt;em&gt;thread-safe&lt;/em&gt;, agnostique du driver via un &lt;code&gt;Protocol&lt;/code&gt;, configurable via une &lt;code&gt;dataclass&lt;/code&gt;. Supporte le burst.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;protocol&#34;&gt;&lt;code&gt;Protocol&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#protocol&#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;ScreenshotDriver&lt;/span&gt;(Protocol):&#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;save_screenshot&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, path: &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; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&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;&amp;#34;&amp;#34;Save standard viewport screenshot.&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;p&gt;C&amp;rsquo;est tout. &lt;em&gt;N&amp;rsquo;importe quel objet&lt;/em&gt; qui a une méthode &lt;code&gt;save_screenshot(path: str) -&amp;gt; bool&lt;/code&gt; est utilisable. Selenium WebDriver l&amp;rsquo;a nativement (&lt;code&gt;driver.save_screenshot&lt;/code&gt;), Playwright peut être wrappé, un fake driver en test l&amp;rsquo;expose trivialement.&lt;/p&gt;&#xA;&lt;h2 id=&#34;screenshotterconfigtdriver&#34;&gt;&lt;code&gt;ScreenshotterConfig[TDriver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#screenshotterconfigtdriver&#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(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;ScreenshotterConfig&lt;/span&gt;[TDriver: ScreenshotDriver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    output_dir: Path&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    file_ext: &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;.png&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    health_check: HealthCheck[TDriver] &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;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    save_full_page: SaveFullPageScreenshot[TDriver] &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;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    default_burst_delay: &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:#bd93f9&#34;&gt;0.5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    max_filename_retries: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;500&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    uuid_length: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8&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;Champ&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Default&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;output_dir&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Répertoire des screenshots&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;file_ext&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Extension (&lt;code&gt;.png&lt;/code&gt;, &lt;code&gt;.jpg&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.png&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;health_check&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;HealthCheck[TDriver] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;(driver) -&amp;gt; None&lt;/code&gt; qui lève si driver mort&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;save_full_page&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;SaveFullPageScreenshot[TDriver] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;(driver, path) -&amp;gt; bool&lt;/code&gt; pour les screenshots pleine page (Firefox uniquement)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;default_burst_delay&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Délai entre 2 shots en mode burst&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;s&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;max_filename_retries&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Max essais pour générer un nom unique&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;500&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;uuid_length&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Longueur du suffix UUID dans le nom&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;8&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Immutable. On configure une fois et on exporte.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.11.03 — Auto CLI store &#43; flags &#43; validation</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/03-selenium-cli/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/11-opinionated/03-selenium-cli/</guid>
      <description>&lt;h1 id=&#34;021103auto-cli-store--flags--validation&#34;&gt;02.11.03 — Auto CLI store + flags + validation&lt;a class=&#34;anchor&#34; href=&#34;#021103auto-cli-store--flags--validation&#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/opinionated/cli/selenium/create_cli_store.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/cli/selenium/create_cli_store.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;flags&#34;&gt;Flags&lt;a class=&#34;anchor&#34; href=&#34;#flags&#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;Flag&lt;/th&gt;&#xA;          &lt;th&gt;Default&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Validation&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;--driver-path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_file&lt;/code&gt; (sauf &lt;code&gt;--browser safari&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--profile-path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_none&lt;/code&gt; OR &lt;code&gt;is_dir&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--browser&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;None&lt;/code&gt; (requis)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;choices&lt;/code&gt; argparse (par OS)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--not-headless&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;False&lt;/code&gt; (= headless par défaut)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt; (&lt;code&gt;store_true&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--workers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;5&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_positive&lt;/code&gt; + &lt;code&gt;is_not_zero&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--logger&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;&amp;quot;terminal+file&amp;quot;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;choices=LOGGERS_CHOICES&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--wait-timeout&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;≤ 60&lt;/code&gt;, &lt;code&gt;&amp;gt; 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--dont-force-delete-tmp-dirs&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt; (&lt;code&gt;store_true&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--only&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;list[str]&lt;/code&gt; (&lt;code&gt;nargs=&amp;quot;+&amp;quot;&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt; + mutex&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;--exclude&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;list[str]&lt;/code&gt; (&lt;code&gt;nargs=&amp;quot;+&amp;quot;&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;phantom&lt;/em&gt; + mutex&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&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;_DEFAULT_WORKERS_AMOUNT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_DEFAULT_LOGGER: SupportedLogger &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terminal+file&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_DEFAULT_BROWSER_AUTOMATION_TIMEOUT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_MAX_BROWSER_AUTOMATION_TIMEOUT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;literal&#34;&gt;&lt;code&gt;Literal[...]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#literal&#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:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; SeleniumCliStoreKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&#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;driver_path&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;profile_path&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;browser&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;headless&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;workers&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;logger&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;wait_timeout&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;force_delete_tmp_dirs&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;only&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;exclude&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;p&gt;Ces clés sont l&amp;rsquo;unique vocabulaire du store. Les call-sites les utilisent : &lt;code&gt;store.get(&amp;quot;workers&amp;quot;)&lt;/code&gt;, &lt;code&gt;store.get(&amp;quot;browser&amp;quot;)&lt;/code&gt;, etc.&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.04 — Workflows ocarina-with-ai-example</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/04-ai-workflows/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/10-cicd/04-ai-workflows/</guid>
      <description>&lt;h1 id=&#34;1004workflows-ocarina-with-ai-example&#34;&gt;10.04 — Workflows &lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1004workflows-ocarina-with-ai-example&#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;Voir aussi &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/09-ci-matrix/&#34;&gt;&lt;code&gt;../08-ai-example/09-ci-matrix.md&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vue-densemble&#34;&gt;Vue d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#vue-densemble&#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;Workflow&lt;/th&gt;&#xA;          &lt;th&gt;Trigger&lt;/th&gt;&#xA;          &lt;th&gt;OS&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;          &lt;th&gt;Note&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;ai_proof_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push main, PR, dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff format src/ --check&lt;/code&gt; + &lt;code&gt;ruff check src/&lt;/code&gt; + &lt;code&gt;mypy src/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Gate PR, rapide&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ai_proof_e2e.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;dispatch&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × &lt;strong&gt;matrice firefox+chrome&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;warm-up Heroku + run + sed filter ChromeDriver C++ stacks + upload&lt;/td&gt;&#xA;          &lt;td&gt;Manuel uniquement&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;différences-avec-ocarina-example&#34;&gt;Différences avec &lt;code&gt;ocarina-example&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#diff%c3%a9rences-avec-ocarina-example&#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;Aspect&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;ocarina-example&lt;/code&gt;&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;ocarina-with-ai-example&lt;/code&gt;&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Matrice browser&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Firefox seul (e2e.yml)&lt;/td&gt;&#xA;          &lt;td&gt;Firefox &lt;strong&gt;+&lt;/strong&gt; Chrome (parallélisés)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;SUT&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Igoristan (GitHub Pages)&lt;/td&gt;&#xA;          &lt;td&gt;CURA (Heroku eco-dyno)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Pre-run&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;warm-up Redis client&lt;/td&gt;&#xA;          &lt;td&gt;warm-up Heroku dyno&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Filtre output&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;em&gt;aucun&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;sed filtre les stack frames C++ ChromeDriver&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;&lt;code&gt;WAIT_TIMEOUT&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;default 10&lt;/td&gt;&#xA;          &lt;td&gt;15 (compense les latences Heroku)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;warm-up-heroku&#34;&gt;warm-up Heroku&lt;a class=&#34;anchor&#34; href=&#34;#warm-up-heroku&#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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -sf --retry &lt;span style=&#34;color:#bd93f9&#34;&gt;6&lt;/span&gt; --retry-delay &lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt; --retry-all-errors &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;  --max-time &lt;span style=&#34;color:#bd93f9&#34;&gt;30&lt;/span&gt; https://katalon-demo-cura.herokuapp.com/ &amp;gt; /dev/null&#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;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dyno is warm&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;CURA runs on a Heroku dyno that sleeps after inactivity.&#xA;Tests hitting a cold dyno produce timeouts (false fails) and slow&#xA;confirmations (false passes on gap tests). Wake it explicitly&#xA;before any browser opens.&lt;/p&gt;</description>
    </item>
    <item>
      <title>01.05 — Posture politique du projet</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/05-political-stance/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/01-philosophy/05-political-stance/</guid>
      <description>&lt;h1 id=&#34;0105posture-politique-du-projet&#34;&gt;01.05 — Posture politique du projet&lt;a class=&#34;anchor&#34; href=&#34;#0105posture-politique-du-projet&#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 Holy Book ne sépare pas la philosophie de la &lt;strong&gt;politique du dépôt&lt;/strong&gt; lui-même. Cinq engagements opérationnels en découlent.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;1-incorruptibilité&#34;&gt;1. Incorruptibilité&lt;a class=&#34;anchor&#34; href=&#34;#1-incorruptibilit%c3%a9&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;« &lt;em&gt;Transmettre Ocarina, c&amp;rsquo;est transmettre une voiture dont j&amp;rsquo;ai fait tout l&amp;rsquo;entretien moi-même, pour moi-même, donc très précautionneusement. En revanche, elle est et restera transmise telle quelle. C&amp;rsquo;est ma voiture.&lt;/em&gt; »&lt;/p&gt;&#xA;&lt;p&gt;« &lt;em&gt;J&amp;rsquo;y ai canalisé toute ma colère, pour y mettre tout mon amour.&lt;/em&gt; »&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.10.05 — Adapters Selenium</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/05-selenium-adapters/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/05-selenium-adapters/</guid>
      <description>&lt;h1 id=&#34;021005adapters-selenium&#34;&gt;02.10.05 — Adapters Selenium&lt;a class=&#34;anchor&#34; href=&#34;#021005adapters-selenium&#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/infra/selenium&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/selenium/&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Tout ce qui matérialise les abstractions (&lt;code&gt;POMBase&lt;/code&gt;, &lt;code&gt;WebDriversPool&lt;/code&gt;, &lt;code&gt;Screenshotter&lt;/code&gt;) en version Selenium. &lt;strong&gt;Vit hors du DSL pur&lt;/strong&gt; — et depuis la &lt;code&gt;1.1.3&lt;/code&gt; il a un jumeau : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/tree/main/src/ocarina/infra/playwright&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/infra/playwright/&lt;/code&gt;&lt;/a&gt; livre exactement les mêmes contrats en version Playwright. Changer de backend, c&amp;rsquo;est désormais choisir un dossier, pas l&amp;rsquo;écrire.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;fichiers-du-dossier&#34;&gt;Fichiers du dossier&lt;a class=&#34;anchor&#34; href=&#34;#fichiers-du-dossier&#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;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;create_driver.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;_build_firefox&lt;/code&gt;, &lt;code&gt;_build_chrome&lt;/code&gt;, &lt;code&gt;_build_edge&lt;/code&gt;, &lt;code&gt;_build_safari&lt;/code&gt; + dispatch&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_drivers_pool.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_drivers_pool&lt;/code&gt; (utilise &lt;code&gt;DriverBuilder&lt;/code&gt; + &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;create_screenshotter.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_selenium_screenshotter&lt;/code&gt; + &lt;code&gt;_selenium_save_full_page&lt;/code&gt; (Firefox-only)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;driver_healthcheck.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;driver_healthcheck(driver)&lt;/code&gt; — ping &lt;code&gt;driver.title&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;mixins.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;SeleniumTitleMixin&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;create_driverpy&#34;&gt;&lt;code&gt;create_driver.py&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#create_driverpy&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_firefox&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    service &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; FirefoxService(executable_path&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver_path)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; FirefoxOptions()&#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; headless:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;-headless&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;if&lt;/span&gt; profile_path:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;profile &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; FirefoxProfile(profile_path)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Firefox(service&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;service, options&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;options)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;implicitly_wait(wait_timeout)&#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;return&lt;/span&gt; driver&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_chrome&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    service &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; ChromeService(executable_path&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver_path)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    options &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; ChromeOptions()&#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; headless:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--headless=new&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;if&lt;/span&gt; profile_path:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;--user-data-dir=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;profile_path&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Chrome(service&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;service, options&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;options)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;implicitly_wait(wait_timeout)&#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;return&lt;/span&gt; driver&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_edge&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#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;# … identique à Chrome avec EdgeService/EdgeOptions/Edge …&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;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_build_safari&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, profile_path, driver_path, headless, wait_timeout) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; WebDriver:&#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;# … Safari ne supporte ni driver_path (utilise safaridriver natif macOS),&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;#   ni profile_path (pas de profile custom), ni headless …&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;1-implicitly_wait-défini-une-seule-fois&#34;&gt;1. &lt;code&gt;implicitly_wait&lt;/code&gt; défini une seule fois&lt;a class=&#34;anchor&#34; href=&#34;#1-implicitly_wait-d%c3%a9fini-une-seule-fois&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Chaque builder appelle &lt;code&gt;driver.implicitly_wait(wait_timeout)&lt;/code&gt; &lt;em&gt;une&lt;/em&gt; fois. Donc, tout &lt;code&gt;find_element&lt;/code&gt; qui ne trouve pas l&amp;rsquo;élément immédiatement attend jusqu&amp;rsquo;à &lt;code&gt;wait_timeout&lt;/code&gt; secondes avant de lever &lt;code&gt;NoSuchElementException&lt;/code&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>07.07 — HumanizedDriver</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/07-humanized-driver/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/07-humanized-driver/</guid>
      <description>&lt;h1 id=&#34;0707humanizeddriver&#34;&gt;07.07 — &lt;code&gt;HumanizedDriver&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0707humanizeddriver&#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-example/blob/main/src/lib/ext/selenium/humanize/proxy.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/lib/ext/selenium/humanize/proxy.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Proxy pattern : wrappe un &lt;code&gt;WebDriver&lt;/code&gt; Selenium et intercepte uniquement &lt;code&gt;find_element*&lt;/code&gt; pour retourner des &lt;code&gt;WebElement&lt;/code&gt; qui font des &lt;code&gt;send_keys&lt;/code&gt; &lt;em&gt;humanisés&lt;/em&gt; (frappe lente avec typos, hésitations, corrections).&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;HumanizedDriver&lt;/span&gt;(WebDriver):&#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;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, driver: WebDriver, &lt;span style=&#34;color:#ff79c6&#34;&gt;**&lt;/span&gt;keyboard_config: Unpack[KeyboardConfig]) &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;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&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;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_driver &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; driver&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;_config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; keyboard_config&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;find_element&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, by: &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; RelativeBy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;, value: &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;-&amp;gt;&lt;/span&gt; _HumanizedWebElement:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        element &lt;span style=&#34;color:#ff79c6&#34;&gt;=&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;_driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;find_element(by, 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;return&lt;/span&gt; _HumanizedWebElement(element, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_config)&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;find_elements&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, by: &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; RelativeBy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;, value: &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;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;list&lt;/span&gt;[WebElement]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        elements &lt;span style=&#34;color:#ff79c6&#34;&gt;=&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;_driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;find_elements(by, 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;return&lt;/span&gt; [_HumanizedWebElement(el, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_config) &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; el &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; elements]&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__getattr__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, name: &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;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;getattr&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;_driver, name)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;1-hérite-de-webdriver&#34;&gt;1. Hérite de &lt;code&gt;WebDriver&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#1-h%c3%a9rite-de-webdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&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;HumanizedDriver&lt;/span&gt;(WebDriver):&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;HumanizedDriver&lt;/code&gt; &lt;em&gt;passe&lt;/em&gt; le &lt;em&gt;type check&lt;/em&gt; &lt;code&gt;isinstance(x, WebDriver)&lt;/code&gt;.&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>08.09 — CI : ai_proof_ci.yml &#43; ai_proof_e2e.yml</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/09-ci-matrix/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/08-ai-example/09-ci-matrix/</guid>
      <description>&lt;h1 id=&#34;0809ci-ai_proof_ciyml--ai_proof_e2eyml&#34;&gt;08.09 — CI : &lt;code&gt;ai_proof_ci.yml&lt;/code&gt; + &lt;code&gt;ai_proof_e2e.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0809ci-ai_proof_ciyml--ai_proof_e2eyml&#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;Deux workflows. PR rapide (lint+typecheck). e2e manuel avec &lt;strong&gt;matrice Firefox/Chrome&lt;/strong&gt; + warm-up Heroku + filtrage stacktrace ChromeDriver.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;ai_proof_ciyml&#34;&gt;&lt;code&gt;ai_proof_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ai_proof_ciyml&#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;name&lt;/span&gt;: ai_proof CI&#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;on&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;push&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;branches&lt;/span&gt;: [main]&#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;pull_request&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;workflow_dispatch&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;defaults&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;run&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;shell&lt;/span&gt;: bash&#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;jobs&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;lint-and-typecheck&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;runs-on&lt;/span&gt;: ubuntu-latest&#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;steps&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;uses&lt;/span&gt;: actions/checkout@v4&#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;uses&lt;/span&gt;: actions/setup-python@v5&#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;:&#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;python-version&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;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;          &lt;span style=&#34;color:#ff79c6&#34;&gt;allow-prereleases&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;uses&lt;/span&gt;: actions/cache@v4&#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;:&#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;path&lt;/span&gt;: .venv&#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;key&lt;/span&gt;: venv-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles(&amp;#39;pyproject.toml&amp;#39;) }}&#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;name&lt;/span&gt;: Install dependencies&#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;: steps.cache-venv.outputs.cache-hit != &amp;#39;true&amp;#39;&#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;run&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;          python -m venv .venv&#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;          .venv/bin/pip install . ruff mypy mypy-extensions typing-extensions pre-commit&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;name&lt;/span&gt;: Ruff format check&#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;run&lt;/span&gt;: .venv/bin/ruff format src/ --check&#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;name&lt;/span&gt;: Ruff lint&#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;run&lt;/span&gt;: .venv/bin/ruff check src/&#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;name&lt;/span&gt;: Mypy&#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;run&lt;/span&gt;: .venv/bin/mypy src/&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;Step&lt;/th&gt;&#xA;          &lt;th&gt;Cmd&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Ruff format check&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff format src/ --check&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Ruff lint&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ruff check src/&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Mypy&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;mypy src/&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;ai_proof_e2eyml&#34;&gt;&lt;code&gt;ai_proof_e2e.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#ai_proof_e2eyml&#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;name&lt;/span&gt;: ai_proof e2e&#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;on&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;workflow_dispatch&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;defaults&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;run&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;shell&lt;/span&gt;: bash&#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;jobs&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;e2e&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;name&lt;/span&gt;: e2e (${{ matrix.browser }})&#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;runs-on&lt;/span&gt;: ubuntu-latest&#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;env&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;FORCE_JAVASCRIPT_ACTIONS_TO_NODE24&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;WAIT_TIMEOUT&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;15&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;strategy&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;fail-fast&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;      &lt;span style=&#34;color:#ff79c6&#34;&gt;matrix&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;include&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;browser&lt;/span&gt;: firefox&#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;driver&lt;/span&gt;: geckodriver&#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;browser&lt;/span&gt;: chrome&#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;driver&lt;/span&gt;: chromedriver&#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;steps&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;uses&lt;/span&gt;: actions/checkout@v4&#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;uses&lt;/span&gt;: actions/setup-python@v5&#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;:&#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;python-version&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;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;          &lt;span style=&#34;color:#ff79c6&#34;&gt;allow-prereleases&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;uses&lt;/span&gt;: actions/cache@v4&#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;:&#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;path&lt;/span&gt;: .venv&#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;key&lt;/span&gt;: venv-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles(&amp;#39;pyproject.toml&amp;#39;) }}&#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;name&lt;/span&gt;: Install dependencies&#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;: steps.cache-venv.outputs.cache-hit != &amp;#39;true&amp;#39;&#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;run&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;          python -m venv .venv&#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;          .venv/bin/pip install . ruff mypy mypy-extensions typing-extensions pre-commit&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;# --- Firefox ---&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;name&lt;/span&gt;: Install geckodriver&#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;: matrix.browser == &amp;#39;firefox&amp;#39;&#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;run&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;          GECKO_VERSION=0.36.0&#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;          wget -q https://github.com/mozilla/geckodriver/releases/download/v$GECKO_VERSION/geckodriver-v$GECKO_VERSION-linux64.tar.gz&#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;          tar -xzf geckodriver-v$GECKO_VERSION-linux64.tar.gz&#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;          chmod +x geckodriver&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;name&lt;/span&gt;: Setup Firefox&#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;: matrix.browser == &amp;#39;firefox&amp;#39;&#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;uses&lt;/span&gt;: browser-actions/setup-firefox@fcf821c621167805dd63a29662bd7cb5676c81a8&#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;# --- Chrome ---&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;name&lt;/span&gt;: Install chromedriver&#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;: matrix.browser == &amp;#39;chrome&amp;#39;&#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;run&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;          CHROME_VER=$(google-chrome-stable --version | grep -oP &amp;#39;\d+\.\d+\.\d+\.\d+&amp;#39;)&#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;          wget -q -O chromedriver.zip \&#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;https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VER}/linux64/chromedriver-linux64.zip&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;          unzip -q chromedriver.zip&#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;          mv chromedriver-linux64/chromedriver ./chromedriver&#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;          chmod +x ./chromedriver&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;# --- Warm-up ---&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;name&lt;/span&gt;: Warm up Heroku dyno&#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;run&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;          # CURA runs on a Heroku dyno that sleeps after inactivity.&#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;          # Tests hitting a cold dyno produce timeouts (false fails) and slow&#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;          # confirmations (false passes on gap tests). Wake it explicitly&#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;          # before any browser opens.&#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;          curl -sf --retry 6 --retry-delay 5 --retry-all-errors \&#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;            --max-time 30 https://katalon-demo-cura.herokuapp.com/ &amp;gt; /dev/null&#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;          echo &amp;#34;Dyno is warm&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;      &lt;span style=&#34;color:#6272a4&#34;&gt;# --- Run ---&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;name&lt;/span&gt;: Run e2e cycle&#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;run&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;          # ChromeDriver release builds ship without debug symbols. On errors it&#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;          # dumps raw C++ stack frames (&amp;#34;#N 0x&amp;lt;addr&amp;gt; &amp;lt;unknown&amp;gt;&amp;#34;) into the WebDriver&#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;          # response, which Selenium surfaces as the exception message — polluting&#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;          # both the live log and the pretty-print summary with unreadable hex noise.&#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;          # No Python-side fix exists. Strip the frames with sed before they land.&#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;          # pipefail preserves Python&amp;#39;s exit code through the pipe.&#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;          set -o pipefail&#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;          .venv/bin/python -u src/main.py \&#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;            --driver-path ./${{ matrix.driver }} \&#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;            --browser ${{ matrix.browser }} \&#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;            --workers 3 \&#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;            --wait-timeout ${{ env.WAIT_TIMEOUT }} \&#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;            --logger terminal+file \&#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;            2&amp;gt;&amp;amp;1 | sed -u \&#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;              -e &amp;#39;/Stacktrace:/{N;/\n#[0-9][0-9]* 0x[0-9a-f][0-9a-f]* &amp;lt;unknown&amp;gt;/{s/Stacktrace:\n[^\n]*/HIDDEN/;b};P;D}&amp;#39; \&#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;              -e &amp;#39;/^#[0-9][0-9]* 0x[0-9a-f][0-9a-f]* &amp;lt;unknown&amp;gt;/d&amp;#39;&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;name&lt;/span&gt;: Enumerate traces&#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;: always()&#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;run&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;          sync&#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;          find $GITHUB_WORKSPACE/.screenshots -type f 2&amp;gt;/dev/null || true&#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;          find $GITHUB_WORKSPACE/.ocarina_logs -type f 2&amp;gt;/dev/null || true&#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;          find $GITHUB_WORKSPACE/.reports -type f 2&amp;gt;/dev/null || 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:#ff79c6&#34;&gt;name&lt;/span&gt;: Upload screenshots&#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;: always()&#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;uses&lt;/span&gt;: actions/upload-artifact@v4&#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;:&#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;name&lt;/span&gt;: screenshots-${{ matrix.browser }}&#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;path&lt;/span&gt;: .screenshots/&#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;retention-days&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;7&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;# … upload logs, reports similar ...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;analyse&#34;&gt;Analyse&lt;a class=&#34;anchor&#34; href=&#34;#analyse&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;fail-fast-false&#34;&gt;&lt;code&gt;fail-fast: false&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#fail-fast-false&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&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;strategy&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;fail-fast&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;  &lt;span style=&#34;color:#ff79c6&#34;&gt;matrix&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;include&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;browser&lt;/span&gt;: firefox&#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;browser&lt;/span&gt;: chrome&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;fail-fast: false&lt;/code&gt; = si Firefox fail, Chrome continue. &lt;strong&gt;Indispensable&lt;/strong&gt; pour ce projet : la valeur du run est de voir les &lt;strong&gt;deux&lt;/strong&gt; browsers, même si l&amp;rsquo;un fail.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Chapitre 02.10 — Infrastructure</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/</guid>
      <description>&lt;h1 id=&#34;chapitre-0210infrastructure&#34;&gt;Chapitre 02.10 — Infrastructure&lt;a class=&#34;anchor&#34; href=&#34;#chapitre-0210infrastructure&#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;Tout ce qui touche aux &lt;em&gt;ressources externes&lt;/em&gt; : pool de drivers, builders, screenshotters, compteur d&amp;rsquo;acts, et les adapters Selenium qui implémentent ces abstractions. Depuis la &lt;code&gt;1.1.3&lt;/code&gt;, un &lt;code&gt;infra/playwright/&lt;/code&gt; parallèle livre les mêmes contrats en saveur Playwright — même pool, même builder, mêmes ports.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;plan&#34;&gt;Plan&lt;a class=&#34;anchor&#34; href=&#34;#plan&#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 style=&#34;text-align: center&#34;&gt;#&lt;/th&gt;&#xA;          &lt;th&gt;Fichier&lt;/th&gt;&#xA;          &lt;th&gt;Sujet&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;01&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/01-drivers-pool/&#34;&gt;&lt;code&gt;01-drivers-pool.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;WebDriversPool[Driver]&lt;/code&gt; : &lt;code&gt;Semaphore&lt;/code&gt;, &lt;code&gt;Queue&lt;/code&gt;, &lt;code&gt;warmup&lt;/code&gt; avec watchdog, &lt;code&gt;shutdown&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;02&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/02-driver-builder/&#34;&gt;&lt;code&gt;02-driver-builder.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;DriverBuilder[Driver]&lt;/code&gt; : gestion du profile et tmp dirs.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;03&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/03-screenshotter/&#34;&gt;&lt;code&gt;03-screenshotter.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Screenshotter[TDriver]&lt;/code&gt; + &lt;code&gt;ScreenshotterConfig&lt;/code&gt; : burst, healthcheck, threadsafe.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;04&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/04-act-counter/&#34;&gt;&lt;code&gt;04-act-counter.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ActCounter&lt;/code&gt; + &lt;code&gt;ThreadsBasedActCounter&lt;/code&gt; (thread-local).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;05&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/05-selenium-adapters/&#34;&gt;&lt;code&gt;05-selenium-adapters.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;create_driver&lt;/code&gt; (Firefox/Chrome/Edge/Safari), &lt;code&gt;create_drivers_pool&lt;/code&gt;, &lt;code&gt;create_screenshotter&lt;/code&gt;, &lt;code&gt;driver_healthcheck&lt;/code&gt;, &lt;code&gt;SeleniumTitleMixin&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;06&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/10-infra/06-playwright-actor/&#34;&gt;&lt;code&gt;06-playwright-actor.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;L&amp;rsquo;acteur &lt;code&gt;PlaywrightDriver&lt;/code&gt; : thread propriétaire, marshallisation par &lt;code&gt;submit&lt;/code&gt;, &lt;code&gt;call_timeout&lt;/code&gt; de liveness, warmup cross-thread, Watcher observe-only.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;builtwebdriverdriver&#34;&gt;&lt;code&gt;BuiltWebDriver[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#builtwebdriverdriver&#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:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; BuiltWebDriver[Driver] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;tuple&lt;/span&gt;[Driver, Effect]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est l&amp;rsquo;unité d&amp;rsquo;échange entre le builder et la pool : un tuple &lt;code&gt;(driver, dispose)&lt;/code&gt;. Le &lt;code&gt;dispose&lt;/code&gt; est un &lt;code&gt;Effect&lt;/code&gt; (sans argument, sans retour) qui sait nettoyer ce driver précisément (&lt;code&gt;driver.quit()&lt;/code&gt; + suppression du profile tmp si nécessaire).&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>
