minitest-cli 0.31.0__tar.gz → 0.32.0__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 (195) hide show
  1. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/PKG-INFO +27 -1
  2. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/README.md +26 -0
  3. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/pyproject.toml +1 -1
  4. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/batch_helpers.py +4 -1
  5. minitest_cli-0.32.0/src/minitest_cli/commands/batch_warnings.py +12 -0
  6. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/flow_types.py +6 -3
  7. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/flow_types_helpers.py +0 -61
  8. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run.py +29 -35
  9. minitest_cli-0.32.0/src/minitest_cli/commands/run_cancel.py +123 -0
  10. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_commit.py +18 -2
  11. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_commit_helpers.py +13 -1
  12. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_display.py +11 -2
  13. minitest_cli-0.32.0/src/minitest_cli/commands/run_tagged.py +44 -0
  14. minitest_cli-0.32.0/src/minitest_cli/commands/run_targets.py +92 -0
  15. minitest_cli-0.32.0/src/minitest_cli/commands/tags.py +130 -0
  16. minitest_cli-0.32.0/src/minitest_cli/commands/tags_helpers.py +71 -0
  17. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story.py +23 -40
  18. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_create.py +7 -9
  19. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_helpers.py +29 -2
  20. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_modify.py +6 -6
  21. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_update.py +2 -4
  22. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/verdicts_projection.py +4 -0
  23. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/main.py +2 -0
  24. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/batch.py +3 -1
  25. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/build.py +3 -0
  26. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/story_run.py +6 -0
  27. minitest_cli-0.32.0/src/minitest_cli/models/tag.py +31 -0
  28. minitest_cli-0.32.0/src/minitest_cli/models/targets.py +56 -0
  29. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/user_story.py +4 -3
  30. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/skill_refresh.py +1 -1
  31. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_flow_types_commands.py +8 -51
  32. minitest_cli-0.32.0/tests/test_native_device_types.py +294 -0
  33. minitest_cli-0.32.0/tests/test_run_cancel.py +154 -0
  34. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_run_commands.py +0 -79
  35. minitest_cli-0.32.0/tests/test_tags_commands.py +285 -0
  36. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_user_story_commands.py +14 -175
  37. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/uv.lock +1 -1
  38. minitest_cli-0.31.0/src/minitest_cli/commands/run_targets.py +0 -40
  39. minitest_cli-0.31.0/src/minitest_cli/models/targets.py +0 -43
  40. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.env.example +0 -0
  41. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.github/workflows/ci.yml +0 -0
  42. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.github/workflows/install-scripts.yml +0 -0
  43. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.github/workflows/release.yml +0 -0
  44. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.gitignore +0 -0
  45. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.opencode/skill/release/SKILL.md +0 -0
  46. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/AGENTS.md +0 -0
  47. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/RELEASE.md +0 -0
  48. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/install.ps1 +0 -0
  49. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/install.sh +0 -0
  50. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/pyrightconfig.json +0 -0
  51. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/renovate.json +0 -0
  52. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/__init__.py +0 -0
  53. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/__init__.py +0 -0
  54. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/apps_manager_client.py +0 -0
  55. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/client.py +0 -0
  56. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/errors.py +0 -0
  57. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/assets/__init__.py +0 -0
  58. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/assets/callback.html +0 -0
  59. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/__init__.py +0 -0
  60. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/_response_errors.py +0 -0
  61. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_knowledge.py +0 -0
  62. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  63. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill.py +0 -0
  64. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill_helpers.py +0 -0
  65. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill_secret.py +0 -0
  66. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill_write.py +0 -0
  67. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps.py +0 -0
  68. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps_dependencies.py +0 -0
  69. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps_get.py +0 -0
  70. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps_helpers.py +0 -0
  71. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/auth.py +0 -0
  72. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/auth_api_key.py +0 -0
  73. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/batch.py +0 -0
  74. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build.py +0 -0
  75. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build_commit.py +0 -0
  76. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build_fix_prompts.py +0 -0
  77. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build_helpers.py +0 -0
  78. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/capabilities.py +0 -0
  79. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/commit_helpers.py +0 -0
  80. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature.py +0 -0
  81. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_apply.py +0 -0
  82. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_conflicts.py +0 -0
  83. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_helpers.py +0 -0
  84. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_show.py +0 -0
  85. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/env.py +0 -0
  86. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/env_helpers.py +0 -0
  87. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/flow_types_write.py +0 -0
  88. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/init.py +0 -0
  89. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/init_helpers.py +0 -0
  90. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/init_playbook.py +0 -0
  91. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues.py +0 -0
  92. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_fix.py +0 -0
  93. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_helpers.py +0 -0
  94. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_projection.py +0 -0
  95. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_service.py +0 -0
  96. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance.py +0 -0
  97. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance_callbacks.py +0 -0
  98. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance_helpers.py +0 -0
  99. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance_state.py +0 -0
  100. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/recording_timeline.py +0 -0
  101. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_feedback.py +0 -0
  102. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_helpers.py +0 -0
  103. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_recording.py +0 -0
  104. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_recording_helpers.py +0 -0
  105. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens.py +0 -0
  106. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_detail.py +0 -0
  107. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_format.py +0 -0
  108. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_helpers.py +0 -0
  109. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_tree.py +0 -0
  110. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/skill.py +0 -0
  111. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_file.py +0 -0
  112. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_file_helpers.py +0 -0
  113. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_file_list.py +0 -0
  114. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile.py +0 -0
  115. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_card.py +0 -0
  116. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_default.py +0 -0
  117. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
  118. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_list.py +0 -0
  119. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_update.py +0 -0
  120. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/upgrade.py +0 -0
  121. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_bindings.py +0 -0
  122. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_camera.py +0 -0
  123. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_criteria.py +0 -0
  124. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_criterion_edits.py +0 -0
  125. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_delete.py +0 -0
  126. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_device_count.py +0 -0
  127. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_overrides.py +0 -0
  128. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_profiles.py +0 -0
  129. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_setup_commit.py +0 -0
  130. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/verdicts.py +0 -0
  131. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/verdicts_helpers.py +0 -0
  132. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/__init__.py +0 -0
  133. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/app_context.py +0 -0
  134. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/auth.py +0 -0
  135. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/config.py +0 -0
  136. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/credentials.py +0 -0
  137. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/oauth.py +0 -0
  138. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/tenants.py +0 -0
  139. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/token_exchange.py +0 -0
  140. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/__init__.py +0 -0
  141. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app.py +0 -0
  142. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app_env_vars.py +0 -0
  143. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app_failure.py +0 -0
  144. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app_screen.py +0 -0
  145. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/base.py +0 -0
  146. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/commit_build.py +0 -0
  147. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/draft_feature.py +0 -0
  148. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/flow_type.py +0 -0
  149. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/issues.py +0 -0
  150. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/recording.py +0 -0
  151. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/__init__.py +0 -0
  152. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/confirm.py +0 -0
  153. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/dependency_sim.py +0 -0
  154. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/mermaid.py +0 -0
  155. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/output.py +0 -0
  156. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/update_check.py +0 -0
  157. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/video_frames.py +0 -0
  158. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/__init__.py +0 -0
  159. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/_commit_transport.py +0 -0
  160. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_api_client_auth.py +0 -0
  161. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_app_knowledge_commands.py +0 -0
  162. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_app_skill_commands.py +0 -0
  163. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_apps_commands.py +0 -0
  164. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_apps_dependencies.py +0 -0
  165. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_auth.py +0 -0
  166. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_auth_api_key.py +0 -0
  167. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_auth_commands.py +0 -0
  168. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_batch_commands.py +0 -0
  169. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_build_commands.py +0 -0
  170. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_build_fix_prompts.py +0 -0
  171. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_capabilities_command.py +0 -0
  172. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_channel_header.py +0 -0
  173. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_code_quality.py +0 -0
  174. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_commit_commands.py +0 -0
  175. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_draft_feature_commands.py +0 -0
  176. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_env.py +0 -0
  177. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_init_command.py +0 -0
  178. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_issues.py +0 -0
  179. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_issues_commands.py +0 -0
  180. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_mermaid.py +0 -0
  181. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_network_errors.py +0 -0
  182. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_recording_timeline.py +0 -0
  183. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_response_errors.py +0 -0
  184. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_run_frames_command.py +0 -0
  185. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_screens_commands.py +0 -0
  186. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_skill_command.py +0 -0
  187. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_suite_change_provenance.py +0 -0
  188. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_test_file_commands.py +0 -0
  189. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_test_profile_commands.py +0 -0
  190. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_upgrade_command.py +0 -0
  191. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_user_story_bindings_commands.py +0 -0
  192. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_user_story_setup_commit.py +0 -0
  193. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_verdicts.py +0 -0
  194. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_verdicts_actionable.py +0 -0
  195. {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: minitest-cli
3
- Version: 0.31.0
3
+ Version: 0.32.0
4
4
  Summary: Minitest CLI – command-line interface for the Minitest testing platform
5
5
  Project-URL: Homepage, https://minitap.ai/
6
6
  Project-URL: Source, https://github.com/minitap-ai/minitest-cli
@@ -111,6 +111,32 @@ minitest --app <app-id> run all --web
111
111
  minitest --app <app-id> run all --ios-build <ios-build-id> --android-build <android-build-id>
112
112
  ```
113
113
 
114
+ ## Native cloud tablets
115
+
116
+ Native lanes default to phones. Use `--ios-device-type phone|tablet` or
117
+ `--android-device-type phone|tablet` on `run start` (including `--tag`), `run all`,
118
+ or `run from-commit` to configure an already selected native cloud lane:
119
+
120
+ ```bash
121
+ minitest --app <app-id> run all --ios-build <ios-build-id> --ios-device-type tablet
122
+ minitest --app <app-id> run start --tag smoke --android-build <android-build-id> --android-device-type tablet
123
+ minitest --app <app-id> run from-commit <full-sha> --platform ios --ios-device-type tablet
124
+ ```
125
+
126
+ The device-type options do not select an OS: `start`/`all` still need the
127
+ corresponding build flag; `from-commit` uses `--platform` (or its existing iOS +
128
+ Android default). An explicit device type, even `phone`, errors when that OS is
129
+ not selected. These options do not apply to physical devices, web/mobile-web
130
+ targets, or Edge. V1 supports one form factor per OS per run; run phone and
131
+ tablet suites separately. Tablet results are labelled `iOS · Tablet` or
132
+ `Android · Tablet`; phone labels stay unchanged.
133
+
134
+ An iPhone-only build may run on iPad in compatibility mode; the server warns
135
+ that the run does not establish native tablet-layout coverage. Unknown
136
+ device-family metadata produces a different warning with the same coverage
137
+ limitation. Neither warning blocks launch. These per-run options do not change
138
+ app defaults.
139
+
114
140
  ## Configuration
115
141
 
116
142
  | Environment Variable | Description | Required |
@@ -78,6 +78,32 @@ minitest --app <app-id> run all --web
78
78
  minitest --app <app-id> run all --ios-build <ios-build-id> --android-build <android-build-id>
79
79
  ```
80
80
 
81
+ ## Native cloud tablets
82
+
83
+ Native lanes default to phones. Use `--ios-device-type phone|tablet` or
84
+ `--android-device-type phone|tablet` on `run start` (including `--tag`), `run all`,
85
+ or `run from-commit` to configure an already selected native cloud lane:
86
+
87
+ ```bash
88
+ minitest --app <app-id> run all --ios-build <ios-build-id> --ios-device-type tablet
89
+ minitest --app <app-id> run start --tag smoke --android-build <android-build-id> --android-device-type tablet
90
+ minitest --app <app-id> run from-commit <full-sha> --platform ios --ios-device-type tablet
91
+ ```
92
+
93
+ The device-type options do not select an OS: `start`/`all` still need the
94
+ corresponding build flag; `from-commit` uses `--platform` (or its existing iOS +
95
+ Android default). An explicit device type, even `phone`, errors when that OS is
96
+ not selected. These options do not apply to physical devices, web/mobile-web
97
+ targets, or Edge. V1 supports one form factor per OS per run; run phone and
98
+ tablet suites separately. Tablet results are labelled `iOS · Tablet` or
99
+ `Android · Tablet`; phone labels stay unchanged.
100
+
101
+ An iPhone-only build may run on iPad in compatibility mode; the server warns
102
+ that the run does not establish native tablet-layout coverage. Unknown
103
+ device-family metadata produces a different warning with the same coverage
104
+ limitation. Neither warning blocks launch. These per-run options do not change
105
+ app defaults.
106
+
81
107
  ## Configuration
82
108
 
83
109
  | Environment Variable | Description | Required |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitest-cli"
3
- version = "0.31.0"
3
+ version = "0.32.0"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -5,6 +5,7 @@ from typing import Any
5
5
  from minitest_cli.api.client import BATCH_CREATE_TIMEOUT, ApiClient
6
6
  from minitest_cli.commands.run_display import _derive_run_status
7
7
  from minitest_cli.commands.run_helpers import handle_response_error
8
+ from minitest_cli.commands.batch_warnings import print_compatibility_warnings
8
9
  from minitest_cli.models.batch import BatchResponse, CreateBatchRequest
9
10
 
10
11
 
@@ -21,7 +22,9 @@ async def post_batch(client: ApiClient, app_id: str, body: CreateBatchRequest) -
21
22
  timeout=BATCH_CREATE_TIMEOUT,
22
23
  )
23
24
  handle_response_error(resp, resource="Batch")
24
- return BatchResponse.model_validate(resp.json())
25
+ batch = BatchResponse.model_validate(resp.json())
26
+ print_compatibility_warnings(batch)
27
+ return batch
25
28
 
26
29
 
27
30
  def batch_summary_payload(batch: BatchResponse) -> dict[str, Any]:
@@ -0,0 +1,12 @@
1
+ from minitest_cli.models.batch import BatchResponse
2
+ from minitest_cli.utils.output import print_warning
3
+
4
+
5
+ def print_compatibility_warnings(batch: BatchResponse, seen: set[str] | None = None) -> set[str]:
6
+ reported = seen if seen is not None else set()
7
+ for target in batch.targets:
8
+ for warning in target.compatibility_warnings:
9
+ if warning not in reported:
10
+ print_warning(warning)
11
+ reported.add(warning)
12
+ return reported
@@ -26,14 +26,17 @@ from minitest_cli.commands.flow_types_helpers import (
26
26
  )
27
27
  from minitest_cli.core.auth import require_auth
28
28
  from minitest_cli.models.flow_type import FlowTypeListItem
29
- from minitest_cli.utils.output import print_error, print_json
29
+ from minitest_cli.utils.output import print_error, print_json, print_warning
30
30
 
31
- app = typer.Typer(name="flow-types", help="List, create and update flow (user-story) types.")
31
+ app = typer.Typer(
32
+ name="flow-types", help="Deprecated: use `minitest tags`. Manage legacy flow types."
33
+ )
32
34
 
33
35
 
34
36
  @app.callback()
35
37
  def _callback() -> None:
36
- """Flow types operations."""
38
+ """Deprecated: use `minitest tags`."""
39
+ print_warning("`minitest flow-types` is deprecated; use `minitest tags` instead.")
37
40
 
38
41
 
39
42
  @app.command(name="list")
@@ -1,7 +1,5 @@
1
1
  """Shared helpers for flow (user-story) types: API paths, fetching, name resolution."""
2
2
 
3
- import asyncio
4
- from typing import NamedTuple
5
3
  from uuid import UUID
6
4
 
7
5
  import httpx
@@ -18,7 +16,6 @@ EXIT_NOT_FOUND = 4
18
16
 
19
17
  APPS_PATH = "/api/v1/apps"
20
18
  BUILTIN_TYPES_PATH = "/api/v1/user-story-types"
21
- CUSTOM_TYPE_VALUE = "custom"
22
19
 
23
20
 
24
21
  def custom_types_path(app_id: str, custom_type_id: str | None = None) -> str:
@@ -50,23 +47,6 @@ def handle_response_error(resp: httpx.Response) -> None:
50
47
  raise typer.Exit(code=EXIT_NETWORK_ERROR)
51
48
 
52
49
 
53
- def fetch_builtin_flow_types(settings: Settings) -> list[str]:
54
- """Fetch the built-in flow type values from the API."""
55
- try:
56
- resp = httpx.get(f"{settings.api_url}{BUILTIN_TYPES_PATH}", timeout=10)
57
- except httpx.HTTPError as exc:
58
- print_error(f"Failed to fetch flow types: {exc}")
59
- raise typer.Exit(code=EXIT_NETWORK_ERROR) from exc
60
- if resp.status_code != 200:
61
- print_error(f"Failed to fetch flow types: HTTP {resp.status_code}")
62
- raise typer.Exit(code=EXIT_NETWORK_ERROR)
63
- data = resp.json()
64
- if not isinstance(data, list) or not data:
65
- print_error("Invalid response from the flow types endpoint.")
66
- raise typer.Exit(code=EXIT_NETWORK_ERROR)
67
- return data
68
-
69
-
70
50
  def get_settings() -> Settings:
71
51
  return typer.Context.settings # type: ignore[attr-defined]
72
52
 
@@ -135,44 +115,3 @@ async def resolve_custom_flow_type_id(client: ApiClient, app_id: str, selector:
135
115
  known = ", ".join(t.name for t in custom_types) or "none"
136
116
  print_error(f"No custom flow type named '{selector}'. Existing custom types: {known}")
137
117
  raise typer.Exit(code=EXIT_NOT_FOUND)
138
-
139
-
140
- def fetch_custom_flow_types(settings: Settings, app_id: str) -> list[CustomFlowType]:
141
- """Fetch the tenant's custom flow types from a synchronous command body."""
142
-
143
- async def _run() -> list[CustomFlowType]:
144
- async with ApiClient(settings) as client:
145
- return await get_custom_flow_types(client, app_id)
146
-
147
- try:
148
- return asyncio.run(_run())
149
- except httpx.HTTPError as exc:
150
- print_error(f"Failed to fetch custom flow types: {exc}")
151
- raise typer.Exit(code=EXIT_NETWORK_ERROR) from exc
152
-
153
-
154
- class ResolvedFlowType(NamedTuple):
155
- """A ``--type`` value resolved into the fields the user-story API expects."""
156
-
157
- value: str
158
- custom_type_id: str | None
159
-
160
-
161
- def resolve_flow_type(value: str, settings: Settings, app_id: str) -> ResolvedFlowType:
162
- """Resolve a ``--type`` value against built-in types, then the tenant's custom ones.
163
-
164
- Custom types resolve to the ``custom`` API type plus the matching type id, which
165
- is what testing-service expects — the custom name itself is not a valid API value.
166
- """
167
- builtin_types = fetch_builtin_flow_types(settings)
168
- if value in builtin_types:
169
- return ResolvedFlowType(value, None)
170
-
171
- custom_types = fetch_custom_flow_types(settings, app_id)
172
- for custom_type in custom_types:
173
- if custom_type.name.lower() == value.lower():
174
- return ResolvedFlowType(CUSTOM_TYPE_VALUE, custom_type.id)
175
-
176
- valid = [*builtin_types, *(t.name for t in custom_types)]
177
- print_error(f"Invalid flow type '{value}'. Valid types: {', '.join(valid)}")
178
- raise typer.Exit(code=1)
@@ -5,7 +5,7 @@ from typing import Annotated
5
5
  import typer
6
6
 
7
7
  from minitest_cli.api.client import ApiClient
8
- from minitest_cli.commands.batch_helpers import batch_summary_payload, post_batch
8
+ from minitest_cli.commands.batch_helpers import post_batch
9
9
  from minitest_cli.commands.run_display import _derive_run_status
10
10
  from minitest_cli.commands.run_helpers import (
11
11
  base_path,
@@ -22,15 +22,19 @@ from minitest_cli.commands.run_helpers import (
22
22
  RUN_TABLE_HEADERS,
23
23
  TERMINAL_STATUSES,
24
24
  )
25
+ from minitest_cli.commands.run_cancel import cancel
25
26
  from minitest_cli.commands.run_commit import from_commit
26
27
  from minitest_cli.commands.run_targets import (
27
28
  AndroidBuildOpt,
29
+ AndroidDeviceTypeOpt,
28
30
  build_targets,
29
31
  IosBuildOpt,
32
+ IosDeviceTypeOpt,
30
33
  WebOpt,
31
34
  )
32
35
  from minitest_cli.commands.run_feedback import feedback
33
36
  from minitest_cli.commands.run_recording import register_recording_commands
37
+ from minitest_cli.commands.run_tagged import print_batch_started, start_tagged
34
38
  from minitest_cli.commands.verdicts import verdicts
35
39
  from minitest_cli.models.batch import BatchResponse, CreateBatchRequest
36
40
  from minitest_cli.models.story_run import (
@@ -51,21 +55,36 @@ app = typer.Typer(name="run", help="Test execution.")
51
55
 
52
56
  @app.command()
53
57
  def start(
54
- user_story: Annotated[str, typer.Argument(help="User-story name or UUID to run.")],
58
+ user_story: Annotated[
59
+ str | None, typer.Argument(help="User-story name or UUID to run. Excludes --tag.")
60
+ ] = None,
61
+ tag: Annotated[
62
+ list[str] | None,
63
+ typer.Option("--tag", help="Run every scenario carrying any of these tags (repeatable)."),
64
+ ] = None,
55
65
  ios_build: IosBuildOpt = None,
56
66
  android_build: AndroidBuildOpt = None,
57
67
  web: WebOpt = False,
68
+ ios_device_type: IosDeviceTypeOpt = None,
69
+ android_device_type: AndroidDeviceTypeOpt = None,
58
70
  watch: Annotated[
59
71
  bool, typer.Option("--watch/--no-watch", help="Poll for results (default: watch).")
60
72
  ] = True,
61
73
  ) -> None:
62
- """Start a new test run for a user story (via the batches endpoint)."""
74
+ """Start a run for one user story, or one batch over every story with --tag."""
75
+ if (user_story is None) == (not tag):
76
+ print_error("Pass either a user story or --tag, not both or neither.")
77
+ raise typer.Exit(code=1)
63
78
  settings, app_id, json_mode = resolve_app()
64
- targets = build_targets(ios_build, android_build, web)
79
+ targets = build_targets(ios_build, android_build, web, ios_device_type, android_device_type)
80
+ if tag:
81
+ print_batch_started(start_tagged(settings, app_id, tag, targets), json_mode)
82
+ return
83
+ story_ref = user_story or ""
65
84
 
66
85
  async def _start() -> StoryRunResponse:
67
86
  async with ApiClient(settings) as client:
68
- user_story_id = await resolve_user_story_id(client, app_id, user_story)
87
+ user_story_id = await resolve_user_story_id(client, app_id, story_ref)
69
88
  body = CreateBatchRequest(user_story_ids=[user_story_id], targets=targets)
70
89
  batch = await post_batch(client, app_id, body)
71
90
  if not batch.story_runs:
@@ -148,52 +167,27 @@ def list_runs(
148
167
  print_info(tip)
149
168
 
150
169
 
151
- @app.command()
152
- def cancel(run_id: Annotated[str, typer.Argument(help="Run ID to cancel.")]) -> None:
153
- """Cancel a pending or running story run."""
154
- settings, app_id, json_mode = resolve_app()
155
- ensure_uuid(run_id, kind="run id")
156
-
157
- async def _cancel() -> StoryRunResponse:
158
- async with ApiClient(settings) as client:
159
- resp = await client.post(f"{base_path(app_id)}/{run_id}/cancel")
160
- handle_response_error(resp, resource="Run")
161
- return StoryRunResponse.model_validate(resp.json())
162
-
163
- run = run_api_call(_cancel())
164
- if json_mode:
165
- output(run.model_dump(mode="json", by_alias=True), json_mode=True)
166
- else:
167
- print_success(f"Run cancelled: {run.id} (status: {_derive_run_status(run)})")
168
-
169
-
170
170
  @app.command(name="all")
171
171
  def run_all(
172
172
  ios_build: IosBuildOpt = None,
173
173
  android_build: AndroidBuildOpt = None,
174
174
  web: WebOpt = False,
175
+ ios_device_type: IosDeviceTypeOpt = None,
176
+ android_device_type: AndroidDeviceTypeOpt = None,
175
177
  ) -> None:
176
178
  """Start a batch covering every user story for the app."""
177
179
  settings, app_id, json_mode = resolve_app()
178
- targets = build_targets(ios_build, android_build, web)
180
+ targets = build_targets(ios_build, android_build, web, ios_device_type, android_device_type)
179
181
 
180
182
  async def _run_all() -> BatchResponse:
181
183
  async with ApiClient(settings) as client:
182
184
  body = CreateBatchRequest(targets=targets)
183
185
  return await post_batch(client, app_id, body)
184
186
 
185
- batch = run_api_call(_run_all())
186
- if json_mode:
187
- print_json(batch_summary_payload(batch))
188
- return
189
- rows = [format_run_row(r) for r in batch.story_runs]
190
- print_table(RUN_TABLE_HEADERS, rows, title=f"Batch {batch.id} — {batch.status.value}")
191
- print_info(
192
- f"Started {len(batch.story_runs)} runs. "
193
- f"Use `minitest batch get {batch.id}` or `minitest run status <id>` to follow up."
194
- )
187
+ print_batch_started(run_api_call(_run_all()), json_mode)
195
188
 
196
189
 
190
+ app.command(name="cancel")(cancel)
197
191
  app.command(name="from-commit")(from_commit)
198
192
  app.command(name="verdicts")(verdicts)
199
193
  app.command(name="feedback")(feedback)
@@ -0,0 +1,123 @@
1
+ """The `minitest run cancel` command: cancel the in-flight platforms of a story run.
2
+
3
+ testing-service only cancels per story-run platform (SRP), so the command reads
4
+ the run, then cancels each selected platform that has not finished yet.
5
+ """
6
+
7
+ from enum import StrEnum
8
+ from typing import Annotated
9
+
10
+ import typer
11
+
12
+ from minitest_cli.api.client import ApiClient
13
+ from minitest_cli.commands.run_display import _derive_run_status
14
+ from minitest_cli.commands.run_helpers import (
15
+ base_path,
16
+ ensure_uuid,
17
+ EXIT_NOT_FOUND,
18
+ handle_response_error,
19
+ resolve_app,
20
+ run_api_call,
21
+ )
22
+ from minitest_cli.models.story_run import PlatformRun, StoryRunResponse
23
+ from minitest_cli.utils.output import output, print_error, print_success
24
+
25
+ # Mirrors testing-service EXECUTION_FINISHED_STATES: the SRP cancel route answers 409 for these.
26
+ FINISHED_EXECUTION_STATES = {"evaluating", "completed", "failed", "skipped", "escalated"}
27
+
28
+
29
+ class RunPlatform(StrEnum):
30
+ ios = "ios"
31
+ android = "android"
32
+ web = "web"
33
+
34
+
35
+ def _srp_cancel_path(app_id: str, srp_id: str | None) -> str:
36
+ return f"/api/v1/apps/{app_id}/story-run-platforms/{srp_id}/cancel"
37
+
38
+
39
+ def _label(platform: PlatformRun) -> str:
40
+ return platform.label or platform.platform
41
+
42
+
43
+ def _is_cancellable(platform: PlatformRun) -> bool:
44
+ return (
45
+ platform.srp_id is not None
46
+ and platform.cancellation_requested_at is None
47
+ and platform.execution_state not in FINISHED_EXECUTION_STATES
48
+ )
49
+
50
+
51
+ def _select(
52
+ run: StoryRunResponse, platform: RunPlatform | None, srp_id: str | None
53
+ ) -> list[PlatformRun]:
54
+ selected = [
55
+ p
56
+ for p in run.platforms
57
+ if (platform is None or p.platform == platform.value)
58
+ and (srp_id is None or p.srp_id == srp_id.lower())
59
+ ]
60
+ if not selected:
61
+ wanted = [
62
+ f"{platform.value} platform" if platform else "",
63
+ f"SRP {srp_id}" if srp_id else "",
64
+ ]
65
+ print_error(f"Run {run.id} has no {' / '.join(w for w in wanted if w) or 'platforms'}.")
66
+ raise typer.Exit(code=EXIT_NOT_FOUND)
67
+ return selected
68
+
69
+
70
+ async def _cancel_platforms(
71
+ client: ApiClient, app_id: str, targets: list[PlatformRun]
72
+ ) -> tuple[StoryRunResponse | None, list[PlatformRun]]:
73
+ latest: StoryRunResponse | None = None
74
+ cancelled: list[PlatformRun] = []
75
+ for target in targets:
76
+ resp = await client.post(_srp_cancel_path(app_id, target.srp_id))
77
+ if resp.status_code == 409:
78
+ continue
79
+ handle_response_error(resp, resource="Run platform")
80
+ latest = StoryRunResponse.model_validate(resp.json())
81
+ cancelled.append(target)
82
+ return latest, cancelled
83
+
84
+
85
+ def cancel(
86
+ run_id: Annotated[str, typer.Argument(help="Run ID to cancel.")],
87
+ platform: Annotated[
88
+ RunPlatform | None,
89
+ typer.Option("--platform", help="Only cancel this platform (ios, android or web)."),
90
+ ] = None,
91
+ srp_id: Annotated[
92
+ str | None,
93
+ typer.Option("--srp", help="Only cancel this story-run platform ID."),
94
+ ] = None,
95
+ ) -> None:
96
+ """Cancel every pending or running platform of a story run."""
97
+ settings, app_id, json_mode = resolve_app()
98
+ ensure_uuid(run_id, kind="run id")
99
+ if srp_id is not None:
100
+ ensure_uuid(srp_id, kind="SRP id")
101
+
102
+ async def _cancel() -> tuple[StoryRunResponse, list[PlatformRun]]:
103
+ async with ApiClient(settings) as client:
104
+ resp = await client.get(f"{base_path(app_id)}/{run_id}")
105
+ handle_response_error(resp, resource="Run")
106
+ run = StoryRunResponse.model_validate(resp.json())
107
+ targets = [p for p in _select(run, platform, srp_id) if _is_cancellable(p)]
108
+ latest, cancelled = await _cancel_platforms(client, app_id, targets)
109
+ return latest or run, cancelled
110
+
111
+ run, cancelled = run_api_call(_cancel())
112
+ if not cancelled:
113
+ states = ", ".join(f"{_label(p)}: {p.execution_state}" for p in run.platforms)
114
+ print_error(
115
+ f"Nothing to cancel on run {run.id}: the selected platforms already finished "
116
+ f"or are being cancelled ({states})."
117
+ )
118
+ raise typer.Exit(code=1)
119
+ if json_mode:
120
+ output(run.model_dump(mode="json", by_alias=True), json_mode=True)
121
+ return
122
+ labels = ", ".join(_label(p) for p in cancelled)
123
+ print_success(f"Run cancelled: {run.id} ({labels}; status: {_derive_run_status(run)})")
@@ -23,6 +23,11 @@ from minitest_cli.commands.run_helpers import (
23
23
  run_api_call,
24
24
  )
25
25
  from minitest_cli.models import BatchResponse, BatchStatus, CreateBatchRequest
26
+ from minitest_cli.commands.run_targets import (
27
+ AndroidDeviceTypeOpt,
28
+ IosDeviceTypeOpt,
29
+ build_commit_targets,
30
+ )
26
31
  from minitest_cli.utils.output import output, print_info, print_success, print_warning
27
32
 
28
33
  CommitShaArg = Annotated[
@@ -49,6 +54,8 @@ TimeoutOpt = Annotated[int, typer.Option("--timeout", help="Seconds to poll befo
49
54
  def from_commit(
50
55
  commit_sha: CommitShaArg,
51
56
  platform: PlatformOpt = None,
57
+ ios_device_type: IosDeviceTypeOpt = None,
58
+ android_device_type: AndroidDeviceTypeOpt = None,
52
59
  user_story: UserStoryOpt = None,
53
60
  watch: WatchOpt = True,
54
61
  timeout: TimeoutOpt = DEFAULT_TIMEOUT_SECONDS,
@@ -57,6 +64,7 @@ def from_commit(
57
64
  settings, app_id, json_mode = resolve_app()
58
65
  sha = validate_commit_sha(commit_sha)
59
66
  platforms = validate_platforms(platform)
67
+ targets = build_commit_targets(platforms, ios_device_type, android_device_type)
60
68
 
61
69
  async def _run() -> BatchResponse:
62
70
  async with ApiClient(settings) as client:
@@ -65,11 +73,19 @@ def from_commit(
65
73
  story_ids = [
66
74
  await resolve_user_story_id(client, app_id, name) for name in user_story
67
75
  ]
68
- body = CreateBatchRequest(user_story_ids=story_ids, commit_sha=sha, platforms=platforms)
76
+ body = CreateBatchRequest(
77
+ user_story_ids=story_ids,
78
+ commit_sha=sha,
79
+ platforms=platforms if targets is None else None,
80
+ targets=targets,
81
+ )
69
82
  batch = await post_batch(client, app_id, body)
70
83
  if not watch:
71
84
  return batch
72
- return await poll_batch(client, app_id, batch.id, timeout)
85
+ seen_warnings = {
86
+ warning for target in batch.targets for warning in target.compatibility_warnings
87
+ }
88
+ return await poll_batch(client, app_id, batch.id, timeout, seen_warnings)
73
89
 
74
90
  batch = run_api_call(_run())
75
91
  payload = batch_commit_payload(batch)
@@ -6,6 +6,7 @@ from typing import Any
6
6
 
7
7
  from minitest_cli.api.client import ApiClient
8
8
  from minitest_cli.commands.batch_helpers import batches_base_path
9
+ from minitest_cli.commands.batch_warnings import print_compatibility_warnings
9
10
  from minitest_cli.commands.run_display import _derive_run_status
10
11
  from minitest_cli.commands.run_helpers import handle_response_error
11
12
  from minitest_cli.models import BatchResponse, BatchStatus
@@ -28,6 +29,11 @@ def batch_commit_payload(batch: BatchResponse) -> dict[str, Any]:
28
29
  "targets": [
29
30
  {
30
31
  "platform": target.platform,
32
+ **(
33
+ {"deviceType": target.device_type, "label": target.label}
34
+ if target.device_type is not None
35
+ else {}
36
+ ),
31
37
  "buildId": target.build_id,
32
38
  "status": target.counters.status,
33
39
  "passed": target.counters.passed,
@@ -48,16 +54,22 @@ def batch_commit_payload(batch: BatchResponse) -> dict[str, Any]:
48
54
 
49
55
 
50
56
  async def poll_batch(
51
- client: ApiClient, app_id: str, batch_id: str, timeout_seconds: int
57
+ client: ApiClient,
58
+ app_id: str,
59
+ batch_id: str,
60
+ timeout_seconds: int,
61
+ seen_warnings: set[str] | None = None,
52
62
  ) -> BatchResponse:
53
63
  path = f"{batches_base_path(app_id)}/{batch_id}"
54
64
  deadline = time.monotonic() + timeout_seconds
65
+ reported_warnings = seen_warnings if seen_warnings is not None else set()
55
66
 
56
67
  with err_console.status("[bold blue]Waiting for the build and the run...") as spinner:
57
68
  while True:
58
69
  resp = await client.get(path)
59
70
  handle_response_error(resp, resource="Batch")
60
71
  batch = BatchResponse.model_validate(resp.json())
72
+ print_compatibility_warnings(batch, reported_warnings)
61
73
  spinner.update(f"[bold blue]Batch {batch.status.value} ({len(batch.story_runs)} runs)")
62
74
  if batch.status in TERMINAL_BATCH_STATUSES:
63
75
  return batch
@@ -60,7 +60,7 @@ def format_run_row(run: StoryRunResponse) -> list[str]:
60
60
 
61
61
 
62
62
  def _platform_label(p: PlatformRun) -> str:
63
- return p.label or target_label(p.platform, p.browser, p.viewport)
63
+ return p.label or target_label(p.platform, p.browser, p.viewport, p.device_type)
64
64
 
65
65
 
66
66
  def _platform_status_line(p: PlatformRun) -> tuple[str, str | None]:
@@ -100,7 +100,16 @@ def display_run_result(run: StoryRunResponse, json_mode: bool) -> None:
100
100
  rows: list[list[str]] = []
101
101
  for cr in run.results:
102
102
  result_str = "[green]✓ pass[/green]" if cr.success else "[red]✗ fail[/red]"
103
- platform_label = target_label(cr.platform, None, None)
103
+ matching_platforms = [
104
+ p
105
+ for p in run.platforms
106
+ if (p.srp_id == cr.srp_id if cr.srp_id else p.platform == cr.platform)
107
+ ]
108
+ platform_label = (
109
+ _platform_label(matching_platforms[0])
110
+ if len(matching_platforms) == 1
111
+ else target_label(cr.platform, None, None)
112
+ )
104
113
  if cr.is_platform_override:
105
114
  platform_label += " *"
106
115
  rows.append([cr.criterion_version_id, platform_label, result_str, cr.fail_reason or ""])
@@ -0,0 +1,44 @@
1
+ """Batch launch over every scenario carrying a tag, and the shared batch summary output."""
2
+
3
+ import typer
4
+
5
+ from minitest_cli.api.client import ApiClient
6
+ from minitest_cli.commands.batch_helpers import batch_summary_payload, post_batch
7
+ from minitest_cli.commands.run_helpers import RUN_TABLE_HEADERS, format_run_row, run_api_call
8
+ from minitest_cli.commands.tags_helpers import resolve_tag_ids
9
+ from minitest_cli.commands.user_story_helpers import EXIT_NOT_FOUND, fetch_all_user_stories
10
+ from minitest_cli.core.config import Settings
11
+ from minitest_cli.models.batch import BatchResponse, CreateBatchRequest
12
+ from minitest_cli.models.targets import BatchTarget
13
+ from minitest_cli.utils.output import print_error, print_info, print_json, print_table
14
+
15
+
16
+ def print_batch_started(batch: BatchResponse, json_mode: bool) -> None:
17
+ if json_mode:
18
+ print_json(batch_summary_payload(batch))
19
+ return
20
+ rows = [format_run_row(r) for r in batch.story_runs]
21
+ print_table(RUN_TABLE_HEADERS, rows, title=f"Batch {batch.id} — {batch.status.value}")
22
+ print_info(
23
+ f"Started {len(batch.story_runs)} runs. "
24
+ f"Use `minitest batch get {batch.id}` or `minitest run status <id>` to follow up."
25
+ )
26
+
27
+
28
+ def start_tagged(
29
+ settings: Settings, app_id: str, tags: list[str], targets: list[BatchTarget]
30
+ ) -> BatchResponse:
31
+ async def _start() -> BatchResponse:
32
+ async with ApiClient(settings) as client:
33
+ tag_ids = await resolve_tag_ids(client, app_id, tags)
34
+ stories = await fetch_all_user_stories(client, app_id, {"tagId": tag_ids})
35
+ # An empty id list would make the backend run every scenario of the app.
36
+ if not stories:
37
+ print_error(f"No scenario carries any of these tags: {', '.join(tags)}.")
38
+ raise typer.Exit(code=EXIT_NOT_FOUND)
39
+ body = CreateBatchRequest(
40
+ user_story_ids=[str(s["id"]) for s in stories], targets=targets
41
+ )
42
+ return await post_batch(client, app_id, body)
43
+
44
+ return run_api_call(_start())