persona-dsl 26.1.21.44__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. persona_dsl/__init__.py +35 -0
  2. persona_dsl/components/action.py +10 -0
  3. persona_dsl/components/base_step.py +251 -0
  4. persona_dsl/components/combined_step.py +68 -0
  5. persona_dsl/components/expectation.py +10 -0
  6. persona_dsl/components/fact.py +10 -0
  7. persona_dsl/components/goal.py +10 -0
  8. persona_dsl/components/ops.py +7 -0
  9. persona_dsl/components/step.py +75 -0
  10. persona_dsl/expectations/generic/__init__.py +15 -0
  11. persona_dsl/expectations/generic/contains_item.py +19 -0
  12. persona_dsl/expectations/generic/contains_the_text.py +15 -0
  13. persona_dsl/expectations/generic/has_entries.py +21 -0
  14. persona_dsl/expectations/generic/is_equal.py +24 -0
  15. persona_dsl/expectations/generic/is_greater_than.py +18 -0
  16. persona_dsl/expectations/generic/path_equal.py +27 -0
  17. persona_dsl/expectations/web/__init__.py +5 -0
  18. persona_dsl/expectations/web/is_displayed.py +13 -0
  19. persona_dsl/expectations/web/matches_aria_snapshot.py +222 -0
  20. persona_dsl/expectations/web/matches_screenshot.py +160 -0
  21. persona_dsl/generators/__init__.py +5 -0
  22. persona_dsl/generators/api_generator.py +423 -0
  23. persona_dsl/generators/cli.py +431 -0
  24. persona_dsl/generators/page_generator.py +1140 -0
  25. persona_dsl/ops/api/__init__.py +5 -0
  26. persona_dsl/ops/api/json_as.py +104 -0
  27. persona_dsl/ops/api/json_response.py +48 -0
  28. persona_dsl/ops/api/send_request.py +41 -0
  29. persona_dsl/ops/db/__init__.py +5 -0
  30. persona_dsl/ops/db/execute_sql.py +22 -0
  31. persona_dsl/ops/db/fetch_all.py +29 -0
  32. persona_dsl/ops/db/fetch_one.py +22 -0
  33. persona_dsl/ops/kafka/__init__.py +4 -0
  34. persona_dsl/ops/kafka/message_in_topic.py +89 -0
  35. persona_dsl/ops/kafka/send_message.py +35 -0
  36. persona_dsl/ops/soap/__init__.py +4 -0
  37. persona_dsl/ops/soap/call_operation.py +24 -0
  38. persona_dsl/ops/soap/operation_result.py +24 -0
  39. persona_dsl/ops/web/__init__.py +37 -0
  40. persona_dsl/ops/web/aria_snapshot.py +87 -0
  41. persona_dsl/ops/web/click.py +30 -0
  42. persona_dsl/ops/web/current_path.py +17 -0
  43. persona_dsl/ops/web/element_attribute.py +24 -0
  44. persona_dsl/ops/web/element_is_visible.py +27 -0
  45. persona_dsl/ops/web/element_text.py +28 -0
  46. persona_dsl/ops/web/elements_count.py +42 -0
  47. persona_dsl/ops/web/fill.py +41 -0
  48. persona_dsl/ops/web/generate_page_object.py +118 -0
  49. persona_dsl/ops/web/input_value.py +23 -0
  50. persona_dsl/ops/web/navigate.py +52 -0
  51. persona_dsl/ops/web/press_key.py +37 -0
  52. persona_dsl/ops/web/rich_aria_snapshot.py +159 -0
  53. persona_dsl/ops/web/screenshot.py +68 -0
  54. persona_dsl/ops/web/table_data.py +43 -0
  55. persona_dsl/ops/web/wait_for_navigation.py +23 -0
  56. persona_dsl/pages/__init__.py +133 -0
  57. persona_dsl/pages/elements.py +998 -0
  58. persona_dsl/pages/page.py +44 -0
  59. persona_dsl/pages/virtual_page.py +94 -0
  60. persona_dsl/persona.py +125 -0
  61. persona_dsl/pytest_plugin.py +1230 -0
  62. persona_dsl/runtime/dist/persona_bundle.js +1077 -0
  63. persona_dsl/skills/__init__.py +7 -0
  64. persona_dsl/skills/core/base.py +41 -0
  65. persona_dsl/skills/core/skill_definition.py +30 -0
  66. persona_dsl/skills/use_api.py +251 -0
  67. persona_dsl/skills/use_browser.py +78 -0
  68. persona_dsl/skills/use_database.py +129 -0
  69. persona_dsl/skills/use_kafka.py +135 -0
  70. persona_dsl/skills/use_soap.py +66 -0
  71. persona_dsl/utils/__init__.py +0 -0
  72. persona_dsl/utils/artifacts.py +22 -0
  73. persona_dsl/utils/config.py +54 -0
  74. persona_dsl/utils/data_providers.py +159 -0
  75. persona_dsl/utils/decorators.py +80 -0
  76. persona_dsl/utils/metrics.py +69 -0
  77. persona_dsl/utils/naming.py +14 -0
  78. persona_dsl/utils/path.py +202 -0
  79. persona_dsl/utils/retry.py +51 -0
  80. persona_dsl/utils/taas_integration.py +124 -0
  81. persona_dsl/utils/waits.py +112 -0
  82. persona_dsl-26.1.21.44.dist-info/METADATA +233 -0
  83. persona_dsl-26.1.21.44.dist-info/RECORD +86 -0
  84. persona_dsl-26.1.21.44.dist-info/WHEEL +5 -0
  85. persona_dsl-26.1.21.44.dist-info/entry_points.txt +6 -0
  86. persona_dsl-26.1.21.44.dist-info/top_level.txt +1 -0
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from dataclasses import dataclass
5
+ from typing import Callable, Optional, Tuple, Type
6
+
7
+
8
+ @dataclass
9
+ class RetryPolicy:
10
+ """
11
+ Политика повторных попыток (ретраев).
12
+
13
+ Attributes:
14
+ max_attempts: Максимальное количество попыток (включая первую). По умолчанию 1 (без ретраев).
15
+ delay: Задержка между попытками в секундах.
16
+ backoff_factor: Множитель увеличения задержки (экспоненциальный рост).
17
+ retry_on: Кортеж типов исключений, при которых нужно повторять попытку.
18
+ retry_if: Предикат для проверки экземпляра исключения (вернуть True, если нужно повторить).
19
+ on_retry_action: Коллбек, вызываемый перед каждой повторной попыткой.
20
+ """
21
+
22
+ max_attempts: int = 1
23
+ delay: float = 0.0
24
+ backoff_factor: float = 1.0
25
+ retry_on: Tuple[Type[Exception], ...] = (Exception,)
26
+ retry_if: Optional[Callable[[Exception], bool]] = None
27
+ on_retry_action: Optional[Callable[[], None]] = None
28
+
29
+ def should_retry(self, attempt: int, exception: Exception) -> bool:
30
+ """Определяет, нужно ли делать ретрай."""
31
+ if attempt >= self.max_attempts:
32
+ return False
33
+
34
+ if not isinstance(exception, self.retry_on):
35
+ return False
36
+
37
+ if self.retry_if is not None and not self.retry_if(exception):
38
+ return False
39
+
40
+ return True
41
+
42
+ def wait(self, attempt: int) -> None:
43
+ """Выполняет ожидание перед следующей попыткой."""
44
+ wait_time = self.delay * (self.backoff_factor ** (attempt - 1))
45
+ if wait_time > 0:
46
+ time.sleep(wait_time)
47
+
48
+ def execute_action(self) -> None:
49
+ """Выполняет действие перед ретраем (если задано)."""
50
+ if self.on_retry_action:
51
+ self.on_retry_action()
@@ -0,0 +1,124 @@
1
+ import json
2
+ import os
3
+ import redis
4
+ import logging
5
+ from typing import Optional, Any
6
+
7
+ _taas_redis_client: Optional[redis.Redis] = None
8
+ _taas_redis_mod: Optional[Any] = None
9
+ _taas_connection_failed: bool = False
10
+
11
+
12
+ def reset_taas_redis_client() -> None:
13
+ """Сбрасывает кеш клиента Redis (для переинициализации соединения)."""
14
+ global _taas_redis_client, _taas_redis_mod, _taas_connection_failed
15
+ _taas_redis_client = None
16
+ _taas_redis_mod = None
17
+ _taas_connection_failed = False
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def _connect_or_raise() -> Optional[redis.Redis]:
24
+ redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379")
25
+ try:
26
+ client = redis.from_url(redis_url, decode_responses=True)
27
+ client.ping()
28
+ return client
29
+ except Exception as e:
30
+ is_strict = os.environ.get("TAAS_STRICT_CONNECT", "false").lower() not in (
31
+ "false",
32
+ "0",
33
+ )
34
+ if is_strict:
35
+ raise RuntimeError(
36
+ f"[TAAS_INTEGRATION] Не удалось подключиться к Redis по REDIS_URL={redis_url}: {e}"
37
+ ) from e
38
+ logger.warning(
39
+ f"[TAAS_INTEGRATION] Non-strict mode: Не удалось подключиться к Redis. "
40
+ f"События TaaS будут отключены. Ошибка: {e}"
41
+ )
42
+ return None
43
+
44
+
45
+ def get_taas_redis_client() -> Optional[redis.Redis]:
46
+ """Инициализирует и возвращает клиент Redis, если запуск в контексте TaaS."""
47
+ global _taas_redis_client, _taas_redis_mod, _taas_connection_failed
48
+ if not os.environ.get("TAAS_RUN_ID") or _taas_connection_failed:
49
+ return None
50
+
51
+ if _taas_redis_mod is not None and _taas_redis_mod is not redis:
52
+ reset_taas_redis_client()
53
+
54
+ if _taas_redis_client is None:
55
+ _taas_redis_client = _connect_or_raise()
56
+ if _taas_redis_client is None:
57
+ _taas_connection_failed = True
58
+ return None
59
+ _taas_redis_mod = redis
60
+ return _taas_redis_client
61
+
62
+ try:
63
+ _taas_redis_client.ping()
64
+ return _taas_redis_client
65
+ except Exception:
66
+ _taas_redis_client = _connect_or_raise()
67
+ if _taas_redis_client is None:
68
+ _taas_connection_failed = True
69
+ return None
70
+ _taas_redis_mod = redis
71
+ return _taas_redis_client
72
+
73
+
74
+ def publish_taas_event(event_payload: dict) -> None:
75
+ """Публикует событие только в Redis Stream (без Pub/Sub и списков)."""
76
+ run_id = os.environ.get("TAAS_RUN_ID")
77
+ client = get_taas_redis_client()
78
+ if client and run_id:
79
+ # Строгая валидация структуры события
80
+ if not isinstance(event_payload, dict):
81
+ raise TypeError("[TAAS_INTEGRATION] event_payload должен быть dict")
82
+ ev = event_payload.get("event")
83
+ if not isinstance(ev, str) or not ev:
84
+ raise ValueError(
85
+ "[TAAS_INTEGRATION] Событие должно содержать непустое строковое поле 'event'"
86
+ )
87
+ data = event_payload.get("data")
88
+ if not isinstance(data, dict):
89
+ raise ValueError(
90
+ "[TAAS_INTEGRATION] Событие должно содержать объект 'data'"
91
+ )
92
+ if str(ev).startswith("step_"):
93
+ t = event_payload.get("type")
94
+ if not isinstance(t, str) or not t:
95
+ raise ValueError(
96
+ "[TAAS_INTEGRATION] Для step_* событий обязательно строковое поле 'type'"
97
+ )
98
+
99
+ json_payload = json.dumps(event_payload)
100
+
101
+ # Краткая сводка в лог
102
+ try:
103
+ event_type = event_payload.get("event")
104
+ component_type = event_payload.get("type")
105
+ description = event_payload.get("data", {}).get("description")
106
+ status = event_payload.get("data", {}).get("status")
107
+ logger.info(
108
+ f"[TAAS_INTEGRATION] Event: {event_type}, type={component_type}, status={status}, desc={description}"
109
+ )
110
+ except Exception as e:
111
+ logger.exception("Ошибка формирования краткой сводки события TaaS: %s", e)
112
+
113
+ # Только Redis Streams
114
+ stream_key = f"run:{run_id}:events"
115
+ raw_maxlen = os.getenv("TAAS_STREAM_MAXLEN", "10000")
116
+ try:
117
+ maxlen = int(raw_maxlen)
118
+ if maxlen <= 0:
119
+ raise ValueError("TAAS_STREAM_MAXLEN должен быть положительным целым")
120
+ except Exception as e:
121
+ raise RuntimeError(
122
+ f"[TAAS_INTEGRATION] Некорректное значение TAAS_STREAM_MAXLEN={raw_maxlen}: {e}"
123
+ ) from e
124
+ client.xadd(stream_key, {"json": json_payload}, maxlen=maxlen, approximate=True)
@@ -0,0 +1,112 @@
1
+ import random
2
+ import time
3
+ from typing import Any, Callable, Literal, Optional
4
+
5
+ from allure import step
6
+
7
+ from ..components.expectation import Expectation
8
+ from .metrics import metrics
9
+ from .taas_integration import publish_taas_event
10
+
11
+ BackoffMode = Literal["linear", "exp"]
12
+
13
+
14
+ def wait_until(
15
+ fact: Callable[[], Any],
16
+ expectation: Expectation,
17
+ timeout: float = 10.0,
18
+ interval: float = 0.5,
19
+ backoff: BackoffMode = "linear",
20
+ jitter: float = 0.0,
21
+ domain: Optional[str] = None,
22
+ on_timeout: Literal["fail", "warn", "continue"] = "fail",
23
+ ) -> Any:
24
+ """
25
+ Универсальный helper ожиданий.
26
+ Периодически вызывает fact(), проверяя expectation._perform(None, value) до тех пор, пока не истечет timeout.
27
+
28
+ Args:
29
+ fact: вызываемая без аргументов функция, возвращающая значение для проверки.
30
+ expectation: экземпляр класса Expectation с методом check(actual).
31
+ timeout: общий таймаут в секундах.
32
+ interval: базовый интервал между попытками.
33
+ backoff: стратегия увеличения интервала: 'linear' или 'exp'.
34
+ jitter: добавочный случайный шум в секундах, 0 — без шума.
35
+ domain: логический домен ожидания ('ui' | 'api' | 'kafka'), используется в метриках и событиях.
36
+ on_timeout: поведение при истечении таймаута: 'fail' | 'warn' | 'continue'.
37
+ """
38
+ start = time.time()
39
+ attempts = 0
40
+ status = "running"
41
+ description = f"Ожидание: {expectation.__class__.__name__}"
42
+
43
+ publish_taas_event(
44
+ {
45
+ "event": "step_start",
46
+ "type": "wait",
47
+ "data": {
48
+ "description": description,
49
+ "timestamp": start,
50
+ "component_name": expectation.__class__.__name__,
51
+ "component_params": {
52
+ "timeout": timeout,
53
+ "interval": interval,
54
+ "backoff": backoff,
55
+ "jitter": jitter,
56
+ "domain": domain,
57
+ },
58
+ },
59
+ }
60
+ )
61
+
62
+ try:
63
+ with step(description):
64
+ while True:
65
+ attempts += 1
66
+ value = fact()
67
+ try:
68
+ expectation._perform(None, value)
69
+ status = "passed"
70
+ return value
71
+ except Exception:
72
+ # еще не готово, продолжаем ждать
73
+ if time.time() - start >= timeout:
74
+ status = "failed" if on_timeout == "fail" else "passed"
75
+ if on_timeout == "fail":
76
+ raise TimeoutError(
77
+ f"Ожидание не выполнено за {timeout}с: {description}"
78
+ )
79
+ return value
80
+ # вычисляем задержку на следующую попытку
81
+ delay = (
82
+ interval
83
+ if backoff == "linear"
84
+ else interval * (2 ** (attempts - 1))
85
+ )
86
+ if jitter > 0:
87
+ delay += random.uniform(0, jitter)
88
+ time.sleep(min(delay, max(0.0, timeout - (time.time() - start))))
89
+ finally:
90
+ duration = time.time() - start
91
+ metrics.gauge(
92
+ "wait.duration",
93
+ duration,
94
+ {"status": status, "domain": domain or "unspecified"},
95
+ )
96
+ metrics.counter("wait.attempts", {"domain": domain or "unspecified"}).inc(
97
+ attempts
98
+ )
99
+ publish_taas_event(
100
+ {
101
+ "event": "step_end",
102
+ "type": "wait",
103
+ "data": {
104
+ "description": description,
105
+ "timestamp": time.time(),
106
+ "duration": duration,
107
+ "status": status,
108
+ "attempts": attempts,
109
+ "domain": domain,
110
+ },
111
+ }
112
+ )
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: persona-dsl
3
+ Version: 26.1.21.44
4
+ Summary: Persona DSL - Framework for implementing Screenplay pattern in Python tests
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: playwright==1.55.0
8
+ Requires-Dist: pytest
9
+ Requires-Dist: pytest-cov
10
+ Requires-Dist: allure-pytest
11
+ Requires-Dist: python-dotenv
12
+ Requires-Dist: pyyaml
13
+ Requires-Dist: pydantic<3,>=2
14
+ Requires-Dist: requests
15
+ Requires-Dist: pyhamcrest
16
+ Requires-Dist: redis
17
+ Requires-Dist: Faker
18
+ Requires-Dist: pillow
19
+ Requires-Dist: zeep
20
+ Requires-Dist: pg8000
21
+ Requires-Dist: oracledb
22
+ Requires-Dist: kafka-python
23
+ Requires-Dist: Unidecode>=1.3
24
+ Requires-Dist: black
25
+ Provides-Extra: dev
26
+ Requires-Dist: black; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+ Requires-Dist: mypy; extra == "dev"
29
+ Requires-Dist: pytest-cov; extra == "dev"
30
+ Requires-Dist: pre-commit; extra == "dev"
31
+ Requires-Dist: types-requests; extra == "dev"
32
+ Requires-Dist: types-PyYAML; extra == "dev"
33
+ Requires-Dist: types-redis; extra == "dev"
34
+ Requires-Dist: pytest-asyncio; extra == "dev"
35
+ Requires-Dist: pytest-timeout; extra == "dev"
36
+ Requires-Dist: pytest-xdist; extra == "dev"
37
+
38
+ # persona-dsl: Фреймворк для автотестов на Python
39
+
40
+ `persona-dsl` — это современная библиотека для написания **E2E** и **API** автотестов, реализующая паттерн **Screenplay**.
41
+ Она предоставляет выразительный язык для описания сценариев, управляет браузером (через Playwright) и автоматически генерирует детальные отчеты Allure.
42
+
43
+ ## 📦 Установка
44
+
45
+ ```bash
46
+ pip install persona-dsl
47
+ python -m playwright install --with-deps
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 🚀 Быстрый старт
53
+
54
+ ### 1. Описание страницы (Page Object)
55
+
56
+ В `persona-dsl` используется **Универсальная Архитектура Элементов**.
57
+ Основной класс `Page` сам является элементом-контейнером. Вложенные элементы добавляются через `add_element`.
58
+
59
+ ```python
60
+ # pages/login_page.py
61
+ from persona_dsl.pages import Page
62
+ from persona_dsl.pages.elements import Input, Button
63
+
64
+ class LoginPage(Page):
65
+ def __init__(self):
66
+ super().__init__()
67
+ # Имя атрибута (self.username) будет использовано как имя элемента в отчетах
68
+ self.username = self.add_element(Input(xpath="//input[@id='user']", name="Логин"))
69
+ self.password = self.add_element(Input(xpath="//input[@id='pass']", name="Пароль"))
70
+ self.submit = self.add_element(Button(xpath="//button[@type='submit']", name="Вход"))
71
+ ```
72
+
73
+ ### 2. Написание теста
74
+
75
+ Тесты пишутся в стиле "Persona делает действия":
76
+
77
+ ```python
78
+ # tests/test_login.py
79
+ import pytest
80
+ from persona_dsl.ops.web import NavigateTo, Fill, Click
81
+ from persona_dsl.facts.web import CurrentPath
82
+ from persona_dsl.expectations.web import IsEqualTo
83
+ from pages.login_page import LoginPage
84
+
85
+ def test_login_flow(persona):
86
+ login_page = LoginPage()
87
+
88
+ # 1. Действия (Actions)
89
+ persona.make(
90
+ NavigateTo("/login"),
91
+ Fill(login_page.username, "admin"),
92
+ Fill(login_page.password, "secret"),
93
+ Click(login_page.submit)
94
+ )
95
+
96
+ # 2. Проверка (Verification)
97
+ # Получаем Факт (CurrentPath) и проверяем Ожидание (IsEqualTo)
98
+ current_path = persona.get(CurrentPath())
99
+ persona.check(current_path, IsEqualTo("/dashboard"))
100
+ ```
101
+
102
+ ---
103
+
104
+ ## 🏗 Архитектура
105
+
106
+ ### 1. Ключевые компоненты (Persona Core)
107
+
108
+ | Компонент | Назначение | Пример |
109
+ |-----------|------------|--------|
110
+ | **Persona** | Исполнитель. Хранит состояние и навыки. | `persona.make(...)` |
111
+ | **Skill** | "Навык". Дает доступ к инструментам. | `UseBrowser`, `UseAPI`, `UseDB` |
112
+ | **Ops** | Атомарная операция. Работает со Skill. | `Click`, `HttpRequest`, `ExecuteSQL` |
113
+ | **Step** | Бизнес-шаг. Группирует Ops. | `LoginStep`, `CreateOrder` |
114
+ | **Fact** | Запрос состояния (без изменений). | `CurrentPath`, `ElementText` |
115
+ | **Expectation** | Проверка значения. | `IsEqualTo`, `ContainsText` |
116
+
117
+ ### 2. Универсальные Элементы (New!)
118
+
119
+ Больше нет разделения на `Element` и `ElementContainer`. Любой элемент может содержать другие элементы.
120
+
121
+ * **`element.add_element(...)`**: Регистрирует дочерний элемент.
122
+ * **Иерархия**: `Page -> Container -> Element`.
123
+ * **Локаторы**: Строятся автоматически по цепочке родителей.
124
+
125
+ ### 3. Генераторный подход
126
+ Все шаги (`Step`, `CombinedStep`) — это генераторы, которые "выдают" (`yield`) операции для исполнения Персоной. Это позволяет `persona-dsl` перехватывать каждый шаг, логировать его в Allure и обрабатывать ошибки.
127
+
128
+ ---
129
+
130
+ ## 🛠 Инструменты и Утилиты
131
+
132
+ ### Генерация Page Objects
133
+ Не пишите селекторы вручную. Используйте генератор:
134
+
135
+ ```bash
136
+ # Снять ARIA-снапшот и сгенерировать класс страницы
137
+ persona-page-gen --url http://localhost:8080 --output pages/home.py
138
+ ```
139
+
140
+ ### Генерация API клиентов
141
+ ```bash
142
+ # Генерация из OpenAPI/Swagger
143
+ persona-api-gen --url http://api.example.com/openapi.json --output skills/my_api.py
144
+ ```
145
+
146
+ ### Запуск тестов
147
+
148
+ Тесты запускаются стандартной командой pytest:
149
+
150
+ ```bash
151
+ pytest --env=dev tests/
152
+ ```
153
+
154
+ ---
155
+
156
+ ## ⚙️ Конфигурация
157
+
158
+ ### `conftest.py`
159
+ Декларативное описание навыков и ролей:
160
+ ```python
161
+ PERSONA_SKILLS = [BaseSkill.BROWSER, BaseSkill.API]
162
+ ```
163
+
164
+ ### `config/{env}.yaml`
165
+ Параметры окружения (dev, test, prod). Выбирается через `--env=test`.
166
+
167
+ ### Примеры конфигурации
168
+
169
+ Ниже представлен **полный справочник** всех возможных настроек.
170
+ Большинство параметров унифицированы и работают идентично для **Локального** запуска и **Selenoid**.
171
+
172
+ #### Полный справочник (Global Reference)
173
+
174
+ Этот конфиг покрывает все возможности фреймворка. Неиспользуемые параметры можно опускать.
175
+
176
+ ```yaml
177
+ skills:
178
+ browser:
179
+ default:
180
+ base_url: "https://my-app.test" # URL должен быть внутри навыка
181
+
182
+ # --- Базовые настройки (Basic) ---
183
+ type: chromium # Тип: chromium, firefox, webkit, sberbrowser, msedge
184
+ headless: true # true = без UI (в контейнере тоже работает)
185
+ channel: chrome # (Optional) Канал: chrome, msedge, chrome-beta
186
+ executable_path: "" # (Optional) Кастомный путь к бинарнику
187
+ version: "128.0" # (Optional) Версия (для Selenoid или скачивания)
188
+
189
+ # --- Размеры и Тайминги (Dimensions & Timings) ---
190
+ viewport:
191
+ width: 1920
192
+ height: 1080 # Всегда используйте многострочный формат для читаемости
193
+ slow_mo: 50.0 # Задержка между действиями (мс)
194
+ default_timeout: 30000.0 # Таймаут поиска элементов (мс)
195
+ default_navigation_timeout: 30000.0 # Таймаут загрузки (мс)
196
+
197
+ # --- Аргументы и Флаги (Arguments) ---
198
+ # Применяются и локально, и удаленно (через goog:chromeOptions)
199
+ no_sandbox: true # --no-sandbox
200
+ ignore_https_errors: true # --ignore-certificate-errors
201
+ disable_features: # --disable-features=...
202
+ - SidePanel
203
+ - SberWhatsNew
204
+ args: # Дополнительные аргументы
205
+ - "--start-maximized"
206
+ - "--disable-notifications"
207
+ - "--disable-gpu"
208
+
209
+ # --- Удаленный запуск (Remote / Selenoid) ---
210
+ # Если URL задан, тесты пойдут в облако/контейнер
211
+ selenoid_url: "http://selenoid.company.com:4444"
212
+
213
+ session_retries: 3 # Повторные попытки при ошибке создания сессии
214
+ session_timeout: 60.0 # Таймаут ожидания слота (сек)
215
+
216
+ selenoid_options:
217
+ enableVNC: true # Включить Live-просмотр
218
+ enableVideo: false # Включить запись видео
219
+ # name/videoName проставляются автоматически
220
+ ```
221
+
222
+
223
+
224
+ ---
225
+
226
+ ## 📊 Отчетность (Allure + TaaS)
227
+
228
+ Фреймворк автоматически:
229
+ 1. Создает вложенные **Allure Steps** для каждого действия.
230
+ 2. Прикрепляет **Скриншоты** и **Page Source** при падении.
231
+ 3. Логирует **HTTP запросы/ответы** (если включено).
232
+ 4. Интегрируется с платформой **TaaS** (Test as a Service) для аналитики.
233
+
@@ -0,0 +1,86 @@
1
+ persona_dsl/__init__.py,sha256=C47lvwHDzdM-fsLOy9knGvB5tbqCS9c_rO1SnG7_sc4,875
2
+ persona_dsl/persona.py,sha256=tQXb6BOYKIQvny-0ry06kneus-jZYyhKwrLq0sjQzks,5278
3
+ persona_dsl/pytest_plugin.py,sha256=lg8raO30kxNiudlJ_SwHMglWXNrnZH_xDuHgcuCDtXs,54527
4
+ persona_dsl/components/action.py,sha256=z456GYCf10hR1tIYeC7IN1V-7bn5uY1ZwNk053sWEls,218
5
+ persona_dsl/components/base_step.py,sha256=nZJUKu5oHsOhiAW3g7_K82fGXjkg3HMnSLvKdacBHbs,9661
6
+ persona_dsl/components/combined_step.py,sha256=-SEKei_1qFDbnQvtKJBN00MJNnM_bMIPp6X0lhZrSbw,3598
7
+ persona_dsl/components/expectation.py,sha256=i2HtkVPQSXsm8H7G-2yg3PAgaORfxI3PnyTERpe-2uI,223
8
+ persona_dsl/components/fact.py,sha256=bf938rQkoIn5oVRb0a-HgSYH9aL_171uPq_-DYLZgyM,216
9
+ persona_dsl/components/goal.py,sha256=q2YPrUFoykWxXHSJkasK_ITshRlifA-BU9RXWO2P2yI,249
10
+ persona_dsl/components/ops.py,sha256=yPzdCusn4daf09OjLWa0qx1z00oWj7LYxQkOOxrg1AA,188
11
+ persona_dsl/components/step.py,sha256=GmIH01pdaknPjg3SL68FBoElHABoTbyEnSKiCdexyt4,4296
12
+ persona_dsl/expectations/generic/__init__.py,sha256=OZ2fPo6dTVRU1B4tP_YuHm06jd_bVwScqWG5ZK2qLhU,363
13
+ persona_dsl/expectations/generic/contains_item.py,sha256=GL7GJQJy5h6ChwRbkBrjelr1n22LIG3Am2Yd_g3GyPg,756
14
+ persona_dsl/expectations/generic/contains_the_text.py,sha256=8ATPDAD8gEB0fw9Lp2GMFrk4IBBbrTCk7SjODMU0L_g,559
15
+ persona_dsl/expectations/generic/has_entries.py,sha256=voCVBKk7Q41vGj3wZYY-lu2E6I_aZMHdTmeS0Lx2y5Y,860
16
+ persona_dsl/expectations/generic/is_equal.py,sha256=hRrVGfGVKWUZkLLybe5AoTOcD03kUB_ZKE7Qfp2UgI0,857
17
+ persona_dsl/expectations/generic/is_greater_than.py,sha256=k6V34pyxPhHX1V7uDOmxP9QlAi9hDrXd6L9vfvMLfLQ,688
18
+ persona_dsl/expectations/generic/path_equal.py,sha256=cvxwCdfiDXC_WhosfmIRqfwaQUogfU0YhUtP2duPTlI,967
19
+ persona_dsl/expectations/web/__init__.py,sha256=lBKeJqO4GO9yjKID37tbdbzS8iD_bZJlSk7GcJ8LCAM,214
20
+ persona_dsl/expectations/web/is_displayed.py,sha256=VNrGo6yJ_kvmUPrT8gVO0YPL-o-5XMXVM6jsBWqtO9s,578
21
+ persona_dsl/expectations/web/matches_aria_snapshot.py,sha256=TP8oe95SUfX64mU4jbsYioIENTJsQS_IvdTqk2zWECg,8658
22
+ persona_dsl/expectations/web/matches_screenshot.py,sha256=JOg9IdREJ7LNOsZpIbXesLiJa0nHAvMmmgl8x3jgV-Y,7376
23
+ persona_dsl/generators/__init__.py,sha256=xgjQ0rhC7p6sRir9MFJ4WGt0CWT1fX3_3rsrcy-wkSk,163
24
+ persona_dsl/generators/api_generator.py,sha256=epA8VEblLt09h5F0oMS04ltn8btDXHtonh4rhDI7OSU,17863
25
+ persona_dsl/generators/cli.py,sha256=vvXrgFZmUs_PtDBJychfIIeRc13Jp2IJvbnycHTdTlI,18278
26
+ persona_dsl/generators/page_generator.py,sha256=LBFXsFGtKc_DDRhAX7cRgoknSUxDsTJffAU-WSTDhmw,42901
27
+ persona_dsl/ops/api/__init__.py,sha256=6kJaE1wd2Nusk5nBK7CKInMm-x0qjTP202N2LvCYdD8,159
28
+ persona_dsl/ops/api/json_as.py,sha256=0ALfCgkG8quUyTCUIGYt0IWNo_4vwY_orut0uw8sI3Q,3725
29
+ persona_dsl/ops/api/json_response.py,sha256=ipUENkam2I-z6SXy13pfGblzZlC_xJcqJ8q6cvhntIM,1891
30
+ persona_dsl/ops/api/send_request.py,sha256=WH_Bus_p6nqG3qG-_e3Po2E2pAYqPtmmLQmMH9j4WII,1227
31
+ persona_dsl/ops/db/__init__.py,sha256=jGXZxcVdjNK1ObwrNYZ906yghRwtv4ogwmpQNB_aDkQ,150
32
+ persona_dsl/ops/db/execute_sql.py,sha256=ZSKLHHBvxJLcmOAVO5WIaYRf_tKgDnETZUxYJ5b3SsI,804
33
+ persona_dsl/ops/db/fetch_all.py,sha256=OYiMoz12l-rySRPA9q451dk-Ryzck49t2jYRe7Mir-E,1039
34
+ persona_dsl/ops/db/fetch_one.py,sha256=RPsgwDqBpaNleYJVZLoIlR4xO4PPELjTiimKYLT4CFU,840
35
+ persona_dsl/ops/kafka/__init__.py,sha256=bV-znLr2TEEfrnCx2driJwufhbuN5hJUP62Cpeuklxs,128
36
+ persona_dsl/ops/kafka/message_in_topic.py,sha256=_TicXtm1MoRoYgeLiKJwadjUcbpZAKcTQwnfXNRTJvs,3125
37
+ persona_dsl/ops/kafka/send_message.py,sha256=N7rTeZcOgKW9shEHDHwOaisIuQxiHkMy0xzC8m_5tto,1074
38
+ persona_dsl/ops/soap/__init__.py,sha256=MFC8cJNwxzjbwShk06ucya2_FN2WQo7Uqj2CKBBzFgI,136
39
+ persona_dsl/ops/soap/call_operation.py,sha256=AuvL1cuNcTVGKGY9iRFBj779JnztW49dK4OCVPQb-n4,936
40
+ persona_dsl/ops/soap/operation_result.py,sha256=jz9KWysvgOLc1sFZah444F5UWhx5ZOOubPb7nX_TmNI,961
41
+ persona_dsl/ops/web/__init__.py,sha256=E9bYemtUYa411u_bJZowdyINt_uMdI5xm_Q5TYBDCqA,1063
42
+ persona_dsl/ops/web/aria_snapshot.py,sha256=wi3Dbhax_9KhrEUVMxp0Vyw9KBZnBPrPx1IVVHJHxUE,2987
43
+ persona_dsl/ops/web/click.py,sha256=I2tyN4awqooad9t83vB6FOq8qaMEHGrdI-QmYuWd8Go,1243
44
+ persona_dsl/ops/web/current_path.py,sha256=au9Q-P0lXn-i_WHXT8P3r21QdPNodicuJeeScHvXURI,679
45
+ persona_dsl/ops/web/element_attribute.py,sha256=vanVngWmdISQ0zL3JDY-hkevsCu9yUJ2YFXUD5lWQp0,1008
46
+ persona_dsl/ops/web/element_is_visible.py,sha256=xEotfLF9hw83gzRP1ax1Bb7cukC4pUN-BcAA1J7QJAo,1099
47
+ persona_dsl/ops/web/element_text.py,sha256=MQe1uaerDpqi4pp3w0FUw7K1QY1yKgQbV5dvPDCVGVg,1147
48
+ persona_dsl/ops/web/elements_count.py,sha256=ME6bjLCxeZIKkiFBx4-jlJELy-CjHG6yFueKcHhq-9I,2034
49
+ persona_dsl/ops/web/fill.py,sha256=WD4DvcMaTdkUFfVgnWgt05TP3NzQtL48IMcahbDh5l0,1755
50
+ persona_dsl/ops/web/generate_page_object.py,sha256=NKo6-vdo792Ns7F9lefIiyiSZ_A1h1_YIdHtQepzuLQ,5407
51
+ persona_dsl/ops/web/input_value.py,sha256=BAMmJ2XsDBNWT0mjXyCJswwhyk5RJx1dM_zvNtYBjdc,918
52
+ persona_dsl/ops/web/navigate.py,sha256=hsG6PCt6tnKn1BU3a4kZDpAPDubnZzgQv_yNxXchlUo,2241
53
+ persona_dsl/ops/web/press_key.py,sha256=QcTo8PtP0Ks-plBOZ6ojPL8bJerB4IUGhvYJRlCNmtY,1709
54
+ persona_dsl/ops/web/rich_aria_snapshot.py,sha256=M1-C5LsGPUo96uv3EArMzAjdFtrdlHO5_N5W-ECsQMo,5948
55
+ persona_dsl/ops/web/screenshot.py,sha256=Wue5XKIXXbkDNhS5tkHZ5jty7NHd0LLFrFSQ_F01w7c,2378
56
+ persona_dsl/ops/web/table_data.py,sha256=4nKOUDPofFwzjSUcxiiZauNO7V-jXvNTlXXDmuqZpqQ,1920
57
+ persona_dsl/ops/web/wait_for_navigation.py,sha256=a1026yNa3LmWzacfFLUXnjleID3sfXIZ67G3SZuTxRI,977
58
+ persona_dsl/pages/__init__.py,sha256=eqJdLJ-yOTTpBBCoGSxDQCwjC8k4PKKtCOhIQGfeE6s,1917
59
+ persona_dsl/pages/elements.py,sha256=DLofNRG6W77B_Vk8QMAjdTxxCYPMUt2mv3Yw045T3lc,32711
60
+ persona_dsl/pages/page.py,sha256=Q30hUkd5Cpr_1Tyo7-xaif0IAIDD60bsFS7t0wCCjC4,1355
61
+ persona_dsl/pages/virtual_page.py,sha256=WhSwa6MScu3yUlLmA3NgE0Y5CxTvasPDTTmHZZeSP0E,3737
62
+ persona_dsl/runtime/dist/persona_bundle.js,sha256=LvYkg3j6E3bgZ0ZpPgJOtPtR7OsDtvHM5Q7gglwzgME,36423
63
+ persona_dsl/skills/__init__.py,sha256=zE2YgYZvRYslH4MHiV8VStCwXK0lw1F2RiT07RHGd3Q,238
64
+ persona_dsl/skills/use_api.py,sha256=jMsgGWgociuctGdf-CAznRAS1FNu0IXfTsUJ8Bl7VNU,11318
65
+ persona_dsl/skills/use_browser.py,sha256=uM6bAxjN3ZZfjU_islg83LVtEZTKqsLgchKz4qw7KKE,3290
66
+ persona_dsl/skills/use_database.py,sha256=enJ1C8FdXjVSLm9pWnuBg3oeIISJIMnXNy0OgVktfoo,5601
67
+ persona_dsl/skills/use_kafka.py,sha256=ZwkvzdYw-vuG8pRUTSLXOLBza6Vnif5FS06zgeXdRQs,4686
68
+ persona_dsl/skills/use_soap.py,sha256=FK_ou5lLzpno0JY2Q4_H-ImX69TB3CtMZTdBzWabfoY,2351
69
+ persona_dsl/skills/core/base.py,sha256=KquyGkVKkEQ2TsprlsRoGi3ULgVLukaQeVg1n-q8y0E,1416
70
+ persona_dsl/skills/core/skill_definition.py,sha256=_yU7WCjC9oPg72V9eMBgHmbzKHyqz_pGD6ZLjE-ZRgE,1052
71
+ persona_dsl/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
+ persona_dsl/utils/artifacts.py,sha256=tMiGcw1udjv-9d8m2CG_Kx3HDc7oTWphziNyt0Zm0jA,811
73
+ persona_dsl/utils/config.py,sha256=3qaGsSbLAQaAXTg4sIdxE1zzYDmLxwZjJLqh8Kcu2tw,2033
74
+ persona_dsl/utils/data_providers.py,sha256=piHRgeV48iuqDgLbcIT5F7G3CWTHT3d0LZ8FJye1MPg,5514
75
+ persona_dsl/utils/decorators.py,sha256=Q2ETGhyZa9cHxdPAbJi-crztbC-cAfcrdGflsh4ovhI,3171
76
+ persona_dsl/utils/metrics.py,sha256=yVNuHaGwqKS7AuWFS8k3cFIzVRRRH11yRvi7DS7J5d0,2314
77
+ persona_dsl/utils/naming.py,sha256=wOn6QKCO53B0BaAe5IM8GlzSLKfE2eTJpvRo_WGXslo,504
78
+ persona_dsl/utils/path.py,sha256=wwYUS3euYqwKQdxWHD8Lq93Ln1G0M5LIJ6AMqiwpmDI,7251
79
+ persona_dsl/utils/retry.py,sha256=kBbu6rpySdeaqRzUkgdrsEWrc9PwkvRhYtRzyhrYj9g,2215
80
+ persona_dsl/utils/taas_integration.py,sha256=OZzSjgC1_90eTYXo3WPsCgCtYc35U216SBnxClpdIkw,5046
81
+ persona_dsl/utils/waits.py,sha256=I9e_9rs_0Rju61yx_hS-qwcXd4zNWfVNyfhY0mOHrj0,4386
82
+ persona_dsl-26.1.21.44.dist-info/METADATA,sha256=e4MtAv71qGO4ZMhnYuOyeCdumafm59q90B4Y-cRTb7M,9879
83
+ persona_dsl-26.1.21.44.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
84
+ persona_dsl-26.1.21.44.dist-info/entry_points.txt,sha256=8lYko6uS1gGeYX9_FYbbeO5XV_BP62T7RngyKuKDD7k,180
85
+ persona_dsl-26.1.21.44.dist-info/top_level.txt,sha256=N1YAJab5h4iPt-srbrjzDk6bY40bwR1AHZS5Z0eOzvM,12
86
+ persona_dsl-26.1.21.44.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ persona-api-gen = persona_dsl.generators.cli:api_main
3
+ persona-page-gen = persona_dsl.generators.cli:page_main
4
+
5
+ [pytest11]
6
+ persona_dsl = persona_dsl.pytest_plugin
@@ -0,0 +1 @@
1
+ persona_dsl