eletrotupi / tcc/ frontend/scripts/e2e.sh6974d5508f215de8c94b04469cf20d238069477f
5.5 KB | 187 lines Raw
1
#!/bin/sh
2
#
3
# E2E harness for the nexo frontend: Maestro flows on a physical Android
4
# device over adb (no emulator)
5
#
6
# POSIX sh on purpose: this runs on Debian/Ubuntu and on Alpine (busybox ash),
7
# so keep it free of bashisms and of GNU-only tools
8
#
9
# The app talks to whatever API `EXPO_PUBLIC_API_BASE_URL` points at, so there is
10
# no second API service to boot here: bring the normal stack up and point Metro
11
# at it:
12
#
13
#   docker compose up -d db valkey api
14
#   cd api && npm run seed                  # known e2e user (idempotent)
15
#   cd frontend && npx expo start           # with .env.local set (see print-env)
16
#
17
# Usage: e2e.sh [test|print-env]
18
#   test       prep the device (adb) and run the tagged Maestro flows
19
#   print-env  print the EXPO_PUBLIC_* vars to set before `expo start`
20
#
21
# Optional env: HOST_IP (LAN IP of this machine) is auto-detected when unset
22
set -eu
23

24
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
25
ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
26
FRONTEND="$ROOT/frontend"
27

28
# The app under test: the dev build (`eas build --profile development`)
29
APP_ID=${APP_ID:-com.eletrotupi.orbit}
30
API_PORT=${API_PORT:-3000}
31
EXPO_PORT=${EXPO_PORT:-8081}
32
E2E_EMAIL=${E2E_EMAIL:-e2e@example.com}
33
E2E_PASSWORD=${E2E_PASSWORD:-e2e-password}
34

35
ANIMATION_KEYS="window_animation_scale transition_animation_scale animator_duration_scale"
36
animation_state=""
37

38
die() { echo "e2e: $*" >&2; exit 1; }
39

40
# True for a plausible, non-loopback IPv4 address
41
is_ipv4() {
42
  case "$1" in
43
    ""|127.*|0.0.0.0) return 1 ;;
44
  esac
45
  printf '%s\n' "$1" | awk -F. '
46
    NF == 4 { for (i = 1; i <= 4; i++) if ($i !~ /^[0-9]+$/ || $i > 255) exit 1; exit 0 }
47
    { exit 1 }
48
  '
49
}
50

51
detect_host_ip() {
52
  if [ -n "${HOST_IP:-}" ]; then
53
    echo "$HOST_IP"
54
    return 0
55
  fi
56

57
  addr=""
58

59
  # iproute2 (Debian/Ubuntu): `ip route get` names the interface the default
60
  # route leaves through
61
  if command -v ip >/dev/null 2>&1; then
62
    addr=$(ip route get 1 2>/dev/null | awk '/src/{for (i = 1; i <= NF; i++) if ($i == "src") {print $(i + 1); exit}}')
63
    if ! is_ipv4 "$addr"; then
64
      addr=$(ip -4 -o addr show scope global 2>/dev/null | awk '$4 ~ /\./ {sub(/\/.*/, "", $4); print $4; exit}')
65
    fi
66
  fi
67

68
  # busybox (Alpine): its `ip` has no `route get`, so fall back to ifconfig
69
  if ! is_ipv4 "$addr" && command -v ifconfig >/dev/null 2>&1; then
70
    addr=$(ifconfig 2>/dev/null | awk '/inet (addr:)?[0-9]/ {sub(/addr:/, "", $2); print $2; exit}')
71
  fi
72

73
  # GNU `hostname -I` / busybox `hostname -i`
74
  if ! is_ipv4 "$addr" && command -v hostname >/dev/null 2>&1; then
75
    for candidate in $(hostname -I 2>/dev/null) $(hostname -i 2>/dev/null); do
76
      if is_ipv4 "$candidate"; then
77
        addr=$candidate
78
        break
79
      fi
80
    done
81
  fi
82

83
  is_ipv4 "$addr" || die "could not detect LAN IP; export HOST_IP=<lan-ip>"
84
  echo "$addr"
85
}
86

87
# `settings put global` writes to the persisted settings provider, so animation
88
# scales stay turned off across reboots and reinstalls. Remember what was there
89
# first, and put it back on the way out
90
remember_animations() {
91
  animation_state=""
92
  for key in $ANIMATION_KEYS; do
93
    value=$(adb shell settings get global "$key" 2>/dev/null | tr -d '[:space:]')
94
    animation_state="$animation_state $key=$value"
95
  done
96
}
97

98
restore_animations() {
99
  for entry in $animation_state; do
100
    key=${entry%%=*}
101
    value=${entry#*=}
102
    case "$value" in
103
      ""|null) adb shell settings delete global "$key" >/dev/null 2>&1 || true ;;
104
      *) adb shell settings put global "$key" "$value" >/dev/null 2>&1 || true ;;
105
    esac
106
  done
107
}
108

109
adb_wait_device() {
110
  i=0
111
  while [ "$i" -lt 15 ]; do
112
    if [ "$(adb get-state 2>/dev/null)" = "device" ]; then
113
      return 0
114
    fi
115
    sleep 1
116
    i=$((i + 1))
117
  done
118
  return 1
119
}
120

121
adb_prepare() {
122
  command -v adb >/dev/null 2>&1 || die "adb not found; install android-tools (Linux) or platform-tools"
123
  echo "e2e: waiting for an Android device (up to 15s)..."
124

125
  if ! adb_wait_device; then
126
    adb devices >&2 || true
127
    die "no Android device attached. Connect one over USB/wireless debugging (or set ANDROID_SERIAL when several are connected) and re-run"
128
  fi
129

130
  echo "e2e: adb devices:"
131
  adb devices
132

133
  # No `pm clear` here: each flow starts with Maestro `clearState`, which needs no
134
  # host-side help, and clearing here only re-triggered the expo-dev-client
135
  # first-run overlays. Granting the runtime permission is still worth it:
136
  # clearState resets it and the app asks for notifications right after login
137
  adb shell pm grant "$APP_ID" android.permission.POST_NOTIFICATIONS >/dev/null 2>&1 || true
138

139
  # Disable animations for speed and stability, then put them back on exit
140
  remember_animations
141
  trap restore_animations EXIT HUP INT TERM
142
  for key in $ANIMATION_KEYS; do
143
    adb shell settings put global "$key" 0 >/dev/null
144
  done
145
}
146

147
run_maestro() {
148
  command -v maestro >/dev/null 2>&1 || die "maestro not found; install via https://get.maestro.mobile.dev"
149
  host_ip=$(detect_host_ip)
150

151
  (
152
    cd "$FRONTEND"
153
    maestro test e2e \
154
      --include-tags=smoke \
155
      --env HOST_IP="$host_ip" \
156
      --env PORT="$EXPO_PORT" \
157
      --env E2E_EMAIL="$E2E_EMAIL" \
158
      --env E2E_PASSWORD="$E2E_PASSWORD"
159
  )
160
}
161

162
print_env() {
163
  host_ip=$(detect_host_ip)
164

165
  cat <<EOF
166
Put this in frontend/.env.local (gitignored) before starting Metro:
167

168
  EXPO_PUBLIC_API_BASE_URL=http://$host_ip:$API_PORT
169
  EXPO_PUBLIC_TOKEN_KEY=orbit-jwt
170

171
  npx expo start --port $EXPO_PORT
172
EOF
173
}
174

175
case "${1:-}" in
176
  test)
177
    adb_prepare
178
    run_maestro
179
    ;;
180
  print-env)
181
    print_env
182
    ;;
183
  *)
184
    echo "usage: $0 [test|print-env]" >&2
185
    exit 1
186
    ;;
187
esac