| | |
| | | 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:-7.0}" |
| | | REPORT_WAIT="${ALIGNER_ROUND1_FINDER_TABS_REPORT_WAIT:-35.0}" |
| | | |
| | | fail() { |
| | | echo "Round01 Finder tabs live QA failed: $*" >&2 |
| | |
| | | --round0-skip-permissions \ |
| | | --round01-open-quick-switch \ |
| | | --round01-disable-screenshot-refresh \ |
| | | --round01-quick-switch-auto-hide-after=0.8 \ |
| | | --round01-quick-switch-quit-after=1.3 \ |
| | | --round01-quick-switch-report="$report" >"$app_log" 2>&1 |
| | | wait_for_report "$report" "loaded" |
| | | --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 app_group_index="$1" |
| | | local window_index="$2" |
| | | local report="$3" |
| | | local app_log="$4" |
| | | local window_id="$1" |
| | | local report="$2" |
| | | local app_log="$3" |
| | | stop_current_aligner |
| | | rm -f "$report" |
| | | ALIGNER_DIAGNOSTICS_INCLUDE_SENSITIVE=1 \ |
| | |
| | | --round0-skip-permissions \ |
| | | --round01-open-quick-switch \ |
| | | --round01-disable-screenshot-refresh \ |
| | | --round01-debug-mouse-sequence="click-card:$app_group_index:$window_index" \ |
| | | --round01-quick-switch-quit-after=1.8 \ |
| | | --round01-quick-switch-report="$report" >"$app_log" 2>&1 |
| | | wait_for_report "$report" "activated" |
| | | --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() { |
| | |
| | | continue |
| | | ids = [int(value) for value in re.findall(r"\d+", match.group(1))] |
| | | |
| | | if not ids: |
| | | print("No attributed Finder tab/page windowIDs found in this QA run", file=sys.stderr) |
| | | sys.exit(3) |
| | | |
| | | print(",".join(str(value) for value in sorted(set(ids)))) |
| | | PY |
| | | } |
| | | |
| | | select_target() { |
| | | 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: |
| | |
| | | sys.exit(4) |
| | | |
| | | snapshot_windows = {window.get("id"): window for window in snapshot.get("windows", [])} |
| | | finder_attributed = [ |
| | | finder_candidates = [ |
| | | window for window_id, window in snapshot_windows.items() |
| | | if window_id in attributed_ids |
| | | and window.get("app", {}).get("bundleIdentifier") == "com.apple.finder" |
| | | if window.get("app", {}).get("bundleIdentifier") == "com.apple.finder" |
| | | and window.get("identifierSource") == "cgWindow" |
| | | and window.get("spaceIDs") |
| | | and window.get("title") |
| | | ] |
| | | require(finder_attributed, "snapshot must contain at least one attributed Finder CG-only tab/page") |
| | | require(finder_candidates, "snapshot must contain at least one Finder CG-only tab/page") |
| | | |
| | | root = report.get("rootView", {}) |
| | | cards = [] |
| | |
| | | if column.get("bundleIdentifier") != "com.apple.finder": |
| | | continue |
| | | for card in column.get("cards", []): |
| | | if card.get("windowID") not in attributed_ids: |
| | | 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 |
| | | cards.append((column, card, title)) |
| | | |
| | | require(cards, "Quick Switch report must expose at least one visible attributed Finder tab/page card") |
| | | 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), |
| | | )) |
| | | column, card, title = cards[0] |
| | | target = { |
| | | "windowID": card["windowID"], |
| | | "appGroupIndex": column["appGroupIndex"], |
| | | "windowIndex": card["windowIndex"], |
| | | "primarySpaceID": card.get("primarySpaceID"), |
| | | "title": title, |
| | | "titleLength": len(title), |
| | | "titleHash": card.get("titleHash") or snapshot_windows.get(card.get("windowID"), {}).get("titleHash"), |
| | | "visibleFrame": card.get("visibleFrame"), |
| | | } |
| | | 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(target, file, indent=2, ensure_ascii=False) |
| | | 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"APP_GROUP_INDEX={target['appGroupIndex']}") |
| | | print(f"WINDOW_INDEX={target['windowIndex']}") |
| | | print(f"TARGET_SPACE_ID={target['primarySpaceID']}") |
| | | print(f"TARGET_SPACE_ID={target.get('primarySpaceID')}") |
| | | PY |
| | | } |
| | | |
| | |
| | | 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_DEV_LOG="$REPORT_DIR/snapshot-dev.log" |
| | | INITIAL_REPORT="$REPORT_DIR/initial-report.json" |
| | | INITIAL_APP_LOG="$REPORT_DIR/initial-app.log" |
| | | CLICK_REPORT="$REPORT_DIR/click-report.json" |
| | | CLICK_APP_LOG="$REPORT_DIR/click-app.log" |
| | | CLICK_DEV_LOG="$REPORT_DIR/click-dev.log" |
| | | TARGET_JSON="$REPORT_DIR/target.json" |
| | | FOCUSED_JSON="$REPORT_DIR/focused-finder.json" |
| | | TARGETS_JSON="$REPORT_DIR/targets.json" |
| | | |
| | | SNAPSHOT_LOG_OFFSET="$(dev_log_size)" |
| | | run_snapshot_dump "$SNAPSHOT_JSON" "$SNAPSHOT_STDERR" |
| | |
| | | ATTRIBUTED_IDS="$(extract_attributed_ids "$SNAPSHOT_DEV_LOG")" |
| | | |
| | | run_report "$INITIAL_REPORT" "$INITIAL_APP_LOG" |
| | | eval "$(select_target "$SNAPSHOT_JSON" "$INITIAL_REPORT" "$ATTRIBUTED_IDS" "$TARGET_JSON")" |
| | | eval "$(select_targets "$SNAPSHOT_JSON" "$INITIAL_REPORT" "$ATTRIBUTED_IDS" "$TARGETS_JSON")" |
| | | |
| | | CLICK_LOG_OFFSET="$(dev_log_size)" |
| | | run_click "$APP_GROUP_INDEX" "$WINDOW_INDEX" "$CLICK_REPORT" "$CLICK_APP_LOG" |
| | | sleep 0.8 |
| | | extract_dev_log_since "$CLICK_LOG_OFFSET" "$CLICK_DEV_LOG" |
| | | focused_finder_window_report "$FOCUSED_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" |
| | | |
| | | assert_click_report "$CLICK_REPORT" "$TARGET_JSON" |
| | | assert_focused_finder_title "$TARGET_JSON" "$FOCUSED_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 |
| | | |
| | |
| | | Round01 Finder tabs live QA passed |
| | | Log directory: $REPORT_DIR |
| | | Covered: |
| | | - Finder CG-only tab/page records inherit a high-confidence fullscreen Space. |
| | | - Quick Switch click targets the exact Finder tab/page windowID. |
| | | - Finder focused window title matches the clicked card after activation. |
| | | - Finder CG-only tab/page records are clicked even when Space attribution is unavailable. |
| | | - Quick Switch clicks target exact Finder tab/page windowIDs. |
| | | - Finder focused window title matches each clicked card after activation. |
| | | - Finder activation logs contain no hostMissing/noSelectableHost/activationFailed for clicked targets. |
| | | - Multiple Finder tab/page targets are exercised in one run to catch intermittent host mismatch. |
| | | EOF |