test: verify Android public staging shell
This commit is contained in:
parent
44865635e4
commit
c6aa3d1ed2
|
|
@ -60,6 +60,19 @@ be published as a release. The manifest accepts same-origin HTTPS deep links,
|
|||
but verified Android App Links additionally require the final signing
|
||||
certificate fingerprint in the deployment's `/.well-known/assetlinks.json`.
|
||||
|
||||
With the temporary public origin reachable, run the API 37 emulator smoke test:
|
||||
|
||||
```sh
|
||||
./scripts/android-staging-smoke.sh
|
||||
```
|
||||
|
||||
The script installs the exported staging APK into a fresh project-scoped
|
||||
emulator container, loads the configured HTTPS home page, follows a
|
||||
same-origin `/safety` deep link, verifies that the package does not claim an
|
||||
external HTTPS origin, and retains UI dumps, screenshots, package metadata,
|
||||
and logcat diagnostics under ignored `output/android-staging-smoke/`. The
|
||||
one-run container and image are removed on success or failure.
|
||||
|
||||
## Reproducible Docker build
|
||||
|
||||
From the repository root:
|
||||
|
|
|
|||
273
scripts/android-staging-smoke.sh
Executable file
273
scripts/android-staging-smoke.sh
Executable file
|
|
@ -0,0 +1,273 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
umask 077
|
||||
|
||||
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
ENV_FILE="$ROOT/.env"
|
||||
APK="$ROOT/android/dist-staging/who-need-help-staging.apk"
|
||||
run_id=$(date -u +%Y%m%d%H%M%S)-$$
|
||||
image="who-need-help-android:staging-smoke-$run_id"
|
||||
container="who-need-help-android-staging-smoke-$run_id"
|
||||
output="$ROOT/output/android-staging-smoke/$run_id"
|
||||
package=org.whoneedhelp.mobile.staging
|
||||
activity=org.whoneedhelp.mobile.MainActivity
|
||||
|
||||
if [ ! -e /dev/kvm ]; then
|
||||
echo "/dev/kvm is required for the containerized Android emulator." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
echo "Missing $ENV_FILE." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -s "$APK" ]; then
|
||||
echo "Missing staging APK: $APK. Run scripts/android-staging-build.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
: "${WNH_BASE_URL:?Set WNH_BASE_URL in .env}"
|
||||
: "${WNH_TRACKING_MIN_TIME_MS:?Set WNH_TRACKING_MIN_TIME_MS in .env}"
|
||||
: "${WNH_TRACKING_HTTP_TIMEOUT_MS:?Set WNH_TRACKING_HTTP_TIMEOUT_MS in .env}"
|
||||
|
||||
case "$WNH_BASE_URL" in
|
||||
https://*/* | https://*) ;;
|
||||
*)
|
||||
echo "WNH_BASE_URL must be an HTTPS origin." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
origin_without_scheme=${WNH_BASE_URL#https://}
|
||||
|
||||
case "$origin_without_scheme" in
|
||||
"" | */* | *\?* | *\#* | *@*)
|
||||
echo "WNH_BASE_URL must be a credential-free HTTPS origin without a path." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
expected_host=${origin_without_scheme%%:*}
|
||||
|
||||
mkdir -p "$output"
|
||||
chmod 700 "$ROOT/output" "$ROOT/output/android-staging-smoke" "$output"
|
||||
|
||||
cleanup() {
|
||||
status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
if docker inspect "$container" >/dev/null 2>&1; then
|
||||
docker exec "$container" adb logcat -d \
|
||||
>"$output/logcat.txt" 2>&1 || true
|
||||
docker exec "$container" adb shell dumpsys activity activities \
|
||||
>"$output/activities.txt" 2>&1 || true
|
||||
docker logs "$container" >"$output/emulator.log" 2>&1 || true
|
||||
fi
|
||||
|
||||
docker rm -f "$container" >/dev/null 2>&1 || true
|
||||
docker image rm "$image" >/dev/null 2>&1 || true
|
||||
|
||||
{
|
||||
printf 'container_absent='
|
||||
if docker inspect "$container" >/dev/null 2>&1; then
|
||||
printf 'false\n'
|
||||
else
|
||||
printf 'true\n'
|
||||
fi
|
||||
|
||||
printf 'image_absent='
|
||||
if docker image inspect "$image" >/dev/null 2>&1; then
|
||||
printf 'false\n'
|
||||
else
|
||||
printf 'true\n'
|
||||
fi
|
||||
} >"$output/cleanup.txt"
|
||||
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
sha256sum "$APK" >"$output/apk.sha256"
|
||||
|
||||
docker build \
|
||||
--build-arg "WNH_DEBUG_BASE_URL=$WNH_BASE_URL" \
|
||||
--build-arg "WNH_TRACKING_MIN_TIME_MS=$WNH_TRACKING_MIN_TIME_MS" \
|
||||
--build-arg "WNH_TRACKING_HTTP_TIMEOUT_MS=$WNH_TRACKING_HTTP_TIMEOUT_MS" \
|
||||
--build-arg \
|
||||
"ANDROID_EMULATOR_SYSTEM_IMAGE=system-images/android-37.0/google_apis_ps16k/x86_64" \
|
||||
--target emulator \
|
||||
--tag "$image" \
|
||||
"$ROOT/android" >"$output/image-build.txt"
|
||||
|
||||
docker run -d \
|
||||
--name "$container" \
|
||||
--device /dev/kvm \
|
||||
"$image" >"$output/container-id.txt"
|
||||
|
||||
docker exec "$container" adb wait-for-device
|
||||
|
||||
booted=
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 90 ]; do
|
||||
booted=$(
|
||||
docker exec "$container" adb shell getprop sys.boot_completed 2>/dev/null |
|
||||
tr -d '\r'
|
||||
)
|
||||
|
||||
if [ "$booted" = "1" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$booted" != "1" ]; then
|
||||
echo "Android staging emulator did not finish booting." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec "$container" adb shell input keyevent 82
|
||||
docker exec "$container" adb shell settings put global window_animation_scale 0
|
||||
docker exec "$container" adb shell settings put global transition_animation_scale 0
|
||||
docker exec "$container" adb shell settings put global animator_duration_scale 0
|
||||
docker cp "$APK" "$container:/tmp/who-need-help-staging.apk"
|
||||
docker exec "$container" adb install -r /tmp/who-need-help-staging.apk \
|
||||
>"$output/install.txt"
|
||||
docker exec "$container" adb shell dumpsys package "$package" \
|
||||
>"$output/package.txt"
|
||||
|
||||
if ! grep -Fq "versionName=0.1.0-staging" "$output/package.txt"; then
|
||||
echo "The installed package is not the expected staging variant." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
same_origin="$WNH_BASE_URL/safety"
|
||||
external_origin=https://example.com/
|
||||
|
||||
if ! grep -Fq "Authority: \"$expected_host\"" "$output/package.txt"; then
|
||||
echo "The staging APK does not declare its exact HTTPS host." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Fq 'Authority: "example.com"' "$output/package.txt"; then
|
||||
echo "The staging APK incorrectly declares an external HTTPS host." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec "$container" adb shell cmd package resolve-activity --brief \
|
||||
-a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d "$same_origin" >"$output/same-origin-resolver.txt"
|
||||
|
||||
docker exec "$container" adb shell cmd package resolve-activity --brief \
|
||||
-a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d "$external_origin" >"$output/external-origin-resolver.txt"
|
||||
|
||||
if grep -Fq "$package/" "$output/external-origin-resolver.txt"; then
|
||||
echo "The staging APK incorrectly claimed an external HTTPS origin." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec "$container" adb shell pm get-app-links "$package" \
|
||||
>"$output/app-links.txt"
|
||||
|
||||
docker exec "$container" adb logcat -c
|
||||
docker exec "$container" adb shell am start -W \
|
||||
-n "$package/$activity" >"$output/home-start.txt"
|
||||
|
||||
home_loaded=false
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 45 ]; do
|
||||
docker exec "$container" adb logcat -d -s WhoNeedHelpWebView:D '*:S' \
|
||||
>"$output/webview-current.txt" 2>&1 || true
|
||||
|
||||
if grep -Fq "Main-frame load finished: path=/" "$output/webview-current.txt"; then
|
||||
home_loaded=true
|
||||
break
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$home_loaded" != true ]; then
|
||||
echo "The staging WebView did not finish loading the public home page." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec "$container" adb shell uiautomator dump /sdcard/home-window.xml \
|
||||
>"$output/home-ui-dump-command.txt" 2>&1
|
||||
docker exec "$container" adb exec-out cat /sdcard/home-window.xml \
|
||||
>"$output/home-window.xml"
|
||||
docker exec "$container" adb exec-out screencap -p >"$output/home.png"
|
||||
|
||||
docker exec "$container" adb shell am start -W \
|
||||
-a android.intent.action.VIEW \
|
||||
-c android.intent.category.BROWSABLE \
|
||||
-d "$same_origin" \
|
||||
-n "$package/$activity" >"$output/safety-start.txt"
|
||||
|
||||
safety_loaded=false
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 45 ]; do
|
||||
docker exec "$container" adb logcat -d -s WhoNeedHelpWebView:D '*:S' \
|
||||
>"$output/webview-current.txt" 2>&1 || true
|
||||
|
||||
if grep -Fq "Main-frame load finished: path=/safety" "$output/webview-current.txt"; then
|
||||
safety_loaded=true
|
||||
break
|
||||
fi
|
||||
|
||||
attempt=$((attempt + 1))
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$safety_loaded" != true ]; then
|
||||
echo "The same-origin Android deep link did not finish loading /safety." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec "$container" adb shell uiautomator dump /sdcard/safety-window.xml \
|
||||
>"$output/safety-ui-dump-command.txt" 2>&1
|
||||
docker exec "$container" adb exec-out cat /sdcard/safety-window.xml \
|
||||
>"$output/safety-window.xml"
|
||||
docker exec "$container" adb exec-out screencap -p >"$output/safety.png"
|
||||
docker exec "$container" adb logcat -d >"$output/logcat-before-cleanup.txt"
|
||||
docker exec "$container" adb shell dumpsys activity activities \
|
||||
>"$output/activities-before-cleanup.txt"
|
||||
|
||||
if grep -Eqi \
|
||||
'Main-frame load failed|net::ERR_|ERR_CERT|SSL handshake failed|chromium.*crash' \
|
||||
"$output/logcat-before-cleanup.txt"; then
|
||||
echo "Android staging logcat contains a public-page load or TLS failure." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -Fq "$package/$activity" "$output/activities-before-cleanup.txt"; then
|
||||
echo "The staging activity was not observed after the deep-link load." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
printf 'run_id=%s\n' "$run_id"
|
||||
printf 'android_api=37.0\n'
|
||||
printf 'package=%s\n' "$package"
|
||||
printf 'public_origin=%s\n' "$WNH_BASE_URL"
|
||||
printf 'home_loaded=true\n'
|
||||
printf 'same_origin_manifest_filter=true\n'
|
||||
printf 'same_origin_deep_link_loaded=true\n'
|
||||
printf 'external_origin_manifest_filter=false\n'
|
||||
printf 'load_or_tls_errors=0\n'
|
||||
} >"$output/summary.txt"
|
||||
|
||||
echo "Android public staging smoke passed."
|
||||
echo "Evidence: $output"
|
||||
Loading…
Reference in New Issue
Block a user