#!/bin/bash
|
# Round01 fullscreen overlay QA. It opens a temporary fullscreen host window,
|
# then verifies Quick Switch can appear above the fullscreen Space and close.
|
|
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"
|
REPORT="$BUILD_REPORT_ROOT/round01-fullscreen-overlay-report.json"
|
WORK_DIR="$(mktemp -d /tmp/aligner-round01-fullscreen-qa.XXXXXX)"
|
READY_FILE="$WORK_DIR/fullscreen-ready.txt"
|
HOST_LOG="$WORK_DIR/fullscreen-host.log"
|
ALIGNER_LOG="$WORK_DIR/aligner.log"
|
TARGET_SCREEN_INDEX="${ALIGNER_ROUND1_FULLSCREEN_SCREEN_INDEX:-0}"
|
MOUSE_SCREEN_INDEX="${ALIGNER_ROUND1_FULLSCREEN_MOUSE_SCREEN_INDEX:-$TARGET_SCREEN_INDEX}"
|
START_WAIT="${ALIGNER_ROUND1_FULLSCREEN_START_WAIT:-1.6}"
|
OPEN_WAIT="${ALIGNER_ROUND1_FULLSCREEN_OPEN_WAIT:-1.0}"
|
CLOSE_WAIT="${ALIGNER_ROUND1_FULLSCREEN_CLOSE_WAIT:-0.8}"
|
|
fail() {
|
echo "Round01 fullscreen overlay QA failed: $*" >&2
|
exit 1
|
}
|
|
post_key_with_system_events() {
|
local shortcut="$1"
|
case "$shortcut" in
|
escape)
|
osascript -e 'tell application "System Events" to key code 53'
|
;;
|
*)
|
fail "unsupported System Events shortcut: $shortcut"
|
;;
|
esac
|
}
|
|
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"
|
}
|
|
wait_for_file() {
|
local path="$1"
|
local timeout="$2"
|
/usr/bin/python3 - "$path" "$timeout" <<'PY'
|
import pathlib
|
import sys
|
import time
|
|
path = pathlib.Path(sys.argv[1])
|
timeout = float(sys.argv[2])
|
deadline = time.monotonic() + timeout
|
while time.monotonic() < deadline:
|
if path.exists() and path.stat().st_size > 0:
|
print(path.read_text(encoding="utf-8"), end="")
|
sys.exit(0)
|
time.sleep(0.1)
|
sys.exit(1)
|
PY
|
}
|
|
require_fullscreen_ready() {
|
local path="$1"
|
/usr/bin/python3 - "$path" <<'PY'
|
import pathlib
|
import sys
|
|
text = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")
|
if "readyReason=windowDidEnterFullScreen" not in text:
|
print(text[-6000:], end="", file=sys.stderr)
|
sys.exit(1)
|
print(text[-6000:], end="")
|
PY
|
}
|
|
require_fullscreen_target_screen() {
|
local path="$1"
|
/usr/bin/python3 - "$path" <<'PY'
|
import pathlib
|
import re
|
import sys
|
|
text = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
def frame(name):
|
match = re.search(rf"^{name}=x:([-0-9]+),y:([-0-9]+),w:([0-9]+),h:([0-9]+)$", text, re.MULTILINE)
|
if not match:
|
print(text[-6000:], end="", file=sys.stderr)
|
print(f"missing {name}", file=sys.stderr)
|
sys.exit(1)
|
return tuple(int(value) for value in match.groups())
|
|
requested = frame("requestedScreenFrame")
|
target = frame("targetScreenFrame")
|
if requested != target:
|
print(text[-6000:], end="", file=sys.stderr)
|
print(f"fullscreen host entered wrong screen: requested={requested} target={target}", file=sys.stderr)
|
sys.exit(1)
|
|
print(f"fullscreen host target screen verified: {target}")
|
PY
|
}
|
|
wait_for_no_quick_switch() {
|
local timeout="$1"
|
/usr/bin/python3 - "$SCRIPT_DIR" "$timeout" <<'PY'
|
import subprocess
|
import sys
|
import time
|
|
script_dir = sys.argv[1]
|
timeout = float(sys.argv[2])
|
deadline = time.monotonic() + timeout
|
last_output = ""
|
|
while time.monotonic() < deadline:
|
result = subprocess.run(
|
["swift", f"{script_dir}/window-logic-qa.swift", "--expect-no-quick-switch"],
|
text=True,
|
stdout=subprocess.PIPE,
|
stderr=subprocess.STDOUT,
|
)
|
last_output = result.stdout
|
if result.returncode == 0:
|
print(last_output[-6000:], end="")
|
sys.exit(0)
|
time.sleep(0.2)
|
|
print(last_output[-6000:], end="")
|
sys.exit(1)
|
PY
|
}
|
|
run_window_logic_check() {
|
swift "$SCRIPT_DIR/window-logic-qa.swift" "$@" 2>&1 | head -c 6000
|
local status="${PIPESTATUS[0]}"
|
return "$status"
|
}
|
|
cleanup() {
|
post_key_with_system_events escape 2>/dev/null || true
|
if [ -n "${ALIGNER_PID:-}" ] && kill -0 "$ALIGNER_PID" 2>/dev/null; then
|
kill "$ALIGNER_PID" 2>/dev/null || true
|
wait "$ALIGNER_PID" 2>/dev/null || true
|
fi
|
if [ -n "${HOST_PID:-}" ] && kill -0 "$HOST_PID" 2>/dev/null; then
|
kill "$HOST_PID" 2>/dev/null || true
|
wait "$HOST_PID" 2>/dev/null || true
|
fi
|
rm -rf "$WORK_DIR"
|
}
|
|
trap cleanup EXIT
|
|
[ -x "$APP/Contents/MacOS/Aligner" ] || fail "missing built app: $APP"
|
|
stop_current_aligner
|
rm -f "$REPORT"
|
|
echo "preparing fullscreen host screen index: $TARGET_SCREEN_INDEX"
|
swift "$SCRIPT_DIR/round1-move-mouse-to-screen-center.swift" "$TARGET_SCREEN_INDEX" 2>&1 | head -c 6000
|
|
swift "$SCRIPT_DIR/round1-fullscreen-host.swift" \
|
--ready-file="$READY_FILE" \
|
--screen-index="$TARGET_SCREEN_INDEX" >"$HOST_LOG" 2>&1 &
|
HOST_PID=$!
|
|
if ! wait_for_file "$READY_FILE" 16.0; then
|
tail -c 6000 "$HOST_LOG" >&2 || true
|
fail "fullscreen host did not become ready"
|
fi
|
if ! require_fullscreen_ready "$READY_FILE"; then
|
fail "fullscreen host did not report windowDidEnterFullScreen"
|
fi
|
if ! require_fullscreen_target_screen "$READY_FILE"; then
|
fail "fullscreen host did not enter requested screen"
|
fi
|
|
echo "fullscreen host screen index: $TARGET_SCREEN_INDEX"
|
echo "quick switch mouse screen index: $MOUSE_SCREEN_INDEX"
|
swift "$SCRIPT_DIR/round1-move-mouse-to-screen-center.swift" "$MOUSE_SCREEN_INDEX" 2>&1 | head -c 6000
|
|
"$APP/Contents/MacOS/Aligner" \
|
--round0-skip-permissions \
|
--round01-open-quick-switch \
|
--round01-quick-switch-report="$REPORT" >"$ALIGNER_LOG" 2>&1 &
|
ALIGNER_PID=$!
|
|
sleep "$START_WAIT"
|
sleep "$OPEN_WAIT"
|
|
run_window_logic_check \
|
--expect-quick-switch \
|
--expect-single-aligner-overlay \
|
--expect-overlay-on-mouse-screen \
|
--expect-overlay-uses-screen-frame
|
|
post_key_with_system_events escape
|
sleep "$CLOSE_WAIT"
|
wait_for_no_quick_switch 5.0
|
|
kill "$ALIGNER_PID" 2>/dev/null || true
|
wait "$ALIGNER_PID" 2>/dev/null || true
|
kill "$HOST_PID" 2>/dev/null || true
|
wait "$HOST_PID" 2>/dev/null || true
|
trap - EXIT
|
rm -rf "$WORK_DIR"
|
|
echo "Round01 fullscreen overlay QA passed"
|