Imports:
  - Types:
      - Config AS PrettyConfig
      - load_config
    From: prettyplay/config
  - Types:
      - StepHooks
      - StepReporter
    Usages:
      - hooks
    From: prettyplay/reporting
  - Types:
      - ProductDefectError
      - IncurableStepError
      - LlmUnavailableError
      - PrettyplayError
    Usages:
      - taxonomy
    From: prettyplay/failures
  - Types:
      - DriverSession
      - PageFacade
    From: prettyplay/driver
  - Types:
      - StepCache
      - StepIdentity
      - normalize_step_text
      - RunBudgets
    From: prettyplay/cache
  - Types:
      - LlmProvider
      - create_provider
    From: prettyplay/llm
  - Types:
      - StepGenerator
      - StepHealer
      - run_step_code
    Usages:
      - generation
      - healing
    From: prettyplay/engine

Usages:
  conventions: .goga/usages/conventions.md

Annotations: |
  Use `conventions` for code writing rules and testing.
  Use `taxonomy` from Imports for the failure kinds the step methods propagate.
  Use `hooks` from Imports for the callback contract accepted by add_hooks.
  Use `generation` and `healing` from Imports for the engine cycles the executor delegates to.

  The facade of the library: one main object per test; the engineer writes steps as plain sentences and reads the suite as a scenario.
  Framework-agnostic: no plugin machinery, no runner integration — the integrator wires the library in a few lines.
  Step sentences are visible in the test output through the standard logger prettyplay; step texts land in the cache and in LLM requests — never put secrets or personal data into a step sentence.
  `PrettyConfig` — the public name of the settings model — is re-exported by this facade (see the embedding).

---

->PrettyConfig: {}

"PrettyTest(cache_key: str, cache_path: str | None, config: PrettyConfig | None)":
  location: scenario.py
  annotations: |
    The main integrator object — one instance per test. Owns the cache addressing and the isolated browser context of the test; the step cycle is delegated to `StepExecutor`.

    `cache_key`: the mandatory explicit context key — part of the step address; equal keys in the shared root reuse steps across tests.
    `cache_path`: the optional cache subdirectory — part of the address; steps never leak across subdirectories.
    `config`: per-test overrides — the same full model; explicitly set values win, unset/empty fields resolve from pyproject+env; None — everything resolves from pyproject+env, as before.

    Supports the context manager protocol: exit closes the test.

    Algorithm:
    1. Resolve the effective config: `load_config` with overrides = config
    2. Build the own `PrettyplayRuntime` with the effective config — no process-wide singleton exists
    3. Construct the per-test reporter: `StepReporter` with an empty hooks list; add_hooks appends to it
    4. Construct the per-test `StepCache` from the runtime config, `cache_path` and the reporter
    5. Construct `StepGenerator` and `StepHealer` from the runtime config, provider and budgets, the step cache and the reporter
    6. Construct `StepExecutor` with `cache_key`, the cache, the engines, the runtime budgets and the reporter
    7. The test page opens lazily on the first step via the runtime open_page

    Requirements:
    - Construction is cheap: the browser starts lazily on the first step; no LLM credentials are required to construct
    - The instance holds no cross-test state: identical outcomes regardless of execution order
  properties:
    "cache_key -> str": |
      The explicit context key, exposed for diagnostics.
  methods:
    "action(text: str)": |
      Execute the action step `text`.

      Delegates to the executor execute with the step type action, the sentence and the test page; failures propagate by kind — `ProductDefectError`, `IncurableStepError`, `LlmUnavailableError` (see `taxonomy`).
      A `PrettyplayError` leaving this method carries its traceback folded to the library boundary: internal library frames — engine, healing, provider — do not appear in what the runner shows.
    "assertion(text: str)": |
      Execute the assertion step `text` — a legitimately failed expectation surfaces as the product defect failure.

      Delegates to the executor execute with the step type assertion; failures propagate by kind — `ProductDefectError`, `IncurableStepError`, `LlmUnavailableError` (see `taxonomy`).
      A `PrettyplayError` leaving this method carries its traceback folded to the library boundary: internal library frames — engine, healing, provider — do not appear in what the runner shows.
    "get_screenshot() -> image: bytes": |
      Return a full-page PNG image of the current state of the test page — uniform with the facade screenshot.

      `image`: the full-page PNG bytes.

      Requirements:
      - Requires an opened test page: calling before the first step raises a loud actionable `PrettyplayError` telling to run a step first
      - No screenshot is taken automatically on step failures: the decision to capture belongs to the test author
    "save_screenshot(filepath: str)": |
      Write a full-page PNG image of the current state of the test page to `filepath`.

      `filepath`: the explicit destination path chosen by the user — any directory, any filename; no default directory is imposed.

      Requirements:
      - Requires an opened test page: calling before the first step raises a loud actionable `PrettyplayError` telling to run a step first
      - A write failure — e.g. a missing parent directory — surfaces as a loud actionable `PrettyplayError`; nothing is created silently
    "add_hooks(hooks: StepHooks)": |
      Register a callback implementation (see `hooks`); applies to the steps of this test.
    "close()": |
      Close the test page context and stop the whole runtime of this test — the browser process, the Playwright driver and the driver thread; idempotent; the context manager exit does the same.

"StepExecutor(cache_key: str, cache: StepCache, generator: StepGenerator, healer: StepHealer, budgets: RunBudgets, reporter: StepReporter)":
  location: executor.py
  annotations: |
    The owner of the step cycle: cache hit — execute; cache miss — generate and store; cached failure — heal.

    `cache_key`: the context key of the owning test object.
    `cache`: the step cache of the test.
    `generator` and `healer`: the engines (see `generation` and `healing` from Imports).
    `budgets`: the per-test attempt registry.
    `reporter`: the visibility point.
  methods:
    "execute(step_text: str, step_type: str, page: PageFacade)": |
      Run one step through the full cycle.

      Algorithm:
      1. Report on_step_started with the sentence and the step type
      2. Build the step identity: `normalize_step_text`, then `StepIdentity` with the test cache key and the step type
      3. Load the cached step: a hit executes its code with `run_step_code` against `page`
      4. On a hit execution failure: delegate to the healer heal with the failure description and the scenario context — a healed step is already re-executed and stored by the engine
      5. On a miss: the generator generate — the engine stores the step on success
      6. Append the sentence to the scenario context of the test — the previous step texts feed the next generation
      7. Report on_step_passed; on a failed step report on_step_failed with the sentence, the step type and the short error description — then, when the terminal failure carries a verdict, report on_step_verdict with the sentence and the three verdict fields; finally raise by kind — `ProductDefectError`, `IncurableStepError`, `LlmUnavailableError` (see `taxonomy`)

      Requirements:
      - The scenario context lives per test: steps of different tests never mix
      - A cached step executes with no LLM involvement whatsoever
      - An assertion step surfaces a legitimately failed expectation as the product defect failure

"PrettyplayRuntime(config: PrettyConfig)":
  location: runtime.py
  annotations: |
    The per-test composition root: one instance per test, owning everything the steps of that test share.

    `config`: the effective settings of the test.

    Requirements:
    - One instance serves exactly one test: construction starts nothing expensive — the browser, the provider and the budgets belong to this test alone
  properties:
    "config -> PrettyConfig": |
      The effective settings of the test.
    "driver -> DriverSession": |
      The browser process of this test, created lazily.
    "budgets -> RunBudgets": |
      The attempt registry of the test — one budget per step within the test.
    "provider -> LlmProvider": |
      The LLM provider instance, created lazily on first access via `create_provider`.

      Requirements:
      - Constructing the runtime never requires LLM credentials: a missing key surfaces as the infrastructure failure on the first generation or classification request
  methods:
    "open_page() -> page: PageFacade": |
      Open a fresh isolated browser context and return its page facade — one per test.
    "close()": |
      Stop the browser, the Playwright driver and the driver thread of this test; safe when nothing was started.

      Requirements:
      - Every instance registers its own close with atexit, so the driver stops synchronously before the process exits even when no test closes the runtime explicitly; manual calls stay valid and idempotent

---

Author: Goga
CreatedAt: 07/09/26
Description: |
  The facade of prettyplay: the per-test scenario object with screenshot abilities, the step cycle executor with verdict reporting, and the per-test composition root.
