From b29a3dbcb3c21851eeddacc0358e449ba1eaf3ab Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Fri, 28 Aug 2026 13:07:57 +0100 Subject: [PATCH] Add interactive local E2E selection Assisted-By: devx/5f10c07c-0803-4f05-bc88-265174164e5a --- dev.yml | 66 +++++++++++++++++++++++++----- e2e/README.md | 5 ++- e2e/scripts/run_local_e2e | 75 ++++++++++++++++++++++++++++++++++ e2e/test/run_local_e2e_test.rb | 53 ++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 13 deletions(-) diff --git a/dev.yml b/dev.yml index 5145628b5..8a02d20fa 100644 --- a/dev.yml +++ b/dev.yml @@ -315,8 +315,18 @@ commands: run: platforms/android/samples/CheckoutKitAndroidDemo/gradlew -p platforms/android/samples/CheckoutKitAndroidDemo installDebug e2e: - desc: Run the Android sample Maestro tests, filtered by tag - syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" + desc: Run Android sample end-to-end tests + long_desc: | + Runs the Kotlin Android application's coverage from the E2E matrix. + With no arguments, opens a picker in an interactive terminal or runs + all enabled coverage noninteractively. Pass matrix tags to select a + group or positional YAML files to run exact tests. + + Examples: + dev android e2e + dev android e2e --tags preload + dev android e2e tests/shared/launch-smoke.yaml + syntax: "[TEST_FILE...] [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" run: ./e2e/scripts/run_local_e2e kotlin-android "$@" test: @@ -467,8 +477,18 @@ commands: ../Scripts/xcode_run build CheckoutKitSwiftDemo e2e: - desc: Run the Swift sample Maestro tests, filtered by tag - syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" + desc: Run Swift sample end-to-end tests + long_desc: | + Runs the Swift iOS application's coverage from the E2E matrix. + With no arguments, opens a picker in an interactive terminal or runs + all enabled coverage noninteractively. Pass matrix tags to select a + group or positional YAML files to run exact tests. + + Examples: + dev swift e2e + dev swift e2e --tags preload + dev swift e2e tests/shared/launch-smoke.yaml + syntax: "[TEST_FILE...] [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" run: ./e2e/scripts/run_local_e2e swift-ios "$@" test: @@ -630,19 +650,43 @@ commands: cd sample/android ./gradlew :shopify_checkout-kit-react-native:test --refresh-dependencies e2e: - desc: Run React Native sample Maestro tests, filtered by tag - syntax: "{ios|android} [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" + desc: Run React Native sample end-to-end tests + long_desc: | + Runs a React Native application's coverage from the E2E matrix. + Choose ios or android, then use the interactive picker, matrix tags, + or positional YAML files to select coverage. + syntax: "{ios|android} [TEST_FILE...] [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" run: | - echo "Usage: dev rn e2e {ios|android} [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" >&2 + echo "Usage: dev rn e2e {ios|android} [TEST_FILE...] [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" >&2 exit 1 subcommands: ios: - desc: Run the React Native iOS Maestro tests - syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" + desc: Run React Native iOS end-to-end tests + long_desc: | + Runs the React Native iOS application's coverage from the E2E matrix. + With no arguments, opens a picker in an interactive terminal or runs + all enabled coverage noninteractively. Pass matrix tags to select a + group or positional YAML files to run exact tests. + + Examples: + dev rn e2e ios + dev rn e2e ios --tags launch + dev rn e2e ios tests/shared/launch-smoke.yaml + syntax: "[TEST_FILE...] [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" run: ./e2e/scripts/run_local_e2e react-native-ios "$@" android: - desc: Run the React Native Android Maestro tests - syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" + desc: Run React Native Android end-to-end tests + long_desc: | + Runs the React Native Android application's coverage from the E2E matrix. + With no arguments, opens a picker in an interactive terminal or runs + all enabled coverage noninteractively. Pass matrix tags to select a + group or positional YAML files to run exact tests. + + Examples: + dev rn e2e android + dev rn e2e android --tags launch + dev rn e2e android tests/shared/launch-smoke.yaml + syntax: "[TEST_FILE...] [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" run: ./e2e/scripts/run_local_e2e react-native-android "$@" lint: desc: Run all React Native lint checks (Swift, module, sample) diff --git a/e2e/README.md b/e2e/README.md index 26a512179..02add4bbb 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -43,8 +43,9 @@ on. Then run the matching command from the repo root. | React Native Android | `dev rn e2e android` | Each command runs the tests enabled for its application in `config/matrix.yml`. -With no selectors, it runs all enabled tests. Narrow a run with enabled tags or -positional YAML files: +With no selectors, an interactive terminal opens an `fzf` picker for all enabled +tests, tag groups, or exact files. A noninteractive run selects all enabled tests. +Explicit tags or positional YAML files bypass the picker: ```bash dev swift e2e --tags checkout-presentation diff --git a/e2e/scripts/run_local_e2e b/e2e/scripts/run_local_e2e index 706d5aeb6..b767fcff7 100755 --- a/e2e/scripts/run_local_e2e +++ b/e2e/scripts/run_local_e2e @@ -284,6 +284,77 @@ select_test_files() { fi } +picker_entries() { + printf 'all\tall\tALL Run all enabled tests\n' + + local tag + while IFS= read -r tag; do + printf 'tag\t%s\tTAG %s\n' "$tag" "$tag" + done < <(printf '%s\n' "$ENABLED_TAGS" | tr ',' '\n') + + local test_file + for test_file in "${ELIGIBLE_TEST_FILES[@]}"; do + printf 'file\t%s\tFILE %s\n' "$test_file" "$test_file" + done +} + +interactive_terminal() { + [ -t 0 ] && [ -t 1 ] +} + +print_reproducible_command() { + printf '\nRunning:\n %s' "$DEV_COMMAND" + local test_file + for test_file in "${TEST_FILES[@]}"; do + printf ' %q' "$test_file" + done + [ "$HAS_EXPLICIT_TAGS" = true ] && printf ' --tags %q' "$INCLUDE_TAGS" + printf '\n\n' +} + +run_picker() { + command -v fzf >/dev/null 2>&1 || { + echo "fzf is required for interactive E2E selection. Run dev up and retry." >&2 + return 1 + } + + local selection + selection="$(picker_entries | fzf \ + --multi \ + --height 80% \ + --reverse \ + --delimiter $'\t' \ + --with-nth 3 \ + --prompt "$TARGET E2E > " \ + --header $'Tab: select multiple • Enter: run • Esc: cancel\nChoose tag groups, exact tests, or Run all')" || return $? + + INCLUDE_TAGS="" + TEST_FILES=() + local type + local value + local display + local selected_all=false + + while IFS=$'\t' read -r type value display; do + case "$type" in + all) selected_all=true ;; + tag) + INCLUDE_TAGS="${INCLUDE_TAGS:+${INCLUDE_TAGS},}${value}" + HAS_EXPLICIT_TAGS=true + ;; + file) TEST_FILES+=("$value") ;; + esac + done <<< "$selection" + + if [ "$selected_all" = true ]; then + INCLUDE_TAGS="$MATRIX_INCLUDE_TAGS" + HAS_EXPLICIT_TAGS=false + TEST_FILES=() + fi + + print_reproducible_command +} + metro_running() { curl --silent --fail http://localhost:8081/status | grep -q "packager-status:running" } @@ -407,6 +478,7 @@ main() { configure_target "$1" shift + SELECTOR_COUNT="$#" INCLUDE_TAGS="" EXCLUDE_TAGS="" @@ -423,6 +495,9 @@ main() { load_application_tags load_eligible_test_files load_enabled_tags + if [ "$SELECTOR_COUNT" -eq 0 ] && interactive_terminal; then + run_picker + fi validate_explicit_tags select_test_files diff --git a/e2e/test/run_local_e2e_test.rb b/e2e/test/run_local_e2e_test.rb index ece8c469f..b9a4e1138 100644 --- a/e2e/test/run_local_e2e_test.rb +++ b/e2e/test/run_local_e2e_test.rb @@ -163,6 +163,39 @@ def test_positional_selection_bypasses_matrix_exclusions assert_equal ["tests/shared/quarantined.yaml"], output.lines.map(&:chomp) end + def test_picker_entries_have_compact_selectable_labels + output, error, status = picker_script("picker_entries") + + assert status.success?, error + assert_equal( + [ + "all\tall\tALL Run all enabled tests", + "tag\tpresentation\tTAG presentation", + "tag\tsmoke\tTAG smoke", + "tag\tpreload\tTAG preload", + "file\ttests/shared/presentation.yaml\tFILE tests/shared/presentation.yaml", + "file\ttests/swift/preload.yaml\tFILE tests/swift/preload.yaml" + ], + output.lines.map(&:chomp) + ) + end + + def test_picker_translates_tags_and_files_into_selectors + command = <<~'SH' + fzf() { + command cat >/dev/null + printf 'tag\tpreload\tTAG preload\n' + printf 'file\ttests/shared/presentation.yaml\tFILE tests/shared/presentation.yaml\n' + } + run_picker >/dev/null + printf '%s\n%s\n%s\n' "$HAS_EXPLICIT_TAGS" "$INCLUDE_TAGS" "${TEST_FILES[*]}" + SH + output, error, status = picker_script(command) + + assert status.success?, error + assert_equal ["true", "preload", "tests/shared/presentation.yaml"], output.lines.map(&:chomp) + end + def test_maestro_runs_shared_and_target_specific_test_files react_native_arguments = maestro_arguments("react-native") swift_arguments = maestro_arguments("swift") @@ -199,6 +232,26 @@ def test_target_specific_and_old_named_runners_are_removed private + def picker_script(command) + Open3.capture3( + "bash", + "-c", + <<~SH, + source "$1" + configure_target swift-ios + MATRIX_INCLUDE_TAGS="presentation,preload" + ENABLED_TAGS="presentation,smoke,preload" + ELIGIBLE_TEST_FILES=(tests/shared/presentation.yaml tests/swift/preload.yaml) + INCLUDE_TAGS="" + HAS_EXPLICIT_TAGS=false + TEST_FILES=() + #{command} + SH + "run-local-e2e-test", + RUNNER + ) + end + def local_selection(*arguments) Open3.capture3( "bash",