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.
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/PKG-INFO +24 -1
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/README.md +23 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/pyproject.toml +4 -1
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/_response_errors.py +29 -7
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch_helpers.py +22 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build.py +26 -8
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_helpers.py +3 -1
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run.py +26 -47
- minitest_cli-0.37.1/src/minitest_cli/commands/run_list.py +55 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_targets.py +61 -11
- minitest_cli-0.37.1/src/minitest_cli/core/app_context.py +68 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/targets.py +9 -1
- minitest_cli-0.37.1/tests/conftest.py +26 -0
- minitest_cli-0.37.1/tests/test_app_context.py +84 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_build_commands.py +1 -1
- minitest_cli-0.37.1/tests/test_physical_devices.py +227 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_run_commands.py +7 -7
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/uv.lock +1 -1
- minitest_cli-0.37.0/src/minitest_cli/core/app_context.py +0 -31
- minitest_cli-0.37.0/tests/conftest.py +0 -11
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.env.example +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.github/workflows/ci.yml +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.github/workflows/install-scripts.yml +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.github/workflows/release.yml +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.gitignore +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/.opencode/skill/release/SKILL.md +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/AGENTS.md +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/RELEASE.md +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/install.ps1 +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/install.sh +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/pyrightconfig.json +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/renovate.json +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/apps_manager_client.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/client.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/api/errors.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/assets/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/assets/callback.html +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_knowledge.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_network_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_secret.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/app_skill_write.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_dependencies.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_get.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/apps_table.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/auth.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/auth_api_key.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/batch_warnings.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_commit.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_fix_prompts.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/build_get.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/capabilities.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/commit_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/deprecated_alias.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_asks.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_conversation.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_create.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_apply.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_conflicts.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_feature_show.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_legacy.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_lifecycle.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_pr.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_render.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_update.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/draft_watch.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/env.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/env_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/flow_types_write.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/init_playbook.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_fix.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_get.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_projection.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/issues_service.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/private_network.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/private_network_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/recording_timeline.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/residential_proxy.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_cancel.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_commit.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_commit_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_display.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_feedback.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_recording.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_recording_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/run_tagged.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_detail.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_format.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/screens_tree.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/skill.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/tags.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/tags_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_file_list.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_card.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_default.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_list.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/test_profile_update.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/upgrade.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_app_skills.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_bindings.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_camera.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_create.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_criteria.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_criterion_edits.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_delete.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_device_count.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_modify.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_overrides.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_profiles.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_setup_commit.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/user_story_update.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts_helpers.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/commands/verdicts_projection.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/auth.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/config.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/credentials.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/oauth.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/tenants.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/core/token_exchange.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/main.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_env_vars.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_failure.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/app_screen.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/base.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/batch.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/build.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/commit_build.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/draft_feature.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/flow_type.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/issues.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/recording.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/story_run.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/tag.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/models/user_story.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/confirm.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/dependency_sim.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/mermaid.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/output.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/skill_refresh.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/update_check.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/src/minitest_cli/utils/video_frames.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/__init__.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/_commit_transport.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/_network_settings_server.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/draft_backend.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_api_client_auth.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_app_knowledge_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_app_skill_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_apps_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_apps_dependencies.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_auth.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_auth_api_key.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_auth_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_batch_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_build_fix_prompts.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_build_get_command.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_capabilities_command.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_channel_header.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_code_quality.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_commit_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_deprecated_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_deprecated_flags.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_ask_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_conversation_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_feature_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_draft_pr_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_env.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_flow_types_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_init_command.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_issues.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_issues_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_issues_get_command.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_mermaid.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_native_device_types.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_network_errors.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_private_network_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_recording_timeline.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_residential_proxy_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_response_errors.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_run_cancel.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_run_frames_command.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_screens_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_skill_command.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_suite_change_provenance.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_tags_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_test_file_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_test_profile_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_upgrade_command.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_user_story_bindings_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_user_story_commands.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_user_story_setup_commit.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_verdicts.py +0 -0
- {minitest_cli-0.37.0 → minitest_cli-0.37.1}/tests/test_verdicts_actionable.py +0 -0
- {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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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 .
|
|
46
|
-
"
|
|
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
|
-
|
|
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
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
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]
|