02.02 — The ocarina module tree#
The Python source module ships 134 .py files across four conceptual layers.
src/ocarina/
├── __init__.py # empty — no global "facade", everything is imported precisely
├── py.typed # PEP 561: declares the package as "typed"
│
├── railway/ # Layer 1 — FP primitives
│ ├── __init__.py
│ └── result.py # Ok[T] / Fail / Result[T] / is_ok / is_fail
│
├── custom_types/ # Layer 2 — types and aliases
│ ├── __init__.py
│ ├── effect.py # type Effect = Callable[[], None]
│ ├── thunk.py # type Thunk[T] = Callable[[], T]
│ ├── tpom.py # TypeVar TPOM bound POMBase
│ ├── supports_write.py # Protocol SupportsWrite[T], mirrors a type not exposed by Python stdlib
│ ├── built_web_driver.py # type BuiltWebDriver[Driver] = tuple[Driver, Effect]
│ ├── scenario.py # Scenario[Driver] (frozen dataclass)
│ ├── test_components.py # TestChain / TestSetup / TestTeardown / TestWatchers
│ ├── test_runner.py # TestRunner[Driver]
│ ├── oc_test.py # TestName / TestScenario / TestScenarioFragment
│ ├── oc_test_layers.py # TestResult / TestSuiteResult / TestCampaignResults / TestCycleResults
│ ├── selenium/
│ │ ├── built_web_driver.py # BuiltSeleniumWebDriver
│ │ ├── oc_test_scenario.py
│ │ ├── supported_browsers.py # type SupportedSeleniumBrowser = Literal["chrome", "firefox", "edge", "safari"]
│ │ └── web_drivers_pool.py # type SeleniumWebDriversPool = WebDriversPool[WebDriver]
│ └── playwright/
│ ├── built_web_driver.py # type BuiltPlaywrightDriver = BuiltWebDriver[PlaywrightDriver]
│ ├── oc_test_scenario.py # PlaywrightTestScenario / PlaywrightTestScenarioFragment
│ ├── supported_browsers.py # type SupportedPlaywrightBrowser = Literal["chromium", "firefox", "webkit"]
│ └── web_drivers_pool.py # type PlaywrightDriversPool = WebDriversPool[PlaywrightDriver]
│
├── custom_errors/ # Layer 2: errors
│ ├── __init__.py
│ └── test_framework/
│ ├── __init__.py
│ ├── no_matching_branch.py # NoMatchingBranchError
│ ├── pages.py # PageVerificationError
│ ├── driver_died.py # DriverDiedError
│ └── campaigns.py
│
├── custom_invariants/ # Layer 2: ready-to-use invariants
│ ├── __init__.py
│ └── testing/
│ ├── __init__.py
│ ├── workers.py # validate_workers_amount
│ ├── oc_test_runners_ids.py # validate_test_runners_ids
│ ├── oc_test_runners_names.py # validate_test_runners_names (unique + valid filename)
│ ├── oc_test_suites_names.py
│ ├── oc_test_campaigns_names.py
│ └── oc_test_cycles_names.py
│
├── ports/ # Layer 3: ports (interfaces)
│ ├── __init__.py
│ ├── ilogger.py # ILogger (ABC)
│ └── itake_screenshot.py # ITakeScreenshot[Driver] (Protocol)
│
├── pom/ # Layer 3: Page Object Model
│ ├── __init__.py
│ ├── base.py # POMBase (ABC: verify + get_current_title)
│ ├── selenium/
│ │ ├── __init__.py
│ │ └── muted.py # MutedPOM utility
│ └── playwright/
│ ├── __init__.py
│ └── muted.py # MutedPlaywrightPOM utility
│
├── aggregates/ # Layer 3: result aggregates
│ ├── __init__.py
│ └── tests_layers.py # is_test_result_ok / _fail / _skipped (TypeGuard)
│
├── dsl/ # Layer 4: Ocarina DSL
│ ├── __init__.py
│ ├── invariants/
│ │ ├── __init__.py
│ │ ├── validate.py # entry-point validate(), BusinessInvariantValidator, FrameworkInvariantValidator
│ │ ├── assertions.py # is_str / is_email / is_positive / is_in / has_unique_elements / each / ...
│ │ ├── errors.py # InvariantViolationError / DuplicatesError / AggregateInvariantViolationError
│ │ └── internals/
│ │ ├── __init__.py
│ │ └── validation_chain.py # ValidationStartBlock / ValidationAssertBlock / _ValidationChain / _any_of / chain_validations
│ ├── testing/
│ │ ├── __init__.py
│ │ ├── oc_test.py # class Test[Driver]
│ │ ├── oc_test_suite.py # class TestSuite[Driver]
│ │ ├── oc_test_campaign.py # class TestCampaign[Driver] + campaign_has_failed
│ │ ├── oc_test_cycle.py # class TestCycle[Driver] + type Mode + has_test_cycle_failed
│ │ ├── filter_tests_by_ids.py
│ │ ├── watcher.py # class Watcher[Driver]
│ │ ├── internals/
│ │ │ ├── __init__.py
│ │ │ ├── test_executor.py # class TestExecutor[Driver] + ExecutionOutcome (frozen, slots)
│ │ │ └── test_flow.py # class TestFlow[Driver]
│ │ ├── selenium/
│ │ │ ├── __init__.py
│ │ │ ├── create_test.py # create_selenium_test(...)
│ │ │ └── create_watcher.py # create_selenium_watcher(...) + type SeleniumWatcher
│ │ └── playwright/
│ │ ├── __init__.py
│ │ ├── create_test.py # create_playwright_test(...)
│ │ └── create_watcher.py # create_playwright_watcher(...) + type PlaywrightWatcher
│ └── testing_with_railway/
│ ├── __init__.py
│ ├── chain_actions.py # class ChainRunner[T] + chain_actions
│ ├── match_page.py # When + _match_page_builder + create_match_page
│ ├── constructors/
│ │ ├── __init__.py
│ │ └── create_act.py # low-level primitive (to be wrapped on the project side)
│ └── internals/
│ ├── __init__.py
│ └── action_chain.py # ActionStart → ActionFailure → ActionSuccess → ActionChain
│ # + Neutral{Start,Failure,Success} (failure rail)
│
├── infra/ # Layer 5: infrastructure
│ ├── __init__.py
│ ├── drivers_pool.py # class WebDriversPool[Driver] + WarmupTimeoutError
│ ├── driver_builder.py # class DriverBuilder[Driver]
│ ├── screenshotter.py # class Screenshotter[TDriver] + ScreenshotterConfig
│ ├── act_counter.py # class ActCounter (interface)
│ ├── selenium/
│ │ ├── __init__.py
│ │ ├── create_driver.py # _build_firefox / _build_chrome / _build_edge / _build_safari
│ │ ├── create_drivers_pool.py # create_selenium_drivers_pool
│ │ ├── create_screenshotter.py # create_selenium_screenshotter
│ │ ├── driver_healthcheck.py # driver_healthcheck (pings driver.title)
│ │ └── mixins.py # SeleniumTitleMixin (typing keyway)
│ └── playwright/
│ ├── __init__.py
│ ├── create_driver.py # create_playwright_driver (chromium / firefox / webkit)
│ ├── create_drivers_pool.py # create_playwright_drivers_pool
│ ├── create_screenshotter.py # create_playwright_screenshotter + _playwright_save_full_page
│ ├── driver.py # class PlaywrightDriver (sync wrapper)
│ ├── driver_healthcheck.py # playwright_driver_healthcheck (pings driver.title)
│ └── mixins.py # PlaywrightTitleMixin (typing keyway)
│
└── opinionated/ # Layer 6: opt-in, everything that is "nice but replaceable"
├── __init__.py
├── consts/loggers_choices.py # LOGGERS_CHOICES = ("terminal", "file", "terminal+file", "muted")
├── infra/
│ ├── __init__.py
│ └── act_counter.py # ThreadsBasedActCounter (thread-local counter)
├── cli/
│ ├── __init__.py
│ ├── builder.py # CliBuilder + CliArg + _SilentArgumentParser
│ ├── store.py # CliStore[TKeys] + _CliField[T] + field(...)
│ ├── phantoms.py # phantom_validate (no-op predicate)
│ ├── selenium/
│ │ ├── __init__.py
│ │ ├── cli_store_singleton.py # SeleniumCliStoreSingleton (push / get)
│ │ └── create_cli_store.py # create_selenium_{auto,win,macos,linux}_cli_store
│ └── playwright/
│ ├── __init__.py
│ ├── cli_store_singleton.py # PlaywrightCliStoreSingleton (push / get)
│ └── create_cli_store.py # create_playwright_{,auto_}cli_store
├── dsl/
│ ├── __init__.py
│ └── drive_page.py # drive_page = chain_actions, a semantic alias
├── launcher/
│ ├── __init__.py
│ └── bootstrap.py # bootstrap(...) + run_plugins(*plugins, exceptions_logger)
├── loggers/
│ ├── __init__.py
│ ├── create_matching_logger.py # create_matching_logger("terminal"|"file"|...) + get_default_log_dir
│ ├── print_logger.py # PrintLogger (ANSI)
│ ├── file_logger.py # FileLogger (taxonomy -> file tree)
│ ├── print_and_file_logger.py
│ ├── muted_logger.py
│ ├── custom_types/supported_loggers.py
│ └── utils/
│ ├── __init__.py
│ └── format_metadata_str.py # format_utc_date_metadata_str / format_current_thread_metadata_str / concat_metadata
└── plugins/
├── __init__.py
└── reports/
├── __init__.py
├── pretty_print_results.py # hierarchical ANSI report
├── results_to_json.py # JSON generator
├── docx_tests_proofs.py # DOCX generator (consumes the log tree)
└── timing.py # context manager `with timing(prefix="Duration:")`Layered diagram#
┌──────────────────────────────────────────────────────────────────────────┐
│ USER PROJECT (e.g. ocarina-example, ocarina-with-ai-example) │
│ ┌────────────────────┐ ┌────────────────────┐ ┌─────────────────────┐ │
│ │ pages/ (POMBase) │ │ scenarios/ (Test) │ │ adapters/ (act, ...)│ │
│ └────────────────────┘ └────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────┬────────────────────────────────────────┘
│ imports
┌─────────────────────────────────▼────────────────────────────────────────┐
│ LAYER 6 — opinionated/ (CLI, loggers, plugins, bootstrap) │
│ Opt-in. Can be replaced wholesale. │
└─────────────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────────────▼────────────────────────────────────────┐
│ LAYER 5 — infra/ (pool, builder, screenshotter, Selenium adapters) │
│ I/O. No DSL logic. │
└─────────────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────────────▼────────────────────────────────────────┐
│ LAYER 4 — dsl/ (testing, invariants, testing_with_railway) │
│ Pure DSL, no I/O. All the grammar. │
└─────────────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────────────▼────────────────────────────────────────┐
│ LAYER 3 — ports/ + pom/ + aggregates/ │
│ Interfaces and abstractions. │
└─────────────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────────────▼────────────────────────────────────────┐
│ LAYER 2 — custom_types/ + custom_errors/ + custom_invariants/ │
│ Shapes, aliases, types, exceptions, ready-to-use invariants. │
└─────────────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────────────▼────────────────────────────────────────┐
│ LAYER 1 — railway/ (Result, Ok, Fail, is_ok, is_fail) │
│ The functional primitive. No dependencies. │
└──────────────────────────────────────────────────────────────────────────┘This strict stratification rules out import cycles and lets you swap any upper layer without touching the ones below:
| Replace | Consequence |
|---|---|
opinionated/ | You keep the DSL and the orchestration. No loss. See 11-opinionated/. |
infra/selenium/ | Ocarina now ships both infra/selenium/ and infra/playwright/. Pick one, or add Puppeteer / a fake driver: the signature to honor is BuiltWebDriver[Driver] = tuple[Driver, Effect]. |
pom/selenium/ | Same: POMBase is agnostic. A pom/playwright/ ships alongside it. |
Import relationships#
| Isolation-breaking import | Verdict |
|---|---|
railway/result.py imports something from Ocarina | No. No internal dependency. |
custom_types/ imports dsl/ | No. All arrows point the other way. |
dsl/ imports infra/ | Almost not: only the orchestration calls the ActCounter (default instance) from opinionated/infra/, and WebDriversPool from infra/. The pure DSL (testing_with_railway/, invariants/) never touches infra. |
ports/ imports infra/ | No. Ports sit above infra. |