From abc98c8fa8cb9b23e977d52bc45c74bd34585617 Mon Sep 17 00:00:00 2001 From: Trey T Date: Fri, 5 Jun 2026 16:35:52 -0500 Subject: [PATCH] Add standalone HoneyDueAPITests target for pure-API suites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the pure-API integration tests (no UI) out of the UITest target into a dedicated standalone unit-test target that runs in seconds without launching the simulator app. - HoneyDueAPITests target: standalone unit-test bundle (no TEST_HOST — touches no app code), shares the API client/seeder/cleaner support files from the UITest target via explicit references, with its own shared scheme. - MultiUserSharingTests -> HoneyDueAPITests/SharingAPITests.swift (18 tests). Runs in ~2.3s vs. ~40-140s per UI test. - run_ui_tests.sh: new Phase 1b runs the API target (fast) between Seed and the parallel UI phase; the helper now takes a scheme so each phase targets the right one; summary reports the API result. Both targets build green; SharingAPITests passes (18/18) against the live stack. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../SharingAPITests.swift} | 2 +- iosApp/honeyDue.xcodeproj/project.pbxproj | 114 ++++++++++++++++++ .../xcschemes/HoneyDueAPITests.xcscheme | 68 +++++++++++ iosApp/run_ui_tests.sh | 65 ++++++---- 4 files changed, 224 insertions(+), 25 deletions(-) rename iosApp/{HoneyDueUITests/Tests/MultiUserSharingTests.swift => HoneyDueAPITests/SharingAPITests.swift} (99%) create mode 100644 iosApp/honeyDue.xcodeproj/xcshareddata/xcschemes/HoneyDueAPITests.xcscheme diff --git a/iosApp/HoneyDueUITests/Tests/MultiUserSharingTests.swift b/iosApp/HoneyDueAPITests/SharingAPITests.swift similarity index 99% rename from iosApp/HoneyDueUITests/Tests/MultiUserSharingTests.swift rename to iosApp/HoneyDueAPITests/SharingAPITests.swift index 2dcccf2..db1ed8d 100644 --- a/iosApp/HoneyDueUITests/Tests/MultiUserSharingTests.swift +++ b/iosApp/HoneyDueAPITests/SharingAPITests.swift @@ -10,7 +10,7 @@ import XCTest /// /// These tests run entirely via API (no app launch needed for most steps) /// with a final UI verification that the shared residence and tasks appear. -final class MultiUserSharingTests: XCTestCase { +final class SharingAPITests: XCTestCase { private var userA: TestSession! private var userB: TestSession! diff --git a/iosApp/honeyDue.xcodeproj/project.pbxproj b/iosApp/honeyDue.xcodeproj/project.pbxproj index fbe496f..94e250f 100644 --- a/iosApp/honeyDue.xcodeproj/project.pbxproj +++ b/iosApp/honeyDue.xcodeproj/project.pbxproj @@ -16,6 +16,11 @@ 1C81F2892EE41BB6000739EA /* HoneyDueQLThumbnail.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1C81F2802EE41BB6000739EA /* HoneyDueQLThumbnail.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1C81F3902EE69AF1000739EA /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 1C81F38F2EE69AF1000739EA /* PostHog */; }; 36A43DA6D19BA51568EC55A5 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 6424E7E39866AD706041F321 /* SnapshotTesting */; }; + 53469EBEDD37557816983B6D /* SharingAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF8E25041D46376FEC29BE2 /* SharingAPITests.swift */; }; + 59A92CA8C3A412D8A18338C7 /* TestAccountAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70FEF27FDF4EFFACCE83F54 /* TestAccountAPIClient.swift */; }; + 91A9D5E4A93A022693888B95 /* TestDataSeeder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C51B2E73D6FB0BDB53123DDC /* TestDataSeeder.swift */; }; + 99FB08E574AA3B88AD73DEAC /* TestDataCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1579B80B44611651771CC51A /* TestDataCleaner.swift */; }; + BEF62D0EDC3E9B922195C7ED /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12403969C38C7CB74B1EA820 /* Foundation.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -73,6 +78,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 12403969C38C7CB74B1EA820 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 1579B80B44611651771CC51A /* TestDataCleaner.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TestDataCleaner.swift; path = HoneyDueUITests/Framework/TestDataCleaner.swift; sourceTree = ""; }; 1C07893D2EBC218B00392B46 /* HoneyDueExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HoneyDueExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 1C07893F2EBC218B00392B46 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 1C0789412EBC218B00392B46 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; @@ -86,7 +93,11 @@ 1CBF1BED2ECD9768001BF56C /* HoneyDueUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HoneyDueUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 4B07E04F794A4C1CAA8CCD5D /* PhotoViewerSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoViewerSheet.swift; sourceTree = ""; }; 96A3DDC05E14B3F83E56282F /* honeyDue.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = honeyDue.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A52A91DEA0ECFB45CBAAE168 /* HoneyDueAPITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HoneyDueAPITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; AD6CD907CA1045CBBC845D91 /* CompletionCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionCardView.swift; sourceTree = ""; }; + C51B2E73D6FB0BDB53123DDC /* TestDataSeeder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TestDataSeeder.swift; path = HoneyDueUITests/Framework/TestDataSeeder.swift; sourceTree = ""; }; + D70FEF27FDF4EFFACCE83F54 /* TestAccountAPIClient.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TestAccountAPIClient.swift; path = HoneyDueUITests/Framework/TestAccountAPIClient.swift; sourceTree = ""; }; + ECF8E25041D46376FEC29BE2 /* SharingAPITests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SharingAPITests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -268,6 +279,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 55A71EFD2C2AB71B02035D05 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BEF62D0EDC3E9B922195C7ED /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -278,6 +297,7 @@ 1C0789412EBC218B00392B46 /* SwiftUI.framework */, 1C81F26A2EE416EE000739EA /* QuickLook.framework */, 1C81F2812EE41BB6000739EA /* QuickLookThumbnailing.framework */, + F9901640A563803981701DD0 /* iOS */, ); name = Frameworks; sourceTree = ""; @@ -306,9 +326,30 @@ 1C07893E2EBC218B00392B46 /* Frameworks */, FA6022B7B844191C54E57EB4 /* Products */, 1C078A1B2EC1820B00392B46 /* Recovered References */, + E7D6E53AF0B8430440E6B3EE /* HoneyDueAPITests */, + D70FEF27FDF4EFFACCE83F54 /* TestAccountAPIClient.swift */, + C51B2E73D6FB0BDB53123DDC /* TestDataSeeder.swift */, + 1579B80B44611651771CC51A /* TestDataCleaner.swift */, ); sourceTree = ""; }; + E7D6E53AF0B8430440E6B3EE /* HoneyDueAPITests */ = { + isa = PBXGroup; + children = ( + ECF8E25041D46376FEC29BE2 /* SharingAPITests.swift */, + ); + name = HoneyDueAPITests; + path = HoneyDueAPITests; + sourceTree = ""; + }; + F9901640A563803981701DD0 /* iOS */ = { + isa = PBXGroup; + children = ( + 12403969C38C7CB74B1EA820 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; FA6022B7B844191C54E57EB4 /* Products */ = { isa = PBXGroup; children = ( @@ -318,6 +359,7 @@ 1CBF1BED2ECD9768001BF56C /* HoneyDueUITests.xctest */, 1C81F2692EE416EE000739EA /* HoneyDueQLPreview.appex */, 1C81F2802EE41BB6000739EA /* HoneyDueQLThumbnail.appex */, + A52A91DEA0ECFB45CBAAE168 /* HoneyDueAPITests.xctest */, ); name = Products; sourceTree = ""; @@ -458,6 +500,23 @@ productReference = 96A3DDC05E14B3F83E56282F /* honeyDue.app */; productType = "com.apple.product-type.application"; }; + E9D862A585C17DD92D22D303 /* HoneyDueAPITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8D660A43441A0D51114CC335 /* Build configuration list for PBXNativeTarget "HoneyDueAPITests" */; + buildPhases = ( + 836DD50B36C6061FE1C3D6E3 /* Sources */, + 55A71EFD2C2AB71B02035D05 /* Frameworks */, + 4100A8774ECB9CF390C44011 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = HoneyDueAPITests; + productName = HoneyDueAPITests; + productReference = A52A91DEA0ECFB45CBAAE168 /* HoneyDueAPITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -514,6 +573,7 @@ 1CBF1BEC2ECD9768001BF56C /* HoneyDueUITests */, 1C81F2682EE416EE000739EA /* HoneyDueQLPreview */, 1C81F27F2EE41BB6000739EA /* HoneyDueQLThumbnail */, + E9D862A585C17DD92D22D303 /* HoneyDueAPITests */, ); }; /* End PBXProject section */ @@ -554,6 +614,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4100A8774ECB9CF390C44011 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50827B76877E1E3968917892 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -627,6 +694,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 836DD50B36C6061FE1C3D6E3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 53469EBEDD37557816983B6D /* SharingAPITests.swift in Sources */, + 59A92CA8C3A412D8A18338C7 /* TestAccountAPIClient.swift in Sources */, + 91A9D5E4A93A022693888B95 /* TestDataSeeder.swift in Sources */, + 99FB08E574AA3B88AD73DEAC /* TestDataCleaner.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1119,6 +1197,33 @@ }; name = Debug; }; + 8B9401AF773E28539812BEB4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + PRODUCT_BUNDLE_IDENTIFIER = com.myhoneydue.HoneyDueAPITests; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 90CF3F8366EF59205F9444AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_STYLE = Automatic; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + PRODUCT_BUNDLE_IDENTIFIER = com.myhoneydue.HoneyDueAPITests; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; E767E942685C7832D51FF978 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1212,6 +1317,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 8D660A43441A0D51114CC335 /* Build configuration list for PBXNativeTarget "HoneyDueAPITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 90CF3F8366EF59205F9444AC /* Release */, + 8B9401AF773E28539812BEB4 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F25B3A5CCAC6BFCC21CD4636 /* Build configuration list for PBXProject "honeyDue" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/iosApp/honeyDue.xcodeproj/xcshareddata/xcschemes/HoneyDueAPITests.xcscheme b/iosApp/honeyDue.xcodeproj/xcshareddata/xcschemes/HoneyDueAPITests.xcscheme new file mode 100644 index 0000000..04ea585 --- /dev/null +++ b/iosApp/honeyDue.xcodeproj/xcshareddata/xcschemes/HoneyDueAPITests.xcscheme @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iosApp/run_ui_tests.sh b/iosApp/run_ui_tests.sh index fa91b53..bc83416 100755 --- a/iosApp/run_ui_tests.sh +++ b/iosApp/run_ui_tests.sh @@ -1,17 +1,18 @@ #!/bin/bash -# run_ui_tests.sh — Phased UI test runner. +# run_ui_tests.sh — Phased test runner for the HoneyDue iOS suites. # -# Architecture: every test mints its OWN isolated Kratos account (see +# Architecture: every UI test mints its OWN isolated Kratos account (see # Core/Fixtures/TestAccount.swift + AuthenticatedUITestCase), so suites are # fully independent and the parallel phase scales to many workers with no -# cross-suite data races. There is no per-suite ordering and no Suite6 -# special-casing anymore. +# cross-suite data races. Pure-API tests live in a separate standalone target +# (HoneyDueAPITests) that runs in seconds without launching the app. # # Phases: # 0. Smoke gate — fast launch/login sanity. Abort the run if it fails. # 1. Seed — ensure baseline accounts exist (AAA_SeedTests). -# 2. Parallel — the WHOLE target minus the four phase-managed suites, via -# -skip-testing. New suites are auto-included (no hand- +# 1b. API — standalone HoneyDueAPITests (no app launch; ~seconds). +# 2. Parallel — the WHOLE UI target minus the four phase-managed suites, +# via -skip-testing. New suites are auto-included (no hand- # maintained list to drift), run at $WORKERS workers. # 3. Sweep — clear-all-data + delete leaked uit_* Kratos identities # (SuiteZZ_CleanupTests). Non-blocking. @@ -23,12 +24,14 @@ # ./run_ui_tests.sh --skip-cleanup # skip phase 3 # ./run_ui_tests.sh --only-parallel # only phase 2 # ./run_ui_tests.sh --smoke # only phase 0 +# ./run_ui_tests.sh --only-api # only phase 1b set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT="$SCRIPT_DIR/honeyDue.xcodeproj" SCHEME="HoneyDueUITests" +API_SCHEME="HoneyDueAPITests" TARGET="HoneyDueUITests" DESTINATION="platform=iOS Simulator,name=iPhone 17 Pro" WORKERS=8 @@ -41,11 +44,7 @@ PHASE_MANAGED=( "$TARGET/AppLaunchUITests" ) -SKIP_SEED=false -SKIP_CLEANUP=false -ONLY_PARALLEL=false -ONLY_SMOKE=false - +SKIP_SEED=false; SKIP_CLEANUP=false; ONLY_PARALLEL=false; ONLY_SMOKE=false; ONLY_API=false POSITIONAL_ARGS=() for arg in "$@"; do case $arg in @@ -53,6 +52,7 @@ for arg in "$@"; do --skip-cleanup) SKIP_CLEANUP=true ;; --only-parallel) ONLY_PARALLEL=true; SKIP_SEED=true; SKIP_CLEANUP=true ;; --smoke) ONLY_SMOKE=true ;; + --only-api) ONLY_API=true ;; *) POSITIONAL_ARGS+=("$arg") ;; esac done @@ -66,11 +66,12 @@ mkdir -p "$RESULTS_DIR" "$DERIVED_DATA" BOLD='\033[1m'; GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[0;33m'; RESET='\033[0m' phase_header() { echo ""; echo -e "${BOLD}════════════════════════════════════════════════════${RESET}"; echo -e "${BOLD} $1${RESET}"; echo -e "${BOLD}════════════════════════════════════════════════════${RESET}"; echo ""; } +# run_xcodebuild [extra xcodebuild args...] run_xcodebuild() { - local result_path="$RESULTS_DIR/$1.xcresult"; shift + local result_path="$RESULTS_DIR/$1.xcresult"; local scheme="$2"; shift 2 rm -rf "$result_path" xcodebuild test \ - -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" \ + -project "$PROJECT" -scheme "$scheme" -destination "$DESTINATION" \ -derivedDataPath "$DERIVED_DATA" -resultBundlePath "$result_path" \ "$@" 2>&1 | tail -40 return ${PIPESTATUS[0]} @@ -78,16 +79,20 @@ run_xcodebuild() { OVERALL_START=$(date +%s) +# ── Phase 1b only ────────────────────────────────────────────── +if [ "$ONLY_API" = true ]; then + phase_header "API tests (standalone)" + run_xcodebuild "API" "$API_SCHEME" && exit 0 || exit 1 +fi + # ── Phase 0: Smoke gate ──────────────────────────────────────── if [ "$ONLY_PARALLEL" = false ]; then phase_header "Phase 0: Smoke gate" - if run_xcodebuild "Smoke" \ - -only-testing:"$TARGET/SmokeUITests" \ - -only-testing:"$TARGET/AppLaunchUITests"; then + if run_xcodebuild "Smoke" "$SCHEME" \ + -only-testing:"$TARGET/SmokeUITests" -only-testing:"$TARGET/AppLaunchUITests"; then echo -e "${GREEN}✓ Smoke passed${RESET}" else - echo -e "${RED}✗ Smoke FAILED — aborting (app can't launch/log in).${RESET}" - exit 1 + echo -e "${RED}✗ Smoke FAILED — aborting (app can't launch/log in).${RESET}"; exit 1 fi [ "$ONLY_SMOKE" = true ] && exit 0 fi @@ -95,19 +100,30 @@ fi # ── Phase 1: Seed ────────────────────────────────────────────── if [ "$SKIP_SEED" = false ]; then phase_header "Phase 1: Seed baseline accounts" - if run_xcodebuild "Seed" -only-testing:"$TARGET/AAA_SeedTests"; then + if run_xcodebuild "Seed" "$SCHEME" -only-testing:"$TARGET/AAA_SeedTests"; then echo -e "${GREEN}✓ Seed passed${RESET}" else echo -e "${RED}✗ Seed FAILED — aborting.${RESET}"; exit 1 fi fi -# ── Phase 2: Parallel (whole target minus phase-managed) ─────── -phase_header "Phase 2: Parallel suite ($WORKERS workers)" +# ── Phase 1b: API contract tests (fast, standalone) ──────────── +API_PASSED=true +if [ "$ONLY_PARALLEL" = false ]; then + phase_header "Phase 1b: API tests (standalone bundle, no app launch)" + if run_xcodebuild "API" "$API_SCHEME"; then + echo -e "${GREEN}✓ API tests passed${RESET}" + else + API_PASSED=false; echo -e "${RED}✗ API tests FAILED${RESET}" + fi +fi + +# ── Phase 2: Parallel (whole UI target minus phase-managed) ──── +phase_header "Phase 2: Parallel UI suite ($WORKERS workers)" SKIP_ARGS=() for t in "${PHASE_MANAGED[@]}"; do SKIP_ARGS+=( -skip-testing:"$t" ); done PARALLEL_START=$(date +%s) -if run_xcodebuild "Parallel" \ +if run_xcodebuild "Parallel" "$SCHEME" \ -only-testing:"$TARGET" "${SKIP_ARGS[@]}" \ -parallel-testing-enabled YES -parallel-testing-worker-count "$WORKERS"; then PARALLEL_PASSED=true; echo -e "${GREEN}✓ Parallel phase passed${RESET}" @@ -119,7 +135,7 @@ PARALLEL_END=$(date +%s) # ── Phase 3: Sweep ───────────────────────────────────────────── if [ "$SKIP_CLEANUP" = false ]; then phase_header "Phase 3: Sweep leaked accounts + data" - if run_xcodebuild "Sweep" -only-testing:"$TARGET/SuiteZZ_CleanupTests"; then + if run_xcodebuild "Sweep" "$SCHEME" -only-testing:"$TARGET/SuiteZZ_CleanupTests"; then echo -e "${GREEN}✓ Sweep passed${RESET}" else echo -e "${YELLOW}⚠ Sweep failed (non-blocking)${RESET}" @@ -130,9 +146,10 @@ fi phase_header "Summary" echo " Total time: $(( $(date +%s) - OVERALL_START ))s" echo " Parallel: $(( PARALLEL_END - PARALLEL_START ))s @ $WORKERS workers" +echo " API tests: $([ "$API_PASSED" = true ] && echo passed || echo FAILED)" echo " Results: $RESULTS_DIR/" echo "" -if [ "${PARALLEL_PASSED:-false}" = true ]; then +if [ "${PARALLEL_PASSED:-false}" = true ] && [ "$API_PASSED" = true ]; then echo -e " ${GREEN}${BOLD}ALL TESTS PASSED${RESET}"; exit 0 else echo -e " ${RED}${BOLD}TESTS FAILED${RESET} — open $RESULTS_DIR/"; exit 1