#!/bin/bash
|
# Round01 multi-page identity fixture QA. It verifies that Quick Switch keeps
|
# same-App/same-title pages distinct for activation and close requests.
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
OUTPUT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
# shellcheck source=build-output-paths.sh
|
source "$SCRIPT_DIR/build-output-paths.sh"
|
APP="$BUILD_CURRENT_APP"
|
BUNDLE_ID="com.ar.Aligner"
|
CONFIRM_DEFAULTS_KEY="QuickSwitchConfirmBeforeClose"
|
REPORT_WAIT="${ALIGNER_ROUND1_MULTI_PAGE_REPORT_WAIT:-7.0}"
|
ACTIVATION_REPORT="$BUILD_REPORT_ROOT/round01-multi-page-activation-report.json"
|
CLOSE_REPORT="$BUILD_REPORT_ROOT/round01-multi-page-close-report.json"
|
|
fail() {
|
echo "Round01 multi-page identity fixture QA failed: $*" >&2
|
exit 1
|
}
|
|
aligner_pids_for_current_app() {
|
ps -axo pid=,args= | while read -r pid command; do
|
case "$command" in
|
"$APP/Contents/MacOS/Aligner"*) echo "$pid" ;;
|
esac
|
done
|
}
|
|
stop_current_aligner() {
|
for pid in $(aligner_pids_for_current_app); do
|
kill "$pid" 2>/dev/null || true
|
done
|
|
for _ in {1..30}; do
|
[ -z "$(aligner_pids_for_current_app)" ] && return
|
sleep 0.1
|
done
|
|
fail "current Aligner app did not exit before QA"
|
}
|
|
reset_close_confirmation_preference() {
|
defaults delete "$BUNDLE_ID" "$CONFIRM_DEFAULTS_KEY" >/dev/null 2>&1 || true
|
}
|
|
wait_for_report() {
|
local report="$1"
|
local mode="$2"
|
/usr/bin/python3 - "$report" "$REPORT_WAIT" "$mode" <<'PY'
|
import json
|
import sys
|
import time
|
|
path = sys.argv[1]
|
timeout = float(sys.argv[2])
|
mode = sys.argv[3]
|
deadline = time.monotonic() + timeout
|
last_report = None
|
|
def ready(report):
|
root = report.get("rootView", {})
|
if report.get("snapshotLoaded") is not True:
|
return False
|
if mode == "activated":
|
return (
|
report.get("quickSwitchVisible") is False
|
and report.get("lastActivationResult") is not None
|
)
|
if mode == "verifiedFailure":
|
return (
|
report.get("quickSwitchVisible") is True
|
and report.get("lastCloseResult") is not None
|
and root.get("closeFeedbackKind") == "failure"
|
)
|
return False
|
|
while time.monotonic() < deadline:
|
try:
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
last_report = report
|
if ready(report):
|
sys.exit(0)
|
except FileNotFoundError:
|
pass
|
except json.JSONDecodeError:
|
pass
|
time.sleep(0.2)
|
|
if last_report is not None:
|
print(json.dumps(last_report, indent=2, ensure_ascii=False), file=sys.stderr)
|
print(f"multi-page identity report {path} did not reach mode={mode} within {timeout:.1f}s", file=sys.stderr)
|
sys.exit(1)
|
PY
|
}
|
|
run_case() {
|
local report="$1"
|
local sequence="$2"
|
local mode="$3"
|
local activation_flag="${4:-}"
|
local close_flag="${5:-}"
|
|
stop_current_aligner
|
reset_close_confirmation_preference
|
rm -f "$report"
|
|
local args=(
|
--round0-skip-permissions
|
--round01-open-quick-switch
|
--round01-waterfall-view-mode=vertical
|
--round01-fixture-multi-page-identity
|
--round01-disable-screenshot-refresh
|
--round01-debug-mouse-sequence="$sequence"
|
--round01-quick-switch-report="$report"
|
)
|
if [ -n "$activation_flag" ]; then
|
args+=(--round01-debug-window-activation)
|
fi
|
if [ -n "$close_flag" ]; then
|
args+=(--round01-debug-window-close)
|
fi
|
|
"$APP/Contents/MacOS/Aligner" "${args[@]}" &
|
APP_PID=$!
|
|
cleanup() {
|
kill "$APP_PID" 2>/dev/null || true
|
wait "$APP_PID" 2>/dev/null || true
|
stop_current_aligner
|
}
|
trap cleanup EXIT
|
|
wait_for_report "$report" "$mode"
|
kill "$APP_PID" 2>/dev/null || true
|
wait "$APP_PID" 2>/dev/null || true
|
trap - EXIT
|
stop_current_aligner
|
}
|
|
assert_activation_report() {
|
/usr/bin/python3 - "$ACTIVATION_REPORT" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
columns = root.get("waterfallColumns", [])
|
cards = columns[0].get("cards", []) if columns else []
|
ids = [card.get("windowID") for card in cards]
|
|
require(report.get("snapshotLoaded") is True, "activation fixture must load")
|
require(report.get("quickSwitchVisible") is False, "activation must close Quick Switch")
|
require(report.get("appCount") == 1, "multi-page fixture must expose one App")
|
require(report.get("windowCount") == 3, "multi-page fixture must expose three pages")
|
require(ids == [70101, 70102, 70103], "same-title pages must keep stable windowID order")
|
require(report.get("lastCommittedAppGroupIndex") == 0, "activation must target App group 0")
|
require(report.get("lastCommittedWindowIndex") == 1, "activation must target second page index")
|
require(report.get("lastCommittedWindowID") == 70102, "activation commit must target page windowID 70102")
|
require(report.get("lastActivationWindowID") == 70102, "activation service must receive exact page windowID 70102")
|
require(report.get("lastActivationResult") == "activated", "debug activation must report activated")
|
require(root.get("selectedWindowID") == 70102, "selectedWindowID must track exact activated page")
|
|
print(json.dumps({
|
"case": "multiPageActivation",
|
"windowIDs": ids,
|
"lastActivationWindowID": report.get("lastActivationWindowID")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_close_report() {
|
/usr/bin/python3 - "$CLOSE_REPORT" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
columns = root.get("waterfallColumns", [])
|
cards = columns[0].get("cards", []) if columns else []
|
ids = [card.get("windowID") for card in cards]
|
|
require(report.get("snapshotLoaded") is True, "close fixture must load")
|
require(report.get("quickSwitchVisible") is True, "close verification failure must keep Quick Switch visible")
|
require(report.get("appCount") == 1, "multi-page close fixture must expose one App")
|
require(report.get("windowCount") == 3, "fixture must still expose all three pages when debug close does not remove them")
|
require(ids == [70101, 70102, 70103], "close must not reorder or fake-remove same-title pages")
|
require(report.get("lastCloseTargetKind") == "window", "close must target a window/page")
|
require(report.get("lastCloseAppGroupIndex") == 0, "close must target App group 0")
|
require(report.get("lastCloseWindowID") == 70103, "close service must receive exact page windowID 70103")
|
require(report.get("lastCloseResult") == "requested", "debug close must report requested")
|
require(report.get("suppressedCloseTargetCount") == 0, "requested-but-still-present must not suppress the card")
|
require(root.get("closeFeedbackKind") == "failure", "still-present debug close must show verification failure")
|
|
print(json.dumps({
|
"case": "multiPageClose",
|
"windowIDs": ids,
|
"lastCloseWindowID": report.get("lastCloseWindowID"),
|
"closeFeedbackKind": root.get("closeFeedbackKind")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
stop_current_aligner
|
"$SCRIPT_DIR/package-app.sh" >&2
|
|
run_case "$ACTIVATION_REPORT" "click-card:0:1" "activated" "activation" ""
|
assert_activation_report
|
|
run_case "$CLOSE_REPORT" "click-card-close:0:2,confirm-close" "verifiedFailure" "" "close"
|
assert_close_report
|
|
echo "Round01 multi-page identity fixture QA passed."
|