From 682f396dc25bb8a7a599ed76a9b608f2e434e252 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 18 May 2026 16:16:10 +0100 Subject: [PATCH 1/2] chore: write shell script for running swiftui tests from terminal --- .gitignore | 6 +- CONTRIBUTING.md | 24 ++++ swiftui-tests.sh | 279 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 308 insertions(+), 1 deletion(-) create mode 100755 swiftui-tests.sh diff --git a/.gitignore b/.gitignore index 707c7f0f5ee..d1bc25e11a5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,8 @@ Pods/ Podfile.lock *.xcworkspace/ samples/**/GoogleService-Info.plist -e2eTest/FirebaseSwiftUIExample/*.xcresult/ \ No newline at end of file +e2eTest/FirebaseSwiftUIExample/*.xcresult/ +e2eTest/FirebaseSwiftUIExample/*.log +e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExample/firebase-debug.log +FirebaseSwiftUIPackageTests.log +FirebaseSwiftUIPackageTests.xcresult/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00a1cc6d0fa..ab9bafcadc2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,30 @@ All submissions, including submissions by project members, require review. We use Github pull requests for this purpose. We adhere to the [Google Objective-C style guide](https://google.github.io/styleguide/objcguide.xml). +### Running SwiftUI Auth checks locally + +The SwiftUI Auth GitHub Actions workflow can be run locally with: + +```bash +./swiftui-tests.sh +``` + +By default, this runs the package unit tests, integration tests, and UI tests. +You can run individual checks with `--unit`, `--integration`, or `--ui`. +Pass `--lint` to run `lint-swift.sh` before the selected tests. + +Examples: + +```bash +./swiftui-tests.sh --unit +./swiftui-tests.sh --integration --ui +./swiftui-tests.sh --lint --all +./swiftui-tests.sh --device "iPhone 17 Pro" --ui +``` + +Integration and UI tests require the Firebase CLI, Node.js, and npm because +they run against the Firebase Auth emulator. + ### The small print Contributions made by corporations are covered by a different agreement than the diff --git a/swiftui-tests.sh b/swiftui-tests.sh new file mode 100755 index 00000000000..2f1e83e74b8 --- /dev/null +++ b/swiftui-tests.sh @@ -0,0 +1,279 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IOS_SIMULATOR_DEVICE="${IOS_SIMULATOR_DEVICE:-iPhone 17 Pro}" +SIMULATOR_UDID="" +RUN_LINT=false +RUN_UNIT=false +RUN_INTEGRATION=false +RUN_UI=false +EMULATOR_PID="" + +usage() { + cat </dev/null; then + echo "Stopping Firebase Auth emulator..." + kill "${EMULATOR_PID}" 2>/dev/null || true + wait "${EMULATOR_PID}" 2>/dev/null || true + fi +} +trap cleanup EXIT + +while [[ $# -gt 0 ]]; do + case "$1" in + --all) + RUN_UNIT=true + RUN_INTEGRATION=true + RUN_UI=true + shift + ;; + --unit) + RUN_UNIT=true + shift + ;; + --integration) + RUN_INTEGRATION=true + shift + ;; + --ui) + RUN_UI=true + shift + ;; + --lint) + RUN_LINT=true + shift + ;; + --device) + if [[ -z "${2:-}" ]]; then + echo "--device requires a simulator device name." + exit 1 + fi + IOS_SIMULATOR_DEVICE="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +if [[ "${RUN_UNIT}" == false && "${RUN_INTEGRATION}" == false && "${RUN_UI}" == false ]]; then + RUN_UNIT=true + RUN_INTEGRATION=true + RUN_UI=true +fi + +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" + exit 1 + fi +} + +prepare_simulator() { + if [[ -n "${SIMULATOR_UDID}" ]]; then + return + fi + + echo "Preparing iOS simulator: ${IOS_SIMULATOR_DEVICE}" + local output_file + output_file="$(mktemp)" + + GITHUB_OUTPUT="${output_file}" \ + IOS_SIMULATOR_DEVICE="${IOS_SIMULATOR_DEVICE}" \ + "${ROOT_DIR}/.github/workflows/scripts/prepare-ios-simulator.sh" + + SIMULATOR_UDID="$(awk -F= '/^udid=/{ print $2 }' "${output_file}" | tail -n 1)" + rm -f "${output_file}" + + if [[ -z "${SIMULATOR_UDID}" ]]; then + echo "Failed to resolve simulator UDID for ${IOS_SIMULATOR_DEVICE}." + exit 1 + fi +} + +run_xcodebuild() { + local log_path="$1" + shift + + rm -f "${log_path}" + + if command -v xcpretty >/dev/null 2>&1; then + "$@" | tee "${log_path}" | xcpretty --test --color --simple + else + echo "xcpretty is not installed; writing raw xcodebuild output." + "$@" | tee "${log_path}" + fi +} + +wait_for_emulator() { + local attempt=1 + local max_attempts=60 + + while [[ "${attempt}" -le "${max_attempts}" ]]; do + if curl --output /dev/null --silent --fail http://localhost:9099; then + sleep 15 + if curl --output /dev/null --silent --fail http://localhost:9099; then + echo "Firebase Auth emulator is online." + return + fi + fi + + echo "Waiting for Firebase Auth emulator, check ${attempt} of ${max_attempts}..." + sleep 1 + attempt=$((attempt + 1)) + done + + echo "Firebase Auth emulator did not come online." + return 1 +} + +start_emulator_if_needed() { + if curl --output /dev/null --silent --fail http://localhost:9099; then + echo "Using existing Firebase Auth emulator on localhost:9099." + return + fi + + require_command firebase + require_command node + require_command npm + + local retry=1 + local max_retries=3 + + while [[ "${retry}" -le "${max_retries}" ]]; do + echo "Starting Firebase Auth emulator, try ${retry} of ${max_retries}..." + pushd "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExample" >/dev/null + firebase emulators:start --only auth --project flutterfire-e2e-tests --debug > firebase-debug.log 2>&1 & + EMULATOR_PID="$!" + popd >/dev/null + + if wait_for_emulator; then + return + fi + + if kill -0 "${EMULATOR_PID}" 2>/dev/null; then + kill "${EMULATOR_PID}" 2>/dev/null || true + wait "${EMULATOR_PID}" 2>/dev/null || true + fi + EMULATOR_PID="" + retry=$((retry + 1)) + done + + echo "Firebase Auth emulator did not come online after ${max_retries} attempts." + exit 1 +} + +run_lint() { + echo "Running Swift format lint..." + bash "${ROOT_DIR}/lint-swift.sh" +} + +run_unit_tests() { + echo "Running FirebaseSwiftUI package unit tests..." + prepare_simulator + rm -rf "${ROOT_DIR}/FirebaseSwiftUIPackageTests.xcresult" + + pushd "${ROOT_DIR}" >/dev/null + run_xcodebuild "${ROOT_DIR}/FirebaseSwiftUIPackageTests.log" \ + xcodebuild test \ + -scheme FirebaseUI-Package \ + -destination "id=${SIMULATOR_UDID}" \ + -enableCodeCoverage YES \ + -resultBundlePath FirebaseSwiftUIPackageTests.xcresult + popd >/dev/null +} + +run_integration_tests() { + echo "Running FirebaseSwiftUIExample integration tests..." + start_emulator_if_needed + prepare_simulator + rm -rf "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests.xcresult" + + pushd "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample" >/dev/null + run_xcodebuild "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests.log" \ + xcodebuild test \ + -scheme FirebaseSwiftUIExampleTests \ + -destination "id=${SIMULATOR_UDID}" \ + -parallel-testing-enabled NO \ + -enableCodeCoverage YES \ + -resultBundlePath FirebaseSwiftUIExampleTests.xcresult + popd >/dev/null +} + +run_ui_tests() { + echo "Running FirebaseSwiftUIExample UI tests..." + start_emulator_if_needed + prepare_simulator + rm -rf "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.xcresult" + + pushd "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample" >/dev/null + run_xcodebuild "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests-build.log" \ + xcodebuild build-for-testing \ + -scheme FirebaseSwiftUIExampleUITests \ + -destination "id=${SIMULATOR_UDID}" \ + -enableCodeCoverage YES + + run_xcodebuild "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.log" \ + xcodebuild test-without-building \ + -scheme FirebaseSwiftUIExampleUITests \ + -destination "id=${SIMULATOR_UDID}" \ + -parallel-testing-enabled NO \ + -enableCodeCoverage YES \ + -resultBundlePath FirebaseSwiftUIExampleUITests.xcresult + popd >/dev/null +} + +require_command xcodebuild +require_command xcrun +require_command awk +require_command curl + +if [[ "${RUN_LINT}" == true ]]; then + run_lint +fi + +if [[ "${RUN_UNIT}" == true ]]; then + run_unit_tests +fi + +if [[ "${RUN_INTEGRATION}" == true ]]; then + run_integration_tests +fi + +if [[ "${RUN_UI}" == true ]]; then + run_ui_tests +fi + +echo "SwiftUI Auth checks completed successfully." From e0ea900c97006b894a1989cbc077d48fb87a13aa Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 19 May 2026 10:37:59 +0100 Subject: [PATCH 2/2] chore(pr): address reviewer feedback on SwiftUI test script --- CONTRIBUTING.md | 4 +++- swiftui-tests.sh | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab9bafcadc2..f0dbff662d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,10 +52,12 @@ Examples: ./swiftui-tests.sh --integration --ui ./swiftui-tests.sh --lint --all ./swiftui-tests.sh --device "iPhone 17 Pro" --ui +FIREBASE_PROJECT="my-firebase-project" ./swiftui-tests.sh --integration ``` Integration and UI tests require the Firebase CLI, Node.js, and npm because -they run against the Firebase Auth emulator. +they run against the Firebase Auth emulator. By default, the script uses the +`flutterfire-e2e-tests` Firebase project; set `FIREBASE_PROJECT` to override it. ### The small print diff --git a/swiftui-tests.sh b/swiftui-tests.sh index 2f1e83e74b8..ccec25af506 100755 --- a/swiftui-tests.sh +++ b/swiftui-tests.sh @@ -4,6 +4,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" IOS_SIMULATOR_DEVICE="${IOS_SIMULATOR_DEVICE:-iPhone 17 Pro}" +FIREBASE_PROJECT="${FIREBASE_PROJECT:-flutterfire-e2e-tests}" SIMULATOR_UDID="" RUN_LINT=false RUN_UNIT=false @@ -32,6 +33,7 @@ Examples: ./swiftui-tests.sh --integration --ui ./swiftui-tests.sh --lint --all ./swiftui-tests.sh --device "iPhone 17 Pro" --ui + FIREBASE_PROJECT="my-firebase-project" ./swiftui-tests.sh --integration EOF } @@ -143,7 +145,7 @@ wait_for_emulator() { while [[ "${attempt}" -le "${max_attempts}" ]]; do if curl --output /dev/null --silent --fail http://localhost:9099; then - sleep 15 + sleep 12 if curl --output /dev/null --silent --fail http://localhost:9099; then echo "Firebase Auth emulator is online." return @@ -175,7 +177,7 @@ start_emulator_if_needed() { while [[ "${retry}" -le "${max_retries}" ]]; do echo "Starting Firebase Auth emulator, try ${retry} of ${max_retries}..." pushd "${ROOT_DIR}/e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExample" >/dev/null - firebase emulators:start --only auth --project flutterfire-e2e-tests --debug > firebase-debug.log 2>&1 & + firebase emulators:start --only auth --project "${FIREBASE_PROJECT}" --debug > firebase-debug.log 2>&1 & EMULATOR_PID="$!" popd >/dev/null