quantum-framework 0.9.0__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 (262) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/commands/__init__.py +15 -0
  4. quantum/cli/commands/build.py +417 -0
  5. quantum/cli/commands/dev.py +185 -0
  6. quantum/cli/commands/docs.py +329 -0
  7. quantum/cli/commands/lint.py +523 -0
  8. quantum/cli/commands/migrate.py +527 -0
  9. quantum/cli/commands/new.py +622 -0
  10. quantum/cli/commands/serve.py +190 -0
  11. quantum/cli/commands/test.py +193 -0
  12. quantum/cli/deploy.py +810 -0
  13. quantum/cli/hot_reload.py +951 -0
  14. quantum/cli/jobs.py +356 -0
  15. quantum/cli/mq.py +582 -0
  16. quantum/cli/pkg.py +390 -0
  17. quantum/cli/runner.py +547 -0
  18. quantum/cli/server_process.py +159 -0
  19. quantum/cli/utils.py +334 -0
  20. quantum/compiler/__init__.py +30 -0
  21. quantum/compiler/base_generator.py +367 -0
  22. quantum/compiler/cli.py +295 -0
  23. quantum/compiler/expression_transformer.py +444 -0
  24. quantum/compiler/javascript/__init__.py +10 -0
  25. quantum/compiler/javascript/generator.py +659 -0
  26. quantum/compiler/optimizer.py +270 -0
  27. quantum/compiler/python/__init__.py +10 -0
  28. quantum/compiler/python/generator.py +883 -0
  29. quantum/compiler/python/runtime.py +863 -0
  30. quantum/compiler/transpiler.py +330 -0
  31. quantum/core/__init__.py +3 -0
  32. quantum/core/ast_nodes.py +2611 -0
  33. quantum/core/expression_diagnostics.py +87 -0
  34. quantum/core/expression_stdlib.py +148 -0
  35. quantum/core/expressions.py +591 -0
  36. quantum/core/features/agents/src/__init__.py +30 -0
  37. quantum/core/features/agents/src/ast_node.py +540 -0
  38. quantum/core/features/conditionals/src/__init__.py +8 -0
  39. quantum/core/features/conditionals/src/ast_node.py +68 -0
  40. quantum/core/features/data_fetching/src/__init__.py +21 -0
  41. quantum/core/features/data_fetching/src/ast_node.py +312 -0
  42. quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
  43. quantum/core/features/data_fetching/src/html_adapter.py +474 -0
  44. quantum/core/features/data_fetching/src/parser.py +225 -0
  45. quantum/core/features/data_import/src/__init__.py +0 -0
  46. quantum/core/features/data_import/src/ast_node.py +291 -0
  47. quantum/core/features/data_import/src/runtime.py +538 -0
  48. quantum/core/features/dump/src/__init__.py +12 -0
  49. quantum/core/features/dump/src/ast_node.py +106 -0
  50. quantum/core/features/dump/src/parser.py +61 -0
  51. quantum/core/features/dump/src/runtime.py +246 -0
  52. quantum/core/features/functions/src/__init__.py +8 -0
  53. quantum/core/features/functions/src/ast_node.py +149 -0
  54. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  55. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  56. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  57. quantum/core/features/invocation/src/__init__.py +0 -0
  58. quantum/core/features/invocation/src/ast_node.py +146 -0
  59. quantum/core/features/invocation/src/runtime.py +327 -0
  60. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  61. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  62. quantum/core/features/knowledge_base/src/parser.py +82 -0
  63. quantum/core/features/logging/src/__init__.py +12 -0
  64. quantum/core/features/logging/src/ast_node.py +111 -0
  65. quantum/core/features/logging/src/parser.py +50 -0
  66. quantum/core/features/logging/src/runtime.py +190 -0
  67. quantum/core/features/loops/src/__init__.py +8 -0
  68. quantum/core/features/loops/src/ast_node.py +60 -0
  69. quantum/core/features/query/src/__init__.py +0 -0
  70. quantum/core/features/query/src/database_service.py +322 -0
  71. quantum/core/features/query/src/query_validators.py +20 -0
  72. quantum/core/features/state_management/src/__init__.py +11 -0
  73. quantum/core/features/state_management/src/ast_node.py +228 -0
  74. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  75. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  76. quantum/core/features/terminal_engine/src/parser.py +361 -0
  77. quantum/core/features/testing_engine/src/__init__.py +41 -0
  78. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  79. quantum/core/features/testing_engine/src/parser.py +604 -0
  80. quantum/core/features/theming/src/__init__.py +48 -0
  81. quantum/core/features/theming/src/ast_node.py +137 -0
  82. quantum/core/features/theming/src/presets.py +405 -0
  83. quantum/core/features/ui_engine/src/__init__.py +20 -0
  84. quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
  85. quantum/core/features/ui_engine/src/parser.py +1106 -0
  86. quantum/core/features/websocket/src/__init__.py +24 -0
  87. quantum/core/features/websocket/src/ast_node.py +247 -0
  88. quantum/core/html_compat.py +299 -0
  89. quantum/core/parser.py +1235 -0
  90. quantum/core/parser_registry.py +213 -0
  91. quantum/core/parsers/__init__.py +76 -0
  92. quantum/core/parsers/ai/__init__.py +12 -0
  93. quantum/core/parsers/ai/agent_parser.py +106 -0
  94. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  95. quantum/core/parsers/ai/llm_parser.py +78 -0
  96. quantum/core/parsers/ai/team_parser.py +88 -0
  97. quantum/core/parsers/base.py +322 -0
  98. quantum/core/parsers/composition/__init__.py +10 -0
  99. quantum/core/parsers/composition/import_parser.py +50 -0
  100. quantum/core/parsers/composition/slot_parser.py +48 -0
  101. quantum/core/parsers/control_flow/__init__.py +11 -0
  102. quantum/core/parsers/control_flow/if_parser.py +68 -0
  103. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  104. quantum/core/parsers/control_flow/set_parser.py +89 -0
  105. quantum/core/parsers/data/__init__.py +12 -0
  106. quantum/core/parsers/data/data_parser.py +217 -0
  107. quantum/core/parsers/data/invoke_parser.py +116 -0
  108. quantum/core/parsers/data/query_parser.py +188 -0
  109. quantum/core/parsers/data/transaction_parser.py +77 -0
  110. quantum/core/parsers/events/__init__.py +9 -0
  111. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  112. quantum/core/parsers/forms/__init__.py +11 -0
  113. quantum/core/parsers/forms/action_parser.py +54 -0
  114. quantum/core/parsers/forms/flash_parser.py +34 -0
  115. quantum/core/parsers/forms/redirect_parser.py +33 -0
  116. quantum/core/parsers/functions/__init__.py +11 -0
  117. quantum/core/parsers/functions/function_parser.py +137 -0
  118. quantum/core/parsers/functions/param_parser.py +66 -0
  119. quantum/core/parsers/functions/return_parser.py +31 -0
  120. quantum/core/parsers/html/__init__.py +10 -0
  121. quantum/core/parsers/html/component_call_parser.py +130 -0
  122. quantum/core/parsers/html/html_parser.py +115 -0
  123. quantum/core/parsers/jobs/__init__.py +11 -0
  124. quantum/core/parsers/jobs/job_parser.py +71 -0
  125. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  126. quantum/core/parsers/jobs/thread_parser.py +57 -0
  127. quantum/core/parsers/messaging/__init__.py +17 -0
  128. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  130. quantum/core/parsers/messaging/message_parser.py +114 -0
  131. quantum/core/parsers/messaging/queue_parser.py +61 -0
  132. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  133. quantum/core/parsers/persistence/__init__.py +9 -0
  134. quantum/core/parsers/persistence/persist_parser.py +64 -0
  135. quantum/core/parsers/routing/__init__.py +9 -0
  136. quantum/core/parsers/routing/route_parser.py +41 -0
  137. quantum/core/parsers/scripting/__init__.py +12 -0
  138. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  139. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  140. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  141. quantum/core/parsers/scripting/python_parser.py +49 -0
  142. quantum/core/parsers/services/__init__.py +12 -0
  143. quantum/core/parsers/services/dump_parser.py +52 -0
  144. quantum/core/parsers/services/file_parser.py +48 -0
  145. quantum/core/parsers/services/log_parser.py +46 -0
  146. quantum/core/parsers/services/mail_parser.py +65 -0
  147. quantum/core/tiers.py +82 -0
  148. quantum/packages/__init__.py +28 -0
  149. quantum/packages/manager.py +413 -0
  150. quantum/packages/manifest.py +351 -0
  151. quantum/packages/registry.py +399 -0
  152. quantum/packages/resolver.py +336 -0
  153. quantum/plugins/__init__.py +33 -0
  154. quantum/plugins/hooks.py +329 -0
  155. quantum/plugins/loader.py +479 -0
  156. quantum/plugins/manifest.py +336 -0
  157. quantum/plugins/registry.py +371 -0
  158. quantum/runtime/__init__.py +28 -0
  159. quantum/runtime/action_handler.py +443 -0
  160. quantum/runtime/adapters/__init__.py +88 -0
  161. quantum/runtime/adapters/memory_adapter.py +690 -0
  162. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  163. quantum/runtime/adapters/redis_adapter.py +582 -0
  164. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  165. quantum/runtime/agent_service.py +1133 -0
  166. quantum/runtime/api_server.py +86 -0
  167. quantum/runtime/ast_cache.py +506 -0
  168. quantum/runtime/auth_service.py +267 -0
  169. quantum/runtime/component.py +990 -0
  170. quantum/runtime/component_composer.py +319 -0
  171. quantum/runtime/component_resolver.py +174 -0
  172. quantum/runtime/database_service.py +598 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +286 -0
  176. quantum/runtime/executor_registry.py +171 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +262 -0
  184. quantum/runtime/executors/control_flow/__init__.py +11 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +93 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +307 -0
  187. quantum/runtime/executors/control_flow/set_executor.py +412 -0
  188. quantum/runtime/executors/data/__init__.py +12 -0
  189. quantum/runtime/executors/data/data_executor.py +145 -0
  190. quantum/runtime/executors/data/invoke_executor.py +176 -0
  191. quantum/runtime/executors/data/query_executor.py +256 -0
  192. quantum/runtime/executors/data/transaction_executor.py +91 -0
  193. quantum/runtime/executors/jobs/__init__.py +11 -0
  194. quantum/runtime/executors/jobs/job_executor.py +190 -0
  195. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  196. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  197. quantum/runtime/executors/messaging/__init__.py +17 -0
  198. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  199. quantum/runtime/executors/messaging/message_executor.py +174 -0
  200. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  201. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  202. quantum/runtime/executors/scripting/__init__.py +11 -0
  203. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  204. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  205. quantum/runtime/executors/scripting/python_executor.py +249 -0
  206. quantum/runtime/executors/services/__init__.py +12 -0
  207. quantum/runtime/executors/services/dump_executor.py +72 -0
  208. quantum/runtime/executors/services/file_executor.py +89 -0
  209. quantum/runtime/executors/services/log_executor.py +77 -0
  210. quantum/runtime/executors/services/mail_executor.py +81 -0
  211. quantum/runtime/expression_cache.py +498 -0
  212. quantum/runtime/file_upload_service.py +326 -0
  213. quantum/runtime/function_registry.py +118 -0
  214. quantum/runtime/game_builder.py +166 -0
  215. quantum/runtime/game_code_generator.py +2371 -0
  216. quantum/runtime/game_templates.py +2006 -0
  217. quantum/runtime/godot_code_generator.py +4681 -0
  218. quantum/runtime/godot_templates.py +1449 -0
  219. quantum/runtime/job_executor.py +1599 -0
  220. quantum/runtime/knowledge_service.py +500 -0
  221. quantum/runtime/llm_cache.py +100 -0
  222. quantum/runtime/llm_providers.py +704 -0
  223. quantum/runtime/llm_service.py +287 -0
  224. quantum/runtime/logging_setup.py +140 -0
  225. quantum/runtime/message_broker.py +364 -0
  226. quantum/runtime/message_queue_service.py +571 -0
  227. quantum/runtime/param_validation.py +184 -0
  228. quantum/runtime/pypy_compat.py +315 -0
  229. quantum/runtime/python_bridge.py +698 -0
  230. quantum/runtime/query_validators.py +304 -0
  231. quantum/runtime/renderer.py +733 -0
  232. quantum/runtime/service_container.py +444 -0
  233. quantum/runtime/terminal_builder.py +76 -0
  234. quantum/runtime/terminal_code_generator.py +607 -0
  235. quantum/runtime/terminal_templates.py +243 -0
  236. quantum/runtime/testing_builder.py +77 -0
  237. quantum/runtime/testing_code_generator.py +833 -0
  238. quantum/runtime/testing_templates.py +85 -0
  239. quantum/runtime/ui_builder.py +188 -0
  240. quantum/runtime/ui_desktop_adapter.py +1730 -0
  241. quantum/runtime/ui_desktop_templates.py +307 -0
  242. quantum/runtime/ui_html_adapter.py +2691 -0
  243. quantum/runtime/ui_html_templates.py +2297 -0
  244. quantum/runtime/ui_mobile_adapter.py +1832 -0
  245. quantum/runtime/ui_mobile_templates.py +1003 -0
  246. quantum/runtime/ui_textual_adapter.py +1866 -0
  247. quantum/runtime/ui_textual_templates.py +45 -0
  248. quantum/runtime/ui_tokens.py +465 -0
  249. quantum/runtime/ui_validator.py +365 -0
  250. quantum/runtime/validators.py +256 -0
  251. quantum/runtime/web_server.py +1766 -0
  252. quantum/runtime/websocket_adapter.py +501 -0
  253. quantum/runtime/websocket_service.py +585 -0
  254. quantum/runtime/websocket_transport.py +289 -0
  255. quantum/runtime/wsgi.py +101 -0
  256. quantum/utils/__init__.py +1 -0
  257. quantum_framework-0.9.0.dist-info/METADATA +244 -0
  258. quantum_framework-0.9.0.dist-info/RECORD +262 -0
  259. quantum_framework-0.9.0.dist-info/WHEEL +5 -0
  260. quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
  261. quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
  262. quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1212 @@
1
+ """
2
+ AST Nodes for Testing Engine (qtest: namespace)
3
+
4
+ All testing-specific AST nodes for the Quantum Testing Engine.
5
+ These represent test suites, cases, assertions, browser interactions,
6
+ and advanced testing features that compile to pytest + Playwright Python code.
7
+ """
8
+
9
+ from typing import Dict, Any, List, Optional
10
+ from dataclasses import dataclass, field
11
+ import sys
12
+ from pathlib import Path
13
+ from quantum.core.ast_nodes import QuantumNode
14
+
15
+
16
+ # ============================================
17
+ # CORE TESTING NODES
18
+ # ============================================
19
+
20
+ class QTestSuiteNode(QuantumNode):
21
+ """Represents <qtest:suite> - Top-level test suite container."""
22
+
23
+ def __init__(self, name: str):
24
+ self.name = name
25
+ self.browser: bool = False
26
+ self.parallel: bool = False
27
+ self.workers: int = 1
28
+ self.browsers: Optional[str] = None # comma-separated: "chromium,firefox"
29
+ self.device: Optional[str] = None
30
+ self.children: List[QuantumNode] = []
31
+
32
+ def add_child(self, child: QuantumNode):
33
+ self.children.append(child)
34
+
35
+ def to_dict(self) -> Dict[str, Any]:
36
+ return {
37
+ "type": "test_suite",
38
+ "name": self.name,
39
+ "browser": self.browser,
40
+ "parallel": self.parallel,
41
+ "workers": self.workers,
42
+ "children_count": len(self.children),
43
+ }
44
+
45
+ def validate(self) -> List[str]:
46
+ errors = []
47
+ if not self.name:
48
+ errors.append("Test suite name is required")
49
+ for child in self.children:
50
+ if hasattr(child, 'validate'):
51
+ errors.extend(child.validate())
52
+ return errors
53
+
54
+
55
+ class QTestCaseNode(QuantumNode):
56
+ """Represents <qtest:case> - Individual test case."""
57
+
58
+ def __init__(self, name: str):
59
+ self.name = name
60
+ self.browser: bool = False
61
+ self.auth: Optional[str] = None
62
+ self.device: Optional[str] = None
63
+ self.viewports: Optional[str] = None # comma-separated
64
+ self.children: List[QuantumNode] = []
65
+
66
+ def add_child(self, child: QuantumNode):
67
+ self.children.append(child)
68
+
69
+ def to_dict(self) -> Dict[str, Any]:
70
+ return {
71
+ "type": "test_case",
72
+ "name": self.name,
73
+ "browser": self.browser,
74
+ "auth": self.auth,
75
+ "children_count": len(self.children),
76
+ }
77
+
78
+ def validate(self) -> List[str]:
79
+ errors = []
80
+ if not self.name:
81
+ errors.append("Test case name is required")
82
+ for child in self.children:
83
+ if hasattr(child, 'validate'):
84
+ errors.extend(child.validate())
85
+ return errors
86
+
87
+
88
+ class ExpectNode(QuantumNode):
89
+ """Represents <qtest:expect> - Assertion."""
90
+
91
+ def __init__(self):
92
+ self.selector: Optional[str] = None
93
+ self.visible: Optional[bool] = None
94
+ self.hidden: Optional[bool] = None
95
+ self.text: Optional[str] = None
96
+ self.text_contains: Optional[str] = None
97
+ self.equals: Optional[str] = None
98
+ self.count: Optional[int] = None
99
+ self.url: Optional[str] = None
100
+ self.url_contains: Optional[str] = None
101
+ self.var: Optional[str] = None
102
+ self.value: Optional[str] = None
103
+ self.enabled: Optional[bool] = None
104
+ self.disabled: Optional[bool] = None
105
+ self.checked: Optional[bool] = None
106
+ self.has_class: Optional[str] = None
107
+ self.has_attribute: Optional[str] = None
108
+ self.not_: bool = False # negate assertion
109
+
110
+ def to_dict(self) -> Dict[str, Any]:
111
+ return {
112
+ "type": "test_expect",
113
+ "selector": self.selector,
114
+ "visible": self.visible,
115
+ "text": self.text,
116
+ "equals": self.equals,
117
+ "var": self.var,
118
+ "not": self.not_,
119
+ }
120
+
121
+ def validate(self) -> List[str]:
122
+ errors = []
123
+ has_assertion = any([
124
+ self.visible is not None, self.hidden is not None,
125
+ self.text is not None, self.text_contains is not None,
126
+ self.equals is not None, self.count is not None,
127
+ self.url is not None, self.url_contains is not None,
128
+ self.value is not None, self.enabled is not None,
129
+ self.disabled is not None, self.checked is not None,
130
+ self.has_class is not None, self.has_attribute is not None,
131
+ ])
132
+ if not has_assertion and not self.selector:
133
+ errors.append("Expect requires at least one assertion attribute or a selector")
134
+ if self.visible is True and self.hidden is True:
135
+ errors.append("Expect has contradictory assertions: visible=True and hidden=True")
136
+ return errors
137
+
138
+
139
+ class MockNode_Testing(QuantumNode):
140
+ """Represents <qtest:mock> - Mock services/queries."""
141
+
142
+ def __init__(self):
143
+ self.query: Optional[str] = None
144
+ self.service: Optional[str] = None
145
+ self.auto_generate: bool = False
146
+ self.when: Optional[str] = None
147
+ self.return_value: Optional[str] = None # inline text content
148
+ self.children: List[QuantumNode] = []
149
+
150
+ def add_child(self, child: QuantumNode):
151
+ self.children.append(child)
152
+
153
+ def to_dict(self) -> Dict[str, Any]:
154
+ return {
155
+ "type": "test_mock",
156
+ "query": self.query,
157
+ "service": self.service,
158
+ "auto_generate": self.auto_generate,
159
+ }
160
+
161
+ def validate(self) -> List[str]:
162
+ errors = []
163
+ if not self.query and not self.service:
164
+ errors.append("Mock requires 'query' or 'service' attribute")
165
+ return errors
166
+
167
+
168
+ class FixtureNode_Testing(QuantumNode):
169
+ """Represents <qtest:fixture> - Test data fixture."""
170
+
171
+ def __init__(self, name: str):
172
+ self.name = name
173
+ self.file: Optional[str] = None
174
+ self.scope: str = "function" # function, class, module, session
175
+ self.data: Optional[str] = None # inline JSON content
176
+
177
+ def to_dict(self) -> Dict[str, Any]:
178
+ return {
179
+ "type": "test_fixture",
180
+ "name": self.name,
181
+ "file": self.file,
182
+ "scope": self.scope,
183
+ }
184
+
185
+ def validate(self) -> List[str]:
186
+ errors = []
187
+ if not self.name:
188
+ errors.append("Fixture name is required")
189
+ if self.scope not in ('function', 'class', 'module', 'session'):
190
+ errors.append(f"Invalid fixture scope: {self.scope}")
191
+ return errors
192
+
193
+
194
+ class SetupNode_Testing(QuantumNode):
195
+ """Represents <qtest:setup> - Before-all hook."""
196
+
197
+ def __init__(self):
198
+ self.children: List[QuantumNode] = []
199
+
200
+ def add_child(self, child: QuantumNode):
201
+ self.children.append(child)
202
+
203
+ def to_dict(self) -> Dict[str, Any]:
204
+ return {
205
+ "type": "test_setup",
206
+ "children_count": len(self.children),
207
+ }
208
+
209
+ def validate(self) -> List[str]:
210
+ errors = []
211
+ for child in self.children:
212
+ if hasattr(child, 'validate'):
213
+ errors.extend(child.validate())
214
+ return errors
215
+
216
+
217
+ class TeardownNode_Testing(QuantumNode):
218
+ """Represents <qtest:teardown> - After-all hook."""
219
+
220
+ def __init__(self):
221
+ self.children: List[QuantumNode] = []
222
+
223
+ def add_child(self, child: QuantumNode):
224
+ self.children.append(child)
225
+
226
+ def to_dict(self) -> Dict[str, Any]:
227
+ return {
228
+ "type": "test_teardown",
229
+ "children_count": len(self.children),
230
+ }
231
+
232
+ def validate(self) -> List[str]:
233
+ errors = []
234
+ for child in self.children:
235
+ if hasattr(child, 'validate'):
236
+ errors.extend(child.validate())
237
+ return errors
238
+
239
+
240
+ class BeforeEachNode(QuantumNode):
241
+ """Represents <qtest:before-each> - Before-each test hook."""
242
+
243
+ def __init__(self):
244
+ self.children: List[QuantumNode] = []
245
+
246
+ def add_child(self, child: QuantumNode):
247
+ self.children.append(child)
248
+
249
+ def to_dict(self) -> Dict[str, Any]:
250
+ return {
251
+ "type": "test_before_each",
252
+ "children_count": len(self.children),
253
+ }
254
+
255
+ def validate(self) -> List[str]:
256
+ errors = []
257
+ for child in self.children:
258
+ if hasattr(child, 'validate'):
259
+ errors.extend(child.validate())
260
+ return errors
261
+
262
+
263
+ class AfterEachNode(QuantumNode):
264
+ """Represents <qtest:after-each> - After-each test hook."""
265
+
266
+ def __init__(self):
267
+ self.children: List[QuantumNode] = []
268
+
269
+ def add_child(self, child: QuantumNode):
270
+ self.children.append(child)
271
+
272
+ def to_dict(self) -> Dict[str, Any]:
273
+ return {
274
+ "type": "test_after_each",
275
+ "children_count": len(self.children),
276
+ }
277
+
278
+ def validate(self) -> List[str]:
279
+ errors = []
280
+ for child in self.children:
281
+ if hasattr(child, 'validate'):
282
+ errors.extend(child.validate())
283
+ return errors
284
+
285
+
286
+ class GenerateNode(QuantumNode):
287
+ """Represents <qtest:generate> - Auto-generate tests."""
288
+
289
+ def __init__(self):
290
+ self.component: Optional[str] = None
291
+ self.coverage: Optional[str] = None # "full", "basic"
292
+ self.include: Optional[str] = None # comma-separated features
293
+
294
+ def to_dict(self) -> Dict[str, Any]:
295
+ return {
296
+ "type": "test_generate",
297
+ "component": self.component,
298
+ "coverage": self.coverage,
299
+ "include": self.include,
300
+ }
301
+
302
+ def validate(self) -> List[str]:
303
+ errors = []
304
+ if not self.component:
305
+ errors.append("Generate requires 'component' attribute")
306
+ return errors
307
+
308
+
309
+ class ScenarioNode(QuantumNode):
310
+ """Represents <qtest:scenario> - BDD scenario."""
311
+
312
+ def __init__(self, name: str):
313
+ self.name = name
314
+ self.children: List[QuantumNode] = []
315
+
316
+ def add_child(self, child: QuantumNode):
317
+ self.children.append(child)
318
+
319
+ def to_dict(self) -> Dict[str, Any]:
320
+ return {
321
+ "type": "test_scenario",
322
+ "name": self.name,
323
+ "children_count": len(self.children),
324
+ }
325
+
326
+ def validate(self) -> List[str]:
327
+ errors = []
328
+ if not self.name:
329
+ errors.append("Scenario name is required")
330
+ for child in self.children:
331
+ if hasattr(child, 'validate'):
332
+ errors.extend(child.validate())
333
+ return errors
334
+
335
+
336
+ class GivenNode(QuantumNode):
337
+ """Represents <qtest:given> - BDD given step."""
338
+
339
+ def __init__(self, text: str):
340
+ self.text = text
341
+ self.children: List[QuantumNode] = []
342
+
343
+ def add_child(self, child: QuantumNode):
344
+ self.children.append(child)
345
+
346
+ def to_dict(self) -> Dict[str, Any]:
347
+ return {
348
+ "type": "test_given",
349
+ "text": self.text,
350
+ "children_count": len(self.children),
351
+ }
352
+
353
+ def validate(self) -> List[str]:
354
+ errors = []
355
+ if not self.text:
356
+ errors.append("Given step text is required")
357
+ return errors
358
+
359
+
360
+ class WhenNode(QuantumNode):
361
+ """Represents <qtest:when> - BDD when step."""
362
+
363
+ def __init__(self, text: str):
364
+ self.text = text
365
+ self.children: List[QuantumNode] = []
366
+
367
+ def add_child(self, child: QuantumNode):
368
+ self.children.append(child)
369
+
370
+ def to_dict(self) -> Dict[str, Any]:
371
+ return {
372
+ "type": "test_when",
373
+ "text": self.text,
374
+ "children_count": len(self.children),
375
+ }
376
+
377
+ def validate(self) -> List[str]:
378
+ errors = []
379
+ if not self.text:
380
+ errors.append("When step text is required")
381
+ return errors
382
+
383
+
384
+ class ThenNode(QuantumNode):
385
+ """Represents <qtest:then> - BDD then step."""
386
+
387
+ def __init__(self, text: str):
388
+ self.text = text
389
+ self.children: List[QuantumNode] = []
390
+
391
+ def add_child(self, child: QuantumNode):
392
+ self.children.append(child)
393
+
394
+ def to_dict(self) -> Dict[str, Any]:
395
+ return {
396
+ "type": "test_then",
397
+ "text": self.text,
398
+ "children_count": len(self.children),
399
+ }
400
+
401
+ def validate(self) -> List[str]:
402
+ errors = []
403
+ if not self.text:
404
+ errors.append("Then step text is required")
405
+ return errors
406
+
407
+
408
+ # ============================================
409
+ # BROWSER TESTING NODES
410
+ # ============================================
411
+
412
+ class BrowserConfigNode(QuantumNode):
413
+ """Represents <qtest:browser-config> - Browser configuration."""
414
+
415
+ def __init__(self):
416
+ self.engine: str = "chromium" # chromium, firefox, webkit
417
+ self.headless: bool = True
418
+ self.viewport_width: int = 1280
419
+ self.viewport_height: int = 720
420
+ self.locale: Optional[str] = None
421
+ self.timezone: Optional[str] = None
422
+ self.base_url: Optional[str] = None
423
+ self.timeout: int = 30000
424
+ self.video: bool = False
425
+ self.trace: bool = False
426
+ self.screenshot: Optional[str] = None # "on", "off", "only-on-failure"
427
+
428
+ def to_dict(self) -> Dict[str, Any]:
429
+ return {
430
+ "type": "test_browser_config",
431
+ "engine": self.engine,
432
+ "headless": self.headless,
433
+ "base_url": self.base_url,
434
+ "timeout": self.timeout,
435
+ }
436
+
437
+ def validate(self) -> List[str]:
438
+ errors = []
439
+ if self.engine not in ('chromium', 'firefox', 'webkit'):
440
+ errors.append(f"Invalid browser engine: {self.engine}")
441
+ return errors
442
+
443
+
444
+ class NavigateNode(QuantumNode):
445
+ """Represents <qtest:navigate> - Browser navigation."""
446
+
447
+ def __init__(self):
448
+ self.to: Optional[str] = None
449
+ self.back: bool = False
450
+ self.forward: bool = False
451
+ self.wait: Optional[str] = None # "load", "domcontentloaded", "networkidle"
452
+
453
+ def to_dict(self) -> Dict[str, Any]:
454
+ return {
455
+ "type": "test_navigate",
456
+ "to": self.to,
457
+ "back": self.back,
458
+ "forward": self.forward,
459
+ }
460
+
461
+ def validate(self) -> List[str]:
462
+ errors = []
463
+ if not self.to and not self.back and not self.forward:
464
+ errors.append("Navigate requires 'to', 'back', or 'forward'")
465
+ return errors
466
+
467
+
468
+ class ClickNode(QuantumNode):
469
+ """Represents <qtest:click> - Click action."""
470
+
471
+ def __init__(self):
472
+ self.selector: Optional[str] = None
473
+ self.text: Optional[str] = None
474
+ self.role: Optional[str] = None
475
+ self.test_id: Optional[str] = None
476
+ self.xpath: Optional[str] = None
477
+
478
+ def to_dict(self) -> Dict[str, Any]:
479
+ return {
480
+ "type": "test_click",
481
+ "selector": self.selector,
482
+ "text": self.text,
483
+ "role": self.role,
484
+ "test_id": self.test_id,
485
+ }
486
+
487
+ def validate(self) -> List[str]:
488
+ errors = []
489
+ if not any([self.selector, self.text, self.role, self.test_id, self.xpath]):
490
+ errors.append("Click requires a target (selector, text, role, test_id, or xpath)")
491
+ return errors
492
+
493
+
494
+ class DblClickNode(QuantumNode):
495
+ """Represents <qtest:dblclick> - Double click action."""
496
+
497
+ def __init__(self):
498
+ self.selector: Optional[str] = None
499
+ self.text: Optional[str] = None
500
+ self.role: Optional[str] = None
501
+ self.test_id: Optional[str] = None
502
+
503
+ def to_dict(self) -> Dict[str, Any]:
504
+ return {
505
+ "type": "test_dblclick",
506
+ "selector": self.selector,
507
+ }
508
+
509
+ def validate(self) -> List[str]:
510
+ errors = []
511
+ if not any([self.selector, self.text, self.role, self.test_id]):
512
+ errors.append("DblClick requires a target")
513
+ return errors
514
+
515
+
516
+ class RightClickNode(QuantumNode):
517
+ """Represents <qtest:right-click> - Right click action."""
518
+
519
+ def __init__(self):
520
+ self.selector: Optional[str] = None
521
+ self.text: Optional[str] = None
522
+ self.role: Optional[str] = None
523
+ self.test_id: Optional[str] = None
524
+
525
+ def to_dict(self) -> Dict[str, Any]:
526
+ return {
527
+ "type": "test_right_click",
528
+ "selector": self.selector,
529
+ }
530
+
531
+ def validate(self) -> List[str]:
532
+ errors = []
533
+ if not any([self.selector, self.text, self.role, self.test_id]):
534
+ errors.append("RightClick requires a target")
535
+ return errors
536
+
537
+
538
+ class HoverNode(QuantumNode):
539
+ """Represents <qtest:hover> - Hover action."""
540
+
541
+ def __init__(self):
542
+ self.selector: Optional[str] = None
543
+ self.text: Optional[str] = None
544
+ self.role: Optional[str] = None
545
+ self.test_id: Optional[str] = None
546
+
547
+ def to_dict(self) -> Dict[str, Any]:
548
+ return {
549
+ "type": "test_hover",
550
+ "selector": self.selector,
551
+ }
552
+
553
+ def validate(self) -> List[str]:
554
+ errors = []
555
+ if not any([self.selector, self.text, self.role, self.test_id]):
556
+ errors.append("Hover requires a target")
557
+ return errors
558
+
559
+
560
+ class FillNode(QuantumNode):
561
+ """Represents <qtest:fill> - Form fill action."""
562
+
563
+ def __init__(self):
564
+ self.selector: Optional[str] = None
565
+ self.value: str = ""
566
+ self.clear: bool = True
567
+
568
+ def to_dict(self) -> Dict[str, Any]:
569
+ return {
570
+ "type": "test_fill",
571
+ "selector": self.selector,
572
+ "value": self.value,
573
+ }
574
+
575
+ def validate(self) -> List[str]:
576
+ errors = []
577
+ if not self.selector:
578
+ errors.append("Fill requires 'selector' attribute")
579
+ return errors
580
+
581
+
582
+ class TypeNode_Testing(QuantumNode):
583
+ """Represents <qtest:type> - Type text with delay."""
584
+
585
+ def __init__(self):
586
+ self.selector: Optional[str] = None
587
+ self.text: str = ""
588
+ self.delay: int = 0 # ms between keystrokes
589
+
590
+ def to_dict(self) -> Dict[str, Any]:
591
+ return {
592
+ "type": "test_type",
593
+ "selector": self.selector,
594
+ "text": self.text,
595
+ "delay": self.delay,
596
+ }
597
+
598
+ def validate(self) -> List[str]:
599
+ errors = []
600
+ if not self.selector:
601
+ errors.append("Type requires 'selector' attribute")
602
+ return errors
603
+
604
+
605
+ class SelectNode_Testing(QuantumNode):
606
+ """Represents <qtest:select> - Dropdown select."""
607
+
608
+ def __init__(self):
609
+ self.selector: Optional[str] = None
610
+ self.value: Optional[str] = None
611
+ self.values: Optional[str] = None # comma-separated for multi-select
612
+
613
+ def to_dict(self) -> Dict[str, Any]:
614
+ return {
615
+ "type": "test_select",
616
+ "selector": self.selector,
617
+ "value": self.value,
618
+ }
619
+
620
+ def validate(self) -> List[str]:
621
+ errors = []
622
+ if not self.selector:
623
+ errors.append("Select requires 'selector' attribute")
624
+ if not self.value and not self.values:
625
+ errors.append("Select requires 'value' or 'values' attribute")
626
+ return errors
627
+
628
+
629
+ class CheckNode(QuantumNode):
630
+ """Represents <qtest:check> - Check checkbox."""
631
+
632
+ def __init__(self):
633
+ self.selector: Optional[str] = None
634
+
635
+ def to_dict(self) -> Dict[str, Any]:
636
+ return {
637
+ "type": "test_check",
638
+ "selector": self.selector,
639
+ }
640
+
641
+ def validate(self) -> List[str]:
642
+ errors = []
643
+ if not self.selector:
644
+ errors.append("Check requires 'selector' attribute")
645
+ return errors
646
+
647
+
648
+ class UncheckNode(QuantumNode):
649
+ """Represents <qtest:uncheck> - Uncheck checkbox."""
650
+
651
+ def __init__(self):
652
+ self.selector: Optional[str] = None
653
+
654
+ def to_dict(self) -> Dict[str, Any]:
655
+ return {
656
+ "type": "test_uncheck",
657
+ "selector": self.selector,
658
+ }
659
+
660
+ def validate(self) -> List[str]:
661
+ errors = []
662
+ if not self.selector:
663
+ errors.append("Uncheck requires 'selector' attribute")
664
+ return errors
665
+
666
+
667
+ class KeyboardNode(QuantumNode):
668
+ """Represents <qtest:keyboard> - Keyboard actions."""
669
+
670
+ def __init__(self):
671
+ self.press: Optional[str] = None
672
+ self.type_text: Optional[str] = None
673
+ self.delay: int = 0
674
+
675
+ def to_dict(self) -> Dict[str, Any]:
676
+ return {
677
+ "type": "test_keyboard",
678
+ "press": self.press,
679
+ "type_text": self.type_text,
680
+ }
681
+
682
+ def validate(self) -> List[str]:
683
+ errors = []
684
+ if not self.press and not self.type_text:
685
+ errors.append("Keyboard requires 'press' or 'type' attribute")
686
+ return errors
687
+
688
+
689
+ class DragNode(QuantumNode):
690
+ """Represents <qtest:drag> - Drag & drop action."""
691
+
692
+ def __init__(self):
693
+ self.from_sel: Optional[str] = None
694
+ self.to_sel: Optional[str] = None
695
+
696
+ def to_dict(self) -> Dict[str, Any]:
697
+ return {
698
+ "type": "test_drag",
699
+ "from": self.from_sel,
700
+ "to": self.to_sel,
701
+ }
702
+
703
+ def validate(self) -> List[str]:
704
+ errors = []
705
+ if not self.from_sel:
706
+ errors.append("Drag requires 'from' attribute")
707
+ if not self.to_sel:
708
+ errors.append("Drag requires 'to' attribute")
709
+ return errors
710
+
711
+
712
+ class ScrollNode_Testing(QuantumNode):
713
+ """Represents <qtest:scroll> - Scroll action."""
714
+
715
+ def __init__(self):
716
+ self.selector: Optional[str] = None
717
+ self.direction: str = "down" # up, down, left, right
718
+ self.amount: Optional[int] = None
719
+ self.to_element: Optional[str] = None
720
+ self.to_top: bool = False
721
+
722
+ def to_dict(self) -> Dict[str, Any]:
723
+ return {
724
+ "type": "test_scroll",
725
+ "selector": self.selector,
726
+ "direction": self.direction,
727
+ }
728
+
729
+ def validate(self) -> List[str]:
730
+ return []
731
+
732
+
733
+ class UploadNode(QuantumNode):
734
+ """Represents <qtest:upload> - File upload test."""
735
+
736
+ def __init__(self):
737
+ self.selector: Optional[str] = None
738
+ self.file: Optional[str] = None
739
+ self.files: Optional[str] = None # comma-separated
740
+
741
+ def to_dict(self) -> Dict[str, Any]:
742
+ return {
743
+ "type": "test_upload",
744
+ "selector": self.selector,
745
+ "file": self.file,
746
+ }
747
+
748
+ def validate(self) -> List[str]:
749
+ errors = []
750
+ if not self.selector:
751
+ errors.append("Upload requires 'selector' attribute")
752
+ if not self.file and not self.files:
753
+ errors.append("Upload requires 'file' or 'files' attribute")
754
+ return errors
755
+
756
+
757
+ class DownloadNode(QuantumNode):
758
+ """Represents <qtest:download> - Download test."""
759
+
760
+ def __init__(self):
761
+ self.trigger_click: Optional[str] = None
762
+ self.save_as: Optional[str] = None
763
+
764
+ def to_dict(self) -> Dict[str, Any]:
765
+ return {
766
+ "type": "test_download",
767
+ "trigger_click": self.trigger_click,
768
+ "save_as": self.save_as,
769
+ }
770
+
771
+ def validate(self) -> List[str]:
772
+ errors = []
773
+ if not self.trigger_click:
774
+ errors.append("Download requires 'trigger_click' attribute")
775
+ return errors
776
+
777
+
778
+ class WaitForNode(QuantumNode):
779
+ """Represents <qtest:wait-for> - Wait strategy."""
780
+
781
+ def __init__(self):
782
+ self.selector: Optional[str] = None
783
+ self.state: Optional[str] = None # visible, hidden, attached, detached
784
+ self.url: Optional[str] = None
785
+ self.request: Optional[str] = None
786
+ self.response: Optional[str] = None
787
+ self.condition: Optional[str] = None
788
+ self.timeout: Optional[int] = None
789
+ self.has_text: Optional[str] = None
790
+
791
+ def to_dict(self) -> Dict[str, Any]:
792
+ return {
793
+ "type": "test_wait_for",
794
+ "selector": self.selector,
795
+ "state": self.state,
796
+ "url": self.url,
797
+ "timeout": self.timeout,
798
+ }
799
+
800
+ def validate(self) -> List[str]:
801
+ errors = []
802
+ if not any([self.selector, self.url, self.request, self.response, self.condition]):
803
+ errors.append("WaitFor requires a wait target")
804
+ return errors
805
+
806
+
807
+ class ScreenshotNode(QuantumNode):
808
+ """Represents <qtest:screenshot> - Take screenshot."""
809
+
810
+ def __init__(self):
811
+ self.name: Optional[str] = None
812
+ self.selector: Optional[str] = None
813
+ self.full_page: bool = False
814
+
815
+ def to_dict(self) -> Dict[str, Any]:
816
+ return {
817
+ "type": "test_screenshot",
818
+ "name": self.name,
819
+ "selector": self.selector,
820
+ "full_page": self.full_page,
821
+ }
822
+
823
+ def validate(self) -> List[str]:
824
+ return []
825
+
826
+
827
+ class InterceptNode(QuantumNode):
828
+ """Represents <qtest:intercept> - Network interception."""
829
+
830
+ def __init__(self):
831
+ self.method: Optional[str] = None
832
+ self.url: Optional[str] = None
833
+ self.delay: Optional[int] = None
834
+ self.passthrough: bool = False
835
+ self.spy: bool = False
836
+ self.children: List[QuantumNode] = []
837
+
838
+ def add_child(self, child: QuantumNode):
839
+ self.children.append(child)
840
+
841
+ def to_dict(self) -> Dict[str, Any]:
842
+ return {
843
+ "type": "test_intercept",
844
+ "method": self.method,
845
+ "url": self.url,
846
+ "spy": self.spy,
847
+ }
848
+
849
+ def validate(self) -> List[str]:
850
+ errors = []
851
+ if not self.url:
852
+ errors.append("Intercept requires 'url' attribute")
853
+ for child in self.children:
854
+ if hasattr(child, 'validate'):
855
+ errors.extend(child.validate())
856
+ return errors
857
+
858
+
859
+ class InterceptRespondNode(QuantumNode):
860
+ """Represents <qtest:respond> - Mock response for intercepted request."""
861
+
862
+ def __init__(self):
863
+ self.status: int = 200
864
+ self.content_type: str = "application/json"
865
+ self.file: Optional[str] = None
866
+ self.body: Optional[str] = None # inline response body
867
+
868
+ def to_dict(self) -> Dict[str, Any]:
869
+ return {
870
+ "type": "test_intercept_respond",
871
+ "status": self.status,
872
+ "content_type": self.content_type,
873
+ }
874
+
875
+ def validate(self) -> List[str]:
876
+ return []
877
+
878
+
879
+ class FrameNode_Testing(QuantumNode):
880
+ """Represents <qtest:frame> - iFrame context."""
881
+
882
+ def __init__(self):
883
+ self.selector: Optional[str] = None
884
+ self.children: List[QuantumNode] = []
885
+
886
+ def add_child(self, child: QuantumNode):
887
+ self.children.append(child)
888
+
889
+ def to_dict(self) -> Dict[str, Any]:
890
+ return {
891
+ "type": "test_frame",
892
+ "selector": self.selector,
893
+ "children_count": len(self.children),
894
+ }
895
+
896
+ def validate(self) -> List[str]:
897
+ errors = []
898
+ if not self.selector:
899
+ errors.append("Frame requires 'selector' attribute")
900
+ for child in self.children:
901
+ if hasattr(child, 'validate'):
902
+ errors.extend(child.validate())
903
+ return errors
904
+
905
+
906
+ class PopupNode(QuantumNode):
907
+ """Represents <qtest:popup> - Popup/new window context."""
908
+
909
+ def __init__(self):
910
+ self.children: List[QuantumNode] = []
911
+
912
+ def add_child(self, child: QuantumNode):
913
+ self.children.append(child)
914
+
915
+ def to_dict(self) -> Dict[str, Any]:
916
+ return {
917
+ "type": "test_popup",
918
+ "children_count": len(self.children),
919
+ }
920
+
921
+ def validate(self) -> List[str]:
922
+ errors = []
923
+ for child in self.children:
924
+ if hasattr(child, 'validate'):
925
+ errors.extend(child.validate())
926
+ return errors
927
+
928
+
929
+ class AuthNode(QuantumNode):
930
+ """Represents <qtest:auth> - Authentication state."""
931
+
932
+ def __init__(self):
933
+ self.name: Optional[str] = None
934
+ self.reuse: bool = True
935
+ self.children: List[QuantumNode] = []
936
+
937
+ def add_child(self, child: QuantumNode):
938
+ self.children.append(child)
939
+
940
+ def to_dict(self) -> Dict[str, Any]:
941
+ return {
942
+ "type": "test_auth",
943
+ "name": self.name,
944
+ "reuse": self.reuse,
945
+ }
946
+
947
+ def validate(self) -> List[str]:
948
+ errors = []
949
+ if not self.name:
950
+ errors.append("Auth requires 'name' attribute")
951
+ return errors
952
+
953
+
954
+ class SaveStateNode(QuantumNode):
955
+ """Represents <qtest:save-state> - Save browser state."""
956
+
957
+ def __init__(self):
958
+ self.file: Optional[str] = None
959
+
960
+ def to_dict(self) -> Dict[str, Any]:
961
+ return {
962
+ "type": "test_save_state",
963
+ "file": self.file,
964
+ }
965
+
966
+ def validate(self) -> List[str]:
967
+ errors = []
968
+ if not self.file:
969
+ errors.append("SaveState requires 'file' attribute")
970
+ return errors
971
+
972
+
973
+ class PauseNode(QuantumNode):
974
+ """Represents <qtest:pause> - Debug pause."""
975
+
976
+ def __init__(self):
977
+ pass
978
+
979
+ def to_dict(self) -> Dict[str, Any]:
980
+ return {"type": "test_pause"}
981
+
982
+ def validate(self) -> List[str]:
983
+ return []
984
+
985
+
986
+ # ============================================
987
+ # ADVANCED TESTING NODES
988
+ # ============================================
989
+
990
+ class SnapshotNode(QuantumNode):
991
+ """Represents <qtest:snapshot> - Visual regression testing."""
992
+
993
+ def __init__(self):
994
+ self.name: Optional[str] = None
995
+ self.threshold: float = 0.1
996
+ self.ai_ignore: Optional[str] = None # comma-separated selectors to ignore
997
+ self.viewports: Optional[str] = None # comma-separated viewport sizes
998
+ self.mask_selectors: Optional[str] = None # comma-separated selectors to mask
999
+
1000
+ def to_dict(self) -> Dict[str, Any]:
1001
+ return {
1002
+ "type": "test_snapshot",
1003
+ "name": self.name,
1004
+ "threshold": self.threshold,
1005
+ }
1006
+
1007
+ def validate(self) -> List[str]:
1008
+ return []
1009
+
1010
+
1011
+ class FuzzNode(QuantumNode):
1012
+ """Represents <qtest:fuzz> - Fuzz testing."""
1013
+
1014
+ def __init__(self):
1015
+ self.target: Optional[str] = None
1016
+ self.iterations: int = 100
1017
+ self.generators: Optional[str] = None
1018
+ self.children: List[QuantumNode] = []
1019
+
1020
+ def add_child(self, child: QuantumNode):
1021
+ self.children.append(child)
1022
+
1023
+ def to_dict(self) -> Dict[str, Any]:
1024
+ return {
1025
+ "type": "test_fuzz",
1026
+ "target": self.target,
1027
+ "iterations": self.iterations,
1028
+ }
1029
+
1030
+ def validate(self) -> List[str]:
1031
+ errors = []
1032
+ if not self.target:
1033
+ errors.append("Fuzz requires 'target' attribute")
1034
+ for child in self.children:
1035
+ if hasattr(child, 'validate'):
1036
+ errors.extend(child.validate())
1037
+ return errors
1038
+
1039
+
1040
+ class PropertyNode(QuantumNode):
1041
+ """Represents <qtest:property> - Fuzz property assertion."""
1042
+
1043
+ def __init__(self, text: str):
1044
+ self.text = text
1045
+
1046
+ def to_dict(self) -> Dict[str, Any]:
1047
+ return {
1048
+ "type": "test_property",
1049
+ "text": self.text,
1050
+ }
1051
+
1052
+ def validate(self) -> List[str]:
1053
+ errors = []
1054
+ if not self.text:
1055
+ errors.append("Property text is required")
1056
+ return errors
1057
+
1058
+
1059
+ class FuzzFieldNode(QuantumNode):
1060
+ """Represents <qtest:field> - Field fuzz configuration."""
1061
+
1062
+ def __init__(self):
1063
+ self.name: Optional[str] = None
1064
+ self.generator: Optional[str] = None # "string", "int", "email", "xss", "sqli"
1065
+
1066
+ def to_dict(self) -> Dict[str, Any]:
1067
+ return {
1068
+ "type": "test_fuzz_field",
1069
+ "name": self.name,
1070
+ "generator": self.generator,
1071
+ }
1072
+
1073
+ def validate(self) -> List[str]:
1074
+ errors = []
1075
+ if not self.name:
1076
+ errors.append("Fuzz field 'name' is required")
1077
+ return errors
1078
+
1079
+
1080
+ class PerfNode(QuantumNode):
1081
+ """Represents <qtest:perf> - Performance testing."""
1082
+
1083
+ def __init__(self):
1084
+ self.baseline: Optional[str] = None
1085
+ self.regression_threshold: float = 0.1
1086
+ self.children: List[QuantumNode] = []
1087
+
1088
+ def add_child(self, child: QuantumNode):
1089
+ self.children.append(child)
1090
+
1091
+ def to_dict(self) -> Dict[str, Any]:
1092
+ return {
1093
+ "type": "test_perf",
1094
+ "baseline": self.baseline,
1095
+ "regression_threshold": self.regression_threshold,
1096
+ }
1097
+
1098
+ def validate(self) -> List[str]:
1099
+ errors = []
1100
+ for child in self.children:
1101
+ if hasattr(child, 'validate'):
1102
+ errors.extend(child.validate())
1103
+ return errors
1104
+
1105
+
1106
+ class PerfMetricNode(QuantumNode):
1107
+ """Represents <qtest:metric> - Performance metric."""
1108
+
1109
+ def __init__(self):
1110
+ self.name: Optional[str] = None
1111
+ self.max_value: Optional[float] = None
1112
+
1113
+ def to_dict(self) -> Dict[str, Any]:
1114
+ return {
1115
+ "type": "test_perf_metric",
1116
+ "name": self.name,
1117
+ "max_value": self.max_value,
1118
+ }
1119
+
1120
+ def validate(self) -> List[str]:
1121
+ errors = []
1122
+ if not self.name:
1123
+ errors.append("Metric name is required")
1124
+ return errors
1125
+
1126
+
1127
+ class A11yNode(QuantumNode):
1128
+ """Represents <qtest:a11y> - Accessibility testing."""
1129
+
1130
+ def __init__(self):
1131
+ self.standard: str = "wcag2aa" # wcag2a, wcag2aa, wcag2aaa
1132
+ self.check: Optional[str] = None # comma-separated specific checks
1133
+ self.ignore: Optional[str] = None # comma-separated rules to ignore
1134
+
1135
+ def to_dict(self) -> Dict[str, Any]:
1136
+ return {
1137
+ "type": "test_a11y",
1138
+ "standard": self.standard,
1139
+ "check": self.check,
1140
+ "ignore": self.ignore,
1141
+ }
1142
+
1143
+ def validate(self) -> List[str]:
1144
+ errors = []
1145
+ if self.standard not in ('wcag2a', 'wcag2aa', 'wcag2aaa'):
1146
+ errors.append(f"Invalid a11y standard: {self.standard}")
1147
+ return errors
1148
+
1149
+
1150
+ class ChaosNode(QuantumNode):
1151
+ """Represents <qtest:chaos> - Chaos testing container."""
1152
+
1153
+ def __init__(self):
1154
+ self.children: List[QuantumNode] = []
1155
+
1156
+ def add_child(self, child: QuantumNode):
1157
+ self.children.append(child)
1158
+
1159
+ def to_dict(self) -> Dict[str, Any]:
1160
+ return {
1161
+ "type": "test_chaos",
1162
+ "children_count": len(self.children),
1163
+ }
1164
+
1165
+ def validate(self) -> List[str]:
1166
+ errors = []
1167
+ for child in self.children:
1168
+ if hasattr(child, 'validate'):
1169
+ errors.extend(child.validate())
1170
+ return errors
1171
+
1172
+
1173
+ class ChaosFailNode(QuantumNode):
1174
+ """Represents <qtest:fail> - Simulate service failure."""
1175
+
1176
+ def __init__(self):
1177
+ self.service: Optional[str] = None
1178
+ self.probability: float = 1.0
1179
+ self.delay: Optional[int] = None # ms
1180
+
1181
+ def to_dict(self) -> Dict[str, Any]:
1182
+ return {
1183
+ "type": "test_chaos_fail",
1184
+ "service": self.service,
1185
+ "probability": self.probability,
1186
+ }
1187
+
1188
+ def validate(self) -> List[str]:
1189
+ errors = []
1190
+ if not self.service:
1191
+ errors.append("ChaosField requires 'service' attribute")
1192
+ return errors
1193
+
1194
+
1195
+ class GeolocationNode(QuantumNode):
1196
+ """Represents <qtest:geolocation> - Mock geolocation."""
1197
+
1198
+ def __init__(self):
1199
+ self.latitude: float = 0.0
1200
+ self.longitude: float = 0.0
1201
+ self.accuracy: float = 100.0
1202
+
1203
+ def to_dict(self) -> Dict[str, Any]:
1204
+ return {
1205
+ "type": "test_geolocation",
1206
+ "latitude": self.latitude,
1207
+ "longitude": self.longitude,
1208
+ "accuracy": self.accuracy,
1209
+ }
1210
+
1211
+ def validate(self) -> List[str]:
1212
+ return []