minitest-cli 0.37.0__tar.gz → 0.37.1__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 (227) hide show
  1. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/PKG-INFO +24 -1
  2. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/README.md +23 -0
  3. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/pyproject.toml +4 -1
  4. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/_response_errors.py +29 -7
  5. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch_helpers.py +22 -0
  6. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build.py +26 -8
  7. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_helpers.py +3 -1
  8. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run.py +26 -47
  9. minitest_cli-0.37.1/src/minitest_cli/commands/run_list.py +55 -0
  10. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_targets.py +61 -11
  11. minitest_cli-0.37.1/src/minitest_cli/core/app_context.py +68 -0
  12. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/targets.py +9 -1
  13. minitest_cli-0.37.1/tests/conftest.py +26 -0
  14. minitest_cli-0.37.1/tests/test_app_context.py +84 -0
  15. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_build_commands.py +1 -1
  16. minitest_cli-0.37.1/tests/test_physical_devices.py +227 -0
  17. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_run_commands.py +7 -7
  18. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/uv.lock +1 -1
  19. minitest_cli-0.37.0/src/minitest_cli/core/app_context.py +0 -31
  20. minitest_cli-0.37.0/tests/conftest.py +0 -11
  21. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.env.example +0 -0
  22. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.github/workflows/ci.yml +0 -0
  23. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.github/workflows/install-scripts.yml +0 -0
  24. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.github/workflows/release.yml +0 -0
  25. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.gitignore +0 -0
  26. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.opencode/skill/release/SKILL.md +0 -0
  27. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/AGENTS.md +0 -0
  28. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/RELEASE.md +0 -0
  29. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/install.ps1 +0 -0
  30. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/install.sh +0 -0
  31. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/pyrightconfig.json +0 -0
  32. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/renovate.json +0 -0
  33. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/__init__.py +0 -0
  34. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/__init__.py +0 -0
  35. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/apps_manager_client.py +0 -0
  36. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/client.py +0 -0
  37. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/errors.py +0 -0
  38. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/assets/__init__.py +0 -0
  39. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/assets/callback.html +0 -0
  40. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/__init__.py +0 -0
  41. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_knowledge.py +0 -0
  42. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  43. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_network_helpers.py +0 -0
  44. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill.py +0 -0
  45. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_helpers.py +0 -0
  46. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_secret.py +0 -0
  47. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_write.py +0 -0
  48. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps.py +0 -0
  49. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_dependencies.py +0 -0
  50. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_get.py +0 -0
  51. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_helpers.py +0 -0
  52. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_table.py +0 -0
  53. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/auth.py +0 -0
  54. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/auth_api_key.py +0 -0
  55. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch.py +0 -0
  56. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch_warnings.py +0 -0
  57. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_commit.py +0 -0
  58. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_fix_prompts.py +0 -0
  59. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_get.py +0 -0
  60. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/capabilities.py +0 -0
  61. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/commit_helpers.py +0 -0
  62. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/deprecated_alias.py +0 -0
  63. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft.py +0 -0
  64. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_asks.py +0 -0
  65. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_conversation.py +0 -0
  66. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_create.py +0 -0
  67. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature.py +0 -0
  68. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_apply.py +0 -0
  69. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_conflicts.py +0 -0
  70. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_helpers.py +0 -0
  71. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_show.py +0 -0
  72. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_helpers.py +0 -0
  73. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_legacy.py +0 -0
  74. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_lifecycle.py +0 -0
  75. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_pr.py +0 -0
  76. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_render.py +0 -0
  77. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_update.py +0 -0
  78. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_watch.py +0 -0
  79. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/env.py +0 -0
  80. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/env_helpers.py +0 -0
  81. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types.py +0 -0
  82. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types_helpers.py +0 -0
  83. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types_write.py +0 -0
  84. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init.py +0 -0
  85. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init_helpers.py +0 -0
  86. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init_playbook.py +0 -0
  87. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues.py +0 -0
  88. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_fix.py +0 -0
  89. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_get.py +0 -0
  90. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_helpers.py +0 -0
  91. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_projection.py +0 -0
  92. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_service.py +0 -0
  93. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/private_network.py +0 -0
  94. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/private_network_helpers.py +0 -0
  95. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/recording_timeline.py +0 -0
  96. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/residential_proxy.py +0 -0
  97. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_cancel.py +0 -0
  98. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_commit.py +0 -0
  99. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_commit_helpers.py +0 -0
  100. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_display.py +0 -0
  101. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_feedback.py +0 -0
  102. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_helpers.py +0 -0
  103. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_recording.py +0 -0
  104. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_recording_helpers.py +0 -0
  105. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_tagged.py +0 -0
  106. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens.py +0 -0
  107. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_detail.py +0 -0
  108. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_format.py +0 -0
  109. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_helpers.py +0 -0
  110. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_tree.py +0 -0
  111. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/skill.py +0 -0
  112. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/tags.py +0 -0
  113. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/tags_helpers.py +0 -0
  114. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file.py +0 -0
  115. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file_helpers.py +0 -0
  116. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file_list.py +0 -0
  117. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile.py +0 -0
  118. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_card.py +0 -0
  119. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_default.py +0 -0
  120. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
  121. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_list.py +0 -0
  122. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_update.py +0 -0
  123. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/upgrade.py +0 -0
  124. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story.py +0 -0
  125. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_app_skills.py +0 -0
  126. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_bindings.py +0 -0
  127. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_camera.py +0 -0
  128. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_create.py +0 -0
  129. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_criteria.py +0 -0
  130. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_criterion_edits.py +0 -0
  131. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_delete.py +0 -0
  132. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_device_count.py +0 -0
  133. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_helpers.py +0 -0
  134. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_modify.py +0 -0
  135. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_overrides.py +0 -0
  136. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_profiles.py +0 -0
  137. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_setup_commit.py +0 -0
  138. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_update.py +0 -0
  139. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts.py +0 -0
  140. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts_helpers.py +0 -0
  141. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts_projection.py +0 -0
  142. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/__init__.py +0 -0
  143. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/auth.py +0 -0
  144. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/config.py +0 -0
  145. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/credentials.py +0 -0
  146. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/oauth.py +0 -0
  147. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/tenants.py +0 -0
  148. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/token_exchange.py +0 -0
  149. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/main.py +0 -0
  150. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/__init__.py +0 -0
  151. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app.py +0 -0
  152. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_env_vars.py +0 -0
  153. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_failure.py +0 -0
  154. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_screen.py +0 -0
  155. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/base.py +0 -0
  156. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/batch.py +0 -0
  157. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/build.py +0 -0
  158. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/commit_build.py +0 -0
  159. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/draft_feature.py +0 -0
  160. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/flow_type.py +0 -0
  161. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/issues.py +0 -0
  162. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/recording.py +0 -0
  163. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/story_run.py +0 -0
  164. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/tag.py +0 -0
  165. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/user_story.py +0 -0
  166. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/__init__.py +0 -0
  167. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/confirm.py +0 -0
  168. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/dependency_sim.py +0 -0
  169. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/mermaid.py +0 -0
  170. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/output.py +0 -0
  171. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/skill_refresh.py +0 -0
  172. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/update_check.py +0 -0
  173. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/video_frames.py +0 -0
  174. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/__init__.py +0 -0
  175. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/_commit_transport.py +0 -0
  176. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/_network_settings_server.py +0 -0
  177. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/draft_backend.py +0 -0
  178. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_api_client_auth.py +0 -0
  179. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_app_knowledge_commands.py +0 -0
  180. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_app_skill_commands.py +0 -0
  181. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_apps_commands.py +0 -0
  182. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_apps_dependencies.py +0 -0
  183. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_auth.py +0 -0
  184. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_auth_api_key.py +0 -0
  185. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_auth_commands.py +0 -0
  186. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_batch_commands.py +0 -0
  187. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_build_fix_prompts.py +0 -0
  188. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_build_get_command.py +0 -0
  189. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_capabilities_command.py +0 -0
  190. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_channel_header.py +0 -0
  191. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_code_quality.py +0 -0
  192. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_commit_commands.py +0 -0
  193. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_deprecated_commands.py +0 -0
  194. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_deprecated_flags.py +0 -0
  195. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_ask_commands.py +0 -0
  196. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_commands.py +0 -0
  197. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_conversation_commands.py +0 -0
  198. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_feature_commands.py +0 -0
  199. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_pr_commands.py +0 -0
  200. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_env.py +0 -0
  201. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_flow_types_commands.py +0 -0
  202. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_init_command.py +0 -0
  203. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_issues.py +0 -0
  204. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_issues_commands.py +0 -0
  205. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_issues_get_command.py +0 -0
  206. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_mermaid.py +0 -0
  207. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_native_device_types.py +0 -0
  208. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_network_errors.py +0 -0
  209. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_private_network_commands.py +0 -0
  210. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_recording_timeline.py +0 -0
  211. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_residential_proxy_commands.py +0 -0
  212. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_response_errors.py +0 -0
  213. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_run_cancel.py +0 -0
  214. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_run_frames_command.py +0 -0
  215. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_screens_commands.py +0 -0
  216. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_skill_command.py +0 -0
  217. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_suite_change_provenance.py +0 -0
  218. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_tags_commands.py +0 -0
  219. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_test_file_commands.py +0 -0
  220. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_test_profile_commands.py +0 -0
  221. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_upgrade_command.py +0 -0
  222. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_user_story_bindings_commands.py +0 -0
  223. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_user_story_commands.py +0 -0
  224. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_user_story_setup_commit.py +0 -0
  225. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_verdicts.py +0 -0
  226. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_verdicts_actionable.py +0 -0
  227. {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: minitest-cli
3
- Version: 0.37.0
3
+ Version: 0.37.1
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
@@ -131,6 +131,29 @@ targets, or Edge. V1 supports one form factor per OS per run; run phone and
131
131
  tablet suites separately. Tablet results are labelled `iOS · Tablet` or
132
132
  `Android · Tablet`; phone labels stay unchanged.
133
133
 
134
+ ## Physical devices
135
+
136
+ By default builds run on virtual devices (iOS Simulator, Android emulator). If
137
+ physical devices are enabled for your workspace, upload a device build with
138
+ `--physical` and select the physical lane when you start the run:
139
+
140
+ ```bash
141
+ # iOS: an Ad Hoc or Development .ipa signed for Minitap's iPhones
142
+ BUILD_ID=$(minitest --json --app <app-id> build upload ./MyApp.ipa --physical | jq -r .id)
143
+ minitest --app <app-id> run all --ios-build "$BUILD_ID" --ios-physical
144
+
145
+ # Android: the same works with --android-physical
146
+ minitest --app <app-id> run start --tag smoke --android-build <android-build-id> --android-physical
147
+ ```
148
+
149
+ - `build upload --physical` checks the build for physical devices instead of
150
+ virtual ones. A Simulator build is rejected (`ios.simulator_only_ipa`), and a
151
+ device build uploaded without `--physical` is rejected with a hint to add it.
152
+ - `--ios-physical` / `--android-physical` need the matching `--ios-build` /
153
+ `--android-build`, and can't be combined with `--*-device-type`.
154
+ - If physical devices aren't enabled for your workspace, the run is refused
155
+ with a message saying so.
156
+
134
157
  An iPhone-only build may run on iPad in compatibility mode; the server warns
135
158
  that the run does not establish native tablet-layout coverage. Unknown
136
159
  device-family metadata produces a different warning with the same coverage
@@ -98,6 +98,29 @@ targets, or Edge. V1 supports one form factor per OS per run; run phone and
98
98
  tablet suites separately. Tablet results are labelled `iOS · Tablet` or
99
99
  `Android · Tablet`; phone labels stay unchanged.
100
100
 
101
+ ## Physical devices
102
+
103
+ By default builds run on virtual devices (iOS Simulator, Android emulator). If
104
+ physical devices are enabled for your workspace, upload a device build with
105
+ `--physical` and select the physical lane when you start the run:
106
+
107
+ ```bash
108
+ # iOS: an Ad Hoc or Development .ipa signed for Minitap's iPhones
109
+ BUILD_ID=$(minitest --json --app <app-id> build upload ./MyApp.ipa --physical | jq -r .id)
110
+ minitest --app <app-id> run all --ios-build "$BUILD_ID" --ios-physical
111
+
112
+ # Android: the same works with --android-physical
113
+ minitest --app <app-id> run start --tag smoke --android-build <android-build-id> --android-physical
114
+ ```
115
+
116
+ - `build upload --physical` checks the build for physical devices instead of
117
+ virtual ones. A Simulator build is rejected (`ios.simulator_only_ipa`), and a
118
+ device build uploaded without `--physical` is rejected with a hint to add it.
119
+ - `--ios-physical` / `--android-physical` need the matching `--ios-build` /
120
+ `--android-build`, and can't be combined with `--*-device-type`.
121
+ - If physical devices aren't enabled for your workspace, the run is refused
122
+ with a message saying so.
123
+
101
124
  An iPhone-only build may run on iPad in compatibility mode; the server warns
102
125
  that the run does not establish native tablet-layout coverage. Unknown
103
126
  device-family metadata produces a different warning with the same coverage
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitest-cli"
3
- version = "0.37.0"
3
+ version = "0.37.1"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -128,6 +128,9 @@ python_functions = [
128
128
  python_classes = [
129
129
  "Test*"
130
130
  ]
131
+ markers = [
132
+ "real_app_lookup: resolve --app names through the API instead of the test shortcut",
133
+ ]
131
134
  addopts = [
132
135
  "-v",
133
136
  "--tb=short",
@@ -24,6 +24,11 @@ BUILD_INVALID_ERROR_CODE = "build_invalid"
24
24
  # rollout: both).
25
25
  _SPLIT_APKS_BACKEND_UNSUPPORTED_MARKER = "not yet supported on this execution backend"
26
26
 
27
+ # Rejection codes that mean "right build, wrong kind of device": the hint
28
+ # points at the upload flag that matches the build.
29
+ _DEVICE_BUILD_CODES = {"ios.device_only_ipa", "ios.device_only_sdk", "ios.no_simulator_slice"}
30
+ _SIMULATOR_BUILD_CODES = {"ios.simulator_only_ipa"}
31
+
27
32
  # Generic FastAPI ``loc`` prefixes that add noise to a field path (they're
28
33
  # implied by "this is a validation error" and repeat across every field).
29
34
  _LOC_PREFIXES_TO_DROP = {"body", "query", "path"}
@@ -86,14 +91,16 @@ def extract_detail(resp: httpx.Response) -> str:
86
91
  return resp.text
87
92
 
88
93
 
89
- def handle_response_error(resp: httpx.Response, *, resource: str = "Build") -> None:
94
+ def handle_response_error(
95
+ resp: httpx.Response, *, resource: str = "Build", physical: bool = False
96
+ ) -> None:
90
97
  """Check response status; exit with proper code on errors."""
91
98
  if resp.status_code == 404:
92
99
  print_error(f"{resource} not found: {extract_detail(resp)}")
93
100
  raise typer.Exit(code=EXIT_NOT_FOUND)
94
101
  if resp.status_code == 422 and _try_handle_split_apks_backend_unsupported(resp):
95
102
  raise typer.Exit(code=EXIT_BUILD_INVALID)
96
- if resp.status_code == 422 and _try_handle_build_invalid(resp):
103
+ if resp.status_code == 422 and _try_handle_build_invalid(resp, physical=physical):
97
104
  raise typer.Exit(code=EXIT_BUILD_INVALID)
98
105
  if resp.status_code >= 400:
99
106
  print_error(f"API error ({resp.status_code}): {extract_detail(resp)}")
@@ -122,7 +129,7 @@ def _try_handle_split_apks_backend_unsupported(resp: httpx.Response) -> bool:
122
129
  return True
123
130
 
124
131
 
125
- def _try_handle_build_invalid(resp: httpx.Response) -> bool:
132
+ def _try_handle_build_invalid(resp: httpx.Response, *, physical: bool = False) -> bool:
126
133
  """If response is a build-invalid error, render issues and return True."""
127
134
  try:
128
135
  body = resp.json()
@@ -130,17 +137,32 @@ def _try_handle_build_invalid(resp: httpx.Response) -> bool:
130
137
  return False
131
138
  if not isinstance(body, dict) or body.get("error_code") != BUILD_INVALID_ERROR_CODE:
132
139
  return False
133
- issues = body.get("issues") or []
134
- print_error("Build rejected: failed validation for virtual-device execution.")
140
+ issues = [issue for issue in body.get("issues") or [] if isinstance(issue, dict)]
141
+ devices = "physical-device" if physical else "virtual-device"
142
+ print_error(f"Build rejected: failed validation for {devices} execution.")
135
143
  for issue in issues:
136
- if not isinstance(issue, dict):
137
- continue
138
144
  code = issue.get("code", "unknown")
139
145
  message = issue.get("message", "")
140
146
  err_console.print(f" [red]✖[/red] {code}: {message}")
147
+ _print_device_kind_hint({str(issue.get("code")) for issue in issues}, physical=physical)
141
148
  return True
142
149
 
143
150
 
151
+ def _print_device_kind_hint(codes: set[str], *, physical: bool) -> None:
152
+ if not physical and codes & _DEVICE_BUILD_CODES:
153
+ err_console.print(
154
+ " [yellow]Hint:[/yellow] this is a device build. To run it on physical iPhones,"
155
+ " upload it with [bold]minitest build upload <file> --physical[/bold];"
156
+ " for virtual devices, build for the iOS Simulator instead."
157
+ )
158
+ elif physical and codes & _SIMULATOR_BUILD_CODES:
159
+ err_console.print(
160
+ " [yellow]Hint:[/yellow] this is a Simulator build. Upload it without"
161
+ " [bold]--physical[/bold] to run it on virtual devices, or export a device"
162
+ " build (Ad Hoc or Development) for physical iPhones."
163
+ )
164
+
165
+
144
166
  def print_validation_warnings(warnings: list[dict] | None) -> None:
145
167
  """Print non-fatal validation warnings to stderr."""
146
168
  if not warnings:
@@ -2,11 +2,16 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
+ import httpx
6
+ import typer
7
+
5
8
  from minitest_cli.api.client import BATCH_CREATE_TIMEOUT, ApiClient
6
9
  from minitest_cli.commands.run_display import _derive_run_status
10
+ from minitest_cli.commands._response_errors import EXIT_NETWORK_ERROR, extract_detail
7
11
  from minitest_cli.commands.run_helpers import handle_response_error
8
12
  from minitest_cli.commands.batch_warnings import print_compatibility_warnings
9
13
  from minitest_cli.models.batch import BatchResponse, CreateBatchRequest
14
+ from minitest_cli.utils.output import err_console, print_error
10
15
 
11
16
 
12
17
  def batches_base_path(app_id: str) -> str:
@@ -21,12 +26,29 @@ async def post_batch(client: ApiClient, app_id: str, body: CreateBatchRequest) -
21
26
  json=body.model_dump(by_alias=True, exclude_none=True),
22
27
  timeout=BATCH_CREATE_TIMEOUT,
23
28
  )
29
+ if resp.status_code == 403 and _requests_physical(body):
30
+ _exit_physical_forbidden(resp)
24
31
  handle_response_error(resp, resource="Batch")
25
32
  batch = BatchResponse.model_validate(resp.json())
26
33
  print_compatibility_warnings(batch)
27
34
  return batch
28
35
 
29
36
 
37
+ def _requests_physical(body: CreateBatchRequest) -> bool:
38
+ return any(target.backend == "physical" for target in body.targets or [])
39
+
40
+
41
+ def _exit_physical_forbidden(resp: httpx.Response) -> None:
42
+ """A physical lane the workspace is not entitled to: say so, and how to proceed."""
43
+ print_error(extract_detail(resp))
44
+ err_console.print(
45
+ " [yellow]Fix:[/yellow] ask Minitap to enable physical devices for this workspace,"
46
+ " or drop [bold]--ios-physical[/bold] / [bold]--android-physical[/bold]"
47
+ " to run on virtual devices."
48
+ )
49
+ raise typer.Exit(code=EXIT_NETWORK_ERROR)
50
+
51
+
30
52
  def batch_summary_payload(batch: BatchResponse) -> dict[str, Any]:
31
53
  """Serialise a batch to a compact JSON payload for --json output (camelCase)."""
32
54
  return {
@@ -42,8 +42,8 @@ def upload(
42
42
  Path,
43
43
  typer.Argument(
44
44
  help=(
45
- "Path to the build file (.apk, .apks, or .ipa). Must be a Simulator"
46
- " build for iOS or an emulator-compatible Android build."
45
+ "Path to the build file (.apk, .apks, .ipa or .app.zip). A Simulator build"
46
+ " for iOS or an emulator-compatible Android build, unless --physical."
47
47
  ),
48
48
  exists=True,
49
49
  readable=True,
@@ -53,17 +53,34 @@ def upload(
53
53
  Platform | None,
54
54
  typer.Option(help="Target platform. Auto-detected from file extension."),
55
55
  ] = None,
56
+ physical: Annotated[
57
+ bool,
58
+ typer.Option(
59
+ "--physical",
60
+ help=(
61
+ "Upload a build for physical devices: a signed iOS device .ipa (Ad Hoc or"
62
+ " Development) or an Android .apk for real phones."
63
+ ),
64
+ ),
65
+ ] = False,
56
66
  ) -> None:
57
- """Upload a build file (.apk or .apks for Android, .ipa for iOS).
67
+ """Upload a build file (.apk or .apks for Android, .ipa or .app.zip for iOS).
58
68
 
59
- Builds must be compatible with virtual devices:
69
+ By default builds are checked for virtual devices:
60
70
 
61
71
  \b
62
- - iOS: provide a Simulator build (.ipa built for the iOS Simulator, not a physical device).
72
+ - iOS: provide a Simulator build (.ipa or .app.zip built for the iOS Simulator).
63
73
  - Android: provide an emulator-compatible .apk or .apks archive.
74
+
75
+ With --physical, builds are checked for physical devices instead: an iOS
76
+ device .ipa signed for Minitap's iPhones, or an Android .apk. Start runs on
77
+ them with --ios-physical / --android-physical.
64
78
  """
65
79
  settings, app_id, json_mode = resolve_app()
66
80
  resolved_platform = platform.value if platform else detect_platform(file)
81
+ form = {"platform": resolved_platform}
82
+ if physical:
83
+ form["backend"] = "physical"
67
84
 
68
85
  async def _run() -> BuildResponse:
69
86
  async with ApiClient(settings) as client:
@@ -74,9 +91,9 @@ def upload(
74
91
  resp = await client.upload_file(
75
92
  f"{base_path(app_id)}/build",
76
93
  files={"file": (file.name, f, "application/octet-stream")},
77
- data={"platform": resolved_platform},
94
+ data=form,
78
95
  )
79
- handle_response_error(resp)
96
+ handle_response_error(resp, physical=physical)
80
97
  return BuildResponse.model_validate(resp.json())
81
98
 
82
99
  result = run_api_call(_run())
@@ -86,7 +103,8 @@ def upload(
86
103
  output(result_dict, json_mode=True)
87
104
  else:
88
105
  print_validation_warnings(result.validation_warnings)
89
- print_success(f"Build uploaded: {file.name} ({resolved_platform})")
106
+ devices = ", physical devices" if physical else ""
107
+ print_success(f"Build uploaded: {file.name} ({resolved_platform}{devices})")
90
108
  output(result_dict, json_mode=False)
91
109
 
92
110
 
@@ -76,6 +76,7 @@ PLATFORM_EXTENSIONS: dict[str, Platform] = {
76
76
  ".ipa": Platform.ios,
77
77
  ".apk": Platform.android,
78
78
  ".apks": Platform.android,
79
+ ".app.zip": Platform.ios,
79
80
  }
80
81
 
81
82
 
@@ -97,7 +98,8 @@ class BuildKindFilter(StrEnum):
97
98
 
98
99
  def detect_platform(file_path: Path) -> str:
99
100
  """Auto-detect platform from file extension."""
100
- suffix = file_path.suffix.lower()
101
+ name = file_path.name.lower()
102
+ suffix = ".app.zip" if name.endswith(".app.zip") else file_path.suffix.lower()
101
103
  platform = PLATFORM_EXTENSIONS.get(suffix)
102
104
  if platform is None:
103
105
  print_error(
@@ -11,15 +11,11 @@ from minitest_cli.commands.run_helpers import (
11
11
  base_path,
12
12
  display_run_result,
13
13
  ensure_uuid,
14
- fetch_runs,
15
- format_run_pagination_info,
16
- format_run_row,
17
14
  handle_response_error,
18
15
  poll_run_status,
19
16
  resolve_app,
20
17
  resolve_user_story_id,
21
18
  run_api_call,
22
- RUN_TABLE_HEADERS,
23
19
  TERMINAL_STATUSES,
24
20
  )
25
21
  from minitest_cli.commands.run_cancel import cancel
@@ -27,27 +23,27 @@ from minitest_cli.commands.run_commit import from_commit
27
23
  from minitest_cli.commands.run_targets import (
28
24
  AndroidBuildOpt,
29
25
  AndroidDeviceTypeOpt,
26
+ AndroidPhysicalOpt,
30
27
  build_targets,
31
28
  IosBuildOpt,
32
29
  IosDeviceTypeOpt,
30
+ IosPhysicalOpt,
33
31
  WebOpt,
34
32
  )
35
33
  from minitest_cli.commands.run_feedback import feedback
34
+ from minitest_cli.commands.run_list import list_runs
36
35
  from minitest_cli.commands.run_recording import register_recording_commands
37
36
  from minitest_cli.commands.run_tagged import print_batch_started, start_tagged
38
37
  from minitest_cli.commands.verdicts import verdicts
39
38
  from minitest_cli.models.batch import BatchResponse, CreateBatchRequest
40
39
  from minitest_cli.models.story_run import (
41
- StoryRunListResponse,
42
40
  StoryRunResponse,
43
41
  )
44
42
  from minitest_cli.utils.output import (
45
- output,
46
43
  print_error,
47
44
  print_info,
48
45
  print_json,
49
46
  print_success,
50
- print_table,
51
47
  )
52
48
 
53
49
  app = typer.Typer(
@@ -69,6 +65,8 @@ def start(
69
65
  web: WebOpt = False,
70
66
  ios_device_type: IosDeviceTypeOpt = None,
71
67
  android_device_type: AndroidDeviceTypeOpt = None,
68
+ ios_physical: IosPhysicalOpt = False,
69
+ android_physical: AndroidPhysicalOpt = False,
72
70
  watch: Annotated[
73
71
  bool, typer.Option("--watch/--no-watch", help="Poll for results (default: watch).")
74
72
  ] = True,
@@ -78,7 +76,15 @@ def start(
78
76
  print_error("Pass either a scenario or --tag, not both or neither.")
79
77
  raise typer.Exit(code=1)
80
78
  settings, app_id, json_mode = resolve_app()
81
- targets = build_targets(ios_build, android_build, web, ios_device_type, android_device_type)
79
+ targets = build_targets(
80
+ ios_build,
81
+ android_build,
82
+ web,
83
+ ios_device_type,
84
+ android_device_type,
85
+ ios_physical,
86
+ android_physical,
87
+ )
82
88
  if tag:
83
89
  print_batch_started(start_tagged(settings, app_id, tag, targets), json_mode)
84
90
  return
@@ -131,44 +137,6 @@ def status(
131
137
  display_run_result(run_api_call(_status()), json_mode)
132
138
 
133
139
 
134
- @app.command(name="list")
135
- def list_runs(
136
- user_story: Annotated[str, typer.Argument(help="Scenario name or UUID to list runs for.")],
137
- page: Annotated[int, typer.Option(help="Page number.")] = 1,
138
- page_size: Annotated[int, typer.Option(help="Items per page.")] = 20,
139
- status_filter: Annotated[
140
- str | None,
141
- typer.Option(
142
- "--status",
143
- help="Filter by status (pending, running, completed, failed, cancelled).",
144
- ),
145
- ] = None,
146
- all_pages: Annotated[bool, typer.Option("--all", help="Fetch all results.")] = False,
147
- ) -> None:
148
- """List the scenario runs of one scenario."""
149
- settings, app_id, json_mode = resolve_app()
150
- if all_pages:
151
- page, page_size = 1, 100
152
-
153
- async def _list() -> StoryRunListResponse:
154
- async with ApiClient(settings) as client:
155
- user_story_id = await resolve_user_story_id(client, app_id, user_story)
156
- return await fetch_runs(client, app_id, user_story_id, page, page_size, status_filter)
157
-
158
- result = run_api_call(_list())
159
- if json_mode:
160
- output(result.model_dump(mode="json", by_alias=True), json_mode=True)
161
- return
162
- if not result.items:
163
- print_info("No runs found.")
164
- return
165
- title, tip = format_run_pagination_info(result)
166
- rows = [format_run_row(r) for r in result.items]
167
- print_table(RUN_TABLE_HEADERS, rows, title=title)
168
- if tip:
169
- print_info(tip)
170
-
171
-
172
140
  @app.command(name="all")
173
141
  def run_all(
174
142
  ios_build: IosBuildOpt = None,
@@ -176,10 +144,20 @@ def run_all(
176
144
  web: WebOpt = False,
177
145
  ios_device_type: IosDeviceTypeOpt = None,
178
146
  android_device_type: AndroidDeviceTypeOpt = None,
147
+ ios_physical: IosPhysicalOpt = False,
148
+ android_physical: AndroidPhysicalOpt = False,
179
149
  ) -> None:
180
150
  """Start a run covering every scenario of the app."""
181
151
  settings, app_id, json_mode = resolve_app()
182
- targets = build_targets(ios_build, android_build, web, ios_device_type, android_device_type)
152
+ targets = build_targets(
153
+ ios_build,
154
+ android_build,
155
+ web,
156
+ ios_device_type,
157
+ android_device_type,
158
+ ios_physical,
159
+ android_physical,
160
+ )
183
161
 
184
162
  async def _run_all() -> BatchResponse:
185
163
  async with ApiClient(settings) as client:
@@ -189,6 +167,7 @@ def run_all(
189
167
  print_batch_started(run_api_call(_run_all()), json_mode)
190
168
 
191
169
 
170
+ app.command(name="list")(list_runs)
192
171
  app.command(name="cancel")(cancel)
193
172
  app.command(name="from-commit")(from_commit)
194
173
  app.command(name="verdicts")(verdicts)
@@ -0,0 +1,55 @@
1
+ """``minitest run list``: the scenario runs of one scenario."""
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from minitest_cli.api.client import ApiClient
8
+ from minitest_cli.commands.run_helpers import (
9
+ fetch_runs,
10
+ format_run_pagination_info,
11
+ format_run_row,
12
+ resolve_app,
13
+ resolve_user_story_id,
14
+ run_api_call,
15
+ RUN_TABLE_HEADERS,
16
+ )
17
+ from minitest_cli.models.story_run import StoryRunListResponse
18
+ from minitest_cli.utils.output import output, print_info, print_table
19
+
20
+
21
+ def list_runs(
22
+ user_story: Annotated[str, typer.Argument(help="Scenario name or UUID to list runs for.")],
23
+ page: Annotated[int, typer.Option(help="Page number.")] = 1,
24
+ page_size: Annotated[int, typer.Option(help="Items per page.")] = 20,
25
+ status_filter: Annotated[
26
+ str | None,
27
+ typer.Option(
28
+ "--status",
29
+ help="Filter by status (pending, running, completed, failed, cancelled).",
30
+ ),
31
+ ] = None,
32
+ all_pages: Annotated[bool, typer.Option("--all", help="Fetch all results.")] = False,
33
+ ) -> None:
34
+ """List the scenario runs of one scenario."""
35
+ settings, app_id, json_mode = resolve_app()
36
+ if all_pages:
37
+ page, page_size = 1, 100
38
+
39
+ async def _list() -> StoryRunListResponse:
40
+ async with ApiClient(settings) as client:
41
+ user_story_id = await resolve_user_story_id(client, app_id, user_story)
42
+ return await fetch_runs(client, app_id, user_story_id, page, page_size, status_filter)
43
+
44
+ result = run_api_call(_list())
45
+ if json_mode:
46
+ output(result.model_dump(mode="json", by_alias=True), json_mode=True)
47
+ return
48
+ if not result.items:
49
+ print_info("No runs found.")
50
+ return
51
+ title, tip = format_run_pagination_info(result)
52
+ rows = [format_run_row(r) for r in result.items]
53
+ print_table(RUN_TABLE_HEADERS, rows, title=title)
54
+ if tip:
55
+ print_info(tip)
@@ -1,12 +1,15 @@
1
1
  """Lane-selection flags and target assembly for run commands."""
2
2
 
3
- from typing import Annotated
3
+ from collections.abc import Sequence
4
+ from typing import Annotated, Literal
4
5
 
5
6
  import typer
6
7
 
7
8
  from minitest_cli.models.targets import BatchTarget, DeviceType
8
9
  from minitest_cli.utils.output import print_error
9
10
 
11
+ NativePlatform = Literal["ios", "android"]
12
+
10
13
  IosBuildOpt = Annotated[
11
14
  str | None, typer.Option("--ios-build", help="iOS build ID (selects the iOS lane).")
12
15
  ]
@@ -34,9 +37,49 @@ AndroidDeviceTypeOpt = Annotated[
34
37
  ),
35
38
  ]
36
39
 
40
+ IosPhysicalOpt = Annotated[
41
+ bool,
42
+ typer.Option(
43
+ "--ios-physical",
44
+ help=(
45
+ "Run the iOS lane on physical iPhones instead of simulators (needs --ios-build"
46
+ " with a device build, and physical devices enabled for the workspace)."
47
+ ),
48
+ ),
49
+ ]
50
+ AndroidPhysicalOpt = Annotated[
51
+ bool,
52
+ typer.Option(
53
+ "--android-physical",
54
+ help=(
55
+ "Run the Android lane on physical phones instead of emulators (needs"
56
+ " --android-build, and physical devices enabled for the workspace)."
57
+ ),
58
+ ),
59
+ ]
60
+
61
+
62
+ def validate_physical(
63
+ platforms: Sequence[str],
64
+ physical: dict[str, bool],
65
+ device_types: dict[str, DeviceType | None],
66
+ ) -> None:
67
+ for platform, enabled in physical.items():
68
+ if not enabled:
69
+ continue
70
+ if platform not in platforms:
71
+ print_error(f"--{platform}-physical requires --{platform}-build.")
72
+ raise typer.Exit(code=1)
73
+ if device_types.get(platform) is not None:
74
+ print_error(
75
+ f"--{platform}-physical cannot be combined with --{platform}-device-type:"
76
+ " device types only apply to virtual devices."
77
+ )
78
+ raise typer.Exit(code=1)
79
+
37
80
 
38
81
  def validate_device_types(
39
- platforms: list[str],
82
+ platforms: Sequence[str],
40
83
  ios_device_type: DeviceType | None,
41
84
  android_device_type: DeviceType | None,
42
85
  ) -> None:
@@ -52,19 +95,26 @@ def build_targets(
52
95
  web: bool,
53
96
  ios_device_type: DeviceType | None = None,
54
97
  android_device_type: DeviceType | None = None,
98
+ ios_physical: bool = False,
99
+ android_physical: bool = False,
55
100
  ) -> list[BatchTarget]:
56
- targets: list[BatchTarget] = []
57
- if ios_build:
58
- targets.append(BatchTarget(platform="ios", build_id=ios_build, device_type=ios_device_type))
59
- if android_build:
60
- targets.append(
61
- BatchTarget(platform="android", build_id=android_build, device_type=android_device_type)
101
+ builds: dict[NativePlatform, str | None] = {"ios": ios_build, "android": android_build}
102
+ device_types = {"ios": ios_device_type, "android": android_device_type}
103
+ physical = {"ios": ios_physical, "android": android_physical}
104
+ native: list[NativePlatform] = [platform for platform, build in builds.items() if build]
105
+ validate_physical(native, physical, device_types)
106
+ validate_device_types(native, ios_device_type, android_device_type)
107
+ targets = [
108
+ BatchTarget(
109
+ platform=platform,
110
+ build_id=builds[platform],
111
+ device_type=device_types[platform],
112
+ backend="physical" if physical[platform] else None,
62
113
  )
114
+ for platform in native
115
+ ]
63
116
  if web:
64
117
  targets.append(BatchTarget(platform="web"))
65
- validate_device_types(
66
- [target.platform for target in targets], ios_device_type, android_device_type
67
- )
68
118
  if not targets:
69
119
  print_error(
70
120
  "Select at least one lane: --ios-build, --android-build, or --web. "
@@ -0,0 +1,68 @@
1
+ """App targeting: resolve the active app ID from --app flag or MINITEST_APP_ID.
2
+
3
+ Both accept an app id or an app name; a name is looked up once in the apps the
4
+ caller can see (the same lookup `draft` and `env` use), an id is used as is.
5
+ """
6
+
7
+ import asyncio
8
+ import sys
9
+ import threading
10
+ from uuid import UUID
11
+
12
+ from minitest_cli.core.config import Settings
13
+
14
+ EXIT_CODE_GENERAL_ERROR = 1
15
+
16
+
17
+ def resolve_app_id(settings: Settings, app_flag: str | None = None) -> str:
18
+ """Resolve the target app ID.
19
+
20
+ Priority:
21
+ 1. --app flag value (passed explicitly)
22
+ 2. MINITEST_APP_ID environment variable (via settings)
23
+ 3. Exit with error if neither is set
24
+
25
+ Returns:
26
+ The resolved app ID string.
27
+ """
28
+ target = app_flag or settings.app_id
29
+ if not target:
30
+ print( # noqa: T201
31
+ "Error: No app specified. Use --app <id-or-name> or set MINITEST_APP_ID.",
32
+ file=sys.stderr,
33
+ )
34
+ raise SystemExit(EXIT_CODE_GENERAL_ERROR)
35
+ if _is_uuid(target):
36
+ return target
37
+ return _app_id_by_name(settings, target)
38
+
39
+
40
+ def _is_uuid(value: str) -> bool:
41
+ try:
42
+ UUID(value)
43
+ except ValueError:
44
+ return False
45
+ return True
46
+
47
+
48
+ def _app_id_by_name(settings: Settings, name: str) -> str:
49
+ """Look the name up; exits 4 when no app has it, 1 when several do."""
50
+ from minitest_cli.commands.env_helpers import resolve_app_and_tenant
51
+
52
+ result: list[str] = []
53
+ failure: list[BaseException] = []
54
+
55
+ def lookup() -> None:
56
+ try:
57
+ app_id, _ = asyncio.run(resolve_app_and_tenant(settings, name))
58
+ result.append(app_id)
59
+ except BaseException as exc: # noqa: BLE001 - re-raised on the caller's thread
60
+ failure.append(exc)
61
+
62
+ # A thread of its own, so this works whether or not the caller runs an event loop.
63
+ worker = threading.Thread(target=lookup)
64
+ worker.start()
65
+ worker.join()
66
+ if failure:
67
+ raise failure[0]
68
+ return result[0]