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.
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/PKG-INFO +27 -1
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/README.md +26 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/pyproject.toml +1 -1
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/batch_helpers.py +4 -1
- minitest_cli-0.32.0/src/minitest_cli/commands/batch_warnings.py +12 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/flow_types.py +6 -3
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/flow_types_helpers.py +0 -61
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run.py +29 -35
- minitest_cli-0.32.0/src/minitest_cli/commands/run_cancel.py +123 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_commit.py +18 -2
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_commit_helpers.py +13 -1
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_display.py +11 -2
- minitest_cli-0.32.0/src/minitest_cli/commands/run_tagged.py +44 -0
- minitest_cli-0.32.0/src/minitest_cli/commands/run_targets.py +92 -0
- minitest_cli-0.32.0/src/minitest_cli/commands/tags.py +130 -0
- minitest_cli-0.32.0/src/minitest_cli/commands/tags_helpers.py +71 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story.py +23 -40
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_create.py +7 -9
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_helpers.py +29 -2
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_modify.py +6 -6
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_update.py +2 -4
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/verdicts_projection.py +4 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/main.py +2 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/batch.py +3 -1
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/build.py +3 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/story_run.py +6 -0
- minitest_cli-0.32.0/src/minitest_cli/models/tag.py +31 -0
- minitest_cli-0.32.0/src/minitest_cli/models/targets.py +56 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/user_story.py +4 -3
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/skill_refresh.py +1 -1
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_flow_types_commands.py +8 -51
- minitest_cli-0.32.0/tests/test_native_device_types.py +294 -0
- minitest_cli-0.32.0/tests/test_run_cancel.py +154 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_run_commands.py +0 -79
- minitest_cli-0.32.0/tests/test_tags_commands.py +285 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_user_story_commands.py +14 -175
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/uv.lock +1 -1
- minitest_cli-0.31.0/src/minitest_cli/commands/run_targets.py +0 -40
- minitest_cli-0.31.0/src/minitest_cli/models/targets.py +0 -43
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.env.example +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.github/workflows/ci.yml +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.github/workflows/install-scripts.yml +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.github/workflows/release.yml +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.gitignore +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/.opencode/skill/release/SKILL.md +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/AGENTS.md +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/RELEASE.md +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/install.ps1 +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/install.sh +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/pyrightconfig.json +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/renovate.json +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/apps_manager_client.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/client.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/api/errors.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/assets/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/assets/callback.html +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/_response_errors.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_knowledge.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill_secret.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/app_skill_write.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps_dependencies.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps_get.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/apps_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/auth.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/auth_api_key.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/batch.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build_commit.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build_fix_prompts.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/build_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/capabilities.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/commit_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_apply.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_conflicts.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/draft_feature_show.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/env.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/env_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/flow_types_write.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/init.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/init_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/init_playbook.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_fix.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_projection.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/issues_service.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance_callbacks.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/maintenance_state.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/recording_timeline.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_feedback.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_recording.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/run_recording_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_detail.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_format.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/screens_tree.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/skill.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_file.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_file_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_file_list.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_card.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_default.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_list.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/test_profile_update.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/upgrade.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_bindings.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_camera.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_criteria.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_criterion_edits.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_delete.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_device_count.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_overrides.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_profiles.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/user_story_setup_commit.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/verdicts.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/commands/verdicts_helpers.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/app_context.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/auth.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/config.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/credentials.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/oauth.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/tenants.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/core/token_exchange.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app_env_vars.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app_failure.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/app_screen.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/base.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/commit_build.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/draft_feature.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/flow_type.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/issues.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/models/recording.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/confirm.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/dependency_sim.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/mermaid.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/output.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/update_check.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/src/minitest_cli/utils/video_frames.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/__init__.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/_commit_transport.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_api_client_auth.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_app_knowledge_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_app_skill_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_apps_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_apps_dependencies.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_auth.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_auth_api_key.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_auth_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_batch_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_build_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_build_fix_prompts.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_capabilities_command.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_channel_header.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_code_quality.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_commit_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_draft_feature_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_env.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_init_command.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_issues.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_issues_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_mermaid.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_network_errors.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_recording_timeline.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_response_errors.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_run_frames_command.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_screens_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_skill_command.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_suite_change_provenance.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_test_file_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_test_profile_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_upgrade_command.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_user_story_bindings_commands.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_user_story_setup_commit.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_verdicts.py +0 -0
- {minitest_cli-0.31.0 → minitest_cli-0.32.0}/tests/test_verdicts_actionable.py +0 -0
- {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.
|
|
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 |
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
"""
|
|
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
|
|
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[
|
|
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
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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())
|