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
|
but verified Android App Links additionally require the final signing
|
||||||
certificate fingerprint in the deployment's `/.well-known/assetlinks.json`.
|
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
|
## Reproducible Docker build
|
||||||
|
|
||||||
From the repository root:
|
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