#!/bin/bash # Round01 real-desktop QA for Finder tabs/pages. It verifies that CG-only # Finder pages inherit the high-confidence fullscreen Space from their visible # host, and that clicking such a card actually selects the matching Finder tab # instead of only reporting a WindowServer activation success. 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" RUN_ID="$(date +%Y%m%d_%H%M%S)" REPORT_DIR="$BUILD_REPORT_ROOT/round01-finder-tabs-live-$RUN_ID" DEV_LOG="$HOME/Library/Logs/Aligner/aligner-dev.log" REPORT_WAIT="${ALIGNER_ROUND1_FINDER_TABS_REPORT_WAIT:-35.0}" fail() { echo "Round01 Finder tabs live 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" } dev_log_size() { if [ -f "$DEV_LOG" ]; then stat -f%z "$DEV_LOG" else echo 0 fi } extract_dev_log_since() { local offset="$1" local output="$2" if [ ! -f "$DEV_LOG" ]; then : >"$output" return fi dd if="$DEV_LOG" bs=1 skip="$offset" 2>/dev/null >"$output" || : >"$output" } 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): if report.get("snapshotLoaded") is not True: return False if mode == "loaded": return True if mode == "activated": return ( report.get("quickSwitchVisible") is False and report.get("lastActivationResult") is not None ) 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"Finder tabs report {path} did not reach mode={mode} within {timeout:.1f}s", file=sys.stderr) sys.exit(1) PY } run_snapshot_dump() { local snapshot="$1" local stderr_log="$2" stop_current_aligner ALIGNER_DIAGNOSTICS_INCLUDE_SENSITIVE=1 \ "$APP/Contents/MacOS/Aligner" \ --round0-skip-permissions \ --round01-dump-window-snapshot \ --round01-dump-window-snapshot-pretty >"$snapshot" 2>"$stderr_log" } run_report() { local report="$1" local app_log="$2" stop_current_aligner rm -f "$report" ALIGNER_DIAGNOSTICS_INCLUDE_SENSITIVE=1 \ "$APP/Contents/MacOS/Aligner" \ --round0-skip-permissions \ --round01-open-quick-switch \ --round01-disable-screenshot-refresh \ --round01-quick-switch-auto-hide-after=20.0 \ --round01-quick-switch-quit-after=21.0 \ --round01-quick-switch-report="$report" >"$app_log" 2>&1 & local app_pid="$!" if ! wait_for_report "$report" "loaded"; then kill "$app_pid" 2>/dev/null || true wait "$app_pid" 2>/dev/null || true return 1 fi kill "$app_pid" 2>/dev/null || true wait "$app_pid" 2>/dev/null || true } run_click() { local window_id="$1" local report="$2" local app_log="$3" stop_current_aligner rm -f "$report" ALIGNER_DIAGNOSTICS_INCLUDE_SENSITIVE=1 \ "$APP/Contents/MacOS/Aligner" \ --round0-skip-permissions \ --round01-open-quick-switch \ --round01-disable-screenshot-refresh \ --round01-debug-mouse-sequence="click-card-id:$window_id" \ --round01-quick-switch-quit-after=34.0 \ --round01-quick-switch-report="$report" >"$app_log" 2>&1 & local app_pid="$!" if ! wait_for_report "$report" "activated"; then kill "$app_pid" 2>/dev/null || true wait "$app_pid" 2>/dev/null || true return 1 fi wait "$app_pid" 2>/dev/null || true } focused_finder_window_report() { local output="$1" swift - "$output" <<'SWIFT' import AppKit import ApplicationServices import Foundation func fail(_ message: String, code: Int32 = 2) -> Never { fputs(message + "\n", stderr) exit(code) } func jsonString(_ value: String) -> String { let data = try! JSONSerialization.data(withJSONObject: ["value": value], options: []) let text = String(data: data, encoding: .utf8)! let prefix = "{\"value\":\"" let suffix = "\"}" return String(text.dropFirst(prefix.count).dropLast(suffix.count)) } func stableFingerprint(_ text: String) -> String { var hash: UInt64 = 0xcbf29ce484222325 for byte in text.utf8 { hash ^= UInt64(byte) hash = hash &* 0x100000001b3 } return String(format: "%016llx", hash) } func title(of element: AXUIElement) -> String? { var value: CFTypeRef? guard AXUIElementCopyAttributeValue(element, kAXTitleAttribute as CFString, &value) == .success else { return nil } return value as? String } func focusedWindowID(_ element: AXUIElement) -> UInt32? { var value: CFTypeRef? if AXUIElementCopyAttributeValue(element, "AXWindowNumber" as CFString, &value) == .success, let number = value as? NSNumber { return number.uint32Value } return nil } let outputPath = CommandLine.arguments[1] guard let finder = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == "com.apple.finder" }) else { fail("Finder is not running") } let appElement = AXUIElementCreateApplication(finder.processIdentifier) var windowValue: CFTypeRef? var axError = AXUIElementCopyAttributeValue(appElement, kAXFocusedWindowAttribute as CFString, &windowValue) if axError != .success || windowValue == nil || CFGetTypeID(windowValue!) != AXUIElementGetTypeID() { axError = AXUIElementCopyAttributeValue(appElement, kAXMainWindowAttribute as CFString, &windowValue) } guard axError == .success, let rawWindow = windowValue, CFGetTypeID(rawWindow) == AXUIElementGetTypeID() else { fail("Finder focused/main window is unavailable: \(axError)") } let window = rawWindow as! AXUIElement let windowTitle = title(of: window) ?? "" let output = """ { "bundleIdentifier": "com.apple.finder", "pid": \(finder.processIdentifier), "focusedWindowID": \(focusedWindowID(window).map(String.init) ?? "null"), "title": "\(jsonString(windowTitle))", "titleLength": \(windowTitle.count), "titleHash": "\(stableFingerprint(windowTitle))" } """ try output.write(toFile: outputPath, atomically: true, encoding: .utf8) SWIFT } extract_attributed_ids() { local log_file="$1" /usr/bin/python3 - "$log_file" <<'PY' import re import sys ids = [] for line in open(sys.argv[1], "r", encoding="utf-8", errors="replace"): if "event=windowEnumeration.finderTabSpaceAttribution" not in line: continue match = re.search(r'attributedWindowIDs="?(\[[^\]"]*\])"?', line) if not match: continue ids = [int(value) for value in re.findall(r"\d+", match.group(1))] print(",".join(str(value) for value in sorted(set(ids)))) PY } select_targets() { local snapshot="$1" local report="$2" local attributed_ids_csv="$3" local output="$4" /usr/bin/python3 - "$snapshot" "$report" "$attributed_ids_csv" "$output" <<'PY' import json import os import sys snapshot_path, report_path, ids_csv, output_path = sys.argv[1:5] attributed_ids = {int(value) for value in ids_csv.split(",") if value} max_targets = max(1, int(os.environ.get("ALIGNER_ROUND1_FINDER_TABS_MAX_TARGETS", "4"))) with open(snapshot_path, "r", encoding="utf-8") as file: snapshot = json.load(file) with open(report_path, "r", encoding="utf-8") as file: report = json.load(file) def require(condition, message): if not condition: print(message, file=sys.stderr) sys.exit(4) snapshot_windows = {window.get("id"): window for window in snapshot.get("windows", [])} finder_candidates = [ window for window_id, window in snapshot_windows.items() if window.get("app", {}).get("bundleIdentifier") == "com.apple.finder" and window.get("identifierSource") == "cgWindow" and window.get("title") ] require(finder_candidates, "snapshot must contain at least one Finder CG-only tab/page") root = report.get("rootView", {}) cards = [] for column in root.get("waterfallColumns", []): if column.get("bundleIdentifier") != "com.apple.finder": continue for card in column.get("cards", []): window = snapshot_windows.get(card.get("windowID"), {}) if window.get("app", {}).get("bundleIdentifier") != "com.apple.finder": continue if window.get("identifierSource") != "cgWindow": continue visible = card.get("visibleFrame") or {} if visible.get("width", 0) <= 1 or visible.get("height", 0) <= 1: continue title = card.get("title") or snapshot_windows.get(card.get("windowID"), {}).get("title") or "" if not title.strip(): continue cards.append((column, card, title)) require(cards, "Quick Switch report must expose at least one visible Finder CG tab/page card") title_counts = {} for _, _, title in cards: title_counts[title] = title_counts.get(title, 0) + 1 cards.sort(key=lambda item: ( 0 if not (snapshot_windows.get(item[1].get("windowID"), {}).get("spaceIDs") or []) else 1, 0 if title_counts[item[2]] == 1 else 1, item[1].get("globalIndex", 10**9), item[1].get("windowID", 10**9), )) targets = [] seen_window_ids = set() for column, card, title in cards: window_id = card["windowID"] if window_id in seen_window_ids: continue seen_window_ids.add(window_id) targets.append({ "windowID": window_id, "appGroupIndex": column["appGroupIndex"], "windowIndex": card["windowIndex"], "primarySpaceID": card.get("primarySpaceID"), "hasAttributedSpace": window_id in attributed_ids, "snapshotSpaceIDs": snapshot_windows.get(window_id, {}).get("spaceIDs") or [], "title": title, "titleLength": len(title), "titleHash": card.get("titleHash") or snapshot_windows.get(window_id, {}).get("titleHash"), "visibleFrame": card.get("visibleFrame"), }) if len(targets) >= max_targets: break require(targets, "Quick Switch report must expose at least one selectable Finder tab/page target") with open(output_path, "w", encoding="utf-8") as file: json.dump(targets, file, indent=2, ensure_ascii=False) print(f"TARGET_COUNT={len(targets)}") PY } target_at_index() { local targets="$1" local index="$2" local output="$3" /usr/bin/python3 - "$targets" "$index" "$output" <<'PY' import json import sys targets_path, index_text, output_path = sys.argv[1:4] index = int(index_text) with open(targets_path, "r", encoding="utf-8") as file: targets = json.load(file) if index < 0 or index >= len(targets): print(f"target index {index} is out of range", file=sys.stderr) sys.exit(4) target = targets[index] with open(output_path, "w", encoding="utf-8") as file: json.dump(target, file, indent=2, ensure_ascii=False) print(f"TARGET_WINDOW_ID={target['windowID']}") print(f"TARGET_SPACE_ID={target.get('primarySpaceID')}") PY } assert_click_report() { local report="$1" local target="$2" /usr/bin/python3 - "$report" "$target" <<'PY' import json import sys with open(sys.argv[1], "r", encoding="utf-8") as file: report = json.load(file) with open(sys.argv[2], "r", encoding="utf-8") as file: target = json.load(file) def require(condition, message): if not condition: print(message, file=sys.stderr) print(json.dumps({ "targetWindowID": target.get("windowID"), "lastCommittedWindowID": report.get("lastCommittedWindowID"), "lastActivationWindowID": report.get("lastActivationWindowID"), "lastActivationResult": report.get("lastActivationResult"), "lastActivationError": report.get("lastActivationError"), "quickSwitchVisible": report.get("quickSwitchVisible"), }, indent=2, ensure_ascii=False), file=sys.stderr) sys.exit(5) require(report.get("lastCommittedWindowID") == target["windowID"], "commit must target the clicked Finder card") require(report.get("lastActivationWindowID") == target["windowID"], "activation must receive exact Finder tab/page windowID") require(report.get("lastActivationResult") == "activated", "activation result must be activated") require(report.get("quickSwitchVisible") is False, "Quick Switch must close after click activation") PY } assert_focused_finder_title() { local target="$1" local focused="$2" /usr/bin/python3 - "$target" "$focused" <<'PY' import json import sys with open(sys.argv[1], "r", encoding="utf-8") as file: target = json.load(file) with open(sys.argv[2], "r", encoding="utf-8") as file: focused = json.load(file) target_title = target.get("title", "").strip() focused_title = focused.get("title", "").strip() if target_title != focused_title: print("Focused Finder window title must match clicked tab/page title", file=sys.stderr) print(json.dumps({ "targetWindowID": target.get("windowID"), "targetTitleHash": target.get("titleHash"), "targetTitleLength": target.get("titleLength"), "focusedWindowID": focused.get("focusedWindowID"), "focusedTitleHash": focused.get("titleHash"), "focusedTitleLength": focused.get("titleLength"), }, indent=2, ensure_ascii=False), file=sys.stderr) sys.exit(6) print(json.dumps({ "case": "finderTabRealActivation", "targetWindowID": target.get("windowID"), "targetSpaceID": target.get("primarySpaceID"), "targetTitleHash": target.get("titleHash"), "targetTitleLength": target.get("titleLength"), "focusedWindowID": focused.get("focusedWindowID"), "focusedTitleHash": focused.get("titleHash"), "focusedTitleLength": focused.get("titleLength"), }, indent=2, ensure_ascii=False)) PY } assert_no_finder_activation_failure_log() { local log_file="$1" local target="$2" /usr/bin/python3 - "$log_file" "$target" <<'PY' import json import sys log_path, target_path = sys.argv[1:3] with open(target_path, "r", encoding="utf-8") as file: target = json.load(file) window_id = str(target.get("windowID")) bad_lines = [] for line in open(log_path, "r", encoding="utf-8", errors="replace"): if f"windowID={window_id}" not in line: continue if "event=windowActivation.finderTab.noSelectableHost" in line: bad_lines.append(line.strip()) elif "event=windowActivation.finderTab.hostMissing" in line: bad_lines.append(line.strip()) elif "event=windowActivation.activate.finderTabResult" in line and "activated=false" in line: bad_lines.append(line.strip()) elif "event=quickSwitch.activation.commit.result" in line and "result=activationFailed" in line: bad_lines.append(line.strip()) if bad_lines: print("Finder activation log contains failure markers for clicked target", file=sys.stderr) print(json.dumps({ "targetWindowID": target.get("windowID"), "hasAttributedSpace": target.get("hasAttributedSpace"), "snapshotSpaceIDs": target.get("snapshotSpaceIDs"), "lines": bad_lines[-8:], }, indent=2, ensure_ascii=False), file=sys.stderr) sys.exit(7) PY } mkdir -p "$REPORT_DIR" stop_current_aligner "$SCRIPT_DIR/package-app.sh" >&2 SNAPSHOT_JSON="$REPORT_DIR/snapshot.json" SNAPSHOT_STDERR="$REPORT_DIR/snapshot.stderr.log" SNAPSHOT_DEV_LOG="$REPORT_DIR/snapshot-dev.log" INITIAL_REPORT="$REPORT_DIR/initial-report.json" INITIAL_APP_LOG="$REPORT_DIR/initial-app.log" TARGETS_JSON="$REPORT_DIR/targets.json" SNAPSHOT_LOG_OFFSET="$(dev_log_size)" run_snapshot_dump "$SNAPSHOT_JSON" "$SNAPSHOT_STDERR" sleep 0.4 extract_dev_log_since "$SNAPSHOT_LOG_OFFSET" "$SNAPSHOT_DEV_LOG" ATTRIBUTED_IDS="$(extract_attributed_ids "$SNAPSHOT_DEV_LOG")" run_report "$INITIAL_REPORT" "$INITIAL_APP_LOG" eval "$(select_targets "$SNAPSHOT_JSON" "$INITIAL_REPORT" "$ATTRIBUTED_IDS" "$TARGETS_JSON")" for ((target_index = 0; target_index < TARGET_COUNT; target_index++)); do TARGET_JSON="$REPORT_DIR/target-$target_index.json" CLICK_REPORT="$REPORT_DIR/click-report-$target_index.json" CLICK_APP_LOG="$REPORT_DIR/click-app-$target_index.log" CLICK_DEV_LOG="$REPORT_DIR/click-dev-$target_index.log" FOCUSED_JSON="$REPORT_DIR/focused-finder-$target_index.json" eval "$(target_at_index "$TARGETS_JSON" "$target_index" "$TARGET_JSON")" CLICK_LOG_OFFSET="$(dev_log_size)" run_click "$TARGET_WINDOW_ID" "$CLICK_REPORT" "$CLICK_APP_LOG" sleep 0.8 extract_dev_log_since "$CLICK_LOG_OFFSET" "$CLICK_DEV_LOG" focused_finder_window_report "$FOCUSED_JSON" assert_click_report "$CLICK_REPORT" "$TARGET_JSON" assert_focused_finder_title "$TARGET_JSON" "$FOCUSED_JSON" assert_no_finder_activation_failure_log "$CLICK_DEV_LOG" "$TARGET_JSON" done stop_current_aligner cat <