Files

12 KiB
Executable File
Raw Permalink Blame History

title, namespace, updated, tags
title namespace updated tags
Legacy XCTest runner (run new-Xcode UI tests on macOS 12) personal 2026-10-17
xctest
ui-tests
macos
duckduckgo
virfield

Legacy XCTest runner

Runs DDG macOS UI tests built with the current Xcode (27.x) on a Mac that can only run an old Xcode, e.g. macOS 12 + Xcode 13.4.1. Работает на двух типах целей:

  • MBP 2014 node (Monterey 12.6.8, Intel x86_64), 2026-10-12;
  • Tart VM из virfield, шаблон monterey-xcode-13.4.1 (macOS 12.7.6, arm64, Xcode 13.4.1 в /Applications/Xcode.app), 2026-10-17.

Location: ~/Developer/legacy-xctest-runner/ (not committed anywhere). macOS 12 не входит в CI matrix (PR: только macOS 27; полный прогон: 14, 15, 27), это чисто локальная проверка. Минимум приложения — macOS 12.3.

Why it is needed

  • New Xcode's XCTest.framework and UI Tests-Runner.app require macOS 14+.
  • The test bundle itself targets the app's deployment target (12.3), so it can run if hosted by the old Xcode's runner.
  • The bundle imports 2 Swift symbols missing from Xcode 13 XCTest: XCUIElement.typeKey(_:modifierFlags:) (mangled under XCUIAutomation) and XCTIssue.init(…severity:). XCTestCompat.swift forwards them (так что typeKey(XCUIKeyboardKey, …) в тестах работает).

How it works

  • package.sh <derived-data> <out.tgz> — collects DuckDuckGo Review.app, tests-server, UI Tests.xctest + runner files. Ждёт Build/Products/Review с DuckDuckGo Review.app, tests-server, UI Tests-Runner.app.
  • run.sh [--gui] <TestClass[/testMethod]> [result-name] on the target Mac:
    • builds XCTestCompat.framework (re-exports old XCTest + libXCTestSwiftSupport, adds forwarders);
    • hosts the bundle in Xcode 13's XCTRunner.app, install_name_tool repoints the bundle's XCTest/libXCTestSwiftSupport imports at XCTestCompat;
    • режет runner до нативного слайса (lipo -thin, см. ниже), добавлено 2026-10-17;
    • repeats macOS UI Tests CI scheme pre-actions (clean-app review, suppress Move-to-Applications, start tests-server);
    • xcodebuild test-without-building with UITests.xctestrun (FormatVersion 1), then kills tests-server and the app (old XCTest doesn't terminate it).
    • --gui: over SSH xcodebuild can't reach the runner; relaunches itself via a Terminal .command in the console session, waits, closes finished windows.
  • Env SKIP_BUILD=1 (патч на VM, в локальный run.sh не внесён): пропустить build_compat и assemble_runner, если уже собраны. Нужен для запуска по одному тесту.

Build on the host (spell flags out — zsh doesn't word-split $var):

xcodebuild build-for-testing -workspace DuckDuckGo.xcworkspace -scheme "macOS UI Tests CI" -destination "platform=macOS,arch=x86_64" -derivedDataPath <dd> ONLY_ACTIVE_ARCH=YES -skipPackagePluginValidation -skipMacroValidation
xcodebuild build -workspace DuckDuckGo.xcworkspace -scheme "macOS Browser" -configuration Review -destination "platform=macOS,arch=x86_64" -derivedDataPath <dd> ONLY_ACTIVE_ARCH=YES -skipPackagePluginValidation -skipMacroValidation

Для Apple Silicon VM архитектура arm64. Подписанное на хосте приложение 2026-10-17 собирали так: xcodebuild archive -scheme "macOS Browser Review" -configuration Review … EXCLUDED_ARCHS=x86_64 RELEASE_PRODUCT_NAME_OVERRIDE=DuckDuckGo + -exportArchive с копией macOS/scripts/assets/ExportOptions.plist (Developer ID, signingCertificate = SHA-1 из keychain; tracked plist не править, как делает archive.sh). Нотаризация внутри VM не нужна. Профили match Direct …review уже лежат в ~/Library/Developer/Xcode/UserData/Provisioning Profiles.

Target Mac prerequisites (once per boot volume, needs sudo)

sudo DevToolsSecurity -enable
sudo automationmodetool enable-automationmode-without-authentication

Without them testmanagerd shows an admin-password prompt on the display ("Timed out while enabling automation mode"). Virfield's node probe now reports ui_testing (ready / devtools_disabled / automation_mode_requires_auth / no_console_session); setup script /Users/admin/Developer/virfield/deploy/node-ui-testing.sh. В Tart VM monterey-xcode-13.4.1 Developer Mode уже включён, sudo без пароля; automationmodetool выполнить надо.

Gotchas found

Запуск

  • arm64e: SIGKILL 137 на старте раннера (Apple Silicon). XCTRunner.app из Xcode 13.4.1 — universal x86_64 arm64 arm64e, ядро выбирает arm64e: exec_mach_imgact: not running binary "UI Tests-Runner" built against preview arm64e ABI; xcodebuild: «Launched process exited during launch» / Failed to install or launch the test runner. Лечится lipo -thin $(uname -m) в assemble_runner (уже в run.sh). На Intel не проявлялось.
  • --gui запускает себя из .command по $0; из SSH вызывать run.sh только по абсолютному пути, иначе ./run.sh: No such file or directory в Terminal (cwd=home).
  • LEGACY_XCODE из окружения SSH не доходит до Terminal .command: xcrun: error: missing DEVELOPER_DIR path: /Applications/Xcode13.4.1.app. Либо sudo ln -sfn /Applications/Xcode.app /Applications/Xcode13.4.1.app, либо править дефолт.
  • Over SSH xcodebuild без --gui не достаёт раннер; remote shell — zsh: нет timeout (используй perl -e 'alarm N; exec @ARGV'), $var не делится на слова, log — builtin (/usr/bin/log show).
  • Exit code .command-обёртки (exit=0 в логах) не равен результату тестов. Смотреть Executed N tests, with M failures и Test Case … passed|failed. Executed 0 tests, with 1 failure = упал class setUp.
  • Старый xcodebuild пишет LLVM Profile Error: Failed to write file default.profraw: Read-only file system — безвредно.
  • Приложение: LSMinimumSystemVersion 12.3. arm64 VM требует arm64-сборку (Review app после EXCLUDED_ARCHS=x86_64, test bundle arm64).

Тесты

  • Старый XCTest не завершает приложение между тестами. При запуске класса целиком каждый следующий тест стартует поверх предыдущего: Failed to synthesize event: Unable to synthesize gesture request N 'click' because gesture request M 'Type 'n' key with modifiers ⌘' is still in progress, Timed out while evaluating UI query, лавина падений без реальных assert (первый прогон: 49 «failures» на 25 тестах Duck.ai, 42 на 24 тестах поискового класса). Тот же тест по одному проходит за ~5 с. Решение: один xcodebuild на каждый тест со свежим prepare_environment (чистые defaults, перезапуск tests-server и приложения), SKIP_BUILD=1 после первого. 88 тестов ≈ 25–30 мин. Цикл each.sh читает tests-list.txt (Class/method), пишет summary-each.txt, запускается из Terminal .command.
  • UITests.setAutocompleteToggleBeforeTestcaseRuns(false) на macOS 12 падает по таймауту (Failed to get matching snapshots: Timed out while evaluating UI query, окно Settings) → тесты класса не стартуют (Executed 0 tests, with 1 failure, ~190 с). Затронуты все классы с этим setUp, включая AddressBarKeyboardShortcutsTests. Локальный обход без коммита: убрать вызов хелпера при сборке бандла и в prepare_environment писать defaults write com.duckduckgo.macos.browser.review preferences.appearance.show-autocomplete-suggestions -bool false.
  • Duck.ai: ввод сразу после Shift+Enter теряется, пока text view не получил фокус (на macOS 12 в ~3 из 25 тестов) → повторять Cmd+A и ввод, пока содержимое не совпало.
  • Редкий флейк: Failed to compute hit point for (null) → раннер падает при снятии скриншота (Restarting after unexpected exit, crash, or test timeout, итог noresult). Перезапуск одного теста помогает.
  • Repo Logger (macOS/UITests/Common/UITests.swift) calls private -[XCTContext _recordActivityMessageWithFormat:]; old XCTest lacks it → guard with responds(to:) (в main уже есть).
  • Hover tracking in the app uses .activeInKeyWindow; popovers that open without focus don't get hover events.
  • Текст Duck.ai omnibar (AIChatOmnibarTextContainerViewController.textView) не виден в accessibility-дереве (контейнер …view — одиночный Other), в том числе на macOS 27. Ключи шлются через app.typeKey, текст читается через Cmd+A, Cmd+C и NSPasteboard.

Окружение / запись

  • screencapture -v на macOS 12 VM: capture error The operation could not be completed и по SSH, и из Terminal в GUI-сессии (нет Screen Recording TCC, SIP включён, выдать нельзя). На macOS 27 golden uitest-27-v1 работает по обычному SSH. Для macOS 12 записи экрана нет — только логи и .xcresult.
  • MBP has a different SSH host key per boot volume; use the Virfield lease's generated ssh-config (pins the booted volume's key). Для Tart VM IP (192.168.64.x) доступен напрямую с хоста.
  • Диск Tart: samsung-tart ≈214 ГБ, macOS 27 clone резервирует 140 ГБ, Monterey 120 ГБ — вместе не помещаются, см. virfield.

Находки на macOS 12 (PR 4548, address bar word selection)

2026-10-17, ветка alex/bug-08b-addressbar-selection: тесты Opt+Shift+←/→, Cmd+Shift+←/→, Shift+←/→, Cmd+A, delete/forward-delete в адресной строке (URL, поисковый запрос, суффикс) и Duck.ai.

  • На macOS 12 после Cmd+Shift+← selectionAffinity остаётся прежним (на 27 становится .upstream) → Opt+Shift+→ не сжимала выделение слева. Исправлено явной установкой affinity в AddressBarTextEditor (moveToBeginningOfLine/LeftEndOfLine/EndOfLine/RightEndOfLine…AndModifySelection). Тот же класс проблемы — selectAll: affinity теперь явно .downstream.

Результаты запусков

  • 2026-10-12, MBP 2014 (Intel): воспроизведён Sentry crash NSInternalInconsistencyException SaveCredentialsViewController.loadView (isfinite(newOrigin.x) on macOS 12) до фикса.
  • 2026-10-17, Tart VM macOS 12.7.6 arm64: по-тестовый прогон 88 новых тестов. Первый проход 85 passed / 2 failed (находка про Cmd+Shift+← affinity) / 1 noresult (флейк скриншота). После фикса три теста перепроверены — passed.
  • Полный прогон на финальном коде: 85 passed / 3 failed. Все три — Duck.ai: setUp набирает prompt сразу после Shift+Enter, клавиши теряются, пока text view не взял фокус (XCTAssertEqual failed: ("") is not equal to ("alpha beta gamma delta")). В тесте добавлен retry (до 3 попыток Cmd+A и ввод), класс Duck.ai перегнан — 25/25 passed. Итого 88/88 на macOS 12.
  • Те же тесты на macOS 27 (Xcode 27.0) — 88/88 с записью экрана; PR 4548 в duckduckgo/apple-browsers, CI UI Tests на 14/15/27 запущен вручную (workflow_dispatch).

See also virfield.