Skip to main content

Persona DSL - Framework for implementing Screenplay pattern in Python tests

Project description

persona-dsl: фреймворк для автотестов на Python

persona-dsl — это библиотека для E2E-, API- и интеграционных тестов, построенная вокруг Screenplay-подхода и тесно связанная с runtime/discovery/tooling, которые использует TaaS.

Установка

pip install persona-dsl
python -m playwright install --with-deps

Базовая модель

Публичный слой строится вокруг:

  • Persona
  • top-level test_* сценариев в scenarios/**
  • directory-level __suite__.py
  • Ops
  • Step
  • CombinedStep
  • алиасов Action, Fact, Expectation, Goal
  • Page и Element

Skills и resources

Skill является capability-границей runtime: browser, api, db, kafka и другие интеграции. Сценарий получает persona: Persona, а reusable domain-объекты поверх skills поднимаются через native resource-layer.

Базовое правило такое:

  • persona.skill(...) для прямого доступа к capability
  • persona.resource(...) для typed/reusable объектов проекта
  • lifecycle hooks для setup/cleanup orchestration

Ресурсы объявляются в support/resources/** или scenarios/**/support/resources/**:

from persona_dsl import Persona, define_resource


@define_resource("workspace.dashboard", scope="test")
def workspace_dashboard(persona: Persona):
    browser = persona.skill("browser")
    browser.page.goto("/dashboard")
    yield {"page": browser.page}

Использование в сценарии остаётся persona-centric:

from persona_dsl import Persona


def test_dashboard(persona: Persona) -> None:
    dashboard = persona.resource("workspace.dashboard")
    assert dashboard["page"] is not None

scope="test" очищается после attempt, scope="session" живёт до закрытия runtime session, а scope="worker" переиспользуется между сценариями внутри одного worker launch-а. Этот контракт даёт повторное использование дорогих ресурсов без fixture injection в сигнатуру сценария.

Инженерный контракт

В persona-dsl жёстко зафиксирован contract по длине файлов:

  • рабочая цель для production и verification модулей — держать файл в диапазоне 300-400 строк;
  • после 300 строк файл считается кандидатом на разбиение;
  • 500 строк — жёсткий предел;
  • всё, что временно не помещается в лимит, должно быть явно перечислено в scripts/file_length_policy.toml как tracked overflow-долг.

Проверка выполняется через:

make structure-policy

Это правило распространяется на src/persona_dsl/** и tests/**, включая TypeScript-часть runtime.

from __future__ import annotations

from persona_dsl import Persona, story, title
from persona_dsl.expectations.generic import IsEqualTo
from persona_dsl.ops.web import Click, CurrentPath, Fill, NavigateTo
from persona_dsl.pages import Button, Page, TextField


class LoginPage(Page):
    expected_path = "/login"

    username = TextField(name="username", accessible_name="Логин")
    password = TextField(name="password", accessible_name="Пароль")
    submit_button = Button(name="submit_button", accessible_name="Войти")


@story("Авторизация")
@title("Логин пользователя")
def test_login(persona: Persona) -> None:
    login_page = LoginPage()
    persona.parameter("env", persona.runtime.env)

    with persona.step("Авторизоваться под admin"):
        persona.make(
            NavigateTo(login_page),
            Fill(login_page.username, "admin"),
            Fill(login_page.password, "secret"),
            Click(login_page.submit_button),
        )
        current_path = persona.get(CurrentPath())
        persona.check(current_path, IsEqualTo("/dashboard"))

Страницы и элементы

Page наследуется от Element и добавляет:

  • expected_path
  • default_query_params
  • build_url(base_url=None)

Element поддерживает:

  • declarative class attrs
  • aria_ref
  • multi-strategy resolve
  • .using(...), .robust(), .resolve_or(...)
  • ElementList
  • Table и declarative table DSL

VirtualPage используется для динамической in-memory модели страницы.

Генерация page object'ов

PageGenerator и GeneratePageObject генерируют declarative page objects.

Для generated страниц контракт такой:

  • committed generated file не содержит def __init__(...)
  • не содержит self.add_element(...)
  • не содержит self.add_element_list(...)
  • хранит expected_path, snapshot/screenshot paths и aria_ref
  • для повторяющихся анонимных контролов может выпускать declarative group, доступную по индексу как page.otp_kod[0]
  • внутри таких групп item-level aria_ref может отсутствовать; resolve идёт через container scope и role + index
  • поддерживает merge/regeneration
  • использует footer-секции unresolved/archived diagnostics

CLI:

persona-page-gen --url http://localhost:8080 --output support/pages/home_page.py

Сгенерированный файл не считается исключением из структурного контракта. Если page object растёт, его нужно дробить:

  • выделять отдельные Page/Section/Table по доменным зонам экрана;
  • генерировать несколько page-файлов вместо одного монолита по всей странице;
  • выносить повторяющиеся блоки в переиспользуемые sections/components;
  • для schema/codegen разбивать входные XSD/API-пакеты по доменным границам, а не наращивать один огромный generated module.

Из сценария:

from persona_dsl import Persona
from persona_dsl.ops.web import GeneratePageObject, NavigateTo


def test_generate_home_page(persona: Persona) -> None:
    with persona.step("Открыть страницу и сгенерировать page object"):
        persona.make(NavigateTo("/"))
        persona.make(
            GeneratePageObject(
                class_name="HomePage",
                output_path="support/pages/home_page.py",
                wait_for_state="networkidle",
                sleep_before=1,
            )
        )

Таблицы

Актуальный table contract включает:

  • Column(...)
  • TableColumn
  • table.filters.*
  • row.elements.*
  • row.element(column)
  • row.value(column)
  • row.details
  • table.select_all_checkbox

Generator использует этот DSL и для обычных, и для сложных таблиц.

Структура native-проекта

Для новых native-проектов Persona authoring contract строится вокруг:

  • pyproject.toml
  • config/{env}.yaml
  • scenarios/
  • scenario_data/
  • support/
  • reports/
  • artifacts/

Канонический масштабируемый проект выглядит так:

pyproject.toml
config/
  dev.yaml
  stage.yaml
scenarios/
  __suite__.py
  checkout/
    __suite__.py
    smoke/
      __suite__.py
      test_checkout_smoke.py
      support/
      data/
    matrix/
      __suite__.py
      test_checkout_matrix.py
  session/
    __suite__.py
    core/
      __suite__.py
      test_persona_core.py
scenario_data/
  shared/
  variants/
  schemas/
support/
  pages/
  resources/
  contracts/
  shared/
reports/
artifacts/
scripts/

Правила раскладки такие:

  • scenarios/ содержит только исполняемые top-level test_* и directory-level __suite__.py.
  • scenarios/**/support/ и scenarios/**/data/ нужны для domain-specific helper-кода и данных, которые живут рядом с конкретной веткой сценариев и не должны засорять общий слой проекта.
  • support/pages/, support/resources/, support/contracts/ и support/shared/ предназначены для по-настоящему общих артефактов проекта: page objects, typed resources, generated contracts и reusable helpers.
  • support/pages/, support/resources/ и support/contracts/ масштабируются через доменную группировку внутри каталога: support/pages/checkout/, support/resources/session/, support/contracts/billing/.
  • support/shared/ держит только cross-domain helper-код без привязки к одному продуктовому разделу.
  • переносить helper из scenarios/**/support/ в верхний support/** стоит только после появления повторного использования между независимыми ветками сценариев.
  • scenario_data/ хранит статические декларативные входные данные, variant sets, схемы и другие committed inputs, которые не исполняются как сценарии.
  • reports/ и artifacts/ являются output-каталогами раннера и не входят в пользовательский authoring-контур.
  • scripts/ опционален и используется только для проектных служебных проверок.

Публичный native-контракт не предполагает пользовательские tests/, pytest.ini, conftest.py, class-based suites и marker-driven discovery. Discovery читает только scenarios/**.

Минимальный pyproject.toml для native-проекта фиксирует:

  • [project].name как project_id
  • [tool.persona].default_env
  • [tool.persona].default_role_id
  • [tool.persona.runner].default_workers
  • [tool.persona.runner].default_console
  • [tool.persona.runner].default_log_console

Если проекту нужны default bindings для навыков, они задаются там же:

  • [tool.persona.skills.by_role.<role_id>] для role-specific overrides

config/{env}.yaml задаёт env-specific роли, skill profiles, lifecycle bindings и retry bindings.

Типичный runner-блок:

[tool.persona]
default_env = "dev"
default_role_id = "default"

[tool.persona.runner]
default_workers = "auto"
default_console = "auto"
default_log_console = "warning"

Suite metadata и inheritance

Directory-level __suite__.py нужен для декларативных suite-метаданных, suite-level lifecycle и suite-level retry без xUnit-классов и без импорта пользовательского runtime-кода во время discovery.

Базовые правила такие:

  • runner читает только scenarios/**/*.py и scenarios/**/__suite__.py;
  • исполняемыми считаются только top-level функции test_*;
  • __suite__.py читается AST-ом и не импортируется как обычный модуль;
  • файл должен содержать только import-ы, docstring и ровно один top-level вызов suite(...);
  • аргументы suite(...) должны быть статическими строковыми литералами или tuple/list строковых литералов;
  • поддерживаемые аргументы сейчас: name, epic, feature, role_id, tags, lifecycle, retry.

Типичный __suite__.py выглядит так:

from persona_dsl import suite

suite(
    name="Checkout",
    epic="Витрина магазина",
    feature="Оформление заказа",
    role_id="web",
    tags=("checkout", "smoke"),
    lifecycle=("ui_smoke",),
    retry="fragile_ui",
)

Наследование идёт сверху вниз по каталогу:

scenarios/__suite__.py -> доменный __suite__.py -> leaf __suite__.py -> test_*

Вложенность каталогов с __suite__.py не ограничена. Runner проходит всю цепочку от каталога файла вверх до scenarios/ и сохраняет полную иерархию в suite_path.

Практический смысл inheritance такой:

  • корневой scenarios/__suite__.py задаёт верхнеуровневую карту проекта: верхнее имя suite, общие теги, глобальные lifecycle-профили;
  • доменные __suite__.py раскладывают проект по доменным границам и добавляют epic, feature, доменные теги и role bindings;
  • leaf __suite__.py уточняют самый узкий контур конкретной папки сценариев;
  • сами test_* оставляют persona-centric код и точечные метаданные уровня сценария: title, story, tag, use_*, apply_*.

Складывание метаданных и политик происходит не по правилу "что позже написано, то главнее", а по типу поля.

Внутри самой цепочки __suite__.py правила такие:

  • name не переопределяется, а накапливается в порядке root -> ... -> deepest;
  • tags накапливаются как уникальное объединение в порядке root -> ... -> deepest;
  • lifecycle накапливается в порядке root -> ... -> deepest; если один и тот же profile id встретился несколько раз, в runtime остаётся первое вхождение;
  • epic и feature не имеют single-winner semantics: разные значения остаются как отдельные labels, точные дубликаты не дублируются;
  • role_id переопределяется самым узким suite;
  • retry переопределяется самым узким suite.

role_id задаёт роль для аргумента persona: Persona. Сценарии с несколькими ролями используют personas: PersonaSession и доступ к ролям через personas.get(...).

Правила разрешения для test-level деклараций и внешних bindings:

  • lifecycle additive, а не winner-takes-all;
  • retry singleton, то есть выбирается первый подходящий источник по жёсткому порядку.

Порядок применения lifecycle:

  1. @use_lifecycle(...) на самом test_*;
  2. @apply_lifecycle(...) на самом test_*;
  3. suite lifecycle=(...), уже собранный из всей цепочки root -> ... -> deepest;
  4. config.framework.lifecycle.tests;
  5. config.framework.lifecycle.test_prefixes;
  6. config.framework.lifecycle.selectors;
  7. config.framework.lifecycle.roles;
  8. config.framework.lifecycle.all_tests;
  9. кодовые bind_lifecycle(..., all_tests=True);
  10. кодовые bind_lifecycle(..., selector=...).

Порядок применения retry:

  1. @use_retry(...) на самом test_*;
  2. @apply_retry(...) на самом test_*;
  3. suite retry=..., уже разрешённый из deepest __suite__.py;
  4. config.framework.retry.tests;
  5. config.framework.retry.test_prefixes;
  6. config.framework.retry.selectors;
  7. config.framework.retry.roles;
  8. config.framework.retry.all_tests;
  9. кодовые bind_retry(..., all_tests=True);
  10. кодовые bind_retry(..., selector=...).

Для retry выбирается первый подходящий источник по порядку precedence. Более широкие уровни после этого не применяются. Для lifecycle источники суммируются в указанном порядке.

Retry не накапливается. Retry-policy задаёт единый набор max_attempts, retry_on, backoff и retry hooks.

component.with_retry(...) остаётся самым узким уровнем и влияет только на конкретный шаг, а не на retry-политику всего сценария.

Хорошая практика для масштабируемого проекта:

  • использовать scenarios/__suite__.py для общей карты проекта, а не для набора локальных исключений;
  • держать epic/feature/tags на уровне каталогов, где это действительно общая семантика для всех сценариев внутри;
  • не дублировать в __suite__.py то, что относится только к одному тесту;
  • не писать в __suite__.py вычисления, helper-функции, условную логику и runtime side effects: такой файл должен оставаться статическим DSL-описанием каталога.

Основные команды:

persona run --project-root .
persona run --project-root . --max-workers auto
persona run --project-root . --console rich
persona run --project-root . --console tui
persona run --project-root . --log-console info
persona run --project-root . --include-tag smoke
persona run --project-root . --scenario-id checkout.matrix --variant-id guest
persona launch start --project-root . --max-workers 8
persona launch start --project-root . --scenario-id checkout.matrix --variant-id auth
persona launch list --project-root .
persona launch status --project-root .
persona launch workers --project-root . --launch-id <launch_id>
persona launch watch --project-root .
persona launch pause --project-root . --launch-id <launch_id>
persona launch resume --project-root . --launch-id <launch_id>
persona launch cancel --project-root . --launch-id <launch_id>
persona launch cancel-item --project-root . --launch-id <launch_id> --work-item-id <scenario_id>::<variant_id>
persona launch terminate --project-root . --launch-id <launch_id>
persona launch scale --project-root . --launch-id <launch_id> --max-workers 4
persona launch rerun-failed --project-root . --launch-id <launch_id> --max-workers 2
persona launch retry-stuck --project-root . --launch-id <launch_id> --max-workers 2
persona report info --project-root . --launch-id <launch_id>
persona report build --project-root . --launch-id <launch_id>
persona report serve --project-root . --launch-id <launch_id>

Локальный Runner

persona run использует native discovery, canonical execution journal и изолированный Allure export adapter вне execution path.

Публичный CLI-контракт:

  • --max-workers auto|N
  • --console auto|plain|rich|tui
  • --log-console quiet|error|warning|info|debug

Правила по умолчанию:

  • default_workers = "auto" означает min(selected_work_items, os.cpu_count())
  • default_console = "auto" выбирает rich в интерактивном TTY и plain в неинтерактивном потоке
  • default_log_console = "warning" выводит в консоль runtime logs уровней warning и error

Назначение режимов консоли:

  • plain — append-only вывод для CI, пайпов и лог-файлов
  • rich — live progress bar, активные worker/item, recent items, runtime logs и failure cards
  • tui — интерактивный монитор локального прогона с pause/resume/cancel launch и cancel selected item

Артефакты локального запуска:

  • summary.json
  • journal.jsonl
  • allure-results/
  • console.txt
  • console.html для rich
  • runtime.log
  • runtime.ndjson
  • stdout.log
  • stderr.log
  • traceback.txt для failed/broken execution

Локальный report flow:

  • persona report info показывает raw paths выбранного launch, текущую reports/latest/ projection и размер historical chain для выбранного env
  • persona report build собирает reports/latest/allure-report/ для выбранного launch с history по предыдущим terminal launch того же env
  • persona report serve собирает тот же historical report и открывает его через Allure CLI
  • reports/latest/allure-results/ хранит raw allure-results текущей projection
  • reports/latest/allure-report/ хранит materialized HTML report
  • reports/latest/allure-report.json фиксирует launch_id, env и список launch, вошедших в historical chain

Публичный execution-контракт строится вокруг native scenario_id, persona run, persona launch * и run_scenario в MCP.

MCP Server

Persona DSL включает встроенный MCP JSON-RPC server:

persona-mcp --project-root .

--project-root указывает на Persona-проект, который индексирует сервер. MCP читает тот же native contract, что и локальный runner, discovery и canonical report model.

Публичный набор инструментов:

  • persona_guide — встроенное руководство по Persona DSL без project docs.
  • project_inventory, project_search, project_entity — project-aware inventory, typed search и точечное чтение сущности по entity_id.
  • lifecycle_preview, retry_preview, retry_explain — explain lifecycle/retry политики для scenario_id или component symbol.
  • runtime_open, runtime_get, runtime_call, runtime_close — runtime session для skills, resources и symbol invocation.
  • run_scenario — адресный запуск одного scenario_id.
  • generate_preview — preview генерации page, xsd, api, test и step без записи файлов.

Runtime Logging

Runtime logging доступен в активном сценарии, ops, компонентах, ресурсах и других слоях, где уже поднят runtime context.

Публичный API:

  • persona.log(level, message, **fields)
  • persona.debug(...)
  • persona.info(...)
  • persona.warning(...)
  • persona.error(...)
  • persona_dsl.runtime.log(...)
  • persona_dsl.runtime.debug(...)
  • persona_dsl.runtime.info(...)
  • persona_dsl.runtime.warning(...)
  • persona_dsl.runtime.error(...)

level фиксирован как debug | info | warning | error. Вызов вне активного runtime даёт явный RuntimeError.

Если нужен локальный генератор страниц или шаблонный проект, ориентируйтесь на example_template/templates/persona_project_starter.

Где смотреть дальше

Полная пользовательская карта Persona DSL живёт именно в starter template: там для каждого публичного capability указан конкретный test_*, соседний README и support-layer файл, включая lifecycle/retry, resources, browser/UI, service skills, tooling, local launch UX и report surface.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

persona_dsl-2026.4.19rc177.tar.gz (494.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

persona_dsl-2026.4.19rc177-py3-none-any.whl (622.9 kB view details)

Uploaded Python 3

File details

Details for the file persona_dsl-2026.4.19rc177.tar.gz.

File metadata

  • Download URL: persona_dsl-2026.4.19rc177.tar.gz
  • Upload date:
  • Size: 494.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for persona_dsl-2026.4.19rc177.tar.gz
Algorithm Hash digest
SHA256 aba0f95d6aa8afa4d072c624a9248bf696c43dccd5d60f70bd14a535cdc1df72
MD5 c49cb9199be0b0eb5850ec5d583dd065
BLAKE2b-256 28fbd559e0caf8bacf0024d5334ca179de82c449057d71f330cd16c386d65bd4

See more details on using hashes here.

File details

Details for the file persona_dsl-2026.4.19rc177-py3-none-any.whl.

File metadata

File hashes

Hashes for persona_dsl-2026.4.19rc177-py3-none-any.whl
Algorithm Hash digest
SHA256 bf8e11d85b1e15948fe27b83442762dff2b99c900e635a40c5a42b798726b183
MD5 ca93e9527e4184d5319d10f49b3b27b8
BLAKE2b-256 5281b84cfed0b1f682895e0b4c95f6ca65be9518259f4af59261a9b5ac801e3a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page