testql 1.2.63__tar.gz → 1.2.65__tar.gz

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 (357) hide show
  1. {testql-1.2.63/testql.egg-info → testql-1.2.65}/PKG-INFO +4 -4
  2. {testql-1.2.63 → testql-1.2.65}/README.md +3 -3
  3. {testql-1.2.63 → testql-1.2.65}/pyproject.toml +1 -1
  4. {testql-1.2.63 → testql-1.2.65}/testql/__init__.py +1 -1
  5. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_api_runner.py +2 -2
  6. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_gui.py +20 -1
  7. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_testtoon_parser.py +59 -5
  8. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/engine.py +8 -1
  9. {testql-1.2.63 → testql-1.2.65/testql.egg-info}/PKG-INFO +4 -4
  10. {testql-1.2.63 → testql-1.2.65}/tests/test_gui_execution.py +71 -0
  11. {testql-1.2.63 → testql-1.2.65}/tests/test_interpreter.py +44 -0
  12. {testql-1.2.63 → testql-1.2.65}/tests/test_ir_runner_captures.py +20 -0
  13. {testql-1.2.63 → testql-1.2.65}/LICENSE +0 -0
  14. {testql-1.2.63 → testql-1.2.65}/setup.cfg +0 -0
  15. {testql-1.2.63 → testql-1.2.65}/testql/__main__.py +0 -0
  16. {testql-1.2.63 → testql-1.2.65}/testql/_base_fallback.py +0 -0
  17. {testql-1.2.63 → testql-1.2.65}/testql/adapters/__init__.py +0 -0
  18. {testql-1.2.63 → testql-1.2.65}/testql/adapters/base.py +0 -0
  19. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nl/__init__.py +0 -0
  20. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nl/entity_extractor.py +0 -0
  21. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nl/grammar.py +0 -0
  22. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nl/intent_recognizer.py +0 -0
  23. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nl/lexicon/__init__.py +0 -0
  24. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nl/llm_fallback.py +0 -0
  25. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nl/nl_adapter.py +0 -0
  26. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2dsl/__init__.py +0 -0
  27. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2dsl/client.py +0 -0
  28. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2dsl/live_llm.py +0 -0
  29. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2dsl/llm_provider.py +0 -0
  30. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2dsl/mock_llm.py +0 -0
  31. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2dsl/nlp2dsl_adapter.py +0 -0
  32. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2env/__init__.py +0 -0
  33. {testql-1.2.63 → testql-1.2.65}/testql/adapters/nlp2env/nlp2env_adapter.py +0 -0
  34. {testql-1.2.63 → testql-1.2.65}/testql/adapters/registry.py +0 -0
  35. {testql-1.2.63 → testql-1.2.65}/testql/adapters/scenario_yaml.py +0 -0
  36. {testql-1.2.63 → testql-1.2.65}/testql/adapters/testtoon_adapter.py +0 -0
  37. {testql-1.2.63 → testql-1.2.65}/testql/artifacts/__init__.py +0 -0
  38. {testql-1.2.63 → testql-1.2.65}/testql/artifacts/base.py +0 -0
  39. {testql-1.2.63 → testql-1.2.65}/testql/artifacts/email_checker.py +0 -0
  40. {testql-1.2.63 → testql-1.2.65}/testql/artifacts/file_checker.py +0 -0
  41. {testql-1.2.63 → testql-1.2.65}/testql/artifacts/registry.py +0 -0
  42. {testql-1.2.63 → testql-1.2.65}/testql/assertion_suite.py +0 -0
  43. {testql-1.2.63 → testql-1.2.65}/testql/autoloop_runner.py +0 -0
  44. {testql-1.2.63 → testql-1.2.65}/testql/base.py +0 -0
  45. {testql-1.2.63 → testql-1.2.65}/testql/cli.py +0 -0
  46. {testql-1.2.63 → testql-1.2.65}/testql/commands/__init__.py +0 -0
  47. {testql-1.2.63 → testql-1.2.65}/testql/commands/auto_cmd.py +0 -0
  48. {testql-1.2.63 → testql-1.2.65}/testql/commands/conversation_cmd.py +0 -0
  49. {testql-1.2.63 → testql-1.2.65}/testql/commands/discover_cmd.py +0 -0
  50. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/__init__.py +0 -0
  51. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/cli.py +0 -0
  52. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/context.py +0 -0
  53. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/formatters/__init__.py +0 -0
  54. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/formatters/text.py +0 -0
  55. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/parsers/__init__.py +0 -0
  56. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/parsers/doql.py +0 -0
  57. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo/parsers/toon.py +0 -0
  58. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo.py +0 -0
  59. {testql-1.2.63 → testql-1.2.65}/testql/commands/echo_helpers.py +0 -0
  60. {testql-1.2.63 → testql-1.2.65}/testql/commands/encoder_routes.py +0 -0
  61. {testql-1.2.63 → testql-1.2.65}/testql/commands/endpoints_cmd.py +0 -0
  62. {testql-1.2.63 → testql-1.2.65}/testql/commands/generate_cmd.py +0 -0
  63. {testql-1.2.63 → testql-1.2.65}/testql/commands/generate_from_page_cmd.py +0 -0
  64. {testql-1.2.63 → testql-1.2.65}/testql/commands/generate_ir_cmd.py +0 -0
  65. {testql-1.2.63 → testql-1.2.65}/testql/commands/generate_topology_cmd.py +0 -0
  66. {testql-1.2.63 → testql-1.2.65}/testql/commands/heal_scenario_cmd.py +0 -0
  67. {testql-1.2.63 → testql-1.2.65}/testql/commands/inspect_cmd.py +0 -0
  68. {testql-1.2.63 → testql-1.2.65}/testql/commands/misc_cmds.py +0 -0
  69. {testql-1.2.63 → testql-1.2.65}/testql/commands/nlp2env_cmd.py +0 -0
  70. {testql-1.2.63 → testql-1.2.65}/testql/commands/run_cmd.py +0 -0
  71. {testql-1.2.63 → testql-1.2.65}/testql/commands/run_ir_cmd.py +0 -0
  72. {testql-1.2.63 → testql-1.2.65}/testql/commands/self_test_cmd.py +0 -0
  73. {testql-1.2.63 → testql-1.2.65}/testql/commands/suite/__init__.py +0 -0
  74. {testql-1.2.63 → testql-1.2.65}/testql/commands/suite/cli.py +0 -0
  75. {testql-1.2.63 → testql-1.2.65}/testql/commands/suite/collection.py +0 -0
  76. {testql-1.2.63 → testql-1.2.65}/testql/commands/suite/execution.py +0 -0
  77. {testql-1.2.63 → testql-1.2.65}/testql/commands/suite/listing.py +0 -0
  78. {testql-1.2.63 → testql-1.2.65}/testql/commands/suite/reports.py +0 -0
  79. {testql-1.2.63 → testql-1.2.65}/testql/commands/suite_cmd.py +0 -0
  80. {testql-1.2.63 → testql-1.2.65}/testql/commands/templates/__init__.py +0 -0
  81. {testql-1.2.63 → testql-1.2.65}/testql/commands/templates/content.py +0 -0
  82. {testql-1.2.63 → testql-1.2.65}/testql/commands/templates/templates.py +0 -0
  83. {testql-1.2.63 → testql-1.2.65}/testql/commands/topology_cmd.py +0 -0
  84. {testql-1.2.63 → testql-1.2.65}/testql/commands/watchdog_cmd.py +0 -0
  85. {testql-1.2.63 → testql-1.2.65}/testql/context/__init__.py +0 -0
  86. {testql-1.2.63 → testql-1.2.65}/testql/context/runtime.py +0 -0
  87. {testql-1.2.63 → testql-1.2.65}/testql/conversation/__init__.py +0 -0
  88. {testql-1.2.63 → testql-1.2.65}/testql/conversation/runner.py +0 -0
  89. {testql-1.2.63 → testql-1.2.65}/testql/data/__init__.py +0 -0
  90. {testql-1.2.63 → testql-1.2.65}/testql/data/verification-request-v1.schema.json +0 -0
  91. {testql-1.2.63 → testql-1.2.65}/testql/data/verification-result-v1.schema.json +0 -0
  92. {testql-1.2.63 → testql-1.2.65}/testql/detectors/__init__.py +0 -0
  93. {testql-1.2.63 → testql-1.2.65}/testql/detectors/base.py +0 -0
  94. {testql-1.2.63 → testql-1.2.65}/testql/detectors/config_detector.py +0 -0
  95. {testql-1.2.63 → testql-1.2.65}/testql/detectors/django_detector.py +0 -0
  96. {testql-1.2.63 → testql-1.2.65}/testql/detectors/express_detector.py +0 -0
  97. {testql-1.2.63 → testql-1.2.65}/testql/detectors/fastapi_detector.py +0 -0
  98. {testql-1.2.63 → testql-1.2.65}/testql/detectors/flask_detector.py +0 -0
  99. {testql-1.2.63 → testql-1.2.65}/testql/detectors/graphql_detector.py +0 -0
  100. {testql-1.2.63 → testql-1.2.65}/testql/detectors/models.py +0 -0
  101. {testql-1.2.63 → testql-1.2.65}/testql/detectors/openapi_detector.py +0 -0
  102. {testql-1.2.63 → testql-1.2.65}/testql/detectors/test_detector.py +0 -0
  103. {testql-1.2.63 → testql-1.2.65}/testql/detectors/unified.py +0 -0
  104. {testql-1.2.63 → testql-1.2.65}/testql/detectors/websocket_detector.py +0 -0
  105. {testql-1.2.63 → testql-1.2.65}/testql/discovery/__init__.py +0 -0
  106. {testql-1.2.63 → testql-1.2.65}/testql/discovery/manifest.py +0 -0
  107. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/__init__.py +0 -0
  108. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/base.py +0 -0
  109. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/browser/__init__.py +0 -0
  110. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/browser/playwright_page.py +0 -0
  111. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/filesystem/__init__.py +0 -0
  112. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/filesystem/api_openapi.py +0 -0
  113. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/filesystem/container_compose.py +0 -0
  114. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/filesystem/container_dockerfile.py +0 -0
  115. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/filesystem/package_node.py +0 -0
  116. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/filesystem/package_python.py +0 -0
  117. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/network/__init__.py +0 -0
  118. {testql-1.2.63 → testql-1.2.65}/testql/discovery/probes/network/http_endpoint.py +0 -0
  119. {testql-1.2.63 → testql-1.2.65}/testql/discovery/registry.py +0 -0
  120. {testql-1.2.63 → testql-1.2.65}/testql/discovery/source.py +0 -0
  121. {testql-1.2.63 → testql-1.2.65}/testql/doql_parser.py +0 -0
  122. {testql-1.2.63 → testql-1.2.65}/testql/echo_schemas.py +0 -0
  123. {testql-1.2.63 → testql-1.2.65}/testql/endpoint_detector.py +0 -0
  124. {testql-1.2.63 → testql-1.2.65}/testql/export/__init__.py +0 -0
  125. {testql-1.2.63 → testql-1.2.65}/testql/export/scenario_builder.py +0 -0
  126. {testql-1.2.63 → testql-1.2.65}/testql/generator.py +0 -0
  127. {testql-1.2.63 → testql-1.2.65}/testql/generators/__init__.py +0 -0
  128. {testql-1.2.63 → testql-1.2.65}/testql/generators/analyzers.py +0 -0
  129. {testql-1.2.63 → testql-1.2.65}/testql/generators/api_generator.py +0 -0
  130. {testql-1.2.63 → testql-1.2.65}/testql/generators/base.py +0 -0
  131. {testql-1.2.63 → testql-1.2.65}/testql/generators/convenience.py +0 -0
  132. {testql-1.2.63 → testql-1.2.65}/testql/generators/conversation_generator.py +0 -0
  133. {testql-1.2.63 → testql-1.2.65}/testql/generators/generators.py +0 -0
  134. {testql-1.2.63 → testql-1.2.65}/testql/generators/llm/__init__.py +0 -0
  135. {testql-1.2.63 → testql-1.2.65}/testql/generators/llm/coverage_optimizer.py +0 -0
  136. {testql-1.2.63 → testql-1.2.65}/testql/generators/llm/edge_case_generator.py +0 -0
  137. {testql-1.2.63 → testql-1.2.65}/testql/generators/multi.py +0 -0
  138. {testql-1.2.63 → testql-1.2.65}/testql/generators/page_analyzer.py +0 -0
  139. {testql-1.2.63 → testql-1.2.65}/testql/generators/pipeline.py +0 -0
  140. {testql-1.2.63 → testql-1.2.65}/testql/generators/pytest_generator.py +0 -0
  141. {testql-1.2.63 → testql-1.2.65}/testql/generators/scenario_generator.py +0 -0
  142. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/__init__.py +0 -0
  143. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/base.py +0 -0
  144. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/config_source.py +0 -0
  145. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/conversation.py +0 -0
  146. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/nl_source.py +0 -0
  147. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/openapi_source.py +0 -0
  148. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/oql_models.py +0 -0
  149. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/oql_parser.py +0 -0
  150. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/oql_source.py +0 -0
  151. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/page_source.py +0 -0
  152. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/pytest_source.py +0 -0
  153. {testql-1.2.63 → testql-1.2.65}/testql/generators/sources/ui_source.py +0 -0
  154. {testql-1.2.63 → testql-1.2.65}/testql/generators/specialized_generator.py +0 -0
  155. {testql-1.2.63 → testql-1.2.65}/testql/generators/targets/__init__.py +0 -0
  156. {testql-1.2.63 → testql-1.2.65}/testql/generators/targets/base.py +0 -0
  157. {testql-1.2.63 → testql-1.2.65}/testql/generators/targets/nl_target.py +0 -0
  158. {testql-1.2.63 → testql-1.2.65}/testql/generators/targets/pytest_target.py +0 -0
  159. {testql-1.2.63 → testql-1.2.65}/testql/generators/targets/testtoon_target.py +0 -0
  160. {testql-1.2.63 → testql-1.2.65}/testql/generators/test_generator.py +0 -0
  161. {testql-1.2.63 → testql-1.2.65}/testql/integrations/planfile_hook.py +0 -0
  162. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/__init__.py +0 -0
  163. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_assertions.py +0 -0
  164. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_context.py +0 -0
  165. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_converter.py +0 -0
  166. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_desktop.py +0 -0
  167. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_dom_scan.py +0 -0
  168. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_encoder.py +0 -0
  169. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_flow.py +0 -0
  170. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_gui_expand.py +0 -0
  171. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_hardware.py +0 -0
  172. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_modbus.py +0 -0
  173. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_node_playwright.py +0 -0
  174. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_node_playwright_bridge.cjs +0 -0
  175. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_parser.py +0 -0
  176. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_shell.py +0 -0
  177. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_unit.py +0 -0
  178. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_validation.py +0 -0
  179. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/_websockets.py +0 -0
  180. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/__init__.py +0 -0
  181. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/core.py +0 -0
  182. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/dispatcher.py +0 -0
  183. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/__init__.py +0 -0
  184. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/api.py +0 -0
  185. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/assertions.py +0 -0
  186. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/encoder.py +0 -0
  187. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/flow.py +0 -0
  188. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/include.py +0 -0
  189. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/navigate.py +0 -0
  190. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/record.py +0 -0
  191. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/select.py +0 -0
  192. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/unknown.py +0 -0
  193. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/handlers/wait.py +0 -0
  194. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/models.py +0 -0
  195. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/parsers.py +0 -0
  196. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/converter/renderer.py +0 -0
  197. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/dispatcher.py +0 -0
  198. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/dom_scan_formatters.py +0 -0
  199. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/dom_scan_mixin.py +0 -0
  200. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/dom_scan_models.py +0 -0
  201. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/dom_scanner.py +0 -0
  202. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/interpreter.py +0 -0
  203. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/testtoon_models.py +0 -0
  204. {testql-1.2.63 → testql-1.2.65}/testql/interpreter/testtoon_parser.py +0 -0
  205. {testql-1.2.63 → testql-1.2.65}/testql/interpreter.py +0 -0
  206. {testql-1.2.63 → testql-1.2.65}/testql/ir/__init__.py +0 -0
  207. {testql-1.2.63 → testql-1.2.65}/testql/ir/assertions.py +0 -0
  208. {testql-1.2.63 → testql-1.2.65}/testql/ir/captures.py +0 -0
  209. {testql-1.2.63 → testql-1.2.65}/testql/ir/fixtures.py +0 -0
  210. {testql-1.2.63 → testql-1.2.65}/testql/ir/metadata.py +0 -0
  211. {testql-1.2.63 → testql-1.2.65}/testql/ir/plan.py +0 -0
  212. {testql-1.2.63 → testql-1.2.65}/testql/ir/steps.py +0 -0
  213. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/__init__.py +0 -0
  214. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/assertion_eval.py +0 -0
  215. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/context.py +0 -0
  216. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/__init__.py +0 -0
  217. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/api.py +0 -0
  218. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/assert_json.py +0 -0
  219. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/base.py +0 -0
  220. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/encoder.py +0 -0
  221. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/graphql.py +0 -0
  222. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/gui.py +0 -0
  223. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/nl.py +0 -0
  224. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/proto.py +0 -0
  225. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/shell.py +0 -0
  226. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/sql.py +0 -0
  227. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/executors/unit.py +0 -0
  228. {testql-1.2.63 → testql-1.2.65}/testql/ir_runner/interpolation.py +0 -0
  229. {testql-1.2.63 → testql-1.2.65}/testql/mcp/__init__.py +0 -0
  230. {testql-1.2.63 → testql-1.2.65}/testql/mcp/server.py +0 -0
  231. {testql-1.2.63 → testql-1.2.65}/testql/meta/__init__.py +0 -0
  232. {testql-1.2.63 → testql-1.2.65}/testql/meta/confidence_scorer.py +0 -0
  233. {testql-1.2.63 → testql-1.2.65}/testql/meta/coverage_analyzer.py +0 -0
  234. {testql-1.2.63 → testql-1.2.65}/testql/meta/mutator.py +0 -0
  235. {testql-1.2.63 → testql-1.2.65}/testql/meta/self_test.py +0 -0
  236. {testql-1.2.63 → testql-1.2.65}/testql/nlp2env/__init__.py +0 -0
  237. {testql-1.2.63 → testql-1.2.65}/testql/nlp2env/llm.py +0 -0
  238. {testql-1.2.63 → testql-1.2.65}/testql/nlp2env/mcp_client.py +0 -0
  239. {testql-1.2.63 → testql-1.2.65}/testql/nlp2env/runner.py +0 -0
  240. {testql-1.2.63 → testql-1.2.65}/testql/nlp2env/scenarios.py +0 -0
  241. {testql-1.2.63 → testql-1.2.65}/testql/openapi_generator.py +0 -0
  242. {testql-1.2.63 → testql-1.2.65}/testql/pipeline.py +0 -0
  243. {testql-1.2.63 → testql-1.2.65}/testql/proto_schema/__init__.py +0 -0
  244. {testql-1.2.63 → testql-1.2.65}/testql/proto_schema/descriptor_loader.py +0 -0
  245. {testql-1.2.63 → testql-1.2.65}/testql/proto_schema/message_validator.py +0 -0
  246. {testql-1.2.63 → testql-1.2.65}/testql/report_generator.py +0 -0
  247. {testql-1.2.63 → testql-1.2.65}/testql/reporters/__init__.py +0 -0
  248. {testql-1.2.63 → testql-1.2.65}/testql/reporters/console.py +0 -0
  249. {testql-1.2.63 → testql-1.2.65}/testql/reporters/json_reporter.py +0 -0
  250. {testql-1.2.63 → testql-1.2.65}/testql/reporters/junit.py +0 -0
  251. {testql-1.2.63 → testql-1.2.65}/testql/results/__init__.py +0 -0
  252. {testql-1.2.63 → testql-1.2.65}/testql/results/analyzer.py +0 -0
  253. {testql-1.2.63 → testql-1.2.65}/testql/results/artifacts.py +0 -0
  254. {testql-1.2.63 → testql-1.2.65}/testql/results/models.py +0 -0
  255. {testql-1.2.63 → testql-1.2.65}/testql/results/serializers.py +0 -0
  256. {testql-1.2.63 → testql-1.2.65}/testql/runner.py +0 -0
  257. {testql-1.2.63 → testql-1.2.65}/testql/runners/__init__.py +0 -0
  258. {testql-1.2.63 → testql-1.2.65}/testql/sql_schema/__init__.py +0 -0
  259. {testql-1.2.63 → testql-1.2.65}/testql/sql_schema/ddl_parser.py +0 -0
  260. {testql-1.2.63 → testql-1.2.65}/testql/sql_schema/dialect_resolver.py +0 -0
  261. {testql-1.2.63 → testql-1.2.65}/testql/sumd_generator.py +0 -0
  262. {testql-1.2.63 → testql-1.2.65}/testql/sumd_parser.py +0 -0
  263. {testql-1.2.63 → testql-1.2.65}/testql/toon_parser.py +0 -0
  264. {testql-1.2.63 → testql-1.2.65}/testql/topology/__init__.py +0 -0
  265. {testql-1.2.63 → testql-1.2.65}/testql/topology/builder.py +0 -0
  266. {testql-1.2.63 → testql-1.2.65}/testql/topology/generator.py +0 -0
  267. {testql-1.2.63 → testql-1.2.65}/testql/topology/models.py +0 -0
  268. {testql-1.2.63 → testql-1.2.65}/testql/topology/serializers.py +0 -0
  269. {testql-1.2.63 → testql-1.2.65}/testql/topology/sitemap.py +0 -0
  270. {testql-1.2.63 → testql-1.2.65}/testql/verification.py +0 -0
  271. {testql-1.2.63 → testql-1.2.65}/testql.egg-info/SOURCES.txt +0 -0
  272. {testql-1.2.63 → testql-1.2.65}/testql.egg-info/dependency_links.txt +0 -0
  273. {testql-1.2.63 → testql-1.2.65}/testql.egg-info/entry_points.txt +0 -0
  274. {testql-1.2.63 → testql-1.2.65}/testql.egg-info/requires.txt +0 -0
  275. {testql-1.2.63 → testql-1.2.65}/testql.egg-info/top_level.txt +0 -0
  276. {testql-1.2.63 → testql-1.2.65}/tests/test_adapter_capture_syntax.py +0 -0
  277. {testql-1.2.63 → testql-1.2.65}/tests/test_adapters_base.py +0 -0
  278. {testql-1.2.63 → testql-1.2.65}/tests/test_api_handler.py +0 -0
  279. {testql-1.2.63 → testql-1.2.65}/tests/test_assertion_suite.py +0 -0
  280. {testql-1.2.63 → testql-1.2.65}/tests/test_browser_discovery.py +0 -0
  281. {testql-1.2.63 → testql-1.2.65}/tests/test_cc_refactor_helpers.py +0 -0
  282. {testql-1.2.63 → testql-1.2.65}/tests/test_cli.py +0 -0
  283. {testql-1.2.63 → testql-1.2.65}/tests/test_cli_no_block.py +0 -0
  284. {testql-1.2.63 → testql-1.2.65}/tests/test_conversation_live_llm.py +0 -0
  285. {testql-1.2.63 → testql-1.2.65}/tests/test_conversation_nlp2dsl.py +0 -0
  286. {testql-1.2.63 → testql-1.2.65}/tests/test_converter.py +0 -0
  287. {testql-1.2.63 → testql-1.2.65}/tests/test_converter_handlers.py +0 -0
  288. {testql-1.2.63 → testql-1.2.65}/tests/test_desktop_assert_elements.py +0 -0
  289. {testql-1.2.63 → testql-1.2.65}/tests/test_desktop_execution.py +0 -0
  290. {testql-1.2.63 → testql-1.2.65}/tests/test_detectors.py +0 -0
  291. {testql-1.2.63 → testql-1.2.65}/tests/test_discovery.py +0 -0
  292. {testql-1.2.63 → testql-1.2.65}/tests/test_dispatcher.py +0 -0
  293. {testql-1.2.63 → testql-1.2.65}/tests/test_doql_parser_sumd_gen.py +0 -0
  294. {testql-1.2.63 → testql-1.2.65}/tests/test_echo.py +0 -0
  295. {testql-1.2.63 → testql-1.2.65}/tests/test_echo_doql_parser.py +0 -0
  296. {testql-1.2.63 → testql-1.2.65}/tests/test_echo_schemas_helpers.py +0 -0
  297. {testql-1.2.63 → testql-1.2.65}/tests/test_encoder_routes.py +0 -0
  298. {testql-1.2.63 → testql-1.2.65}/tests/test_environment_regressions.py +0 -0
  299. {testql-1.2.63 → testql-1.2.65}/tests/test_generate_cmd.py +0 -0
  300. {testql-1.2.63 → testql-1.2.65}/tests/test_generate_from_page_cli.py +0 -0
  301. {testql-1.2.63 → testql-1.2.65}/tests/test_generate_ir_cli.py +0 -0
  302. {testql-1.2.63 → testql-1.2.65}/tests/test_generators.py +0 -0
  303. {testql-1.2.63 → testql-1.2.65}/tests/test_ir.py +0 -0
  304. {testql-1.2.63 → testql-1.2.65}/tests/test_ir_captures.py +0 -0
  305. {testql-1.2.63 → testql-1.2.65}/tests/test_ir_runner_assertion_eval.py +0 -0
  306. {testql-1.2.63 → testql-1.2.65}/tests/test_ir_runner_engine.py +0 -0
  307. {testql-1.2.63 → testql-1.2.65}/tests/test_ir_runner_executors.py +0 -0
  308. {testql-1.2.63 → testql-1.2.65}/tests/test_ir_runner_interpolation.py +0 -0
  309. {testql-1.2.63 → testql-1.2.65}/tests/test_mcp_autoloop.py +0 -0
  310. {testql-1.2.63 → testql-1.2.65}/tests/test_meta_confidence.py +0 -0
  311. {testql-1.2.63 → testql-1.2.65}/tests/test_meta_coverage.py +0 -0
  312. {testql-1.2.63 → testql-1.2.65}/tests/test_meta_mutator.py +0 -0
  313. {testql-1.2.63 → testql-1.2.65}/tests/test_meta_self_test.py +0 -0
  314. {testql-1.2.63 → testql-1.2.65}/tests/test_misc_cmds.py +0 -0
  315. {testql-1.2.63 → testql-1.2.65}/tests/test_modbus_commands.py +0 -0
  316. {testql-1.2.63 → testql-1.2.65}/tests/test_navigate_json_path.py +0 -0
  317. {testql-1.2.63 → testql-1.2.65}/tests/test_network_discovery.py +0 -0
  318. {testql-1.2.63 → testql-1.2.65}/tests/test_nl_adapter.py +0 -0
  319. {testql-1.2.63 → testql-1.2.65}/tests/test_nl_entity_extractor.py +0 -0
  320. {testql-1.2.63 → testql-1.2.65}/tests/test_nl_grammar.py +0 -0
  321. {testql-1.2.63 → testql-1.2.65}/tests/test_nl_intent_recognizer.py +0 -0
  322. {testql-1.2.63 → testql-1.2.65}/tests/test_nl_scenarios_e2e.py +0 -0
  323. {testql-1.2.63 → testql-1.2.65}/tests/test_nlp2env_adapter.py +0 -0
  324. {testql-1.2.63 → testql-1.2.65}/tests/test_node_playwright_fallback.py +0 -0
  325. {testql-1.2.63 → testql-1.2.65}/tests/test_openapi_generator.py +0 -0
  326. {testql-1.2.63 → testql-1.2.65}/tests/test_page_analyzer.py +0 -0
  327. {testql-1.2.63 → testql-1.2.65}/tests/test_pipeline.py +0 -0
  328. {testql-1.2.63 → testql-1.2.65}/tests/test_plugin_registry.py +0 -0
  329. {testql-1.2.63 → testql-1.2.65}/tests/test_proto_descriptor_loader.py +0 -0
  330. {testql-1.2.63 → testql-1.2.65}/tests/test_proto_graphql_scenarios_e2e.py +0 -0
  331. {testql-1.2.63 → testql-1.2.65}/tests/test_proto_message_validator.py +0 -0
  332. {testql-1.2.63 → testql-1.2.65}/tests/test_report_generator.py +0 -0
  333. {testql-1.2.63 → testql-1.2.65}/tests/test_reporters.py +0 -0
  334. {testql-1.2.63 → testql-1.2.65}/tests/test_results.py +0 -0
  335. {testql-1.2.63 → testql-1.2.65}/tests/test_run_cmd.py +0 -0
  336. {testql-1.2.63 → testql-1.2.65}/tests/test_run_ir_cli.py +0 -0
  337. {testql-1.2.63 → testql-1.2.65}/tests/test_runner.py +0 -0
  338. {testql-1.2.63 → testql-1.2.65}/tests/test_runtime_context.py +0 -0
  339. {testql-1.2.63 → testql-1.2.65}/tests/test_scenario_yaml_adapter.py +0 -0
  340. {testql-1.2.63 → testql-1.2.65}/tests/test_shell_execution.py +0 -0
  341. {testql-1.2.63 → testql-1.2.65}/tests/test_smoke_decisions.py +0 -0
  342. {testql-1.2.63 → testql-1.2.65}/tests/test_sources.py +0 -0
  343. {testql-1.2.63 → testql-1.2.65}/tests/test_sql_ddl_parser.py +0 -0
  344. {testql-1.2.63 → testql-1.2.65}/tests/test_sql_dialect_resolver.py +0 -0
  345. {testql-1.2.63 → testql-1.2.65}/tests/test_suite_cmd_helpers.py +0 -0
  346. {testql-1.2.63 → testql-1.2.65}/tests/test_suite_execution.py +0 -0
  347. {testql-1.2.63 → testql-1.2.65}/tests/test_suite_listing.py +0 -0
  348. {testql-1.2.63 → testql-1.2.65}/tests/test_sumd_parser.py +0 -0
  349. {testql-1.2.63 → testql-1.2.65}/tests/test_targets.py +0 -0
  350. {testql-1.2.63 → testql-1.2.65}/tests/test_test_generator.py +0 -0
  351. {testql-1.2.63 → testql-1.2.65}/tests/test_testtoon_adapter.py +0 -0
  352. {testql-1.2.63 → testql-1.2.65}/tests/test_toon_parser.py +0 -0
  353. {testql-1.2.63 → testql-1.2.65}/tests/test_topology.py +0 -0
  354. {testql-1.2.63 → testql-1.2.65}/tests/test_topology_generator.py +0 -0
  355. {testql-1.2.63 → testql-1.2.65}/tests/test_unit_execution.py +0 -0
  356. {testql-1.2.63 → testql-1.2.65}/tests/test_validation.py +0 -0
  357. {testql-1.2.63 → testql-1.2.65}/tests/test_verification_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testql
3
- Version: 1.2.63
3
+ Version: 1.2.65
4
4
  Summary: TestQL — Multi-DSL Test Platform: TestTOON / NL / SQL / Proto / GraphQL adapters with Unified IR, generator engine, and meta-testing
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: <3.14,>=3.11
@@ -62,10 +62,10 @@ Dynamic: license-file
62
62
  ## AI Cost Tracking
63
63
 
64
64
  ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-1.2.63-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
65
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$29.97-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-74.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
65
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$30.60-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-75.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
66
 
67
- - 🤖 **LLM usage:** $29.9715 (130 commits)
68
- - 👤 **Human dev:** ~$7463 (74.6h @ $100/h, 30min dedup)
67
+ - 🤖 **LLM usage:** $30.6010 (133 commits)
68
+ - 👤 **Human dev:** ~$7576 (75.8h @ $100/h, 30min dedup)
69
69
 
70
70
  Generated on 2026-07-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
71
71
 
@@ -4,10 +4,10 @@
4
4
  ## AI Cost Tracking
5
5
 
6
6
  ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-1.2.63-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$29.97-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-74.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$30.60-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-75.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $29.9715 (130 commits)
10
- - 👤 **Human dev:** ~$7463 (74.6h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $30.6010 (133 commits)
10
+ - 👤 **Human dev:** ~$7576 (75.8h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-07-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "testql"
7
- version = "1.2.63"
7
+ version = "1.2.65"
8
8
  description = "TestQL — Multi-DSL Test Platform: TestTOON / NL / SQL / Proto / GraphQL adapters with Unified IR, generator engine, and meta-testing"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
@@ -6,4 +6,4 @@ external artifacts into IR; meta-testing (`testql.meta`) analyses the
6
6
  generated plans for coverage, confidence and mutation resilience.
7
7
  """
8
8
 
9
- __version__ = "1.2.63"
9
+ __version__ = "1.2.64"
@@ -72,7 +72,7 @@ class ApiRunnerMixin:
72
72
  continue
73
73
  # Re-raise non-retryable errors
74
74
  raise
75
- except Exception as e:
75
+ except Exception:
76
76
  # Non-retryable errors
77
77
  raise
78
78
 
@@ -128,7 +128,7 @@ class ApiRunnerMixin:
128
128
  return
129
129
 
130
130
  method = parts[0].upper()
131
- url = parts[1].strip("\"'")
131
+ url = self.vars.interpolate(parts[1].strip("\"'"))
132
132
  body_str = parts[2] if len(parts) > 2 else ""
133
133
 
134
134
  if url.startswith("/"):
@@ -1243,10 +1243,29 @@ class GuiMixin:
1243
1243
  return
1244
1244
 
1245
1245
  try:
1246
- actual = self._read_gui_value(selector)
1246
+ # Form values are often updated after an asynchronous request. Keep
1247
+ # value assertions consistent with GUI_ASSERT_TEXT by polling within
1248
+ # the configured operation budget instead of racing the first read.
1249
+ budget_ms = self._gui_operation_timeout()
1250
+ deadline = time.monotonic() + budget_ms / 1000.0
1251
+ actual = ""
1252
+ last_error: Exception | None = None
1253
+ while True:
1254
+ try:
1255
+ actual = self._read_gui_value(selector)
1256
+ last_error = None
1257
+ except Exception as read_error:
1258
+ last_error = read_error
1259
+ actual = ""
1260
+ if self._compare_value(actual, op.upper(), expected) or time.monotonic() >= deadline:
1261
+ break
1262
+ time.sleep(0.1)
1263
+
1247
1264
  if self._compare_value(actual, op.upper(), expected):
1248
1265
  self.out.step("✅", f'{name} (actual: "{actual}")')
1249
1266
  self.results.append(StepResult(name=name, status=StepStatus.PASSED))
1267
+ elif last_error is not None and not actual:
1268
+ raise last_error
1250
1269
  else:
1251
1270
  self.out.step("❌", f'{name} (actual: "{actual}")')
1252
1271
  self.results.append(StepResult(
@@ -19,9 +19,6 @@ import json
19
19
 
20
20
  from ._gui_expand import expand_gui_row
21
21
  from ._parser import OqlLine, OqlScript
22
- from .testtoon_models import ToonSection, ToonScript
23
- from .testtoon_parser import parse_testtoon
24
-
25
22
  # Backward compatibility: re-export classes that were moved
26
23
  from .testtoon_models import ToonSection, ToonScript # noqa: F401
27
24
  from .testtoon_parser import parse_testtoon # noqa: F401
@@ -110,9 +107,38 @@ def _append_api_asserts(row: dict, lines: list[OqlLine], line_num: int) -> int:
110
107
  return line_num
111
108
 
112
109
 
113
- def _expand_api(section: ToonSection, lines: list[OqlLine], line_num: int) -> int:
110
+ def _append_api_captures(
111
+ row: dict,
112
+ api_step: int,
113
+ capture_rows: list[dict],
114
+ lines: list[OqlLine],
115
+ line_num: int,
116
+ ) -> int:
117
+ """Append captures assigned to an API row by its 1-based index or name."""
118
+ step_name = str(row.get('name', '')).strip()
119
+ for capture in capture_rows:
120
+ target = str(capture.get('step', '')).strip()
121
+ if target != str(api_step) and (not step_name or target != step_name):
122
+ continue
123
+ var_name = str(capture.get('var', '')).strip()
124
+ from_path = str(capture.get('from', '')).strip()
125
+ if not var_name or not from_path:
126
+ continue
127
+ args = f'{var_name} FROM "{from_path}"'
128
+ line_num = _append_raw_command(lines, line_num, 'CAPTURE', args)
129
+ return line_num
130
+
131
+
132
+ def _expand_api(
133
+ section: ToonSection,
134
+ lines: list[OqlLine],
135
+ line_num: int,
136
+ *,
137
+ capture_rows: list[dict] | None = None,
138
+ step_offset: int = 0,
139
+ ) -> int:
114
140
  """Expand API section → API + ASSERT_STATUS commands."""
115
- for row in section.rows:
141
+ for row_index, row in enumerate(section.rows, 1):
116
142
  method = row.get('method', 'GET')
117
143
  endpoint = row.get('endpoint', '/')
118
144
  body = row.get('body')
@@ -127,6 +153,13 @@ def _expand_api(section: ToonSection, lines: list[OqlLine], line_num: int) -> in
127
153
  raw = f'API {method} "{endpoint}"{body_str}'
128
154
  lines.append(OqlLine(number=line_num, command='API', args=f'{method} "{endpoint}"{body_str}', raw=raw))
129
155
  line_num = _append_api_asserts(row, lines, line_num + 1)
156
+ line_num = _append_api_captures(
157
+ row,
158
+ step_offset + row_index,
159
+ capture_rows or [],
160
+ lines,
161
+ line_num,
162
+ )
130
163
  return line_num
131
164
 
132
165
 
@@ -649,8 +682,29 @@ def testtoon_to_oql(text: str, filename: str = "<string>") -> OqlScript:
649
682
  toon = parse_testtoon(text, filename)
650
683
  lines: list[OqlLine] = []
651
684
  line_num = 1
685
+ api_step_count = 0
686
+ capture_rows = [
687
+ row
688
+ for section in toon.sections
689
+ if section.type == 'CAPTURE'
690
+ for row in section.rows
691
+ ]
652
692
 
653
693
  for section in toon.sections:
694
+ if section.type == 'CAPTURE':
695
+ # Captures must run while the referenced API response is still the
696
+ # current response, so API expansion interleaves them above.
697
+ continue
698
+ if section.type == 'API':
699
+ line_num = _expand_api(
700
+ section,
701
+ lines,
702
+ line_num,
703
+ capture_rows=capture_rows,
704
+ step_offset=api_step_count,
705
+ )
706
+ api_step_count += len(section.rows)
707
+ continue
654
708
  expander = _SECTION_EXPANDERS.get(section.type, _expand_generic)
655
709
  line_num = expander(section, lines, line_num)
656
710
 
@@ -49,7 +49,14 @@ def _apply_captures(step, result: StepResult, ctx: ExecutionContext) -> None:
49
49
  return
50
50
  payload = (result.details or {}).get("payload", {})
51
51
  for capture in captures:
52
- value = navigate(payload, capture.from_path)
52
+ # API capture paths are relative to the response body (`response.data.id`
53
+ # is normalized to `data.id`). Other executors expose their result fields
54
+ # directly. Fall back to the full envelope so API status/header captures
55
+ # remain possible.
56
+ capture_root = payload.get("data", {}) if getattr(step, "kind", "") == "api" else payload
57
+ value = navigate(capture_root, capture.from_path)
58
+ if value is None and capture_root is not payload:
59
+ value = navigate(payload, capture.from_path)
53
60
  if value is None:
54
61
  ctx.warnings.append(
55
62
  f"capture {capture.var_name!r} from {capture.from_path!r}: path not found"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testql
3
- Version: 1.2.63
3
+ Version: 1.2.65
4
4
  Summary: TestQL — Multi-DSL Test Platform: TestTOON / NL / SQL / Proto / GraphQL adapters with Unified IR, generator engine, and meta-testing
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: <3.14,>=3.11
@@ -62,10 +62,10 @@ Dynamic: license-file
62
62
  ## AI Cost Tracking
63
63
 
64
64
  ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-1.2.63-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
65
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$29.97-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-74.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
65
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$30.60-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-75.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
66
 
67
- - 🤖 **LLM usage:** $29.9715 (130 commits)
68
- - 👤 **Human dev:** ~$7463 (74.6h @ $100/h, 30min dedup)
67
+ - 🤖 **LLM usage:** $30.6010 (133 commits)
68
+ - 👤 **Human dev:** ~$7576 (75.8h @ $100/h, 30min dedup)
69
69
 
70
70
  Generated on 2026-07-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
71
71
 
@@ -294,6 +294,77 @@ class TestGuiExecution:
294
294
  assert interpreter.results[-1].status.value == "failed"
295
295
  assert elapsed < 2.0
296
296
 
297
+ def test_gui_assert_value_polls_until_async_value_appears(self, interpreter):
298
+ """GUI_ASSERT_VALUE waits for an asynchronously-updated form value."""
299
+ from testql.interpreter._parser import OqlLine
300
+
301
+ interpreter.dry_run = False
302
+ interpreter._gui_driver = "playwright"
303
+
304
+ class _Locator:
305
+ def __init__(self):
306
+ self.reads = 0
307
+
308
+ @property
309
+ def first(self):
310
+ return self
311
+
312
+ def input_value(self):
313
+ self.reads += 1
314
+ return "Analizowanie…" if self.reads < 3 else '{"kind":"organization_status"}'
315
+
316
+ class _AsyncPage:
317
+ def __init__(self):
318
+ self.value_locator = _Locator()
319
+
320
+ def locator(self, selector):
321
+ return self.value_locator
322
+
323
+ interpreter._gui_page = _AsyncPage()
324
+ interpreter.timeout_ms = 3000
325
+ line = OqlLine(
326
+ number=1, command="GUI_ASSERT_VALUE",
327
+ args='"#llmIntentJson" CONTAINS "organization_status"',
328
+ raw='GUI_ASSERT_VALUE "#llmIntentJson" CONTAINS "organization_status"',
329
+ )
330
+ interpreter._cmd_gui_assert_value(line.args, line)
331
+
332
+ assert interpreter.results[-1].status.value == "passed"
333
+ assert interpreter._gui_page.value_locator.reads >= 3
334
+
335
+ def test_gui_assert_value_fails_bounded_when_value_never_appears(self, interpreter):
336
+ """A stable wrong form value fails within the operation timeout."""
337
+ import time
338
+ from testql.interpreter._parser import OqlLine
339
+
340
+ interpreter.dry_run = False
341
+ interpreter._gui_driver = "playwright"
342
+
343
+ class _Locator:
344
+ @property
345
+ def first(self):
346
+ return self
347
+
348
+ def input_value(self):
349
+ return "Analizowanie…"
350
+
351
+ class _StablePage:
352
+ def locator(self, selector):
353
+ return _Locator()
354
+
355
+ interpreter._gui_page = _StablePage()
356
+ interpreter.timeout_ms = 300
357
+ line = OqlLine(
358
+ number=1, command="GUI_ASSERT_VALUE",
359
+ args='"#x" CONTAINS "done"', raw='GUI_ASSERT_VALUE "#x" CONTAINS "done"',
360
+ )
361
+ started = time.monotonic()
362
+ interpreter._cmd_gui_assert_value(line.args, line)
363
+ elapsed = time.monotonic() - started
364
+
365
+ assert interpreter.results[-1].status.value == "failed"
366
+ assert elapsed < 2.0
367
+
297
368
  def test_gui_start_no_args_error(self, interpreter):
298
369
  """Test GUI_START without arguments."""
299
370
  interpreter.dry_run = False
@@ -103,6 +103,34 @@ class TestTestTOONExpansion:
103
103
  assert script.lines[3].command == "ASSERT_STATUS"
104
104
  assert "201" in script.lines[3].args
105
105
 
106
+ def test_capture_is_interleaved_after_referenced_api_step(self):
107
+ source = (
108
+ "API[2]{method, endpoint, status}:\n"
109
+ " POST, /devices, 201\n"
110
+ " GET, /devices/${device_id}, 200\n"
111
+ "CAPTURE[1]{step, var, from}:\n"
112
+ " 1, device_id, data.id\n"
113
+ )
114
+
115
+ script = _testtoon_to_oql(source, "test.testql.toon.yaml")
116
+
117
+ assert [line.command for line in script.lines] == [
118
+ "API", "ASSERT_STATUS", "CAPTURE", "API", "ASSERT_STATUS",
119
+ ]
120
+ assert script.lines[2].args == 'device_id FROM "data.id"'
121
+
122
+ def test_capture_can_reference_named_api_step(self):
123
+ source = (
124
+ "API[1]{method, endpoint, status, name}:\n"
125
+ " POST, /devices, 201, createDevice\n"
126
+ "CAPTURE[1]{step, var, from}:\n"
127
+ " createDevice, device_id, data.id\n"
128
+ )
129
+
130
+ script = _testtoon_to_oql(source, "test.testql.toon.yaml")
131
+
132
+ assert script.lines[-1].command == "CAPTURE"
133
+
106
134
  def test_encoder_expansion(self):
107
135
  source = (
108
136
  "ENCODER[2]{action, target, value, wait_ms}:\n"
@@ -227,6 +255,22 @@ class TestOqlInterpreter:
227
255
  assert result.ok
228
256
  assert result.passed >= 1
229
257
 
258
+ def test_api_interpolates_captured_variable_in_url(self, monkeypatch):
259
+ requested_urls = []
260
+
261
+ def fake_request(_self, _method, url, _body):
262
+ requested_urls.append(url)
263
+ return 200, {"ok": True}, {}
264
+
265
+ monkeypatch.setattr(OqlInterpreter, "_do_http_request_with_retry", fake_request)
266
+ interp = OqlInterpreter(quiet=True, api_url="http://localhost:8101")
267
+ interp.vars.set("device_id", "dev-7")
268
+
269
+ result = interp.run('API GET "/devices/${device_id}"', "test.tql")
270
+
271
+ assert result.ok
272
+ assert requested_urls == ["http://localhost:8101/devices/dev-7"]
273
+
230
274
  def test_set_get(self):
231
275
  source = 'SET foo "bar"\nGET foo'
232
276
  interp = OqlInterpreter(dry_run=True, quiet=True)
@@ -77,6 +77,26 @@ class TestShellCaptures:
77
77
  assert "hello" in runner.ctx.vars.get("out")
78
78
 
79
79
 
80
+ class TestApiCaptures:
81
+ def test_capture_is_relative_to_response_body(self, monkeypatch):
82
+ from testql.ir_runner.executors import api
83
+
84
+ monkeypatch.setattr(api, "_do_request", lambda *args: (201, {"data": {"id": "sc-1"}}))
85
+ plan = _plan(
86
+ ApiStep(
87
+ method="POST",
88
+ path="/api/v3/scenarios",
89
+ captures=[Capture(var_name="scenario_id", from_path="data.id")],
90
+ )
91
+ )
92
+ runner = IRRunner()
93
+
94
+ result = runner.run(plan)
95
+
96
+ assert result.ok
97
+ assert runner.ctx.vars.get("scenario_id") == "sc-1"
98
+
99
+
80
100
  # ── Missing path → warning, not failure ─────────────────────────────────────
81
101
 
82
102
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes