<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Watcher on Ocarina — manual</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/tags/watcher/</link>
    <description>Recent content in Watcher on Ocarina — manual</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    <lastBuildDate>Tue, 02 Jun 2026 14:06:55 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/tags/watcher/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>08.01 — Code: 99% Claude, 1% Igor, intelligence: 50-50</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/08-ai-example/01-ai-manifesto/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/08-ai-example/01-ai-manifesto/</guid>
      <description>&lt;h1 id=&#34;0801code-99-claude-1-igor-intelligence-50-50&#34;&gt;08.01 — Code: 99% Claude, 1% Igor, intelligence: 50-50&lt;a class=&#34;anchor&#34; href=&#34;#0801code-99-claude-1-igor-intelligence-50-50&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;readme&#34;&gt;README&lt;a class=&#34;anchor&#34; href=&#34;#readme&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;h2 id=&#34;a-note-to-the-reader&#34;&gt;A note to the reader&lt;a class=&#34;anchor&#34; href=&#34;#a-note-to-the-reader&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;This project is an experiment in AI-driven test engineering. In the interest of honesty about how it was built:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Claude Code&lt;/th&gt;&#xA;          &lt;th&gt;Human&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Code written&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;99%&lt;/td&gt;&#xA;          &lt;td&gt;1%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Intelligence&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;50%&lt;/td&gt;&#xA;          &lt;td&gt;50%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Almost every line was machine-written. The judgement behind it — what to test, what to distrust, when to dig and when to stop — was shared.&lt;/p&gt;</description>
    </item>
    <item>
      <title>09.03.01 — Review skills</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/09-holy-book/03-skills/01-review/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/09-holy-book/03-skills/01-review/</guid>
      <description>&lt;h1 id=&#34;090301review-skills&#34;&gt;09.03.01 — Review skills&lt;a class=&#34;anchor&#34; href=&#34;#090301review-skills&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;strong&gt;Static&lt;/strong&gt; reads, surface findings. A large family. Lets the AI &lt;em&gt;review&lt;/em&gt; its project systematically.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;listing-potentially-non-exhaustive&#34;&gt;Listing (potentially non-exhaustive)&lt;a class=&#34;anchor&#34; href=&#34;#listing-potentially-non-exhaustive&#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;Skill&lt;/th&gt;&#xA;          &lt;th&gt;Target&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;review-spec-gaps&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Clarification questions on the FRDs&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-watcher-misuse&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Verifies the &amp;ldquo;&lt;em&gt;negative-only&lt;/em&gt;&amp;rdquo; principle of &lt;code&gt;watcher.report(...)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-compartmentalisation-leaks&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Detects URLs, selectors, magic numbers in wrong places&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-dead-code&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Detects unused connectors / POMs / scenarios / suites / fragments / constants&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-report&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Classifies each FAIL / SKIP of an execution&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-type-ignore&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audits &lt;code&gt;# type: ignore&lt;/code&gt;s (are they justified?)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-match-candidates&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Spots places where a &lt;code&gt;match&lt;/code&gt; (Python, &lt;em&gt;pattern matching&lt;/em&gt;) could be used instead of &lt;code&gt;if&lt;/code&gt; &lt;code&gt;elif&lt;/code&gt; &lt;code&gt;elif&lt;/code&gt; &lt;code&gt;if&lt;/code&gt; &lt;code&gt;if&lt;/code&gt; &lt;code&gt;elif&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-unverified-transitions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Verifies that every &lt;em&gt;page transition&lt;/em&gt; has a &lt;code&gt;verify&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-submit-dispatchers&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audits input-confirmation methods (click vs enter key&amp;hellip;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-comment-drift&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Detects comments that have drifted from the code&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-suite-stability&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Evaluates a suite&amp;rsquo;s stability (proportion of retries, transient_errors hits)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-intent-collisions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Detects tests that overwrite each other (conflicting intents) and asks/proposes clarifications&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-watcher-emissions&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audits watcher emissions (volume, dedup, relevance)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;review-hierarchy-naming&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Audits the test-hierarchy naming (&lt;code&gt;TestCycle&lt;/code&gt; / &lt;code&gt;TestCampaign&lt;/code&gt; / &lt;code&gt;TestSuite&lt;/code&gt; / &lt;code&gt;Test&lt;/code&gt;) for the lazy-naming antipattern where a child carries the parent&amp;rsquo;s name&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;review-dead-code&#34;&gt;&lt;code&gt;review-dead-code&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-dead-code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : test base&#xA;output : list of unused elements (connectors / POMs / scenarios / fragments / constants)&#xA;         + per-element recommendation:&#xA;            - delete&#xA;            - move to incubator (&amp;lt;root-source&amp;gt;/incubator/, dependency tree preserved)&#xA;            - keep (justify)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;review-report&#34;&gt;&lt;code&gt;review-report&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-report&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : a recent execution (logs + reports)&#xA;output : per-test classification:&#xA;            - PASS                  (nothing to do)&#xA;            - SKIP                  (why?)&#xA;            - intentional gap FAIL  (G-DATA-*, G-SEC-*, ...)&#xA;            - cross-browser FAIL    (B-BROWSER-*)&#xA;            - transient FAIL        (A-ENV-*)&#xA;            - regression            ⚠️ ALERT&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;review-watcher-misuse&#34;&gt;&lt;code&gt;review-watcher-misuse&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-watcher-misuse&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : all watcher.report(...) calls&#xA;output : list of reports that look positive (&amp;#34;success&amp;#34;, &amp;#34;completed&amp;#34;, &amp;#34;ok&amp;#34;, ...)&#xA;         → recommendation: remove or rephrase as negative&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;review-comment-drift&#34;&gt;&lt;code&gt;review-comment-drift&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#review-comment-drift&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input  : every code comment&#xA;output : list of comments that no longer match the adjacent code&#xA;         (typical: comment mentions foo, code mentions bar)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Helps remove stale comments.&lt;br&gt;&#xA;&amp;ldquo;&lt;em&gt;Teach the pattern, not the symptom&lt;/em&gt;.&amp;rdquo;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.05.02 — TestExecutor[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/02-ocarina/05-orchestration/02-test-executor/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/02-ocarina/05-orchestration/02-test-executor/</guid>
      <description>&lt;h1 id=&#34;020502testexecutordriver&#34;&gt;02.05.02 — &lt;code&gt;TestExecutor[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020502testexecutordriver&#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;Source file: &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/internals/test_executor.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/internals/test_executor.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;strong&gt;One job&lt;/strong&gt;: run &lt;strong&gt;one attempt&lt;/strong&gt; of a test with &lt;strong&gt;one driver&lt;/strong&gt;. Knows nothing about retries, the driver pool, or suite-level aggregation.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;executionoutcome&#34;&gt;&lt;code&gt;ExecutionOutcome&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#executionoutcome&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;, slots&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;ExecutionOutcome&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result: TestResult&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    skipped: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setup_failed: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    should_retry: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;bool&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    steps_count: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Field&lt;/th&gt;&#xA;          &lt;th&gt;Type&lt;/th&gt;&#xA;          &lt;th&gt;Meaning&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;result&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;TestResult&lt;/code&gt; = &lt;code&gt;Result[Any] | None&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;The chain&amp;rsquo;s result (Ok, Fail), or &lt;code&gt;None&lt;/code&gt; if skip / setup_failed.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;skipped&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;True if &lt;code&gt;test_runner.skipped&lt;/code&gt; (i.e. &lt;code&gt;Test(skipped=True)&lt;/code&gt;).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;setup_failed&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;True if the scenario&amp;rsquo;s &lt;code&gt;setup()&lt;/code&gt; raised.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;should_retry&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;True if the &lt;em&gt;retry rule&lt;/em&gt; applies (transient_error detected).&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;steps_count&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Number of &lt;code&gt;act&lt;/code&gt; calls; &lt;code&gt;-1&lt;/code&gt; if skip or setup_failed.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;slots=True&lt;/code&gt;&lt;/strong&gt;: blocks dynamic attribute addition, saves memory. This object rides the hot path — &lt;code&gt;slots&lt;/code&gt; earns its keep.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;frozen=True&lt;/code&gt;&lt;/strong&gt;: immutable, safe to share across threads.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;@final&lt;/code&gt;&lt;/strong&gt;: no inheritance.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;execution-order-for-one-attempt&#34;&gt;Execution order for one attempt&lt;a class=&#34;anchor&#34; href=&#34;#execution-order-for-one-attempt&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────┐&#xA;│  test_runner = test.spawn(driver, logger_with_taxonomy)              │&#xA;└──────────────────────────────┬───────────────────────────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  test_runner.skipped ?          │── True ─► return Outcome(skipped=True, ...)&#xA;              └────────────────┬────────────────┘&#xA;                               │ False&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  logger.test_name(test.name)    │   (annotation)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  setup() (if not None)          │── raises ─► teardown() (if not None)&#xA;              └────────────────┬────────────────┘            ↓&#xA;                               │                  return Outcome(setup_failed=True, should_retry=True, ...)&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  watchers.start(driver, logger, │&#xA;              │                 take_screenshot)│   (1 daemon thread per watcher)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  _run_chain(chain_runners, ...) │── returns (result, should_retry)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  watchers.stop()                │   (always)&#xA;              └────────────────┬────────────────┘&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  teardown() (if not None)       │   (ALWAYS, even if chain failed)&#xA;              └────────────────┬────────────────┘     (exceptions are logged &amp;amp; swallowed)&#xA;                               │&#xA;                               ▼&#xA;              ┌─────────────────────────────────┐&#xA;              │  steps_count = act_counter.get()│&#xA;              │  return Outcome(...)            │&#xA;              └─────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;execute&#34;&gt;&lt;code&gt;execute&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#execute&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;execute&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;, test: Test[Driver], &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;    driver: Driver,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    taxonomy: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;tuple&lt;/span&gt;[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger_with_taxonomy: ILogger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger_without_taxonomy: ILogger,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    attempt: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;, max_attempts: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ExecutionOutcome:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_runner &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;spawn(driver, logger_with_taxonomy)&#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; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;skipped:&#xA;&lt;/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; ExecutionOutcome(result&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, skipped&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;, setup_failed&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                should_retry&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;, steps_count&lt;span style=&#34;color:#ff79c6&#34;&gt;=-&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger_without_taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;test_name(test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;setup &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&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;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;setup()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; exc:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            msg &lt;span style=&#34;color:#ff79c6&#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;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt; -- Setup failed (attempt &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;attempt&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;max_attempts&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;): &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;exc&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;            logger_with_taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;warning(msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&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;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_run_teardown(test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_with_taxonomy)&#xA;&lt;/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; ExecutionOutcome(result&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;, skipped&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;, setup_failed&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                    should_retry&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;, steps_count&lt;span style=&#34;color:#ff79c6&#34;&gt;=-&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    watchers: Sequence[Watcher[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;watchers &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_start_watchers(watchers, driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;taxonomy)&#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;    result, should_retry &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;_run_chain(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;chain_runners, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, attempt&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;attempt,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        driver&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;driver, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_without_taxonomy,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        logger_with_taxonomy&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_with_taxonomy, max_attempts&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;max_attempts,&#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;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_stop_watchers(watchers)&#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; test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&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;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_run_teardown(test_runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;teardown, test_name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;test&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name, logger&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;logger_with_taxonomy)&#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;    steps_count &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;_act_counter&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get()&#xA;&lt;/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; ExecutionOutcome(result&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;result, skipped&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;, setup_failed&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;False&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            should_retry&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;should_retry, steps_count&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;steps_count)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;two-loggers-why&#34;&gt;Two loggers: why&lt;a class=&#34;anchor&#34; href=&#34;#two-loggers-why&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;logger_with_taxonomy&lt;/code&gt; vs &lt;code&gt;logger_without_taxonomy&lt;/code&gt;:&lt;/p&gt;</description>
    </item>
    <item>
      <title>05.02 — Igoristan routes</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/05-igoristan/02-routes/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/05-igoristan/02-routes/</guid>
      <description>&lt;h1 id=&#34;0502igoristan-routes&#34;&gt;05.02 — Igoristan routes&lt;a class=&#34;anchor&#34; href=&#34;#0502igoristan-routes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;h2 id=&#34;srcconfigroutests&#34;&gt;&lt;code&gt;src/config/routes.ts&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#srcconfigroutests&#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-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; __ROOT &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/igoristan/&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;const&lt;/span&gt; DASHBOARD &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashboard&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; __ROUTES &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;  DONKEY_SAUSAGE_DETECTOR&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;donkey-sausage-eater-detector&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DASHBOARD_NESTED&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;DASHBOARD&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/nested`&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  RANDOM_LOADERS&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;random-loaders&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  SACRED_UPLOAD&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;  RANDOM_ERROR&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;random-error&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  CHAOTIC_FORM&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;chaotic-form&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  CORSICAMON&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;  MADNESS&lt;span style=&#34;color:#ff79c6&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;madness&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DASHBOARD,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  HOME&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;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; satisfies Routes;&#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;const&lt;/span&gt; ROUTES &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; createRoutes(__ROUTES, __ROOT);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Route&lt;/th&gt;&#xA;          &lt;th&gt;URL&lt;/th&gt;&#xA;          &lt;th&gt;Role&lt;/th&gt;&#xA;          &lt;th&gt;Deliberate chaos&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;HOME&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Home with links to other pages, autoplay audio&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;RANDOM_LOADERS&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/random-loaders&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pseudo-random loaders&lt;/td&gt;&#xA;          &lt;td&gt;Loaders that stay visible for a variable time&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;SACRED_UPLOAD&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/sacred-upload&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;react-dropzone&lt;/code&gt; upload form&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;DASHBOARD&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/dashboard&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Login (password &lt;code&gt;figatellu&lt;/code&gt;), optional MFA OTP&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;useAuth&lt;/code&gt; may fail 10% of the time even with the right pwd&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;DASHBOARD_NESTED&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/dashboard/nested&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Protected page, requires MFA&lt;/td&gt;&#xA;          &lt;td&gt; — &lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CORSICAMON&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/corsicamon&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&amp;ldquo;&lt;em&gt;Corsican Pokédex&lt;/em&gt;&amp;rdquo;, 3 random picks via API key&lt;/td&gt;&#xA;          &lt;td&gt;1/5 chance of artificially raising &lt;code&gt;Error(&#39;lol&#39;)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;RANDOM_ERROR&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/random-error&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fake error page&lt;/td&gt;&#xA;          &lt;td&gt;Title matched by &lt;code&gt;ERROR_PAGE_REGEX&lt;/code&gt; on the test side&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CHAOTIC_FORM&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/chaotic-form&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Unstable form&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.catch-me-if-you-can&lt;/code&gt; elements appear at random&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;MADNESS&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/madness&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Alternating stories (Cors, ThisIsBastia)&lt;/td&gt;&#xA;          &lt;td&gt;Renders one OR the other at random&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;DONKEY_SAUSAGE_DETECTOR&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/igoristan/donkey-sausage-eater-detector&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Detector for filthy Sicilian shits&lt;/td&gt;&#xA;          &lt;td&gt;30% random fail (BSOD-style)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;page-by-page&#34;&gt;Page by page&lt;a class=&#34;anchor&#34; href=&#34;#page-by-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-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; links &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;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.RANDOM_LOADERS&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;Random loaders&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.SACRED_UPLOAD&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;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;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.DASHBOARD&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;Dashboard&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.CORSICAMON&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;Corsicamon&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.RANDOM_ERROR&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;Random Error&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.CHAOTIC_FORM&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;Chaotic form&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.MADNESS&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;Madness&amp;#34;&lt;/span&gt; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { href: &lt;span style=&#34;color:#8be9fd&#34;&gt;ROUTES.DONKEY_SAUSAGE_DETECTOR&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;Donkey Sausage Detector&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;h3 id=&#34;home&#34;&gt;home&lt;a class=&#34;anchor&#34; href=&#34;#home&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;8 links to the other pages + autoplay audio Angelus of Jerusalem (amen 🙏).&lt;/p&gt;</description>
    </item>
    <item>
      <title>03.03 — Lazy evaluation</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/03-functional/03-lazy-evaluation/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/03-functional/03-lazy-evaluation/</guid>
      <description>&lt;h1 id=&#34;0303lazy-evaluation&#34;&gt;03.03 — Lazy evaluation&lt;a class=&#34;anchor&#34; href=&#34;#0303lazy-evaluation&#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;In Ocarina, &lt;strong&gt;nothing is executed until you explicitly trigger it&lt;/strong&gt;. That&amp;rsquo;s what makes scenarios composable as values.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;zzz&#34;&gt;Zzz&lt;a class=&#34;anchor&#34; href=&#34;#zzz&#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;Location&lt;/th&gt;&#xA;          &lt;th&gt;Form&lt;/th&gt;&#xA;          &lt;th&gt;Trigger&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;ChainRunner[T]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Thunk[ActionChain[T]]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;runner.run()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate(...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ValidationStartBlock&lt;/code&gt; / &lt;code&gt;ValidationAssertBlock&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.execute()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;match_page(...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;returns a &lt;code&gt;ChainRunner[Any]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;.run()&lt;/code&gt; (via the surrounding chain)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Watcher.callback&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[Watcher], None]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;_loop&lt;/code&gt; when &lt;code&gt;start()&lt;/code&gt; is called&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;logger.set_prefix(thunk)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Thunk[str]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;recomputed at each log call&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Scenario.setup&lt;/code&gt; / &lt;code&gt;teardown&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Effect&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;called by &lt;code&gt;TestExecutor&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;bootstrap(post_exec=...)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[TestCycleResults], None]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;called after &lt;code&gt;run_plugins&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CliBuilder(effects_factory=lambda ns: (...))&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Effects&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;called after the argparse parse&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;test_scenario: TestScenario[Driver]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Callable[[Driver, ILogger], Scenario[Driver]]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;called by &lt;code&gt;Test.spawn&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;dispatch[mode]()&lt;/code&gt; in &lt;code&gt;TestCycle.run_all&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;dict of &lt;code&gt;Thunk[bool]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;called on lookup&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;chainrunner&#34;&gt;&lt;code&gt;ChainRunner&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#chainrunner&#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;runner &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; drive_page(act1, act2, act3)        &lt;span style=&#34;color:#6272a4&#34;&gt;# ⚠️  nothing executed&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;# … later …&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;run()                         &lt;span style=&#34;color:#6272a4&#34;&gt;# ▶︎  execution&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Capability&lt;/th&gt;&#xA;          &lt;th&gt;Without laziness&lt;/th&gt;&#xA;          &lt;th&gt;With laziness&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Store a scenario in a variable&lt;/td&gt;&#xA;          &lt;td&gt;impossible (already executed)&lt;/td&gt;&#xA;          &lt;td&gt;trivial&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Multiply &lt;code&gt;[runner] * 5&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;executes once, you get 5 refs to the result&lt;/td&gt;&#xA;          &lt;td&gt;executes 5 times&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Pass a &lt;code&gt;runner&lt;/code&gt; to another &lt;code&gt;runner&lt;/code&gt; (composition)&lt;/td&gt;&#xA;          &lt;td&gt;impossible&lt;/td&gt;&#xA;          &lt;td&gt;trivial&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Reorder &lt;code&gt;act&lt;/code&gt;s in a test refactor&lt;/td&gt;&#xA;          &lt;td&gt;hard&lt;/td&gt;&#xA;          &lt;td&gt;trivial&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;validateexecute&#34;&gt;&lt;code&gt;validate(...).execute()&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#validateexecute&#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;v &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; validate(value, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_positive)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_not_zero)&#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;# … you can compose …&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;combined &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; chain_validations(v, other_validation)&#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;# … nothing executed so far …&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;combined&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()        &lt;span style=&#34;color:#6272a4&#34;&gt;# ▶︎  execution + aggregation&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s how &lt;code&gt;_ValidationChain&lt;/code&gt; &lt;strong&gt;collects&lt;/strong&gt; every error before raising a single one (&lt;code&gt;AggregateInvariantViolationError&lt;/code&gt;).&lt;/p&gt;</description>
    </item>
    <item>
      <title>07.05 — Randomness scenarios (4 levels)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/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/07-ocarina-example/05-scenarios-randomness/</guid>
      <description>&lt;h1 id=&#34;0705randomness-scenarios-4-levels&#34;&gt;07.05 — Randomness scenarios (4 levels)&lt;a class=&#34;anchor&#34; href=&#34;#0705randomness-scenarios-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;Four progressive levels of chaos. We start simple, ramp up complexity, stress Ocarina more and more.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;campaign&#34;&gt;Campaign&lt;a class=&#34;anchor&#34; href=&#34;#campaign&#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;Mechanic exercised&lt;/th&gt;&#xA;          &lt;th&gt;Difficulty&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;Base DSL (&lt;code&gt;drive_page&lt;/code&gt;, &lt;code&gt;act&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;easy&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;medium&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;hard&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;4&lt;/td&gt;&#xA;          &lt;td&gt;Multi-page chaotic walkthrough&lt;/td&gt;&#xA;          &lt;td&gt;hard&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;Deliberately flaky — exercises the &lt;code&gt;on_failure&lt;/code&gt; hook.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.06 — Scenario[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/02-ocarina/06-scenario/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/02-ocarina/06-scenario/</guid>
      <description>&lt;h1 id=&#34;0206scenariodriver&#34;&gt;02.06 — &lt;code&gt;Scenario[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0206scenariodriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Source file: &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/custom_types/scenario.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/custom_types/scenario.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;dataclass&#34;&gt;Dataclass&lt;a class=&#34;anchor&#34; href=&#34;#dataclass&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Scenario&lt;/span&gt;[Driver]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    test_chain: TestChain&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    setup: TestSetup &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    teardown: TestTeardown &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    watchers: TestWatchers[Driver] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/custom_types/test_components.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestChain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Sequence[ChainRunner[Any]]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestSetup &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestTeardown &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; TestWatchers[Driver] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Sequence[Watcher[Driver]] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;lifecycle-per-attempt&#34;&gt;Lifecycle (per attempt)&lt;a class=&#34;anchor&#34; href=&#34;#lifecycle-per-attempt&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. setup()           — optional, free-form Effect (DB, API, …)&#xA;       → raises    :  test_chain skipped, jump to teardown,&#xA;                      return Outcome(setup_failed=True, should_retry=True)&#xA;       → ok        :  continue to test_chain&#xA;&#xA;2. test_chain        — the actual chain (Sequence[ChainRunner])&#xA;       (watchers run during this time)&#xA;&#xA;3. teardown()        — optional, always executed&#xA;       → raises    :  log warning + ignore (does not affect the verdict)&#xA;&#xA;If EVERY attempt raises during setup:&#xA;    → test marked SKIPPED (not FAILED)&#xA;    → warning log « setup keeps failing »&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;setup-and-teardown&#34;&gt;&lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;teardown&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#setup-and-teardown&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;teardown&lt;/code&gt; are &lt;strong&gt;driver-free and injection-free by design.&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.07 — Watcher[Driver]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/02-ocarina/07-watcher/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/02-ocarina/07-watcher/</guid>
      <description>&lt;h1 id=&#34;0207watcherdriver&#34;&gt;02.07 — &lt;code&gt;Watcher[Driver]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#0207watcherdriver&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Source file: &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing/watcher.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing/watcher.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Parallelized observer running as a &lt;em&gt;daemon thread&lt;/em&gt; alongside the &lt;code&gt;test_chain&lt;/code&gt;. Built to catch &lt;strong&gt;unpredictable frictions with no direct impact on the scenario&lt;/strong&gt; that pop up &lt;em&gt;while&lt;/em&gt; the scenario runs: random error toasts, parasitic validations, unexpected popups.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;the-problem&#34;&gt;The problem&lt;a class=&#34;anchor&#34; href=&#34;#the-problem&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Holy Book quote (&lt;em&gt;First real-world hurdles&lt;/em&gt;):&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Even more surprisingly: apps displaying error toasts for no apparent reason, or forms flagging validation errors on inputs that are actually correct, without blocking the journey.&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/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/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;Detects parasite elements that pop on the page &lt;em&gt;while&lt;/em&gt; the test is running, and traces them.&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;mechanics&#34;&gt;Mechanics&lt;a class=&#34;anchor&#34; href=&#34;#mechanics&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-javascript-to-bypass-implicit-wait&#34;&gt;1. Javascript to bypass implicit wait&lt;a class=&#34;anchor&#34; href=&#34;#1-javascript-to-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;Not &lt;code&gt;find_elements(By.CSS_SELECTOR, ...)&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
