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 OpsStepCombinedStep- алиасов
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(...)для прямого доступа к capabilitypersona.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_pathdefault_query_paramsbuild_url(base_url=None)
Element поддерживает:
- declarative class attrs
aria_ref- multi-strategy resolve
.using(...),.robust(),.resolve_or(...)ElementListTableи 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(...)TableColumntable.filters.*row.elements.*row.element(column)row.value(column)row.detailstable.select_all_checkbox
Generator использует этот DSL и для обычных, и для сложных таблиц.
Структура native-проекта
Для новых native-проектов Persona authoring contract строится вокруг:
pyproject.tomlconfig/{env}.yamlscenarios/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-leveltest_*и 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.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:
@use_lifecycle(...)на самомtest_*;@apply_lifecycle(...)на самомtest_*;- suite
lifecycle=(...), уже собранный из всей цепочкиroot -> ... -> deepest; config.framework.lifecycle.tests;config.framework.lifecycle.test_prefixes;config.framework.lifecycle.selectors;config.framework.lifecycle.roles;config.framework.lifecycle.all_tests;- кодовые
bind_lifecycle(..., all_tests=True); - кодовые
bind_lifecycle(..., selector=...).
Порядок применения retry:
@use_retry(...)на самомtest_*;@apply_retry(...)на самомtest_*;- suite
retry=..., уже разрешённый из deepest__suite__.py; config.framework.retry.tests;config.framework.retry.test_prefixes;config.framework.retry.selectors;config.framework.retry.roles;config.framework.retry.all_tests;- кодовые
bind_retry(..., all_tests=True); - кодовые
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 cardstui— интерактивный монитор локального прогона с pause/resume/cancel launch и cancel selected item
Артефакты локального запуска:
summary.jsonjournal.jsonlallure-results/console.txtconsole.htmlдляrichruntime.logruntime.ndjsonstdout.logstderr.logtraceback.txtдля failed/broken execution
Локальный report flow:
persona report infoпоказывает raw paths выбранного launch, текущуюreports/latest/projection и размер historical chain для выбранногоenvpersona report buildсобираетreports/latest/allure-report/для выбранного launch с history по предыдущим terminal launch того жеenvpersona report serveсобирает тот же historical report и открывает его через Allure CLIreports/latest/allure-results/хранит rawallure-resultsтекущей projectionreports/latest/allure-report/хранит materialized HTML reportreports/latest/allure-report.jsonфиксируетlaunch_id,envи список launch, вошедших в historical chain
Публичный execution-контракт строится вокруг native scenario_id,
persona run, persona launch * и run_scenario в MCP.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file persona_dsl-2026.4.19rc163.tar.gz.
File metadata
- Download URL: persona_dsl-2026.4.19rc163.tar.gz
- Upload date:
- Size: 489.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8efe795090af282c31c44db7dd413fbeb4315f5e833631eed4d7dabf66cf3a38
|
|
| MD5 |
ffa8a54c747eb4a76571ea6ed140ad92
|
|
| BLAKE2b-256 |
fba6ffcf09fbf484868f132ca01d9e1ad8aed20f1239fdbc1ddcf4ff92805eca
|
File details
Details for the file persona_dsl-2026.4.19rc163-py3-none-any.whl.
File metadata
- Download URL: persona_dsl-2026.4.19rc163-py3-none-any.whl
- Upload date:
- Size: 618.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c7d985fd28502e80ec6304f3d28e15488fc697867c64a9a1440b8036c8c698d
|
|
| MD5 |
6b38f1a9a7a3a3a0d54e624e21b10d5f
|
|
| BLAKE2b-256 |
9fddada891df301e7e0a8be6e8025f70bd318f5bf38d91e3fc485d302dcb6b1e
|