diff --git a/test_runner/docs/ascii/flank.jar_-android-run.adoc b/test_runner/docs/ascii/flank.jar_-android-run.adoc
index 8118d39eb5..8eee8c848e 100644
--- a/test_runner/docs/ascii/flank.jar_-android-run.adoc
+++ b/test_runner/docs/ascii/flank.jar_-android-run.adoc
@@ -13,8 +13,6 @@ flank.jar
 
 *flank.jar
  android run* [*-h*] [*--async*] [*--auto-google-login*]
-                       [*--available-devices*] [*--available-environment*]
-                       [*--available-software-versions*]
                        [*--disable-results-upload*] [*--disable-sharding*] [*--dry*]
                        [*--dump-shards*] [*--full-junit-result*]
                        [*--ignore-failed-tests*] [*--keep-file-path*]
@@ -84,18 +82,12 @@ Configuration is read from flank.yml
 *--obfuscate*::
   Replacing internal test names with unique identifiers when using --dump-shards option to avoid exposing internal details
 
-*--available-devices*::
-  Print current list of devices available to test against
-
-*--available-software-versions*::
-  Print current list of OS versions available to test against
-
-*--available-environment*::
-  Print available devices and OS versions list to test against
-
 *--device*=_<String=String>_[,_<String=String>_...]::
   A list of DIMENSION=VALUE pairs which specify a target device to test against. This flag may be repeated to specify multiple devices. The four device dimensions are: model, version, locale, and orientation. If any dimensions are omitted, they will use a default value. Omitting all of the preceding dimension-related flags will run tests against a single device using defaults for all four device dimensions.
 
+*--async*::
+  Invoke a test asynchronously without waiting for test results.
+
 *--results-bucket*=_<resultsBucket>_::
   The name of a Google Cloud Storage bucket where raw test results will be stored (default: "test-lab-<random-UUID>"). Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.
 
@@ -111,9 +103,6 @@ Configuration is read from flank.yml
 *--timeout*=_<timeout>_::
   The max time this test execution can run before it is cancelled (default: 15m). It does not include any time necessary to prepare and clean up the target device. The maximum possible testing time is 30m on physical devices and 60m on virtual devices. The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. 
 
-*--async*::
-  Invoke a test asynchronously without waiting for test results.
-
 *--client-details*=_<String=String>_[,_<String=String>_...]::
   Comma-separated, KEY=VALUE map of additional details to attach to the test matrix.Arbitrary KEY=VALUE pairs may be attached to a test matrix to provide additional context about the tests being run.When consuming the test results, such as in Cloud Functions or a CI system,these details can add additional context such as a link to the corresponding pull request.
 
diff --git a/test_runner/docs/ascii/flank.jar_-android.adoc b/test_runner/docs/ascii/flank.jar_-android.adoc
index fddab01889..11bb9c6a36 100644
--- a/test_runner/docs/ascii/flank.jar_-android.adoc
+++ b/test_runner/docs/ascii/flank.jar_-android.adoc
@@ -32,6 +32,15 @@ flank.jar
 *doctor*::
   Verifies flank firebase is setup correctly
 
+*models*::
+  Information about available models
+
+*versions*::
+  Information about available software versions
+
+*test-environment*::
+  Print available devices and OS versions list to test against
+
 // end::picocli-generated-man-section-commands[]
 
 // end::picocli-generated-full-manpage[]
\ No newline at end of file
diff --git a/test_runner/docs/ascii/flank.jar_-firebase-test-android-run.adoc b/test_runner/docs/ascii/flank.jar_-firebase-test-android-run.adoc
index 3130d33209..ae4d6be85b 100644
--- a/test_runner/docs/ascii/flank.jar_-firebase-test-android-run.adoc
+++ b/test_runner/docs/ascii/flank.jar_-firebase-test-android-run.adoc
@@ -13,9 +13,6 @@ flank.jar
 
 *flank.jar
  firebase test android run* [*-h*] [*--async*] [*--auto-google-login*]
-                                     [*--available-devices*]
-                                     [*--available-environment*]
-                                     [*--available-software-versions*]
                                      [*--disable-results-upload*]
                                      [*--disable-sharding*] [*--dry*]
                                      [*--dump-shards*] [*--full-junit-result*]
@@ -97,18 +94,12 @@ Configuration is read from flank.yml
 *--obfuscate*::
   Replacing internal test names with unique identifiers when using --dump-shards option to avoid exposing internal details
 
-*--available-devices*::
-  Print current list of devices available to test against
-
-*--available-software-versions*::
-  Print current list of OS versions available to test against
-
-*--available-environment*::
-  Print available devices and OS versions list to test against
-
 *--device*=_<String=String>_[,_<String=String>_...]::
   A list of DIMENSION=VALUE pairs which specify a target device to test against. This flag may be repeated to specify multiple devices. The four device dimensions are: model, version, locale, and orientation. If any dimensions are omitted, they will use a default value. Omitting all of the preceding dimension-related flags will run tests against a single device using defaults for all four device dimensions.
 
+*--async*::
+  Invoke a test asynchronously without waiting for test results.
+
 *--results-bucket*=_<resultsBucket>_::
   The name of a Google Cloud Storage bucket where raw test results will be stored (default: "test-lab-<random-UUID>"). Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.
 
@@ -124,9 +115,6 @@ Configuration is read from flank.yml
 *--timeout*=_<timeout>_::
   The max time this test execution can run before it is cancelled (default: 15m). It does not include any time necessary to prepare and clean up the target device. The maximum possible testing time is 30m on physical devices and 60m on virtual devices. The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. 
 
-*--async*::
-  Invoke a test asynchronously without waiting for test results.
-
 *--client-details*=_<String=String>_[,_<String=String>_...]::
   Comma-separated, KEY=VALUE map of additional details to attach to the test matrix.Arbitrary KEY=VALUE pairs may be attached to a test matrix to provide additional context about the tests being run.When consuming the test results, such as in Cloud Functions or a CI system,these details can add additional context such as a link to the corresponding pull request.
 
diff --git a/test_runner/docs/ascii/flank.jar_-firebase-test-android.adoc b/test_runner/docs/ascii/flank.jar_-firebase-test-android.adoc
index b4cd3f5144..c004c66304 100644
--- a/test_runner/docs/ascii/flank.jar_-firebase-test-android.adoc
+++ b/test_runner/docs/ascii/flank.jar_-firebase-test-android.adoc
@@ -32,6 +32,15 @@ flank.jar
 *doctor*::
   Verifies flank firebase is setup correctly
 
+*models*::
+  Information about available models
+
+*versions*::
+  Information about available software versions
+
+*test-environment*::
+  Print available devices and OS versions list to test against
+
 // end::picocli-generated-man-section-commands[]
 
 // end::picocli-generated-full-manpage[]
\ No newline at end of file
diff --git a/test_runner/docs/ascii/flank.jar_-firebase-test-ios-run.adoc b/test_runner/docs/ascii/flank.jar_-firebase-test-ios-run.adoc
index aa0b1f1e9f..41ac499095 100644
--- a/test_runner/docs/ascii/flank.jar_-firebase-test-ios-run.adoc
+++ b/test_runner/docs/ascii/flank.jar_-firebase-test-ios-run.adoc
@@ -12,10 +12,7 @@ flank.jar
 == Synopsis
 
 *flank.jar
- firebase test ios run* [*-h*] [*--async*] [*--available-devices*]
-                                 [*--available-environment*]
-                                 [*--available-software-versions*]
-                                 [*--disable-results-upload*]
+ firebase test ios run* [*-h*] [*--async*] [*--disable-results-upload*]
                                  [*--disable-sharding*] [*--dry*] [*--dump-shards*]
                                  [*--full-junit-result*] [*--ignore-failed-tests*]
                                  [*--keep-file-path*] [*--no-record-video*]
@@ -76,18 +73,12 @@ Configuration is read from flank.yml
 *--obfuscate*::
   Replacing internal test names with unique identifiers when using --dump-shards option to avoid exposing internal details
 
-*--available-devices*::
-  Print current list of devices available to test against
-
-*--available-software-versions*::
-  Print current list of OS versions available to test against
-
-*--available-environment*::
-  Print available devices and OS versions list to test against
-
 *--device*=_<String=String>_[,_<String=String>_...]::
   A list of DIMENSION=VALUE pairs which specify a target device to test against. This flag may be repeated to specify multiple devices. The four device dimensions are: model, version, locale, and orientation. If any dimensions are omitted, they will use a default value. Omitting all of the preceding dimension-related flags will run tests against a single device using defaults for all four device dimensions.
 
+*--async*::
+  Invoke a test asynchronously without waiting for test results.
+
 *--results-bucket*=_<resultsBucket>_::
   The name of a Google Cloud Storage bucket where raw test results will be stored (default: "test-lab-<random-UUID>"). Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.
 
@@ -103,9 +94,6 @@ Configuration is read from flank.yml
 *--timeout*=_<timeout>_::
   The max time this test execution can run before it is cancelled (default: 15m). It does not include any time necessary to prepare and clean up the target device. The maximum possible testing time is 30m on physical devices and 60m on virtual devices. The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. 
 
-*--async*::
-  Invoke a test asynchronously without waiting for test results.
-
 *--client-details*=_<String=String>_[,_<String=String>_...]::
   Comma-separated, KEY=VALUE map of additional details to attach to the test matrix.Arbitrary KEY=VALUE pairs may be attached to a test matrix to provide additional context about the tests being run.When consuming the test results, such as in Cloud Functions or a CI system,these details can add additional context such as a link to the corresponding pull request.
 
diff --git a/test_runner/docs/ascii/flank.jar_-firebase-test-ios.adoc b/test_runner/docs/ascii/flank.jar_-firebase-test-ios.adoc
index 32eedd75e0..68c65073e8 100644
--- a/test_runner/docs/ascii/flank.jar_-firebase-test-ios.adoc
+++ b/test_runner/docs/ascii/flank.jar_-firebase-test-ios.adoc
@@ -32,6 +32,15 @@ flank.jar
 *doctor*::
   Verifies flank firebase is setup correctly
 
+*models*::
+  Information about available models
+
+*versions*::
+  Information about available software versions
+
+*test-environment*::
+  Print available devices and OS versions list to test against
+
 // end::picocli-generated-man-section-commands[]
 
 // end::picocli-generated-full-manpage[]
\ No newline at end of file
diff --git a/test_runner/docs/ascii/flank.jar_-ios-run.adoc b/test_runner/docs/ascii/flank.jar_-ios-run.adoc
index 02b889e38d..1fae5b09a5 100644
--- a/test_runner/docs/ascii/flank.jar_-ios-run.adoc
+++ b/test_runner/docs/ascii/flank.jar_-ios-run.adoc
@@ -12,14 +12,12 @@ flank.jar
 == Synopsis
 
 *flank.jar
- ios run* [*-h*] [*--async*] [*--available-devices*]
-                   [*--available-environment*] [*--available-software-versions*]
-                   [*--disable-results-upload*] [*--disable-sharding*] [*--dry*]
-                   [*--dump-shards*] [*--full-junit-result*]
-                   [*--ignore-failed-tests*] [*--keep-file-path*]
-                   [*--no-record-video*] [*--obfuscate*] [*--record-video*]
-                   [*--smart-flank-disable-upload*] [*-c*=_<configPath>_]
-                   [*--local-result-dir*=_<localResultsDir>_]
+ ios run* [*-h*] [*--async*] [*--disable-results-upload*]
+                   [*--disable-sharding*] [*--dry*] [*--dump-shards*]
+                   [*--full-junit-result*] [*--ignore-failed-tests*]
+                   [*--keep-file-path*] [*--no-record-video*] [*--obfuscate*]
+                   [*--record-video*] [*--smart-flank-disable-upload*]
+                   [*-c*=_<configPath>_] [*--local-result-dir*=_<localResultsDir>_]
                    [*--max-test-shards*=_<maxTestShards>_]
                    [*--network-profile*=_<networkProfile>_]
                    [*--num-flaky-test-attempts*=_<flakyTestAttempts>_]
@@ -67,18 +65,12 @@ Configuration is read from flank.yml
 *--obfuscate*::
   Replacing internal test names with unique identifiers when using --dump-shards option to avoid exposing internal details
 
-*--available-devices*::
-  Print current list of devices available to test against
-
-*--available-software-versions*::
-  Print current list of OS versions available to test against
-
-*--available-environment*::
-  Print available devices and OS versions list to test against
-
 *--device*=_<String=String>_[,_<String=String>_...]::
   A list of DIMENSION=VALUE pairs which specify a target device to test against. This flag may be repeated to specify multiple devices. The four device dimensions are: model, version, locale, and orientation. If any dimensions are omitted, they will use a default value. Omitting all of the preceding dimension-related flags will run tests against a single device using defaults for all four device dimensions.
 
+*--async*::
+  Invoke a test asynchronously without waiting for test results.
+
 *--results-bucket*=_<resultsBucket>_::
   The name of a Google Cloud Storage bucket where raw test results will be stored (default: "test-lab-<random-UUID>"). Note that the bucket must be owned by a billing-enabled project, and that using a non-default bucket will result in billing charges for the storage used.
 
@@ -94,9 +86,6 @@ Configuration is read from flank.yml
 *--timeout*=_<timeout>_::
   The max time this test execution can run before it is cancelled (default: 15m). It does not include any time necessary to prepare and clean up the target device. The maximum possible testing time is 30m on physical devices and 60m on virtual devices. The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. 
 
-*--async*::
-  Invoke a test asynchronously without waiting for test results.
-
 *--client-details*=_<String=String>_[,_<String=String>_...]::
   Comma-separated, KEY=VALUE map of additional details to attach to the test matrix.Arbitrary KEY=VALUE pairs may be attached to a test matrix to provide additional context about the tests being run.When consuming the test results, such as in Cloud Functions or a CI system,these details can add additional context such as a link to the corresponding pull request.
 
diff --git a/test_runner/docs/ascii/flank.jar_-ios.adoc b/test_runner/docs/ascii/flank.jar_-ios.adoc
index afdd43348b..99082ef300 100644
--- a/test_runner/docs/ascii/flank.jar_-ios.adoc
+++ b/test_runner/docs/ascii/flank.jar_-ios.adoc
@@ -32,6 +32,15 @@ flank.jar
 *doctor*::
   Verifies flank firebase is setup correctly
 
+*models*::
+  Information about available models
+
+*versions*::
+  Information about available software versions
+
+*test-environment*::
+  Print available devices and OS versions list to test against
+
 // end::picocli-generated-man-section-commands[]
 
 // end::picocli-generated-full-manpage[]
\ No newline at end of file
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/AndroidCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/AndroidCommand.kt
index b546c7d2e1..8013b03e5e 100644
--- a/test_runner/src/main/kotlin/ftl/cli/firebase/test/AndroidCommand.kt
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/AndroidCommand.kt
@@ -2,6 +2,9 @@ package ftl.cli.firebase.test
 
 import ftl.cli.firebase.test.android.AndroidDoctorCommand
 import ftl.cli.firebase.test.android.AndroidRunCommand
+import ftl.cli.firebase.test.android.AndroidTestEnvironmentCommand
+import ftl.cli.firebase.test.android.models.AndroidModelsCommand
+import ftl.cli.firebase.test.android.versions.AndroidVersionsCommand
 import picocli.CommandLine
 import picocli.CommandLine.Command
 
@@ -10,7 +13,10 @@ import picocli.CommandLine.Command
     synopsisHeading = "",
     subcommands = [
         AndroidRunCommand::class,
-        AndroidDoctorCommand::class
+        AndroidDoctorCommand::class,
+        AndroidModelsCommand::class,
+        AndroidVersionsCommand::class,
+        AndroidTestEnvironmentCommand::class
     ],
     usageHelpAutoWidth = true
 )
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/CommonRunCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/CommonRunCommand.kt
index be25aab857..7c07eb7a4a 100644
--- a/test_runner/src/main/kotlin/ftl/cli/firebase/test/CommonRunCommand.kt
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/CommonRunCommand.kt
@@ -49,21 +49,7 @@ abstract class CommonRunCommand {
     @CommandLine.Option(
         names = ["--obfuscate"],
         description = ["Replacing internal test names with unique identifiers when using --dump-shards option " +
-                "to avoid exposing internal details"])
+                "to avoid exposing internal details"]
+    )
     var obfuscate: Boolean = false
-
-    @CommandLine.Option(
-        names = ["--available-devices"],
-        description = ["Print current list of devices available to test against"])
-    var printAvailableDevices: Boolean = false
-
-    @CommandLine.Option(
-        names = ["--available-software-versions"],
-        description = ["Print current list of OS versions available to test against"])
-    var printAvailableSoftwareVersions: Boolean = false
-
-    @CommandLine.Option(
-        names = ["--available-environment"],
-        description = ["Print available devices and OS versions list to test against"])
-    var printAvailableEnvironment: Boolean = false
 }
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/IosCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/IosCommand.kt
index 87f95254e7..cdb99d881a 100644
--- a/test_runner/src/main/kotlin/ftl/cli/firebase/test/IosCommand.kt
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/IosCommand.kt
@@ -2,6 +2,9 @@ package ftl.cli.firebase.test
 
 import ftl.cli.firebase.test.ios.IosDoctorCommand
 import ftl.cli.firebase.test.ios.IosRunCommand
+import ftl.cli.firebase.test.ios.IosTestEnvironmentCommand
+import ftl.cli.firebase.test.ios.models.IosModelsCommand
+import ftl.cli.firebase.test.ios.versions.IosVersionsCommand
 import picocli.CommandLine
 import picocli.CommandLine.Command
 
@@ -10,7 +13,10 @@ import picocli.CommandLine.Command
     synopsisHeading = "",
     subcommands = [
         IosRunCommand::class,
-        IosDoctorCommand::class
+        IosDoctorCommand::class,
+        IosModelsCommand::class,
+        IosVersionsCommand::class,
+        IosTestEnvironmentCommand::class
     ],
     usageHelpAutoWidth = true
 )
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidDoctorCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidDoctorCommand.kt
index 4b1ba592da..372ce3c4b3 100644
--- a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidDoctorCommand.kt
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidDoctorCommand.kt
@@ -2,6 +2,7 @@ package ftl.cli.firebase.test.android
 
 import ftl.args.AndroidArgs
 import ftl.cli.firebase.test.processValidation
+import ftl.config.FtlConstants
 import ftl.doctor.Doctor.validateYaml
 import java.nio.file.Paths
 import picocli.CommandLine.Command
@@ -28,7 +29,7 @@ class AndroidDoctorCommand : Runnable {
     }
 
     @Option(names = ["-c", "--config"], description = ["YAML config file path"])
-    var configPath: String = "./flank.yml"
+    var configPath: String = FtlConstants.defaultAndroidConfig
 
     @Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
     var usageHelpRequested: Boolean = false
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidRunCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidRunCommand.kt
index 648e3141d4..395c9c1da3 100644
--- a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidRunCommand.kt
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidRunCommand.kt
@@ -1,7 +1,5 @@
 package ftl.cli.firebase.test.android
 
-import ftl.android.AndroidCatalog.devicesCatalogAsTable
-import ftl.android.AndroidCatalog.supportedVersionsAsTable
 import ftl.args.AndroidArgs
 import ftl.cli.firebase.test.CommonRunCommand
 import ftl.config.FtlConstants
@@ -48,16 +46,9 @@ class AndroidRunCommand : CommonRunCommand(), Runnable {
         val config = AndroidArgs.load(Paths.get(configPath), cli = this)
 
         runBlocking {
-            when {
-                dumpShards -> dumpShards(args = config, obfuscatedOutput = obfuscate)
-                printAvailableEnvironment -> {
-                    println(devicesCatalogAsTable(config.project))
-                    println(supportedVersionsAsTable(config.project))
-                }
-                printAvailableDevices -> println(devicesCatalogAsTable(config.project))
-                printAvailableSoftwareVersions -> println(supportedVersionsAsTable(config.project))
-                else -> newTestRun(config)
-            }
+            if (dumpShards)
+                dumpShards(args = config, obfuscatedOutput = obfuscate) else
+                newTestRun(config)
         }
     }
 
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidTestEnvironmentCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidTestEnvironmentCommand.kt
new file mode 100644
index 0000000000..bb72c77a4c
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/AndroidTestEnvironmentCommand.kt
@@ -0,0 +1,32 @@
+package ftl.cli.firebase.test.android
+
+import ftl.android.AndroidCatalog.devicesCatalogAsTable
+import ftl.android.AndroidCatalog.supportedVersionsAsTable
+import ftl.args.AndroidArgs
+import ftl.config.FtlConstants
+import picocli.CommandLine
+import java.nio.file.Paths
+
+@CommandLine.Command(
+    name = "test-environment",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Print available devices and OS versions list to test against"],
+    description = ["Print available Android devices and Android OS versions list to test against"],
+    usageHelpAutoWidth = true
+)
+class AndroidTestEnvironmentCommand : Runnable {
+    override fun run() {
+        println(devicesCatalogAsTable(AndroidArgs.load(Paths.get(configPath)).project))
+        println(supportedVersionsAsTable(AndroidArgs.load(Paths.get(configPath)).project))
+    }
+
+    @CommandLine.Option(names = ["-c", "--config"], description = ["YAML config file path"])
+    var configPath: String = FtlConstants.defaultAndroidConfig
+
+    @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
+    var usageHelpRequested: Boolean = false
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsCommand.kt
new file mode 100644
index 0000000000..f44b23e851
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsCommand.kt
@@ -0,0 +1,21 @@
+package ftl.cli.firebase.test.android.models
+
+import picocli.CommandLine
+
+@CommandLine.Command(
+    name = "models",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Information about available models"],
+    description = ["Information about available models. For example prints list of available models to test against"],
+    subcommands = [AndroidModelsListCommand::class],
+    usageHelpAutoWidth = true
+)
+class AndroidModelsCommand : Runnable {
+    override fun run() {
+        CommandLine.usage(AndroidModelsCommand(), System.out)
+    }
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsListCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsListCommand.kt
new file mode 100644
index 0000000000..3ece9df415
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsListCommand.kt
@@ -0,0 +1,31 @@
+package ftl.cli.firebase.test.android.models
+
+import ftl.android.AndroidCatalog
+import ftl.args.AndroidArgs
+import ftl.config.FtlConstants
+import picocli.CommandLine
+import java.nio.file.Paths
+
+@CommandLine.Command(
+    name = "list",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Print current list of devices available to test against"],
+    description = ["Print current list of Android devices available to test against"],
+    usageHelpAutoWidth = true
+)
+class AndroidModelsListCommand : Runnable {
+    override fun run() {
+        val config = AndroidArgs.load(Paths.get(configPath))
+        println(AndroidCatalog.devicesCatalogAsTable(config.project))
+    }
+
+    @CommandLine.Option(names = ["-c", "--config"], description = ["YAML config file path"])
+    var configPath: String = FtlConstants.defaultAndroidConfig
+
+    @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
+    var usageHelpRequested: Boolean = false
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsCommand.kt
new file mode 100644
index 0000000000..efc3c7e0a2
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsCommand.kt
@@ -0,0 +1,21 @@
+package ftl.cli.firebase.test.android.versions
+
+import picocli.CommandLine
+
+@CommandLine.Command(
+    name = "versions",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Information about available software versions"],
+    description = ["Information about available software versions. For example prints list of available software versions"],
+    subcommands = [AndroidVersionsListCommand::class],
+    usageHelpAutoWidth = true
+)
+class AndroidVersionsCommand : Runnable {
+    override fun run() {
+        CommandLine.usage(AndroidVersionsCommand(), System.out)
+    }
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsListCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsListCommand.kt
new file mode 100644
index 0000000000..b596788699
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsListCommand.kt
@@ -0,0 +1,30 @@
+package ftl.cli.firebase.test.android.versions
+
+import ftl.android.AndroidCatalog.supportedVersionsAsTable
+import ftl.args.AndroidArgs
+import ftl.config.FtlConstants
+import picocli.CommandLine
+import java.nio.file.Paths
+
+@CommandLine.Command(
+    name = "list",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["List of OS versions available to test against"],
+    description = ["Print current list of Android OS versions available to test against"],
+    usageHelpAutoWidth = true
+)
+class AndroidVersionsListCommand : Runnable {
+    override fun run() {
+        println(supportedVersionsAsTable(AndroidArgs.load(Paths.get(configPath)).project))
+    }
+
+    @CommandLine.Option(names = ["-c", "--config"], description = ["YAML config file path"])
+    var configPath: String = FtlConstants.defaultAndroidConfig
+
+    @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
+    var usageHelpRequested: Boolean = false
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosDoctorCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosDoctorCommand.kt
index ab706f8145..c15856f7a0 100644
--- a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosDoctorCommand.kt
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosDoctorCommand.kt
@@ -2,6 +2,7 @@ package ftl.cli.firebase.test.ios
 
 import ftl.args.IosArgs
 import ftl.cli.firebase.test.processValidation
+import ftl.config.FtlConstants
 import ftl.doctor.Doctor.validateYaml
 import java.nio.file.Paths
 import picocli.CommandLine.Command
@@ -28,7 +29,7 @@ class IosDoctorCommand : Runnable {
     }
 
     @Option(names = ["-c", "--config"], description = ["YAML config file path"])
-    var configPath: String = "./flank.ios.yml"
+    var configPath: String = FtlConstants.defaultIosConfig
 
     @Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
     var usageHelpRequested: Boolean = false
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosRunCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosRunCommand.kt
index 2ead8df68c..8c109c1926 100644
--- a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosRunCommand.kt
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosRunCommand.kt
@@ -4,8 +4,6 @@ import ftl.args.IosArgs
 import ftl.cli.firebase.test.CommonRunCommand
 import ftl.config.FtlConstants
 import ftl.config.emptyIosConfig
-import ftl.ios.IosCatalog.devicesCatalogAsTable
-import ftl.ios.IosCatalog.softwareVersionsAsTable
 import ftl.mock.MockServer
 import ftl.run.IOS_SHARD_FILE
 import ftl.run.dumpShards
@@ -47,15 +45,10 @@ class IosRunCommand : CommonRunCommand(), Runnable {
 
         val config = IosArgs.load(Paths.get(configPath), cli = this)
 
-        when {
-            dumpShards -> dumpShards(args = config, obfuscatedOutput = obfuscate)
-            printAvailableEnvironment -> {
-                println(devicesCatalogAsTable(config.project))
-                println(softwareVersionsAsTable(config.project))
-            }
-            printAvailableDevices -> println(devicesCatalogAsTable(config.project))
-            printAvailableSoftwareVersions -> println(softwareVersionsAsTable(config.project))
-            else -> runBlocking { newTestRun(config) }
+        if (dumpShards) {
+            dumpShards(args = config, obfuscatedOutput = obfuscate)
+        } else runBlocking {
+            newTestRun(config)
         }
     }
 
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosTestEnvironmentCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosTestEnvironmentCommand.kt
new file mode 100644
index 0000000000..1c68c4099e
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/IosTestEnvironmentCommand.kt
@@ -0,0 +1,32 @@
+package ftl.cli.firebase.test.ios
+
+import ftl.args.IosArgs
+import ftl.config.FtlConstants
+import ftl.ios.IosCatalog.devicesCatalogAsTable
+import ftl.ios.IosCatalog.softwareVersionsAsTable
+import picocli.CommandLine
+import java.nio.file.Paths
+
+@CommandLine.Command(
+    name = "test-environment",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Print available devices and OS versions list to test against"],
+    description = ["Print available iOS devices and iOS versions list to test against"],
+    usageHelpAutoWidth = true
+)
+class IosTestEnvironmentCommand : Runnable {
+    override fun run() {
+        println(devicesCatalogAsTable(IosArgs.load(Paths.get(configPath)).project))
+        println(softwareVersionsAsTable(IosArgs.load(Paths.get(configPath)).project))
+    }
+
+    @CommandLine.Option(names = ["-c", "--config"], description = ["YAML config file path"])
+    var configPath: String = FtlConstants.defaultIosConfig
+
+    @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
+    var usageHelpRequested: Boolean = false
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/models/IosModelsCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/models/IosModelsCommand.kt
new file mode 100644
index 0000000000..63a56d474d
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/models/IosModelsCommand.kt
@@ -0,0 +1,21 @@
+package ftl.cli.firebase.test.ios.models
+
+import picocli.CommandLine
+
+@CommandLine.Command(
+    name = "models",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Information about available models"],
+    description = ["Information about available models. For example prints list of available models to test against"],
+    subcommands = [IosModelsListCommand::class],
+    usageHelpAutoWidth = true
+)
+class IosModelsCommand : Runnable {
+    override fun run() {
+        CommandLine.usage(IosModelsCommand(), System.out)
+    }
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/models/IosModelsListCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/models/IosModelsListCommand.kt
new file mode 100644
index 0000000000..4daf580ab9
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/models/IosModelsListCommand.kt
@@ -0,0 +1,30 @@
+package ftl.cli.firebase.test.ios.models
+
+import ftl.args.IosArgs
+import ftl.config.FtlConstants
+import ftl.ios.IosCatalog.devicesCatalogAsTable
+import picocli.CommandLine
+import java.nio.file.Paths
+
+@CommandLine.Command(
+    name = "list",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Print current list of devices available to test against"],
+    description = ["Print current list of iOS devices available to test against"],
+    usageHelpAutoWidth = true
+)
+class IosModelsListCommand : Runnable {
+    override fun run() {
+        println(devicesCatalogAsTable(IosArgs.load(Paths.get(configPath)).project))
+    }
+
+    @CommandLine.Option(names = ["-c", "--config"], description = ["YAML config file path"])
+    var configPath: String = FtlConstants.defaultIosConfig
+
+    @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
+    var usageHelpRequested: Boolean = false
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsCommand.kt
new file mode 100644
index 0000000000..219e246943
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsCommand.kt
@@ -0,0 +1,21 @@
+package ftl.cli.firebase.test.ios.versions
+
+import picocli.CommandLine
+
+@CommandLine.Command(
+    name = "versions",
+    subcommands = [IosVersionsListCommand::class],
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["Information about available software versions"],
+    description = ["Information about available software versions. For example prints list of available software versions"],
+    usageHelpAutoWidth = true
+)
+class IosVersionsCommand : Runnable {
+    override fun run() {
+        CommandLine.usage(IosVersionsCommand(), System.out)
+    }
+}
diff --git a/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsListCommand.kt b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsListCommand.kt
new file mode 100644
index 0000000000..6a37fd6ddc
--- /dev/null
+++ b/test_runner/src/main/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsListCommand.kt
@@ -0,0 +1,30 @@
+package ftl.cli.firebase.test.ios.versions
+
+import ftl.args.IosArgs
+import ftl.config.FtlConstants
+import ftl.ios.IosCatalog.softwareVersionsAsTable
+import picocli.CommandLine
+import java.nio.file.Paths
+
+@CommandLine.Command(
+    name = "list",
+    headerHeading = "",
+    synopsisHeading = "%n",
+    descriptionHeading = "%n@|bold,underline Description:|@%n%n",
+    parameterListHeading = "%n@|bold,underline Parameters:|@%n",
+    optionListHeading = "%n@|bold,underline Options:|@%n",
+    header = ["List of OS versions available to test against"],
+    description = ["Print current list of iOS versions available to test against"],
+    usageHelpAutoWidth = true
+)
+class IosVersionsListCommand : Runnable {
+    override fun run() {
+        println(softwareVersionsAsTable(IosArgs.load(Paths.get(configPath)).project))
+    }
+
+    @CommandLine.Option(names = ["-c", "--config"], description = ["YAML config file path"])
+    var configPath: String = FtlConstants.defaultIosConfig
+
+    @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Prints this help message"])
+    var usageHelpRequested: Boolean = false
+}
diff --git a/test_runner/src/test/kotlin/Debug.kt b/test_runner/src/test/kotlin/Debug.kt
index a7ad6ba9d8..d87dbd4904 100644
--- a/test_runner/src/test/kotlin/Debug.kt
+++ b/test_runner/src/test/kotlin/Debug.kt
@@ -20,9 +20,6 @@ fun main() {
 //            "--debug",
             "firebase", "test",
             "android", "run",
-//            "--available-devices",
-//            "--available-software-versions",
-//            "--available-environment",
 //            "--dry",
 //            "--dump-shards",
             "--output-style=single",
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/AndroidCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/AndroidCommandTest.kt
index 08420e16d9..c2a4df7658 100644
--- a/test_runner/src/test/kotlin/ftl/cli/firebase/test/AndroidCommandTest.kt
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/AndroidCommandTest.kt
@@ -20,9 +20,12 @@ class AndroidCommandTest {
         val output = systemOutRule.log.normalizeLineEnding()
         Truth.assertThat(output).startsWith(
             "android [COMMAND]\n" +
-                "Commands:\n" +
-                "  run     Run tests on Firebase Test Lab\n" +
-                "  doctor  Verifies flank firebase is setup correctly\n"
+                    "Commands:\n" +
+                    "  run               Run tests on Firebase Test Lab\n" +
+                    "  doctor            Verifies flank firebase is setup correctly\n" +
+                    "  models            Information about available models\n" +
+                    "  versions          Information about available software versions\n" +
+                    "  test-environment  Print available devices and OS versions list to test against\n"
         )
     }
 }
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/IosCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/IosCommandTest.kt
index d6d3554d76..f14347a170 100644
--- a/test_runner/src/test/kotlin/ftl/cli/firebase/test/IosCommandTest.kt
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/IosCommandTest.kt
@@ -20,9 +20,12 @@ class IosCommandTest {
         val output = systemOutRule.log.normalizeLineEnding()
         Truth.assertThat(output).startsWith(
             "ios [COMMAND]\n" +
-                "Commands:\n" +
-                "  run     Run tests on Firebase Test Lab\n" +
-                "  doctor  Verifies flank firebase is setup correctly\n"
+                    "Commands:\n" +
+                    "  run               Run tests on Firebase Test Lab\n" +
+                    "  doctor            Verifies flank firebase is setup correctly\n" +
+                    "  models            Information about available models\n" +
+                    "  versions          Information about available software versions\n" +
+                    "  test-environment  Print available devices and OS versions list to test against\n"
         )
     }
 }
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt
index 8bfd190438..0cd8459736 100644
--- a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt
@@ -467,28 +467,4 @@ class AndroidRunCommandTest {
 
         assertThat(cmd.obfuscate).isTrue()
     }
-
-    @Test
-    fun `available devices parse`() {
-        val cmd = AndroidRunCommand()
-        CommandLine(cmd).parseArgs("--available-devices")
-
-        assertThat(cmd.printAvailableDevices).isTrue()
-    }
-
-    @Test
-    fun `available software versions parse`() {
-        val cmd = AndroidRunCommand()
-        CommandLine(cmd).parseArgs("--available-software-versions")
-
-        assertThat(cmd.printAvailableSoftwareVersions).isTrue()
-    }
-
-    @Test
-    fun `available environment parse`() {
-        val cmd = AndroidRunCommand()
-        CommandLine(cmd).parseArgs("--available-environment")
-
-        assertThat(cmd.printAvailableEnvironment).isTrue()
-    }
 }
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidTestEnvironmentCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidTestEnvironmentCommandTest.kt
new file mode 100644
index 0000000000..1efec7a2a9
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidTestEnvironmentCommandTest.kt
@@ -0,0 +1,61 @@
+package ftl.cli.firebase.test.android
+
+import com.google.common.truth.Truth.assertThat
+import ftl.config.FtlConstants
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+import picocli.CommandLine
+
+class AndroidTestEnvironmentCommandTest {
+
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun androidTestEnvironmentCommandPrintsHelp() {
+        val modelsListCommand = AndroidTestEnvironmentCommand()
+        assertThat(modelsListCommand.usageHelpRequested).isFalse()
+        CommandLine(modelsListCommand).execute("-h")
+
+        val output = systemOutRule.log.normalizeLineEnding()
+        assertThat(output).startsWith(
+            "Print available devices and OS versions list to test against\n" +
+                    "\n" +
+                    "test-environment [-h] [-c=<configPath>]\n" +
+                    "\n" +
+                    "Description:\n" +
+                    "\n" +
+                    "Print available Android devices and Android OS versions list to test against\n" +
+                    "\n" +
+                    "Options:\n" +
+                    "  -c, --config=<configPath>\n" +
+                    "               YAML config file path\n" +
+                    "  -h, --help   Prints this help message"
+        )
+
+        assertThat(modelsListCommand.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun androidTestEnvironmentCommandOptions() {
+        val cmd = AndroidTestEnvironmentCommand()
+        assertThat(cmd.configPath).isEqualTo(FtlConstants.defaultAndroidConfig)
+        cmd.configPath = "tmp"
+        assertThat(cmd.configPath).isEqualTo("tmp")
+
+        assertThat(cmd.usageHelpRequested).isFalse()
+        cmd.usageHelpRequested = true
+        assertThat(cmd.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun androidTestEnvironmentCommandShouldParseConfig() {
+        val cmd = AndroidTestEnvironmentCommand()
+        CommandLine(cmd).parseArgs("--config=a")
+
+        assertThat(cmd.configPath).isEqualTo("a")
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsCommandTest.kt
new file mode 100644
index 0000000000..a6a97f3037
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsCommandTest.kt
@@ -0,0 +1,28 @@
+package ftl.cli.firebase.test.android.models
+
+import com.google.common.truth.Truth
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+
+class AndroidModelsCommandTest {
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun androidModelsCommandPrintsHelp() {
+        AndroidModelsCommand().run()
+        val output = systemOutRule.log.normalizeLineEnding()
+        Truth.assertThat(output).startsWith(
+            "Information about available models\n\n" +
+            "models [COMMAND]\n\n" +
+            "Description:\n\n" +
+            "Information about available models. For example prints list of available models\n" +
+                    "to test against\n" +
+                    "Commands:\n" +
+                    "  list  Print current list of devices available to test against"
+        )
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsListCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsListCommandTest.kt
new file mode 100644
index 0000000000..f9d8ec2586
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/models/AndroidModelsListCommandTest.kt
@@ -0,0 +1,61 @@
+package ftl.cli.firebase.test.android.models
+
+import com.google.common.truth.Truth.assertThat
+import ftl.config.FtlConstants
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+import picocli.CommandLine
+
+class AndroidModelsListCommandTest {
+
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun androidModelsListCommandPrintsHelp() {
+        val modelsListCommand = AndroidModelsListCommand()
+        assertThat(modelsListCommand.usageHelpRequested).isFalse()
+        CommandLine(modelsListCommand).execute("-h")
+
+        val output = systemOutRule.log.normalizeLineEnding()
+        assertThat(output).startsWith(
+            "Print current list of devices available to test against\n" +
+                    "\n" +
+                    "list [-h] [-c=<configPath>]\n" +
+                    "\n" +
+                    "Description:\n" +
+                    "\n" +
+                    "Print current list of Android devices available to test against\n" +
+                    "\n" +
+                    "Options:\n" +
+                    "  -c, --config=<configPath>\n" +
+                    "               YAML config file path\n" +
+                    "  -h, --help   Prints this help message"
+        )
+
+        assertThat(modelsListCommand.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun androidModelsListCommandOptions() {
+        val cmd = AndroidModelsListCommand()
+        assertThat(cmd.configPath).isEqualTo(FtlConstants.defaultAndroidConfig)
+        cmd.configPath = "tmp"
+        assertThat(cmd.configPath).isEqualTo("tmp")
+
+        assertThat(cmd.usageHelpRequested).isFalse()
+        cmd.usageHelpRequested = true
+        assertThat(cmd.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun androidModelsListCommandShouldParseConfig() {
+        val cmd = AndroidModelsListCommand()
+        CommandLine(cmd).parseArgs("--config=a")
+
+        assertThat(cmd.configPath).isEqualTo("a")
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsCommandTest.kt
new file mode 100644
index 0000000000..77d2af412c
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsCommandTest.kt
@@ -0,0 +1,28 @@
+package ftl.cli.firebase.test.android.versions
+
+import com.google.common.truth.Truth
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+
+class AndroidVersionsCommandTest {
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun iosModelsCommandPrintsHelp() {
+        AndroidVersionsCommand().run()
+        val output = systemOutRule.log.normalizeLineEnding()
+        Truth.assertThat(output).startsWith(
+            "Information about available software versions\n\n" +
+                    "versions [COMMAND]\n\n" +
+                    "Description:\n\n" +
+                    "Information about available software versions. For example prints list of\n" +
+                    "available software versions\n" +
+                    "Commands:\n" +
+                    "  list  List of OS versions available to test against"
+        )
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsListCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsListCommandTest.kt
new file mode 100644
index 0000000000..61e8f154c8
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/versions/AndroidVersionsListCommandTest.kt
@@ -0,0 +1,61 @@
+package ftl.cli.firebase.test.android.versions
+
+import com.google.common.truth.Truth.assertThat
+import ftl.config.FtlConstants
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+import picocli.CommandLine
+
+class AndroidVersionsListCommandTest {
+
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun androidVersionsListCommandPrintsHelp() {
+        val modelsListCommand = AndroidVersionsListCommand()
+        assertThat(modelsListCommand.usageHelpRequested).isFalse()
+        CommandLine(modelsListCommand).execute("-h")
+
+        val output = systemOutRule.log.normalizeLineEnding()
+        assertThat(output).startsWith(
+            "List of OS versions available to test against\n" +
+                    "\n" +
+                    "list [-h] [-c=<configPath>]\n" +
+                    "\n" +
+                    "Description:\n" +
+                    "\n" +
+                    "Print current list of Android OS versions available to test against\n" +
+                    "\n" +
+                    "Options:\n" +
+                    "  -c, --config=<configPath>\n" +
+                    "               YAML config file path\n" +
+                    "  -h, --help   Prints this help message"
+        )
+
+        assertThat(modelsListCommand.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun androidVersionsListCommandOptions() {
+        val cmd = AndroidVersionsListCommand()
+        assertThat(cmd.configPath).isEqualTo(FtlConstants.defaultAndroidConfig)
+        cmd.configPath = "tmp"
+        assertThat(cmd.configPath).isEqualTo("tmp")
+
+        assertThat(cmd.usageHelpRequested).isFalse()
+        cmd.usageHelpRequested = true
+        assertThat(cmd.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun androidVersionsListCommandShouldParseConfig() {
+        val cmd = AndroidVersionsListCommand()
+        CommandLine(cmd).parseArgs("--config=a")
+
+        assertThat(cmd.configPath).isEqualTo("a")
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt
index af995d4239..b14f7dacb0 100644
--- a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt
@@ -326,28 +326,4 @@ class IosRunCommandTest {
 
         assertThat(cmd.obfuscate).isTrue()
     }
-
-    @Test
-    fun `available devices parse`() {
-        val cmd = IosRunCommand()
-        CommandLine(cmd).parseArgs("--available-devices")
-
-        assertThat(cmd.printAvailableDevices).isTrue()
-    }
-
-    @Test
-    fun `available software versions parse`() {
-        val cmd = IosRunCommand()
-        CommandLine(cmd).parseArgs("--available-software-versions")
-
-        assertThat(cmd.printAvailableSoftwareVersions).isTrue()
-    }
-
-    @Test
-    fun `available environment parse`() {
-        val cmd = IosRunCommand()
-        CommandLine(cmd).parseArgs("--available-environment")
-
-        assertThat(cmd.printAvailableEnvironment).isTrue()
-    }
 }
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosTestEnvironmentCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosTestEnvironmentCommandTest.kt
new file mode 100644
index 0000000000..f11b31a5e3
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosTestEnvironmentCommandTest.kt
@@ -0,0 +1,61 @@
+package ftl.cli.firebase.test.ios
+
+import com.google.common.truth.Truth
+import ftl.config.FtlConstants
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+import picocli.CommandLine
+
+class IosTestEnvironmentCommandTest {
+
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun iosTestEnvironmentCommandPrintsHelp() {
+        val modelsListCommand = IosTestEnvironmentCommand()
+        Truth.assertThat(modelsListCommand.usageHelpRequested).isFalse()
+        CommandLine(modelsListCommand).execute("-h")
+
+        val output = systemOutRule.log.normalizeLineEnding()
+        Truth.assertThat(output).startsWith(
+            "Print available devices and OS versions list to test against\n" +
+                    "\n" +
+                    "test-environment [-h] [-c=<configPath>]\n" +
+                    "\n" +
+                    "Description:\n" +
+                    "\n" +
+                    "Print available iOS devices and iOS versions list to test against\n" +
+                    "\n" +
+                    "Options:\n" +
+                    "  -c, --config=<configPath>\n" +
+                    "               YAML config file path\n" +
+                    "  -h, --help   Prints this help message"
+        )
+
+        Truth.assertThat(modelsListCommand.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun iosTestEnvironmentCommandOptions() {
+        val cmd = IosTestEnvironmentCommand()
+        Truth.assertThat(cmd.configPath).isEqualTo(FtlConstants.defaultIosConfig)
+        cmd.configPath = "tmp"
+        Truth.assertThat(cmd.configPath).isEqualTo("tmp")
+
+        Truth.assertThat(cmd.usageHelpRequested).isFalse()
+        cmd.usageHelpRequested = true
+        Truth.assertThat(cmd.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun iosTestEnvironmentCommandShouldParseConfig() {
+        val cmd = IosTestEnvironmentCommand()
+        CommandLine(cmd).parseArgs("--config=a")
+
+        Truth.assertThat(cmd.configPath).isEqualTo("a")
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/models/IosModelsCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/models/IosModelsCommandTest.kt
new file mode 100644
index 0000000000..db17f447f8
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/models/IosModelsCommandTest.kt
@@ -0,0 +1,28 @@
+package ftl.cli.firebase.test.ios.models
+
+import com.google.common.truth.Truth
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+
+class IosModelsCommandTest {
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun iosModelsCommandPrintsHelp() {
+        IosModelsCommand().run()
+        val output = systemOutRule.log.normalizeLineEnding()
+        Truth.assertThat(output).startsWith(
+            "Information about available models\n\n" +
+                    "models [COMMAND]\n\n" +
+                    "Description:\n\n" +
+                    "Information about available models. For example prints list of available models\n" +
+                    "to test against\n" +
+                    "Commands:\n" +
+                    "  list  Print current list of devices available to test against"
+        )
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/models/IosModelsListCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/models/IosModelsListCommandTest.kt
new file mode 100644
index 0000000000..12f5cc297b
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/models/IosModelsListCommandTest.kt
@@ -0,0 +1,61 @@
+package ftl.cli.firebase.test.ios.models
+
+import com.google.common.truth.Truth.assertThat
+import ftl.config.FtlConstants
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+import picocli.CommandLine
+
+class IosModelsListCommandTest {
+
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun iosModelsListCommandPrintsHelp() {
+        val modelsListCommand = IosModelsListCommand()
+        assertThat(modelsListCommand.usageHelpRequested).isFalse()
+        CommandLine(modelsListCommand).execute("-h")
+
+        val output = systemOutRule.log.normalizeLineEnding()
+        assertThat(output).startsWith(
+            "Print current list of devices available to test against\n" +
+                    "\n" +
+                    "list [-h] [-c=<configPath>]\n" +
+                    "\n" +
+                    "Description:\n" +
+                    "\n" +
+                    "Print current list of iOS devices available to test against\n" +
+                    "\n" +
+                    "Options:\n" +
+                    "  -c, --config=<configPath>\n" +
+                    "               YAML config file path\n" +
+                    "  -h, --help   Prints this help message"
+        )
+
+        assertThat(modelsListCommand.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun iosModelsListCommandOptions() {
+        val cmd = IosModelsListCommand()
+        assertThat(cmd.configPath).isEqualTo(FtlConstants.defaultIosConfig)
+        cmd.configPath = "tmp"
+        assertThat(cmd.configPath).isEqualTo("tmp")
+
+        assertThat(cmd.usageHelpRequested).isFalse()
+        cmd.usageHelpRequested = true
+        assertThat(cmd.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun iosModelsListCommandShouldParseConfig() {
+        val cmd = IosModelsListCommand()
+        CommandLine(cmd).parseArgs("--config=a")
+
+        assertThat(cmd.configPath).isEqualTo("a")
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsCommandTest.kt
new file mode 100644
index 0000000000..124057be83
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsCommandTest.kt
@@ -0,0 +1,28 @@
+package ftl.cli.firebase.test.ios.versions
+
+import com.google.common.truth.Truth
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+
+class IosVersionsCommandTest {
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun iosModelsCommandPrintsHelp() {
+        IosVersionsCommand().run()
+        val output = systemOutRule.log.normalizeLineEnding()
+        Truth.assertThat(output).startsWith(
+            "Information about available software versions\n\n" +
+                    "versions [COMMAND]\n\n" +
+                    "Description:\n\n" +
+                    "Information about available software versions. For example prints list of\n" +
+                    "available software versions\n" +
+                    "Commands:\n" +
+                    "  list  List of OS versions available to test against"
+        )
+    }
+}
diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsListCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsListCommandTest.kt
new file mode 100644
index 0000000000..0604dd2055
--- /dev/null
+++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/versions/IosVersionsListCommandTest.kt
@@ -0,0 +1,61 @@
+package ftl.cli.firebase.test.ios.versions
+
+import com.google.common.truth.Truth.assertThat
+import ftl.config.FtlConstants
+import ftl.test.util.TestHelper.normalizeLineEnding
+import org.junit.Rule
+import org.junit.Test
+import org.junit.contrib.java.lang.system.SystemOutRule
+import picocli.CommandLine
+
+class IosVersionsListCommandTest {
+
+    @Rule
+    @JvmField
+    val systemOutRule: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests()
+
+    @Test
+    fun iosVersionsListCommandPrintsHelp() {
+        val modelsListCommand = IosVersionsListCommand()
+        assertThat(modelsListCommand.usageHelpRequested).isFalse()
+        CommandLine(modelsListCommand).execute("-h")
+
+        val output = systemOutRule.log.normalizeLineEnding()
+        assertThat(output).startsWith(
+            "List of OS versions available to test against\n" +
+                    "\n" +
+                    "list [-h] [-c=<configPath>]\n" +
+                    "\n" +
+                    "Description:\n" +
+                    "\n" +
+                    "Print current list of iOS versions available to test against\n" +
+                    "\n" +
+                    "Options:\n" +
+                    "  -c, --config=<configPath>\n" +
+                    "               YAML config file path\n" +
+                    "  -h, --help   Prints this help message"
+        )
+
+        assertThat(modelsListCommand.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun iosVersionsListCommandOptions() {
+        val cmd = IosVersionsListCommand()
+        assertThat(cmd.configPath).isEqualTo(FtlConstants.defaultIosConfig)
+        cmd.configPath = "tmp"
+        assertThat(cmd.configPath).isEqualTo("tmp")
+
+        assertThat(cmd.usageHelpRequested).isFalse()
+        cmd.usageHelpRequested = true
+        assertThat(cmd.usageHelpRequested).isTrue()
+    }
+
+    @Test
+    fun iosVersionsListCommandShouldParseConfig() {
+        val cmd = IosVersionsListCommand()
+        CommandLine(cmd).parseArgs("--config=a")
+
+        assertThat(cmd.configPath).isEqualTo("a")
+    }
+}