[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pythonnative/pythonnative/v0.43.1/scripts/run-e2e.sh [Back]  [Original]

#!/usr/bin/env bash
# Build the e2e-suite example app and run the comprehensive Maestro suite.
#
# This script is the supported way for AI agents and humans to run the
# full E2E pass locally. It mirrors what CI does in .github/workflows/e2e.yml.
#
# Usage:
#     ./scripts/run-e2e.sh android [suite ...]
#     ./scripts/run-e2e.sh ios     [suite ...]
#
# Examples:
#     ./scripts/run-e2e.sh android                     # full suite
#     ./scripts/run-e2e.sh android components          # only the components suite
#     ./scripts/run-e2e.sh ios hooks                   # only the hooks suite on iOS
#     ./scripts/run-e2e.sh android hooks navigation    # two categories, one session
#
# Available suites: inbox, full, components, hooks, navigation, layout, styling,
# animations, gestures, misc.
#
# Multiple category suites can be passed at once; they run sequentially in
# a single Maestro session (and against a single emulator/simulator boot).
# CI uses this to shard the Android run into a few balanced groups so no
# single emulator session has to survive the entire 60-flow marathon;
# GitHub-hosted Android emulators grow unstable under ~15 minutes of
# sustained Maestro driving and start reporting "device offline". See
# .github/workflows/e2e.yml and tests/e2e/AGENTS.md.
#
# Prerequisites:
#     - `pn` CLI available (e.g. via `uv sync`, then run through `uv run`).
#     - `maestro` CLI on PATH (https://maestro.dev/).
#     - For Android: an emulator running.
#     - For iOS: a simulator running.
#
# The script:
#     1. Builds + installs the e2e-suite app via `pn run  --no-logs`.
#     2. Resolves each requested suite to its Maestro YAML target.
#     3. Runs `maestro test` (over all targets) up to ``MAESTRO_MAX_ATTEMPTS``
#        times (default 2) and exits with the last attempt's exit code.
#
# A successful run prints "All E2E suites passed." at the end and exits 0.
# Any failed flow is reported by Maestro in its standard format; see
# tests/e2e/AGENTS.md for guidance on interpreting failures.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$ROOT_DIR"

PLATFORM="${1:-android}"
shift || true
# Remaining args are suite names; default to the full aggregate suite.
SUITES=("$@")
if [[ ${#SUITES[@]} -eq 0 ]]; then
  SUITES=("full")
fi

case "$PLATFORM" in
  android|ios) ;;
  *)
    echo "Error: platform must be 'android' or 'ios' (got: $PLATFORM)" >&2
    exit 2
    ;;
esac

if ! command -v pn > /dev/null; then
  echo "Error: 'pn' CLI not found on PATH. Run 'uv sync', then invoke this" >&2
  echo "script through 'uv run ./scripts/run-e2e.sh ...'." >&2
  exit 2
fi

if ! command -v maestro > /dev/null; then
  echo "Error: 'maestro' CLI not found on PATH." >&2
  echo "Install: curl -Ls 'https://get.maestro.mobile.dev' | bash" >&2
  exit 2
fi

if [[ "${SUITES[*]}" == "inbox" ]]; then
  exec "$SCRIPT_DIR/run-inbox-e2e.sh" "$PLATFORM"
fi

# Resolve the app/bundle id straight from the example's pythonnative.toml so
# this stays correct no matter what id the config declares.
APP_ID="$(cd examples/e2e-suite && pn app-id "$PLATFORM")"
if [[ -z "$APP_ID" ]]; then
  echo "Error: could not resolve APP_ID via 'pn app-id $PLATFORM'." >&2
  exit 2
fi

# Resolve each requested suite to a Maestro YAML target. ``full`` expands
# to the platform's master aggregate; the category names map to the
# per-category suite files under tests/e2e/suites/.
MAESTRO_TARGETS=()
for suite in "${SUITES[@]}"; do
  case "$suite" in
    full)
      if [[ "$PLATFORM" == "android" ]]; then
        MAESTRO_TARGETS+=("tests/e2e/android.yaml")
      else
        MAESTRO_TARGETS+=("tests/e2e/ios.yaml")
      fi
      ;;
    components|components-a|components-b|hooks|navigation|layout|styling|animations|gestures|misc)
      MAESTRO_TARGETS+=("tests/e2e/suites/${suite}.yaml")
      ;;
    *)
      echo "Error: unknown suite '$suite'" >&2
      echo "Available suites: full, components, components-a, components-b, hooks, navigation, layout, styling, animations, gestures, misc" >&2
      exit 2
      ;;
  esac
done

printf "\n==> Building e2e-suite app for %s\n" "$PLATFORM"
pushd examples/e2e-suite > /dev/null
pn run "$PLATFORM" --no-logs
popd > /dev/null

run_maestro() {
  if [[ "$PLATFORM" == "ios" ]]; then
    maestro --platform ios test -e "APP_ID=$APP_ID" "${MAESTRO_TARGETS[@]}"
  else
    maestro test -e "APP_ID=$APP_ID" "${MAESTRO_TARGETS[@]}"
  fi
}

printf "\n==> Running Maestro suite(s): %s\n" "${MAESTRO_TARGETS[*]}"

# Maestro's iOS XCUITest driver occasionally loses its connection to the
# app during long suites and surfaces transient "Application is not
# running" / "Request for viewHierarchy failed" errors that have nothing
# to do with the test under test. Allow one automatic retry of the whole
# suite (overridable via ``MAESTRO_MAX_ATTEMPTS``) so CI doesn't fail on
# driver flakes. A retry can also mask a genuine race in the suite, so
# treat the "retrying..." line as a signal to investigate, not just to
# trust the second pass.

# Kill the app so the next Maestro attempt starts from a cold launch
# (open_demo relaunches a dead app). A failed attempt can leave the app
# mid-demo with dirty in-process state (e.g. module-level counters some
# demos display), which would make a same-process retry fail assertions
# that hold on a first visit.
terminate_app() {
  if [[ "$PLATFORM" == "ios" ]]; then
    xcrun simctl terminate booted "$APP_ID" 2> /dev/null || true
  else
    adb shell am force-stop "$APP_ID" 2> /dev/null || true
  fi
}

MAX_ATTEMPTS="${MAESTRO_MAX_ATTEMPTS:-2}"
attempt=1
while (( attempt &2
    exit 1
  fi
  printf "\n==> Maestro suite failed (attempt %d/%d); restarting app and retrying...\n" \
    "$attempt" "$MAX_ATTEMPTS" >&2
  terminate_app
  attempt=$(( attempt + 1 ))
done

printf "\nAll E2E suites passed.\n"

Web Proxy Viewer  |  New URL  |  Original Page