02.01 — Ocarina's technical identity

02.01 — Ocarina’s technical identity#

pyproject.toml#

[project]
name = "ocarina"
version = "1.1.10"
description = "Websites test framework for Igor"
requires-python = ">=3.14"
authors = [{ name="Igor Casanova", email="[REDACTED]" }]
license = "MIT"
license-files = ["LICEN[CS]E*"]
readme = "README.md"

dependencies = ["python-docx>=1.2.0"]
  1. version = "1.1.10": stable 1.x, not a pre-release.
  2. requires-python = ">=3.14": PEP 695 generic typing everywhere.
  3. dependencies = ["python-docx>=1.2.0"]: one runtime dependency. The rest lives in dev.
  4. license = "MIT".
  5. description = "Websites test framework for Igor": not “for everyone,” not “for humans,” explicitly “for Igor.” Lines up with ../01-philosophy/05-political-stance.md: “It’s my car.”

dev dependencies#

[dependency-groups]
dev = [
    "ruff>=0.15.0",
    "mypy>=1.17.0",
    "mypy-extensions>=1.1.0",
    "typing-extensions>=4.14.0",
    "pytest>=9.0.0",
    "pytest-cov>=7.0.0",
    "hypothesis>=6.151.0",
    "pytest-mypy-plugins>=3.1.0",
    "allure-pytest>=2.15.3",
    "allure-python-commons>=2.15.3",
    "pre-commit>=4.5.1",
    "syrupy>=5.1.0",
    "selenium>=4.40.0",
    "playwright>=1.60.0",
    "twine>=6.2.0",
    "build>=1.4.2",
    "prysk>=0.20.0",
]
ToolRole in Ocarina
ruffLinter + formatter (replaces flake8 + isort + black). select = ["ALL"].
mypy (+ extensions)Type-checker. strict = true (see mypy.ini).
pytestUnit-test runner (the framework is itself tested).
pytest-covTest coverage (of the framework itself). Configured in pyproject.toml#tool.coverage.
hypothesisProperty-based testing (PBT)test_invariants_properties.py.
pytest-mypy-pluginsStatic typing tests (checks expected mypy errors and type-inference tests with reveal_type).
allure-pytest / allure-python-commonsAllure report (deployed to GH Pages).
pre-commitLocal git hooks (ruff-format).
syrupySnapshot testing (output of pretty_print_results, results_to_json).
seleniumPresent in dev because Ocarina doesn’t depend on Selenium to work; it just ships a Selenium adapter so the framework is immediately operational out of box.
playwrightSame logic: a second shipped adapter (since 1.1.3). Ocarina now drives both Selenium and Playwright out of the box, behind the same ports.
twine / buildPyPI publishing.
pryskCLI tests in the cram (.t) format. Successor to cram.

mypy.ini#

[mypy]
python_version = 3.14
strict = true

# * ... Allow missing annotations (type inference is cool)
disallow_incomplete_defs = false

# * ... Allow missing annotations (type inference is cool)
disallow_untyped_defs = false
  • strict = true: enables --warn-redundant-casts, --warn-unused-ignores, --no-implicit-optional, --check-untyped-defs, etc.
  • Two exceptions: disallow_incomplete_defs and disallow_untyped_defs are off. mypy’s type inference is good enough when explicit annotations don’t add value. Annotations live where they actually matter.

pyproject.toml#tool.ruff#

[tool.ruff.lint]
select = ["ALL"]
ignore = [
    "ANN002", "ANN003", "ANN201",
    "TRY003",
    "C901",
    "D203",    # conflict with D211
    "D213",    # conflict with D212
    "COM812",  # conflict with the ruff formatter (auto-fix)
]

[tool.ruff]
exclude = ["**/.venv/**", "**/bin/**", "**/__init__.py", "**/__bypass_linter__"]
  • select = ["ALL"]: every ruff rule on (~800).
  • Short ignore list: six rules, each one justified.
  • B008 must NEVER be ignored ("# "B008", # * ... NEVER ignore this rule without knowing very well what you are doing: https://docs.astral.sh/ruff/rules/function-call-in-default-argument/"). Note left so the past mistake doesn’t repeat.
  • ****/**bypass_linter****: directory convention for explicitly un-linted code (handy for internal experimental modules).

pyproject.toml#tool.pytest.ini_options#

testpaths = ["tests"]
python_files = ["test_*.py"]
norecursedirs = [".*", "__pycache__"]
log_cli = true
log_cli_level = "DEBUG"
log_cli_format = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
addopts = """
--cov=src
--cov-branch
--cov-report=term-missing
--cov-report=html
--cov-report=xml:coverage.xml
"""
SettingEffect
log_cli = trueAll test logs surface in the CLI (useful for scenarios that go through ILogger).
--cov=src + --cov-branchBranch coverage, not just line coverage.
--cov-report=html + xmlThree outputs: terminal, local HTML, XML for CI ingestion.

pyproject.toml#tool.coverage#

Full detail at ../04-internal-tests/07-coverage-policy.md. Short version: anything that’s pure shape (custom_types, ports, errors), anything that needs a real browser (Selenium adapters), and anything covered by cram tests (cli/store, cli/builder) is explicitly omitted. Otherwise the metrics lie.

02.03.01 — The Result[T] type

02.03.01 — The Result[T] type#

Source file: src/ocarina/railway/result.py — zero dependencies.

Code#

from dataclasses import dataclass, field
from typing import TypeGuard, final


class _BaseResult:
    """Base class ensuring both Ok and Fail have error attribute for type narrowing."""
    error: Exception | None


@final
@dataclass(frozen=True)
class Ok[T](_BaseResult):
    value: T
    error: None = None


@final
@dataclass(frozen=True)
class Fail(_BaseResult):
    error: Exception = field(default_factory=lambda: Exception("Unknown error"))


type Result[T] = Ok[T] | Fail


def is_ok[T](result: Result[T]) -> TypeGuard[Ok[T]]:
    return isinstance(result, Ok)


def is_fail[T](result: Result[T]) -> TypeGuard[Fail]:
    return isinstance(result, Fail)

Six design decisions, dissected#

1. _BaseResult as a common parent#

class _BaseResult:
    error: Exception | None

One job: let mypy read result.error without narrowing first. Without the base, you’d have to write:

00.02 — Stack / responsibility / license matrix

00.02 — Stack / responsibility / license matrix#

RepositoryRoleLanguageVersionPrimary stackTooling stackDistributionLicense
ocarinaBrowser e2e test frameworkPython3.14+hatchling, python-docx (sole runtime dependency), selenium + playwright (dev only)ruff (ALL), mypy strict, pytest, hypothesis, pytest-mypy-plugins, syrupy, prysk (cram), allure-pytest, pre-commit, twine, buildPyPI, v1.1.10MIT
ocarina-exampleCanonical e2e suite against the IgoristanPython3.14+ocarina, selenium ≥ 4.40, python-dotenv, dogpile.cache, redisruff, mypy, pre-commitSource onlyMIT
ocarina-with-ai-examplee2e suite against CURA Healthcare, co-written with Claude CodePython3.14+ocarina ≥ 1.0.3, selenium ≥ 4.40ruff, mypy, mypy-extensions, typing-extensions, pre-commitSource onlyMIT
igoristanPublic SUT, deliberately chaotic web appTypeScript6.0.xReact 19.2, Vike 0.4.258 (SSG), Vite 7.3, TailwindCSS 4 (alpha), valibot, react-hook-form, react-dropzone, usehooks-ts, throttleit, lucide-react, uuidpnpm 11, Node 24, wireit (orchestrator), eslint 9 (flat), prettier 3.5, husky, lint-staged, commitlint, commitizen, terser, rollup-plugin-visualizer, vite-plugin-compression, @qalisa/vike-plugin-sitemapGitHub Pages (/igoristan/)none
tests-workersOTP / Corsicadex backend, the substrate that exercises parallelization and API testsTypeScript5.9.3Vercel Edge Functions (runtime: "edge"), @upstash/redis, otplib, next types (NextRequest)pnpm 11.x, @vercel/node (types)Vercel: https://tests-workers.vercel.appISC
ocarina-holy-bookPublic documentation + AI skills + PDFsTypeScript6.0.xVitePress 2 alpha, @sugarat/theme, vue 3.5, pagefind, vitepress-plugin-image-optimizepnpm 11, Node 24, eslint 9, prettier 3.8, husky, lint-staged, commitlint, sass-embeddedGitHub Pages (/ocarina-holy-book/)MIT

Python early adopter#

Python 3.12+ featureUse in Ocarina
PEP 695 (class Foo[T]:)Generic signatures everywhere (TestSuite[Driver], Watcher[Driver], Test[Driver], Result[T], Ok[T], Thunk[T], etc.)
PEP 695 type aliases (type X = ...)Every alias: type Result[T] = Ok[T] | Fail, type Effect = Callable[[], None], type Thunk[T] = Callable[[], T], type Mode = Literal[...]
TypeGuard (Python 3.10+)is_ok, is_fail, is_test_result_ok, is_test_result_fail, is_test_result_skipped
Self (3.11+)Fluent returns from POMs, loggers, invariants
@final (3.8+)Locks critical classes (Watcher, Test, TestCycle, ChainRunner, Ok, Fail, …)
Unpack (3.11+)HumanizedDriver signature in ocarina-example
Protocol (3.8+)ScreenshotDriver, SupportsWrite[str]
Literal (3.8+)All enums (SeleniumCliStoreKeys, LOGGERS_CHOICES, Mode)
Never (3.11+)_SilentArgumentParser.error signature

Practical consequence: there is a dedicated CI workflow unstable_python_full_build.yml that pushes Python 3.15-dev monthly (cron: "0 3 1 * *"), because Ocarina aims to be ready for the next interpreter as soon as it ships. See ../10-cicd/02-ocarina-workflows.md

01.03 — KISS and ostentatious complexity

01.03 — KISS and ostentatious complexity#

The criticism that landed#

First public feedback Ocarina got, systematic per the author:

The first “criticisms” were often the same: “You’re bragging about something that’s very simple.”

The Holy Book takes its time answering. Three pillars: the operating nature of simplicity, the simple ≠ unsophisticated distinction, and the rejection of every “creative” counter-proposal that came in.

Simple ≠ unsophisticated#

This is also why KISS (Keep it simple, stupid) is so misunderstood in the industry. Many assume “simple” means “unsophisticated.” It would be hard to misread a principle more thoroughly.

04.04 — Static typing tests (pytest-mypy-plugins)

04.04 — Static typing tests (pytest-mypy-plugins)#

Five *test_types.yml files that test the checker’s behavior. A testing dimension unique to Ocarina in its way: we make sure that what should be a mypy error actually is one.

Listing#

tests/
├── dsl/
│   ├── invariants/test_types.yml
│   └── testing_with_railway/test_types.yml
└── opinionated/
    ├── cli/test_types.yml
    ├── infra/test_types.yml
    └── plugins/reports/...   (snapshots — not YAML)

YAML#

- case: compatible_predicate_types
  description: Predicates should accept values of compatible types
  main: |
    from ocarina.dsl.invariants.validate import validate
    from ocarina.dsl.invariants.assertions import is_email, is_positive

    validate(1234).assert_that(is_positive)
    validate("a@a.com").assert_that(is_email)

Without out: → we expect mypy to pass without error on the given code.

Chapter 04 — Framework internal tests

Chapter 04 — Framework internal tests#

How Ocarina tests itself. Five test families, a clear-eyed coverage policy, an Allure report archived on GitHub Pages.

Outline#

#FileTopic
0101-strategy.mdFrom the outside like a user” strategy + the conftest.py (FakeDriver, RecordingPOM, builders).
0202-cram-prysk.mdCram tests (prysk): .t files
0303-pytest-scenarios.mdTest scenarios applied to the framework (pytest + allure + hypothesis).
0404-mypy-plugins-types.mdTests on static typing via pytest-mypy-plugins (*.yml).
0505-syrupy-snapshots.mdSnapshot tests (syrupy) for pretty_print_results and results_to_json.
0606-hypothesis-properties.mdProperty-based testing for invariants.
0707-coverage-policy.mdCoverage policy: what’s tested, what is NOT, why.
0808-allure-history.mdAllure + composite action allure-history + GH Pages deploy.

Summary table#

FamilyToolQuantityTopicTarget
Scenariospytest + allure-pytest~15 test_*.py filesDSL, orchestration, Playwright actorCovers behavior
Cramprysk15 .t filesSelenium + Playwright CLI: parsing, validations, defaultsCovers CLI user surface
Static typespytest-mypy-plugins~5 *test_types.yml filesType inferences, narrowing, expected errorsCovers typing
Snapshotssyrupy2 .ambr filesOutput of pretty_print_results, results_to_jsonCovers output format
Property-basedhypothesis1 file (test_invariants_properties.py)Behavior on random valuesCovers robustness to inputs

Approach#

In conftest.py:

12.08 — Ocarina in the testing industry

12.08 — Ocarina in the testing industry#

Ocarina isn’t a better pytest, isn’t a better Robot Framework, isn’t a better Cypress. It’s structurally elsewhere. This chapter tries to explain the shift it represents and the head start it takes.

1. The current industry map#

Three big e2e tool families#

FamilyExamplesModel
Text DSLsRobot Framework, Cucumber + GherkinPermanent translation layer between text and code
Runner pluginspytest-selenium, pytest-playwright, pytest-bddGrafted onto pytest, inherit its lifecycle
Vendor SaaSBrowserStack, Sauce Labs, LambdaTest, Cypress DashboardLocal runner + paid execution cloud

Three shared friction surfaces#

FrictionManifestation
Marketing > TechnicalTools sold on demos, conferences, and oriented certifications. Hype drives adoption.
Vendor lock-inOnce adopted, refactor is costly, hostage taking.
Misplaced cognitive effortUnderstanding the tool takes longer than doing the work.

2. Ocarina’s technical bet#

  1. No text DSL → pure Python, embedded DSL.
  2. No pytest plugin → integrated runner, absolute ISTQB fidelity.
  3. No async/await → simplicity, Selenium-synchronous compatibility, no “geek tricks”.
  4. No SaaS → all local, very dense core, white box.

See also: ../11-independence/03-explicit-refusals.md

12.13 — Lambda calculus, ROP, Haskell / F# / OCaml, monads

12.13 — Lambda calculus, ROP, Haskell / F# / OCaml, monads#

Ocarina invents nothing on the theoretical front. It applies: from Alonzo Church’s lambda calculus (1936) to Eugenio Moggi’s (1989) and Philip Wadler’s (1992-95) monads, formalized as Railway Oriented Programming by Scott Wlaschin (2014).

1. Lambda calculus (Church, 1936)#

Definition#

Lambda calculus (λ-calculus) is a formal system invented by Alonzo Church (1903-1995) to study computability.

PrimitiveNotationSemantics
Variablex, y, zA name
Abstractionλx.MDefinition of a function with parameter x, body M
ApplicationM NCall of the function M on the argument N
RuleEffect
α-conversionRenaming a bound variable (λx.xλy.y)
β-reductionApplying a function: (λx.M) N → M[x := N]
η-conversionλx.(f x) ≡ f if x not free in f

Why it’s foundational#

  1. 1936: Church proves λ-calculus is Turing-complete (Turing publishes his machine in 1937). Church-Turing thesis: anything mechanically computable can be expressed in λ-calculus.
  2. Alan Turing was Church’s doctoral student at Princeton (PhD, 1938).
  3. λ-calculus is simpler than the Turing machine: only 3 constructions. Everything else — integers, booleans, data structures — is encoded with those 3 bricks (Church encoding).

Typed lambda calculus (Church, 1940)#

Church added types in 1940 (simply typed lambda calculus, STLC) to forbid self-application paradoxes (λx.x x). Each variable has a type, and function application is only allowed if types match.

12.14 — AI, and how Ocarina applies it for real

12.14 — AI, and how Ocarina applies it for real#

AI-powered” became a totally idiotic marketing slogan in 2023-2026: 99% of products claiming it bolt a chat.completions endpoint onto an existing product. Ocarina proposes, on the contrary, a software infrastructure designed so an AI can contribute like a senior developer. ocarina-with-ai-example is the proof by example.

1. What AI is today#

LayerExampleAppearance date
Statistical learningRegression, k-means, decision trees1960s-1990s
Deep learningCNN (vision), RNN/LSTM (text)2012 (AlexNet — mainstream adoption)
Transformer-based foundation modelsGPT, Claude, Gemini, Llama2017 (paper), 2018 (BERT), 2022 (ChatGPT)
Agentic workflowsAutoGPT, LangChain, Claude Code, Cursor agents2023+

Note: CNNs were invented by Yann LeCun as early as 1989 (MNIST). AlexNet (Krizhevsky, Sutskever, Hinton, Toronto, 2012) was the industrial breakthrough. LeCun, Hinton, and Bengio share the 2018 Turing Award for their foundational contributions to deep learning.

12.15 — Typing, ISTQB, Reinforcement Learning, probes: why AI works in Ocarina

12.15 — Typing, ISTQB, Reinforcement Learning, probes: why AI works in Ocarina#

Why so much obsession over strict typing, formalizing every interface, alignment on ISTQB, refusing to execute an assertion without having observed? The mechanism is borrowed from Reinforcement Learning: an agent only progresses under constraint + reward signal.

1. A naked LLM is just a bad collaborator#

An LLM without a structured environment is:

  • Optimistic — it assumes its code works.
  • Persuasive — its output is convincing even when wrong.
  • Memoryless — it forgets conventions between sessions.
  • Feedbackless — it has no way to know it was wrong.
  • Loves what it deems “most likely” — it drifts toward popular patterns, not correct ones.

Without an environment, an LLM is an over-confident junior dev producing code that looks right, breaks in prod, and learns nothing from the failure.