<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Chapitre 07 — ocarina-example, la suite canonique on Ocarina — précis</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/</link>
    <description>Recent content in Chapitre 07 — ocarina-example, la suite canonique on Ocarina — précis</description>
    <generator>Hugo</generator>
    <language>fr-FR</language>
    <lastBuildDate>Thu, 04 Jun 2026 08:35:44 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>07.01 — Arborescence</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/01-tree/</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/01-tree/</guid>
      <description>&lt;h1 id=&#34;0701arborescence&#34;&gt;07.01 — Arborescence&lt;a class=&#34;anchor&#34; href=&#34;#0701arborescence&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;src/&#xA;├── main.py                                                     # entry point (cf. README)&#xA;│&#xA;├── api/                                                        # clients HTTP externes&#xA;│   ├── retrieve_dashboard_otp_code.py                          # filter + sort + pick OTP&#xA;│   ├── get_otp_history.py                                      # HTTP GET /api/otp-history&#xA;│   └── constants/endpoints.py                                  # URLs des endpoints tests-workers&#xA;│&#xA;├── caches/                                                     # cache L1 in-memory&#xA;│   ├── l1.py                                                   # dogpile.cache.memory TTL 30m&#xA;│   └── reserve_free_cache_key.py                               # UUID + threading.Lock&#xA;│&#xA;├── constants/                                                  # constantes&#xA;│   ├── pages/&#xA;│   │   ├── homepage.py                                         # HOMEPAGE_URL&#xA;│   │   ├── dashboard.py                                        # DASHBOARD_URL + ids des inputs&#xA;│   │   ├── random_loaders.py&#xA;│   │   ├── sacred_upload.py&#xA;│   │   ├── corsicamon.py&#xA;│   │   ├── chaotic_form.py&#xA;│   │   ├── madness.py&#xA;│   │   ├── random_error_page.py&#xA;│   │   └── donkey_sausage_eater_detector.py&#xA;│   └── sys/&#xA;│       ├── redis_keys.py                                       # OTP_SEND_LOCK_KEY, ...&#xA;│       └── transient_errors.py                                 # (WebDriverException, HttpError...)&#xA;│&#xA;├── lib/&#xA;│   ├── connectors/test_steps/actions/                          # fonctions (TPOM) -&amp;gt; TPOM&#xA;│   │   ├── homepage.py&#xA;│   │   ├── dashboard_login.py                                  # ~10 connectors (with/without retries)&#xA;│   │   ├── dashboard_welcome.py&#xA;│   │   ├── dashboard_protected_page.py&#xA;│   │   ├── sacred_upload.py&#xA;│   │   ├── corsicamon_enter_api_key.py&#xA;│   │   ├── corsicamon_main.py&#xA;│   │   ├── chaotic_form.py&#xA;│   │   ├── random_error.py&#xA;│   │   ├── random_loaders.py&#xA;│   │   ├── madness.py&#xA;│   │   ├── this_is_bastia.py&#xA;│   │   ├── cors_errors.py&#xA;│   │   ├── bsod.py&#xA;│   │   ├── dsed.py&#xA;│   │   └── ids_bypassed.py&#xA;│   ├── custom_errors/&#xA;│   │   ├── http.py                                             # HttpErrorPageReachedError&#xA;│   │   └── transient_error.py                                  # TransientError&#xA;│   └── ext/                                                    # extensions / adapters&#xA;│       ├── ocarina/&#xA;│       │   ├── adapters/&#xA;│       │   │   ├── agnostic/&#xA;│       │   │   │   ├── act.py                                  # ⭐ adapter act (hook on_failure)&#xA;│       │   │   │   ├── env_getters.py                          # ⭐ adapter EnvGetters typé&#xA;│       │   │   │   └── match_page.py                           # ⭐ adapter match_page&#xA;│       │   │   └── selenium/&#xA;│       │   │       ├── test_suite.py                           # ⭐ adapter TestSuite&#xA;│       │   │       ├── test_campaign.py                        # ⭐ adapter TestCampaign&#xA;│       │   │       ├── cli_getters.py                          # getters typés depuis le CliStore&#xA;│       │   │       ├── logs.py                                 # create_just_log_error, etc.&#xA;│       │   │       └── screenshotter.py                        # take_screenshot helper&#xA;│       │   └── regex/error_page.py                             # ERROR_PAGE_REGEX&#xA;│       ├── redis/&#xA;│       │   └── client.py                                       # Singleton client Redis&#xA;│       └── selenium/&#xA;│           ├── humanize/                                       # ⭐ HumanizedDriver + keyboard&#xA;│           │   ├── proxy.py&#xA;│           │   └── keyboard.py&#xA;│           ├── pages/verify_elements_presence.py&#xA;│           └── watchers/catch_me_if_you_can_watcher.py         # ⭐ Watcher callback&#xA;│&#xA;├── pages/                                                      # POMs (Selenium + SeleniumTitleMixin)&#xA;│   ├── homepage.py&#xA;│   ├── random_loaders.py&#xA;│   ├── chaotic_form.py&#xA;│   ├── random_error.py&#xA;│   ├── sacred_upload/&#xA;│   │   ├── sacred_upload.py&#xA;│   │   └── fixtures/                                           # fichiers à uploader&#xA;│   ├── dashboard/&#xA;│   │   ├── login.py                                            # ⭐ avec OTP, retries, redis locks&#xA;│   │   ├── welcome_page.py&#xA;│   │   └── protected_page.py&#xA;│   ├── corsicamon/&#xA;│   │   ├── enter_api_key.py&#xA;│   │   └── main.py&#xA;│   ├── madness/&#xA;│   │   ├── base.py&#xA;│   │   ├── matchers.py                                         # has_cors, has_this_is_bastia&#xA;│   │   ├── cors.py&#xA;│   │   └── this_is_bastia.py&#xA;│   └── donkey_sausage_detector/&#xA;│       └── ids_bypassed.py&#xA;│&#xA;└── tests/&#xA;    ├── cycles/e2e.py                                           # ⭐ TestCycle (smoke + main)&#xA;    ├── campaigns/&#xA;    │   ├── global_smoke_tests.py                               # smoke&#xA;    │   ├── corsicamon.py                                       # main (+ smoke variant)&#xA;    │   ├── dashboard_login.py                                  # main&#xA;    │   ├── randomness.py                                       # main&#xA;    │   └── sacred_upload.py                                    # main&#xA;    ├── suites/&#xA;    │   ├── global_smoke_tests.py&#xA;    │   ├── randomness.py&#xA;    │   ├── dashboard/&#xA;    │   │   ├── access/&#xA;    │   │   │   ├── happy_paths.py&#xA;    │   │   │   └── unhappy_paths.py&#xA;    │   │   └── data_driven/multi_login.py&#xA;    │   ├── sacred_upload/{happy_paths,unhappy_paths}.py&#xA;    │   └── corsicamon/{smoke_tests,happy_paths,unhappy_paths}.py&#xA;    └── scenarios/&#xA;        ├── homepage/verify_homepage.py&#xA;        ├── dashboard/&#xA;        │   ├── access/{happy_paths,unhappy_paths}.py&#xA;        │   ├── back_to_igoristan.py&#xA;        │   └── data_driven/{multi_login,datasets/multi_login}.py&#xA;        ├── sacred_upload/{upload_files,just_go_back_to_igoristan}.py&#xA;        ├── corsicamon/{enter_api_key,new_draw,add_corsicamon,back_to_igoristan}.py&#xA;        └── randomness/&#xA;            ├── level_1/{random_error_page,random_loaders_page}.py&#xA;            ├── level_2/{dsed,madness}.py&#xA;            ├── level_3/chaotic_form.py&#xA;            └── level_4/walkthrough.py&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;sémantique-des-dossiers&#34;&gt;Sémantique des dossiers&lt;a class=&#34;anchor&#34; href=&#34;#s%c3%a9mantique-des-dossiers&#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;Dossier&lt;/th&gt;&#xA;          &lt;th&gt;Rôle&lt;/th&gt;&#xA;          &lt;th&gt;Convention&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;pages/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;POMs (héritent &lt;code&gt;SeleniumTitleMixin, POMBase&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;Une classe par page, fluent (&lt;code&gt;return self&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/connectors/test_steps/actions/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Connectors &lt;code&gt;(TPOM) -&amp;gt; TPOM&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fonctions pures ; si paramètres, closures&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/custom_errors/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Exceptions projet&lt;/td&gt;&#xA;          &lt;td&gt;Sous-classes d&amp;rsquo;Exception&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Adapters au-dessus d&amp;rsquo;Ocarina&lt;/td&gt;&#xA;          &lt;td&gt;Cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/02-adapters/&#34;&gt;&lt;code&gt;02-adapters.md&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/redis/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wrapper Singleton Redis&lt;/td&gt;&#xA;          &lt;td&gt;Pour les locks distribués&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/selenium/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Extensions Selenium spécifiques au projet&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;HumanizedDriver&lt;/code&gt;, &lt;code&gt;Watcher&lt;/code&gt; callback&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;caches/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Cache L1 in-memory&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;dogpile.cache.memory&lt;/code&gt; + UUID reservation&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;constants/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Constantes&lt;/td&gt;&#xA;          &lt;td&gt;URLs, redis keys, transient errors&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;api/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Clients HTTP externes&lt;/td&gt;&#xA;          &lt;td&gt;OTP retrieval, OTP history&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;tests/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hiérarchie ISTQB&lt;/td&gt;&#xA;          &lt;td&gt;cycles / campaigns / suites / scenarios&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;lib-vs-pages&#34;&gt;&lt;code&gt;lib/&lt;/code&gt; vs &lt;code&gt;pages/&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#lib-vs-pages&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;pages/&lt;/code&gt;&lt;/strong&gt; : &lt;em&gt;POMs&lt;/em&gt;, encapsulent l&amp;rsquo;état d&amp;rsquo;une page (locators, méthodes d&amp;rsquo;action).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;lib/connectors/test_steps/actions/&lt;/code&gt;&lt;/strong&gt; : &lt;em&gt;connectors&lt;/em&gt;, fonctions qui &lt;em&gt;réfèrent&lt;/em&gt; aux POMs.&lt;/li&gt;&#xA;&lt;/ol&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;act(on_homepage, open_homepage)&#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;#                ^^^^^^^^^^^^^&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;#                connector défini dans lib/connectors/test_steps/actions/homepage.py&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;open_homepage&lt;/span&gt;(p: Homepage) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Homepage:&#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; p&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;open()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;code&gt;CLAUDE.md&lt;/code&gt; du projet IA en fait une discipline :&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.02 — Les 5 adapters</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/02-adapters/</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/02-adapters/</guid>
      <description>&lt;h1 id=&#34;0702les-5-adapters&#34;&gt;07.02 — Les 5 adapters&lt;a class=&#34;anchor&#34; href=&#34;#0702les-5-adapters&#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 projet Ocarina commence par écrire &lt;strong&gt;cinq&lt;/strong&gt; adapters au-dessus du framework.&lt;br&gt;&#xA;Le projet &lt;code&gt;ocarina-example&lt;/code&gt; les fournit en référence.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing&#34;&gt;Listing&lt;a class=&#34;anchor&#34; href=&#34;#listing&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Adapter&lt;/th&gt;&#xA;          &lt;th&gt;Statut&lt;/th&gt;&#xA;          &lt;th&gt;Fichier&lt;/th&gt;&#xA;          &lt;th&gt;But&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;act&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;requis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/act.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hook &lt;code&gt;on_failure&lt;/code&gt; spécifique au projet&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;facultatif&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/match_page.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Politique d&amp;rsquo;exceptions partagée&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;EnvGetters&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;facultatif&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/agnostic/env_getters.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Accès typé aux env vars&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestSuite&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;requis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/selenium/test_suite.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Façade : fige &lt;code&gt;transient_errors&lt;/code&gt;, &lt;code&gt;max_retries&lt;/code&gt;, &lt;code&gt;autoscreen&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestCampaign&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;requis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;lib/ext/ocarina/adapters/selenium/test_campaign.py&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Façade : &lt;code&gt;max_workers&lt;/code&gt; depuis CLI&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;act&#34;&gt;&lt;code&gt;act&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#act&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# lib/ext/ocarina/adapters/agnostic/act.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; contextlib &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; suppress&#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;from&lt;/span&gt; ocarina.dsl.testing_with_railway.constructors.create_act &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; create_act&#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;from&lt;/span&gt; ocarina.railway.result &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; Fail&#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;from&lt;/span&gt; lib.custom_errors.http &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; HttpErrorPageReachedError&#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;from&lt;/span&gt; lib.ext.ocarina.regex.error_page &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; ERROR_PAGE_REGEX&#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;act&lt;/span&gt;(pom: TPOM, action: Callable[[TPOM], TPOM]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ActionStart[TPOM]:&#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;failure_hook&lt;/span&gt;(pom: TPOM, exc: Exception) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; Fail:&#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; suppress(Exception):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            title &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; pom&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get_current_title()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            is_http_error_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; title &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; ERROR_PAGE_REGEX&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;match&lt;/span&gt;(title&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;strip())&#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; is_http_error_page:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                http_error &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; HttpErrorPageReachedError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;HTTP error page: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;title&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;                http_error&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__cause__ &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; exc&#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; Fail(error&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;http_error)&#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; Fail(error&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;exc)&#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;return&lt;/span&gt; create_act(pom, action, on_failure&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;failure_hook)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;get_current_title()&lt;/code&gt;&lt;/strong&gt; : utilise la méthode obligatoire de &lt;code&gt;POMBase&lt;/code&gt; (cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/08-pom-base/&#34;&gt;&lt;code&gt;../02-ocarina/08-pom-base.md&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;ERROR_PAGE_REGEX&lt;/code&gt;&lt;/strong&gt; : regex projet qui détecte un titre type page d&amp;rsquo;erreur HTTP (par exemple &lt;code&gt;^\d{3}\s.*&lt;/code&gt; pour &lt;code&gt;500 Internal Server Error&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;HttpErrorPageReachedError&lt;/code&gt;&lt;/strong&gt; : exception projet ; sous-classe d&amp;rsquo;Exception, sera dans &lt;code&gt;transient_errors&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;C&amp;rsquo;est cet adapter qui rend le rejeu &lt;strong&gt;automatique&lt;/strong&gt; pour les pages d&amp;rsquo;erreur.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.03 — Scénarios Dashboard login</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/03-scenarios-login/</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/03-scenarios-login/</guid>
      <description>&lt;h1 id=&#34;0703scénarios-dashboard-login&#34;&gt;07.03 — Scénarios Dashboard login&lt;a class=&#34;anchor&#34; href=&#34;#0703sc%c3%a9narios-dashboard-login&#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;Trois familles : happy paths, unhappy paths, data-driven multi-login. Tous exercent le &lt;code&gt;useAuth&lt;/code&gt; à 10% de raté et la coordination OTP.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;dashboard_login&#34;&gt;&lt;code&gt;dashboard_login&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#dashboard_login&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/dashboard_login.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_login_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool: SeleniumWebDriversPool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#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; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dashboard login&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_igoristan_login_happy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_igoristan_login_unhappy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_igoristan_login_data_driven_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;happy-paths&#34;&gt;Happy paths&lt;a class=&#34;anchor&#34; href=&#34;#happy-paths&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/suites/dashboard/access/happy_paths.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_login_happy_paths_test_suite&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestSuite:&#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; TestSuite(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Login happy paths&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tests&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;            test_dashboard_login_without_otp_happy_path,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_dashboard_login_with_otp_happy_path,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_dashboard_login_page_back_to_igoristan_button,&#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;        drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool,&#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;h3 id=&#34;test-1-login-sans-otp&#34;&gt;Test 1 : login sans OTP&lt;a class=&#34;anchor&#34; href=&#34;#test-1-login-sans-otp&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;dashboard_login_without_otp_happy_path&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dashboard_creds &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_env_getters()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get_credentials(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    on_dashboard_login_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; DashboardLoginPage(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;    on_dashboard_welcome_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; DashboardWelcomePage(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;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    just_log_error &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_just_log_error(logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    log_error_with_current_url &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_log_error_with_current_url(logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger, 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;    just_log_success &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_just_log_success(logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    log_success_with_current_url_and_take_screenshot &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_log_success_with_current_url_and_take_screenshot(&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;    retries_amount &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;max&lt;/span&gt;(get_max_workers(), &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;&#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; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, open_dashboard_login_page)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(just_log_error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to open the dashboard login page...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(just_log_success(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Opened the dashboard login page!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, verify_dashboard_login_page)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(log_error_with_current_url(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to verify...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(log_success_with_current_url_and_take_screenshot(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Verified!&amp;#34;&lt;/span&gt;)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                login_without_otp_and_with_retries(dashboard_creds, retries_amount, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(just_log_error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to connect to the dashboard without OTP...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(just_log_success(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Connected to the dashboard!&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;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_welcome_page, verify_dashboard_welcome_page)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(log_error_with_current_url(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Failed to verify...&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(log_success_with_current_url_and_take_screenshot(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Verified!&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;retries_amount = max(get_max_workers(), 10)&lt;/code&gt;&lt;/strong&gt; : on rejoue le login interne (au POM) au moins 10 fois, à cause des 10% d&amp;rsquo;échec aléatoire.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;login_without_otp_and_with_retries(creds, retries_amount, logger=logger)&lt;/code&gt;&lt;/strong&gt; : closure qui capture creds + nb retries + logger. Le connector retourne &lt;code&gt;Callable[[DashboardLoginPage], DashboardLoginPage]&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;2 &lt;code&gt;drive_page&lt;/code&gt;&lt;/strong&gt; : login + welcome page. Chacun ouvre, vérifie, screenshot.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Log factories partout&lt;/strong&gt; : aucune lambda inline.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;SeleniumTitleMixin&lt;/code&gt; côté POM&lt;/strong&gt; : &lt;code&gt;get_current_title()&lt;/code&gt; est utilisé par le hook &lt;code&gt;on_failure&lt;/code&gt; de &lt;code&gt;act&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;test-2-login-avec-otp&#34;&gt;Test 2 : login avec OTP&lt;a class=&#34;anchor&#34; href=&#34;#test-2-login-avec-otp&#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;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;dashboard_login_with_otp_happy_path&lt;/span&gt;(driver, logger):&#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;# ... setup pareil ...&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;    cache &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; in_memory_cache_with_30m_ttl&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fresh_cache_key_for_username &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; reserve_free_cache_key(cache)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fresh_cache_key_for_otp_send_button_click_date &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; reserve_free_cache_key(cache)&#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;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_login_page, open_dashboard_login_page)&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;            act(on_dashboard_login_page, verify_dashboard_login_page)&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;            act(on_dashboard_login_page,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                start_to_login_with_otp_and_with_retries(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    dashboard_creds, retries_amount,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    cache&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;cache, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    username_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_username,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    otp_send_button_click_date_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_otp_send_button_click_date,&#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;            act(on_dashboard_login_page, verify_otp_screen)&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;            act(on_dashboard_login_page,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                type_otp_with_retries(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    retries_amount,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    cache&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;cache, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    username_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_username,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    otp_send_button_click_date_cache_key&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;fresh_cache_key_for_otp_send_button_click_date,&#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;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_welcome_page, verify_dashboard_welcome_page)&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;            act(on_dashboard_welcome_page, click_on_go_to_nested_page_btn)&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;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(on_dashboard_protected_page, verify_dashboard_protected_page)&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;    ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Trois &lt;code&gt;drive_page&lt;/code&gt;. Cinq &lt;code&gt;act&lt;/code&gt; dans le premier. Cache L1 + clés réservées pour partager des valeurs entre acts (cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/08-caches-locks/&#34;&gt;&lt;code&gt;08-caches-locks.md&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.04 — Scénarios Corsicamon</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/04-scenarios-corsicamon/</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/04-scenarios-corsicamon/</guid>
      <description>&lt;h1 id=&#34;0704scénarios-corsicamon&#34;&gt;07.04 — Scénarios Corsicamon&lt;a class=&#34;anchor&#34; href=&#34;#0704sc%c3%a9narios-corsicamon&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La page « &lt;em&gt;Pokédex corse (Corsicadex)&lt;/em&gt; » de l&amp;rsquo;Igoristan. Saisie d&amp;rsquo;API key, tirage au sort avec 1/5 d&amp;rsquo;échec, &lt;code&gt;transient_errors&lt;/code&gt; pour &lt;code&gt;Error(&#39;lol&#39;)&lt;/code&gt;, et retries internes au POM face aux états « &lt;em&gt;Corsicamons chargés / erreur réseau&lt;/em&gt; ».&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;campagne&#34;&gt;Campagne&lt;a class=&#34;anchor&#34; href=&#34;#campagne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/corsicamon.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_corsicamon_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#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; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_corsicamon_happy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_corsicamon_unhappy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#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;create_igoristan_corsicamon_smoke_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#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; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon - Smoke&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[create_corsicamon_smoke_tests_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool)],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;smoke-test&#34;&gt;Smoke test&lt;a class=&#34;anchor&#34; href=&#34;#smoke-test&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# tests/scenarios/corsicamon/enter_api_key.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;scenario_enter_api_key_smoke&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; CorsicamonEnterApiKeyPage(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;color:#ff79c6&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, open_corsicamon_page)&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;            act(page, verify_enter_api_key_screen)&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;    ]&#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;test_enter_api_key_smoke &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_selenium_test(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Corsicamon - Smoke - enter API key screen visible&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_scenario&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; driver, logger: Scenario(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;scenario_enter_api_key_smoke(driver, logger)&#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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Vérifie juste que la page d&amp;rsquo;entrée d&amp;rsquo;API key s&amp;rsquo;affiche. Si elle ne s&amp;rsquo;affiche pas → smoke fail → main campaigns skippées.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.05 — Scénarios randomness (4 levels)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/05-scenarios-randomness/</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/05-scenarios-randomness/</guid>
      <description>&lt;h1 id=&#34;0705scénarios-randomness-4-levels&#34;&gt;07.05 — Scénarios randomness (4 levels)&lt;a class=&#34;anchor&#34; href=&#34;#0705sc%c3%a9narios-randomness-4-levels&#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;Quatre niveaux de chaos progressifs. On commence simple, on monte en complexité, on stresse de plus en plus Ocarina.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;campagne&#34;&gt;Campagne&lt;a class=&#34;anchor&#34; href=&#34;#campagne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/randomness.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_randomness_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#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; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Randomness&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[create_randomness_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool)],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;suite&#34;&gt;Suite&lt;a class=&#34;anchor&#34; href=&#34;#suite&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/suites/randomness.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_randomness_test_suite&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestSuite:&#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; TestSuite(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Randomness&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tests&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;            test_random_error_page,                     &lt;span style=&#34;color:#6272a4&#34;&gt;# level 1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_random_loaders_page,                   &lt;span style=&#34;color:#6272a4&#34;&gt;# level 1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_dsed,                                  &lt;span style=&#34;color:#6272a4&#34;&gt;# level 2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_madness,                               &lt;span style=&#34;color:#6272a4&#34;&gt;# level 2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_chaotic_form,                          &lt;span style=&#34;color:#6272a4&#34;&gt;# level 3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_walkthrough,                           &lt;span style=&#34;color:#6272a4&#34;&gt;# level 4&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;        drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;levels&#34;&gt;Levels&lt;a class=&#34;anchor&#34; href=&#34;#levels&#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;Level&lt;/th&gt;&#xA;          &lt;th&gt;Mécanique exercée&lt;/th&gt;&#xA;          &lt;th&gt;Difficulté&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;1&lt;/td&gt;&#xA;          &lt;td&gt;DSL de base (&lt;code&gt;drive_page&lt;/code&gt;, &lt;code&gt;act&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;facile&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page&lt;/code&gt; / &lt;code&gt;when&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;moyen&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;3&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;HumanizedDriver&lt;/code&gt; + &lt;code&gt;Watcher&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;difficile&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;Walkthrough multi-pages chaotiques&lt;/td&gt;&#xA;          &lt;td&gt;difficile&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;level-1random-error-page&#34;&gt;Level 1 — Random Error Page&lt;a class=&#34;anchor&#34; href=&#34;#level-1random-error-page&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# tests/scenarios/randomness/level_1/random_error_page.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;scenario_random_error_page&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; RandomErrorPage(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;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, open_random_error_page)&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;            act(page, verify_random_error_page)&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;    ]&#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;test_random_error_page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_selenium_test(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Random Error Page - smoke&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_scenario&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; driver, logger: Scenario(test_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;scenario_random_error_page(driver, logger)),&#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;Test volontairement &lt;em&gt;flaky&lt;/em&gt;, exerce le hook &lt;code&gt;on_failure&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.06 — Scénarios sacred upload</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/06-scenarios-sacred-upload/</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/06-scenarios-sacred-upload/</guid>
      <description>&lt;h1 id=&#34;0706scénarios-sacred-upload&#34;&gt;07.06 — Scénarios sacred upload&lt;a class=&#34;anchor&#34; href=&#34;#0706sc%c3%a9narios-sacred-upload&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;La page &lt;code&gt;/igoristan/sacred-upload&lt;/code&gt; permet de glisser-déposer un fichier. Exerce le pattern d&amp;rsquo;&lt;strong&gt;upload de fichiers&lt;/strong&gt; côté Ocarina.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;campagne&#34;&gt;Campagne&lt;a class=&#34;anchor&#34; href=&#34;#campagne&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/tests/campaigns/sacred_upload.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create_igoristan_sacred_upload_campaign&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, drivers_pool) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TestCampaign:&#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; TestCampaign(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Sacred upload&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        suites&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;[&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_sacred_upload_happy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            create_sacred_upload_unhappy_paths_test_suite(drivers_pool&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;drivers_pool),&#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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;scénario-upload-files&#34;&gt;Scénario : upload files&lt;a class=&#34;anchor&#34; href=&#34;#sc%c3%a9nario-upload-files&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# tests/scenarios/sacred_upload/upload_files.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;scenario_upload_files&lt;/span&gt;(driver, logger):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; SacredUploadPage(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;    fixture_dir &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Path(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;__file__&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;parent &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pages&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;sacred_upload&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fixtures&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    file_to_upload &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; fixture_dir &lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;sample.png&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:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            act(page, open_sacred_upload_page)&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;            act(page, verify_sacred_upload_page)&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;            act(page, upload_file(file_to_upload))&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;            act(page, verify_file_uploaded(file_to_upload&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name))&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;    ]&#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;test_upload_files &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; create_selenium_test(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Sacred upload - upload a file&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_scenario&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; driver, logger: Scenario(test_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;scenario_upload_files(driver, logger)),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;input-typefile&#34;&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#input-typefile&#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;SacredUploadPage&lt;/span&gt;(SeleniumTitleMixin, POMBase):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _file_input &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (By&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;CSS_SELECTOR, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;input[type=&amp;#39;file&amp;#39;]&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    _uploaded_preview &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (By&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;CSS_SELECTOR, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.upload-preview&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:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;upload_file&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, file_path: Path) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; SacredUploadPage:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        file_input &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(&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;_file_input)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        file_input&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;send_keys(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;(file_path&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;resolve()))     &lt;span style=&#34;color:#6272a4&#34;&gt;# ← Selenium pattern: send_keys sur input[type=file]&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;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;&#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_file_uploaded&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, filename: &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; SacredUploadPage:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        WebDriverWait(&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, get_timeout())&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;until(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ec&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;text_to_be_present_in_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;_uploaded_preview, filename)&#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;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;strong&gt;pattern Selenium standard&lt;/strong&gt; : &lt;code&gt;send_keys(&amp;lt;path absolu&amp;gt;)&lt;/code&gt; sur un &lt;code&gt;input[type=file]&lt;/code&gt; &lt;em&gt;même si l&amp;rsquo;input est invisible&lt;/em&gt;. Selenium contourne la sécurité du navigateur pour les inputs file uniquement.&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>07.08 — Cache L1 &#43; clés réservées &#43; locks Redis distribués</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/08-caches-locks/</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/08-caches-locks/</guid>
      <description>&lt;h1 id=&#34;0708cache-l1--clés-réservées--locks-redis-distribués&#34;&gt;07.08 — Cache L1 + clés réservées + locks Redis distribués&lt;a class=&#34;anchor&#34; href=&#34;#0708cache-l1--cl%c3%a9s-r%c3%a9serv%c3%a9es--locks-redis-distribu%c3%a9s&#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;Mécanique de coordination interne : un cache in-memory pour partager des valeurs &lt;em&gt;au sein d&amp;rsquo;un test&lt;/em&gt;, un lock Redis pour sérialiser un click &lt;em&gt;entre plusieurs workers&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;cache-l1&#34;&gt;Cache L1&lt;a class=&#34;anchor&#34; href=&#34;#cache-l1&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/caches/l1.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; dogpile.cache &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; make_region&#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;in_memory_cache_with_30m_ttl &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; make_region()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;configure(&#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;dogpile.cache.memory&amp;#34;&lt;/span&gt;, expiration_time&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;30&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;60&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;Un seul cache, global, &lt;code&gt;dogpile.cache.memory&lt;/code&gt; backend, TTL 30 minutes.&lt;/p&gt;&#xA;&lt;p&gt;Choix « &lt;em&gt;memory&lt;/em&gt; » → tout vit en RAM, pas de Redis pour ce cache &lt;em&gt;local&lt;/em&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.09 — catch_me_if_you_can</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/09-watcher-catch-me/</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/09-watcher-catch-me/</guid>
      <description>&lt;h1 id=&#34;0709catch_me_if_you_can&#34;&gt;07.09 — &lt;code&gt;catch_me_if_you_can&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0709catch_me_if_you_can&#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;Détecte les éléments parasites qui pop sur la page &lt;em&gt;pendant&lt;/em&gt; que le test tourne, et les trace.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;callback&#34;&gt;Callback&lt;a class=&#34;anchor&#34; href=&#34;#callback&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/lib/ext/selenium/watchers/catch_me_if_you_can_watcher.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;catch_me_if_you_can_cb&lt;/span&gt;(watcher: SeleniumWatcher) &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;    elements &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_script(&#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;return Array.from(document.querySelectorAll(&amp;#39;.catch-me-if-you-can&amp;#39;));&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; elements:&#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;&#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;    raw &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_script(&#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;&#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;        return arguments[0].map(el =&amp;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;            tag:       el.tagName.toLowerCase(),&#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;            text:      el.innerText.trim(),&#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;            id:        el.id,&#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;            cls:       el.className,&#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;            name:      el.getAttribute(&amp;#39;name&amp;#39;) || &amp;#39;&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;            testid:    el.getAttribute(&amp;#39;data-testid&amp;#39;) || &amp;#39;&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;        }));&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;&#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;for&lt;/span&gt; attrs &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; raw:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fingerprint &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;:&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&#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;filter&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;None&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;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tag&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;cls&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;testid&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#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; fingerprint &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;cache:&#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;continue&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;        watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;cache&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add(fingerprint)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;report(&#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;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;catch-me-if-you-can element detected: &amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;tag&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;attrs[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;text&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color:#f1fa8c&#34;&gt;!r}&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;            label&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;CATCH_ME_IF_YOU_CAN&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;mécaniques&#34;&gt;Mécaniques&lt;a class=&#34;anchor&#34; href=&#34;#m%c3%a9caniques&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-javascript-pour-bypass-implicit-wait&#34;&gt;1. Javascript pour bypass implicit wait&lt;a class=&#34;anchor&#34; href=&#34;#1-javascript-pour-bypass-implicit-wait&#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;elements &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; watcher&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;driver&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_script(&#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;return Array.from(document.querySelectorAll(&amp;#39;.catch-me-if-you-can&amp;#39;));&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;Pas de &lt;code&gt;find_elements(By.CSS_SELECTOR, ...)&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.10 — EnvGetters</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/10-env-getters/</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/10-env-getters/</guid>
      <description>&lt;h1 id=&#34;0710envgetters&#34;&gt;07.10 — &lt;code&gt;EnvGetters&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0710envgetters&#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;Accesseur &lt;strong&gt;typé&lt;/strong&gt; aux variables d&amp;rsquo;environnement. Évite les typos, refuse les clés inconnues à la compilation, apporte l&amp;rsquo;autocomplétion dans l&amp;rsquo;IDE.&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:#6272a4&#34;&gt;# src/lib/ext/ocarina/adapters/agnostic/env_getters.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; typing &lt;span style=&#34;color:#ff79c6&#34;&gt;import&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:#ff79c6&#34;&gt;from&lt;/span&gt; types &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; MappingProxyType&#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;from&lt;/span&gt; ocarina.opinionated.infra.env &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; EnvGetters, Effects&#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:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; _CredsKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; _ValuesKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;igor_api_key&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;redis_url&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_load_env&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;from&lt;/span&gt; dotenv &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; load_dotenv&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    load_dotenv()&#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;_DEFAULT_EFFECTS &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (_load_env,)&#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;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_EnvGetters&lt;/span&gt;(EnvGetters[_CredsKeys, _ValuesKeys]):&#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;, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, effects: Effects) &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;for&lt;/span&gt; effect &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; effects:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            effect()&#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;super&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;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            credentials&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;: MappingProxyType({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_USERNAME&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;password&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_PASSWORD&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            values&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;igor_api_key&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;IGOR_API_KEY&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;redis_url&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;REDIS_URL&amp;#34;&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#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;create_env_getters&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, effects: Effects &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; _EnvGetters:&#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; effects &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;        effects &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _DEFAULT_EFFECTS&#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; _EnvGetters(effects&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;effects)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;_credskeys-et-_valueskeys&#34;&gt;&lt;code&gt;_CredsKeys&lt;/code&gt; et &lt;code&gt;_ValuesKeys&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#_credskeys-et-_valueskeys&#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; _CredsKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; _ValuesKeys &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Literal[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;igor_api_key&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;redis_url&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Credentials&lt;/strong&gt; : paires login/password (dict immutable via &lt;code&gt;MappingProxyType&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Values&lt;/strong&gt; : valeurs brutes.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;mappingproxytype&#34;&gt;&lt;code&gt;MappingProxyType&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#mappingproxytype&#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:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;: MappingProxyType({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;login&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_USERNAME&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;password&amp;#34;&lt;/span&gt;: os&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;environ[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DASH_PASSWORD&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;&lt;code&gt;MappingProxyType&lt;/code&gt; est un wrapper read-only autour d&amp;rsquo;un dict :&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.11 — CI</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/07-ocarina-example/11-ci/</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/11-ci/</guid>
      <description>&lt;h1 id=&#34;0711ci&#34;&gt;07.11 — CI&lt;a class=&#34;anchor&#34; href=&#34;#0711ci&#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;Trois workflows : &lt;code&gt;main_ci.yml&lt;/code&gt; (rapide, lint + typecheck), &lt;code&gt;dev_ci.yml&lt;/code&gt; (idem pour branches dev/feature/fix), &lt;code&gt;e2e.yml&lt;/code&gt; (manuel, service Redis + Firefox).&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;Plateforme&lt;/th&gt;&#xA;          &lt;th&gt;Effet&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push/PR main&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + windows × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;make check-coding-style&lt;/code&gt; (mypy + ruff). &lt;em&gt;Pas de browser.&lt;/em&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;dev_ci.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;push dev / feature/_ / fix/_&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu × py 3.14&lt;/td&gt;&#xA;          &lt;td&gt;id., mais Ubuntu seul&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;e2e.yml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; (manuel)&lt;/td&gt;&#xA;          &lt;td&gt;ubuntu + service Redis + Firefox + geckodriver&lt;/td&gt;&#xA;          &lt;td&gt;Run complet de la suite e2e. Upload &lt;code&gt;.screenshots/&lt;/code&gt;, &lt;code&gt;.ocarina_logs/&lt;/code&gt;, &lt;code&gt;.reports/&lt;/code&gt;.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;main_ciyml&#34;&gt;&lt;code&gt;main_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#main_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;: Main branch 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;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;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;test&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;: ${{ matrix.os }}&#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;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;os&lt;/span&gt;: [ubuntu-latest, windows-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;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;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@v6&#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@v6&#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;: { &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;uses&lt;/span&gt;: actions/cache@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;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 }}-py3.14-${{ 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&#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;: make install-on-ci&#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;: Check coding style&#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;          . .venv/bin/activate&#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;          make check-coding-style&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Matrice OS&lt;/strong&gt; : ubuntu + windows. Vérifie la portabilité Python du code projet.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pas de browser&lt;/strong&gt; : on lance juste &lt;code&gt;make check-coding-style&lt;/code&gt; (= &lt;code&gt;mypy + ruff&lt;/code&gt;). Rapide, pas besoin de Selenium / Redis / OTP.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Cache &lt;code&gt;.venv&lt;/code&gt;&lt;/strong&gt; : reuse entre runs.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;dev_ciyml&#34;&gt;&lt;code&gt;dev_ci.yml&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#dev_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;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;: [dev, &amp;#34;feature/**&amp;#34;, &amp;#34;fix/**&amp;#34;]&#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;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;test&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;    &lt;span style=&#34;color:#6272a4&#34;&gt;# … identique à main_ci.yml, juste Ubuntu seul …&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Identique au &lt;code&gt;main_ci.yml&lt;/code&gt; mais Ubuntu seul (Windows ne run que sur main).&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
