#!/bin/bash
|
# Round01 session snapshot QA. It opens the Quick Switch session through the app
|
# entrypoint and verifies that the session froze a real Space/Window snapshot
|
# into a UI-facing report.
|
|
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-quick-switch-session-report.json"
|
OPEN_WAIT="${ALIGNER_ROUND1_SESSION_SNAPSHOT_OPEN_WAIT:-1.2}"
|
REPORT_WAIT="${ALIGNER_ROUND1_SESSION_SNAPSHOT_REPORT_WAIT:-6.0}"
|
|
fail() {
|
echo "Round01 session snapshot 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"
|
}
|
|
assert_report() {
|
/usr/bin/python3 - "$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)
|
|
def near(lhs, rhs, tolerance=0.01):
|
return abs((lhs or 0) - (rhs or 0)) <= tolerance
|
|
require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
|
require(report.get("displayCount", 0) >= 1, "displayCount must be >= 1")
|
require(report.get("spaceCount", 0) >= 1, "spaceCount must be >= 1")
|
require(report.get("appCount", -1) == report.get("columnCount", -2), "appCount must equal columnCount")
|
require(report.get("windowCount", 0) >= 1, "windowCount must be >= 1 on the QA desktop")
|
require(report.get("initialSelectionWindowID") is not None, "initialSelectionWindowID must exist when windows exist")
|
require(len(report.get("spaceLabels", [])) == report.get("spaceCount"), "spaceLabels count must match spaceCount")
|
require(report.get("snapshotRanOnMainThread") is False, "snapshot must not run on the main thread")
|
require(report.get("rootView", {}).get("viewClass") == "QuickSwitchRootView", "root view must be QuickSwitchRootView")
|
require(report.get("rootView", {}).get("isLayerBacked") is True, "root view must be layer-backed AppKit")
|
require(report.get("rootView", {}).get("waterfallFrame", {}).get("width", 0) > 0, "waterfall frame must be laid out")
|
root_view = report.get("rootView", {})
|
space_labels = report.get("spaceLabels", [])
|
lane_labels = root_view.get("spaceLaneLabels", [])
|
lane_segments = root_view.get("spaceLaneSegments", [])
|
root_bounds = root_view.get("bounds", {})
|
glass_frame = root_view.get("glassFrame", {})
|
safe_area_top_inset = root_view.get("safeAreaTopInset", 0)
|
safe_area_fill_frame = root_view.get("safeAreaFillFrame", {})
|
lane_frame = root_view.get("spaceLaneFrame", {})
|
shelf_frame = root_view.get("appShelfFrame", {})
|
waterfall_frame = root_view.get("waterfallFrame", {})
|
require(near(glass_frame.get("x"), root_bounds.get("x")), "Quick Switch glass layer x must match root bounds")
|
require(near(glass_frame.get("y"), root_bounds.get("y")), "Quick Switch glass layer y must match root bounds")
|
require(near(glass_frame.get("width"), root_bounds.get("width")), "Quick Switch glass layer width must match root bounds")
|
require(near(glass_frame.get("height"), root_bounds.get("height")), "Quick Switch glass layer height must match root bounds")
|
require(root_view.get("glassCornerRadius") == 0, "Quick Switch glass layer must not draw an outer rounded frame")
|
require(root_view.get("glassBorderWidth") == 0, "Quick Switch glass layer must not draw an outer border")
|
require(root_view.get("glassShadowOpacity") == 0, "Quick Switch glass layer must not draw an outer shadow")
|
require(safe_area_top_inset >= 0, "safeAreaTopInset must be non-negative")
|
if safe_area_top_inset > 0.5:
|
require(near(safe_area_fill_frame.get("height"), safe_area_top_inset), "safe-area fill height must match safeAreaTopInset")
|
require(near(safe_area_fill_frame.get("y") + safe_area_fill_frame.get("height"), root_bounds.get("height")), "safe-area fill must sit at the top edge")
|
require(root_view.get("spaceLaneSegmentCount") == report.get("spaceCount"), "Space Lane segment count must match spaceCount")
|
require(lane_labels == space_labels, "Space Lane labels must match report spaceLabels exactly")
|
require(len(set(lane_labels)) == len(lane_labels), "Space Lane labels must be unique")
|
require(len(lane_segments) == report.get("spaceCount"), "Space Lane segment reports must match spaceCount")
|
require([segment.get("label") for segment in lane_segments] == space_labels, "Space Lane segment reports must follow spaceLabels order")
|
require(all(isinstance(segment.get("windowCount"), int) for segment in lane_segments), "Space Lane segments must expose window counts")
|
require(all(isinstance(segment.get("appCount"), int) for segment in lane_segments), "Space Lane segments must expose app counts")
|
require(all(isinstance(segment.get("appNames"), list) for segment in lane_segments), "Space Lane segments must expose app names")
|
require(all(isinstance(segment.get("frame"), dict) for segment in lane_segments), "Space Lane segments must expose content frames")
|
require(all(isinstance(segment.get("visibleFrame"), dict) for segment in lane_segments), "Space Lane segments must expose visible frames")
|
current_labels = root_view.get("spaceLaneCurrentLabels", [])
|
fullscreen_labels = root_view.get("spaceLaneFullscreenLabels", [])
|
require(len(current_labels) >= 1, "Space Lane must mark at least one current Space")
|
require(set(current_labels).issubset(set(space_labels)), "current Space labels must be a subset of Space Lane labels")
|
require(set(fullscreen_labels).issubset(set(space_labels)), "fullscreen Space labels must be a subset of Space Lane labels")
|
require(len(fullscreen_labels) >= 1, "QA desktop must include at least one fullscreen Space in Space Lane")
|
fullscreen_segments = [segment for segment in lane_segments if segment.get("type") == "fullscreen"]
|
require([segment.get("label") for segment in fullscreen_segments] == fullscreen_labels, "fullscreen segment reports must match fullscreen labels")
|
require(all(segment.get("fullscreenMarkerVisible") is True for segment in fullscreen_segments), "fullscreen segments must expose fullscreen marker")
|
for segment in fullscreen_segments:
|
require("fullscreen" in segment.get("visualStates", []), "fullscreen segments must expose fullscreen visual state")
|
for segment in lane_segments:
|
if segment.get("isCurrent") is True:
|
require("current" in segment.get("visualStates", []), "current segment must expose current visual state")
|
if set(current_labels).intersection(fullscreen_labels):
|
for segment in lane_segments:
|
if segment.get("label") in set(current_labels).intersection(fullscreen_labels):
|
states = set(segment.get("visualStates", []))
|
require({"current", "fullscreen"}.issubset(states), "current fullscreen segment must expose both visual states")
|
require(lane_frame.get("width", 0) > lane_frame.get("height", 0) * 4, "Space Lane must be a top horizontal region")
|
require(lane_frame.get("y", 0) > shelf_frame.get("y", 0), "Space Lane must be above App Shelf")
|
require(shelf_frame.get("y", 0) > waterfall_frame.get("y", 0), "App Shelf must be above Waterfall")
|
lane_visible_width = root_view.get("spaceLaneVisibleWidth", 0)
|
lane_content_width = root_view.get("spaceLaneContentWidth", 0)
|
lane_max_scroll_offset = root_view.get("spaceLaneMaxScrollOffset", 0)
|
require(lane_visible_width == lane_frame.get("width", 0), "Space Lane visible width must match frame width")
|
require(lane_content_width >= lane_visible_width, "Space Lane content width must cover visible width")
|
require(lane_max_scroll_offset == max(0, lane_content_width - lane_visible_width), "Space Lane max scroll offset must match content overflow")
|
if lane_content_width > lane_visible_width:
|
require(root_view.get("spaceLaneScrollable") is True, "overflowing Space Lane must be scrollable")
|
require(lane_max_scroll_offset > 0, "overflowing Space Lane must expose positive max scroll offset")
|
else:
|
require(root_view.get("spaceLaneScrollable") is False, "non-overflowing Space Lane should not report scrollable")
|
app_shelf_items = root_view.get("appShelfItems", [])
|
app_shelf_names = root_view.get("appShelfNames", [])
|
require(root_view.get("appShelfItemCount") == report.get("appCount"), "App Shelf item count must match appCount")
|
require(app_shelf_names == report.get("appShelfNames", []), "App Shelf order must match report appShelfNames")
|
require(len(app_shelf_items) == report.get("appCount"), "App Shelf item reports must match appCount")
|
require([item.get("name") for item in app_shelf_items] == report.get("appShelfNames", []), "App Shelf item reports must follow appShelfNames order")
|
require(root_view.get("appShelfMaxRows") == 2, "App Shelf max rows must be 2")
|
require(1 <= root_view.get("appShelfRows", 0) <= 2, "App Shelf must use 1 or 2 rows when apps exist")
|
require(root_view.get("appShelfNormalIconSize") == 56, "App Shelf normal icon size must be 56pt")
|
require(root_view.get("appShelfHoverIconSize") == 68, "App Shelf hover icon size must be 68pt")
|
require(root_view.get("appShelfSelectedIconSize") == 68, "App Shelf selected icon size must be 68pt")
|
require(root_view.get("appShelfMinIconSize") == 36, "App Shelf min icon size must be 36pt")
|
icon_size = root_view.get("appShelfIconSize", 0)
|
require(36 <= icon_size <= 56, "App Shelf layout icon size must stay between 36pt and 56pt")
|
require(all(item.get("labelTruncationMode") == "middle" for item in app_shelf_items), "App Shelf labels must use middle truncation")
|
require(sum(1 for item in app_shelf_items if item.get("isSelected") is True) == 1, "App Shelf must expose one selected app")
|
require(all(isinstance(item.get("frame"), dict) for item in app_shelf_items), "App Shelf items must expose frames")
|
require(all(isinstance(item.get("iconFrame"), dict) for item in app_shelf_items), "App Shelf items must expose icon frames")
|
for item in app_shelf_items:
|
if item.get("isSelected") is True:
|
expected_icon_size = root_view.get("appShelfSelectedIconSize")
|
elif item.get("isHovered") is True:
|
expected_icon_size = root_view.get("appShelfHoverIconSize")
|
else:
|
expected_icon_size = icon_size
|
require(abs(item.get("iconFrame", {}).get("width", -1) - expected_icon_size) < 0.01, "App Shelf icon frame width must match visual icon size")
|
require(abs(item.get("iconFrame", {}).get("height", -1) - expected_icon_size) < 0.01, "App Shelf icon frame height must match visual icon size")
|
row_y_values = {round(item.get("frame", {}).get("y", 0), 2) for item in app_shelf_items}
|
require(len(row_y_values) == root_view.get("appShelfRows"), "App Shelf reported row count must match visible row y positions")
|
app_visible_width = root_view.get("appShelfVisibleWidth", 0)
|
app_content_width = root_view.get("appShelfContentWidth", 0)
|
app_max_scroll_offset = root_view.get("appShelfMaxScrollOffset", 0)
|
require(app_visible_width == shelf_frame.get("width", 0), "App Shelf visible width must match frame width")
|
require(app_content_width >= app_visible_width, "App Shelf content width must cover visible width")
|
require(app_max_scroll_offset == max(0, app_content_width - app_visible_width), "App Shelf max scroll offset must match content overflow")
|
if app_content_width > app_visible_width:
|
require(root_view.get("appShelfScrollable") is True, "overflowing App Shelf must be scrollable")
|
else:
|
require(root_view.get("appShelfScrollable") is False, "non-overflowing App Shelf should not report scrollable")
|
waterfall_columns = root_view.get("waterfallColumns", [])
|
waterfall_names = root_view.get("waterfallColumnNames", [])
|
require(root_view.get("waterfallColumnCount") == report.get("columnCount"), "Waterfall column count must match report columnCount")
|
require(root_view.get("waterfallColumnCount") == report.get("appCount"), "Waterfall column count must match appCount")
|
require(waterfall_names == app_shelf_names, "Waterfall column order must match App Shelf order")
|
require(len(waterfall_columns) == report.get("columnCount"), "Waterfall column reports must match columnCount")
|
require(root_view.get("waterfallClipsToBounds") is True, "Waterfall viewport must clip overflowing columns")
|
waterfall_visible_width = root_view.get("waterfallVisibleWidth", 0)
|
waterfall_content_width = root_view.get("waterfallContentWidth", 0)
|
waterfall_max_scroll_offset = root_view.get("waterfallMaxScrollOffset", 0)
|
require(waterfall_visible_width == waterfall_frame.get("width", 0), "Waterfall visible width must match frame width")
|
require(waterfall_content_width >= waterfall_visible_width, "Waterfall content width must cover visible width")
|
require(waterfall_max_scroll_offset == max(0, waterfall_content_width - waterfall_visible_width), "Waterfall max scroll offset must match content overflow")
|
if waterfall_content_width > waterfall_visible_width:
|
require(root_view.get("waterfallScrollable") is True, "overflowing Waterfall must be scrollable")
|
else:
|
require(root_view.get("waterfallScrollable") is False, "non-overflowing Waterfall should not report scrollable")
|
waterfall_card_count = 0
|
global_indexes = []
|
window_ids = []
|
low_information_card_keys = []
|
valid_thumbnail_strategies = {
|
"skeleton",
|
"skeletonPreferred",
|
"pendingScreenshot",
|
"realScreenshot",
|
"skeletonFallback",
|
}
|
valid_screenshot_sources = {
|
"pending",
|
"notRequested",
|
"realScreenshot",
|
"skeletonFallback",
|
}
|
for expected_index, column in enumerate(waterfall_columns):
|
require(column.get("appGroupIndex") == expected_index, "Waterfall appGroupIndex must be sequential")
|
require(column.get("appName") == app_shelf_names[expected_index], "Waterfall column app name must match App Shelf order")
|
require(isinstance(column.get("frame"), dict), "Waterfall columns must expose frames")
|
require(isinstance(column.get("visibleFrame"), dict), "Waterfall columns must expose visible frames")
|
require(isinstance(column.get("maxVerticalScrollOffset"), (int, float)), "Waterfall columns must expose vertical scroll range")
|
require(isinstance(column.get("verticalScrollOffset"), (int, float)), "Waterfall columns must expose vertical scroll offset")
|
cards = column.get("cards", [])
|
require(len(cards) == column.get("windowCount"), "Waterfall card count must match column windowCount")
|
require([card.get("windowIndex") for card in cards] == list(range(len(cards))), "Waterfall card windowIndex must be sequential per column")
|
for card in cards:
|
require(card.get("appGroupIndex") == expected_index, "Waterfall card appGroupIndex must match its column")
|
require(isinstance(card.get("globalIndex"), int), "Waterfall cards must expose globalIndex")
|
require(isinstance(card.get("windowID"), int), "Waterfall cards must expose windowID")
|
window_ids.append(card.get("windowID"))
|
if card.get("identifierSource") == "cgWindow" and card.get("titleLength") == 0:
|
low_information_card_keys.append((
|
column.get("bundleIdentifier"),
|
card.get("titleHash"),
|
card.get("titleLength"),
|
card.get("primarySpaceID"),
|
card.get("identifierSource"),
|
))
|
require(isinstance(card.get("frame"), dict), "Waterfall cards must expose frames")
|
require(isinstance(card.get("visibleFrame"), dict), "Waterfall cards must expose visible frames")
|
require(card.get("thumbnailStrategy") in valid_thumbnail_strategies, "Waterfall cards must expose valid thumbnail strategy")
|
require(card.get("screenshotSource") in valid_screenshot_sources, "Waterfall cards must expose valid screenshot source")
|
if card.get("screenshotSource") == "skeletonFallback":
|
require(card.get("screenshotFallbackReason") is not None, "skeleton fallback cards must expose fallback reason")
|
if card.get("screenshotSource") == "notRequested":
|
require(card.get("screenshotNotRequestedReason") is not None, "not-requested screenshot cards must expose reason")
|
require(isinstance(card.get("thumbnailFrame"), dict), "Waterfall cards must expose thumbnail frames")
|
require(isinstance(card.get("appIconFrame"), dict), "Waterfall cards must expose App icon frames")
|
require(isinstance(card.get("visualStates"), list), "Waterfall cards must expose visual states")
|
require(card.get("titleTruncationMode") == "middle", "Waterfall card titles must use middle truncation")
|
global_indexes.append(card.get("globalIndex"))
|
waterfall_card_count += len(cards)
|
require(waterfall_card_count == report.get("windowCount"), "Waterfall cards must cover report windowCount")
|
require(len(window_ids) == len(set(window_ids)), "Waterfall windowIDs must be unique")
|
require(
|
len(low_information_card_keys) == len(set(low_information_card_keys)),
|
"Waterfall must not expose duplicate low-information CG window candidates"
|
)
|
require(isinstance(root_view.get("screenshotEligibleCount"), int), "root view must expose screenshotEligibleCount")
|
require(isinstance(root_view.get("screenshotResolvedCount"), int), "root view must expose screenshotResolvedCount")
|
require(isinstance(root_view.get("screenshotPendingCount"), int), "root view must expose screenshotPendingCount")
|
require(isinstance(root_view.get("screenshotNotRequestedCount"), int), "root view must expose screenshotNotRequestedCount")
|
require(root_view.get("screenshotResolvedCount") + root_view.get("screenshotPendingCount") == root_view.get("screenshotEligibleCount"), "resolved + pending screenshots must match eligible count")
|
require(root_view.get("screenshotEligibleCount") + root_view.get("screenshotNotRequestedCount") == waterfall_card_count, "eligible + not-requested screenshots must cover all cards")
|
require(global_indexes == sorted(global_indexes), "Waterfall global indexes must be monotonic across columns")
|
selected_cards = [
|
card
|
for column in waterfall_columns
|
for card in column.get("cards", [])
|
if card.get("isSelected") is True
|
]
|
require(len(selected_cards) == 1, "Waterfall must expose exactly one selected card")
|
require("selected" in selected_cards[0].get("visualStates", []), "selected Waterfall card must expose selected visual state")
|
require(selected_cards[0].get("shineVisible") is True, "selected Waterfall card must expose shine layer")
|
require(selected_cards[0].get("zPosition", 0) > 0, "selected Waterfall card must be above normal cards")
|
require(isinstance(report.get("overlayOpenElapsedMilliseconds"), (int, float)), "overlay open timing must exist")
|
require(isinstance(report.get("snapshotStartElapsedMilliseconds"), (int, float)), "snapshot start timing must exist")
|
require(report["snapshotStartElapsedMilliseconds"] >= report["overlayOpenElapsedMilliseconds"], "snapshot must start after overlay open path")
|
require(isinstance(report.get("snapshotDurationMilliseconds"), (int, float)), "snapshot duration timing must exist")
|
|
print(json.dumps(report, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
click_target_from_report() {
|
/usr/bin/python3 - "$REPORT" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
root = report.get("rootView", {})
|
for column in root.get("waterfallColumns", []):
|
for card in column.get("cards", []):
|
if card.get("identifierSource") == "cgWindow" and card.get("isMinimized") is False:
|
print(f"{card.get('appGroupIndex')}:{card.get('windowIndex')}:{card.get('windowID')}")
|
sys.exit(0)
|
|
print("no non-minimized cgWindow card available for click activation QA", file=sys.stderr)
|
sys.exit(1)
|
PY
|
}
|
|
wait_for_click_report() {
|
/usr/bin/python3 - "$REPORT" "$REPORT_WAIT" <<'PY'
|
import json
|
import sys
|
import time
|
|
path = sys.argv[1]
|
timeout = float(sys.argv[2])
|
deadline = time.monotonic() + timeout
|
last_report = None
|
|
while time.monotonic() < deadline:
|
try:
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
last_report = report
|
if report.get("lastCommitSource") == "mouse" and report.get("lastActivationResult") is not None:
|
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"click activation report did not complete within {timeout:.1f}s", file=sys.stderr)
|
sys.exit(1)
|
PY
|
}
|
|
assert_click_report() {
|
/usr/bin/python3 - "$REPORT" "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
expected_window_id = int(sys.argv[2])
|
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)
|
|
require(report.get("lastCommitSource") == "mouse", "click activation source must be mouse")
|
require(report.get("lastActivationWindowID") == expected_window_id, "click activation must target the selected card windowID")
|
require(report.get("lastActivationResult") == "activated", "click activation must activate the target window")
|
print(json.dumps({
|
"lastCommitSource": report.get("lastCommitSource"),
|
"lastActivationWindowID": report.get("lastActivationWindowID"),
|
"lastActivationResult": report.get("lastActivationResult"),
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
wait_for_loaded_report() {
|
/usr/bin/python3 - "$REPORT" "$REPORT_WAIT" <<'PY'
|
import json
|
import sys
|
import time
|
|
path = sys.argv[1]
|
timeout = float(sys.argv[2])
|
deadline = time.monotonic() + timeout
|
last_report = None
|
|
while time.monotonic() < deadline:
|
try:
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
last_report = report
|
if report.get("snapshotLoaded") is True:
|
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"snapshot report did not become loaded within {timeout:.1f}s", file=sys.stderr)
|
sys.exit(1)
|
PY
|
}
|
|
stop_current_aligner
|
"$SCRIPT_DIR/package-app.sh" >&2
|
rm -f "$REPORT"
|
|
"$APP/Contents/MacOS/Aligner" \
|
--round0-skip-permissions \
|
--round01-open-quick-switch \
|
--round01-quick-switch-report="$REPORT" &
|
|
APP_PID=$!
|
|
cleanup() {
|
if kill -0 "$APP_PID" 2>/dev/null; then
|
kill "$APP_PID" 2>/dev/null || true
|
wait "$APP_PID" 2>/dev/null || true
|
fi
|
}
|
trap cleanup EXIT
|
|
sleep "$OPEN_WAIT"
|
|
swift "$SCRIPT_DIR/window-logic-qa.swift" \
|
--expect-quick-switch \
|
--expect-single-aligner-overlay \
|
--expect-overlay-on-mouse-screen \
|
--expect-overlay-uses-screen-frame
|
|
wait_for_loaded_report
|
assert_report
|
CLICK_TARGET="$(click_target_from_report)"
|
|
cleanup
|
trap - EXIT
|
|
rm -f "$REPORT"
|
IFS=: read -r CLICK_APP_INDEX CLICK_WINDOW_INDEX CLICK_WINDOW_ID <<<"$CLICK_TARGET"
|
"$APP/Contents/MacOS/Aligner" \
|
--round0-skip-permissions \
|
--round01-open-quick-switch \
|
--round01-disable-screenshot-refresh \
|
--round01-debug-mouse-sequence="click-card:$CLICK_APP_INDEX:$CLICK_WINDOW_INDEX" \
|
--round01-quick-switch-report="$REPORT" &
|
|
APP_PID=$!
|
trap cleanup EXIT
|
wait_for_click_report
|
assert_click_report "$CLICK_WINDOW_ID"
|
cleanup
|
trap - EXIT
|
|
swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch
|