동일한 클라우드 Mac에서 SSH로 실행한 xcodebuild build는 정상적으로 끝나더라도, test로 전환하면 “테스트 러너 시작” 전에 멈출 수 있습니다. 테스트 케이스는 하나도 시작되지 않고 시뮬레이터 창도 나타나지 않으며, 로그에는 앱 실행 또는 세션 연결 실패만 남습니다. 이때 DerivedData부터 정리하지 마십시오. 빌드 경로는 이미 검증되었으므로, 실제로 확인해야 할 부분은 작업이 사용 가능한 macOS 그래픽 세션에서 실행되고 있는지입니다.
빌드 환경과 그래픽 세션 구분하기
XCUITest는 테스트 번들만 컴파일하는 도구가 아닙니다. 대상 앱, 테스트 호스트와 시뮬레이터 서비스를 실행하고, 현재 사용자의 그래픽 세션을 통해 프로세스 간 작업을 조율해야 합니다. SSH 로그인에 성공했다는 것은 원격 셸을 사용할 수 있다는 의미일 뿐입니다. 해당 셸과 데스크톱 로그인 사용자가 동일한 launchd GUI 도메인에 속한다는 보장은 없습니다.
처음부터 모든 프로세스를 재시작하지 말고, 먼저 다음 네 가지 사실을 기록하십시오.
| 점검 항목 | 정상 결과 | 이상 징후 |
|---|---|---|
| 콘솔 사용자 | 실제로 자동화를 실행하는 사용자 | root 또는 loginwindow |
| GUI 실행 도메인 | gui/<uid> 조회 가능 |
도메인이 없거나 접근 권한이 없음 |
| WindowServer | 프로세스가 존재함 | 로그인 세션이 아직 생성되지 않음 |
| 대상 시뮬레이터 | 상태가 Booted |
Booting 또는 Shutdown 상태에 계속 머무름 |
WindowServer가 존재하는 것은 여러 필수 조건 중 하나일 뿐입니다. 테스트 작업을 다른 사용자나 시스템 수준 데몬이 시작했다면 대상 사용자의 그래픽 세션에 여전히 접근하지 못할 수 있습니다.
사전 점검 스크립트로 현장 상태 기록하기
아래 스크립트는 시스템 상태를 변경하지 않으므로 테스트 작업의 맨 앞에 배치하기에 적합합니다. 현재 실행 사용자, 콘솔 사용자, GUI 도메인과 사용 가능한 기기를 로그에 기록하며, 하나라도 점검에 실패하면 작업을 즉시 종료합니다.
#!/bin/zsh
set -euo pipefail
RUNNER_USER="$(id -un)"
RUNNER_UID="$(id -u)"
CONSOLE_USER="$(stat -f '%Su' /dev/console)"
echo "runner_user=${RUNNER_USER}"
echo "runner_uid=${RUNNER_UID}"
echo "console_user=${CONSOLE_USER}"
if [[ "${CONSOLE_USER}" == "root" || "${CONSOLE_USER}" == "loginwindow" ]]; then
echo "No interactive console user"
exit 21
fi
CONSOLE_UID="$(id -u "${CONSOLE_USER}")"
if [[ "${RUNNER_UID}" != "${CONSOLE_UID}" ]]; then
echo "Runner and console user differ"
exit 22
fi
if ! launchctl print "gui/${CONSOLE_UID}" >/dev/null 2>&1; then
echo "GUI launch domain is unavailable"
exit 23
fi
if ! pgrep -x WindowServer >/dev/null; then
echo "WindowServer is unavailable"
exit 24
fi
xcrun simctl list devices available
사용자가 일치하지 않는 문제를 일반 경고로 취급한 채 계속 실행하지 마십시오. 이후의 실패는 대개 더 모호한 XCTest 오류만 생성하므로, 가장 중요한 근본 원인 정보가 오히려 사라집니다.
모호한 이름 대신 특정 시뮬레이터 고정하기
GUI 세션이 정상임을 확인한 다음 기기 선택 문제를 처리하십시오. name=iPhone만 지정하면 여러 런타임이 설치된 환경에서 대상이 모호해질 수 있습니다. 먼저 전용 기기를 생성하거나 선택하고 UDID를 저장한 뒤, 부팅이 완료될 때까지 기다립니다.
DEVICE_UDID="${IOS_TEST_DEVICE_UDID:?missing device udid}"
xcrun simctl boot "${DEVICE_UDID}" 2>/dev/null || true
xcrun simctl bootstatus "${DEVICE_UDID}" -b
xcodebuild test \
-workspace App.xcworkspace \
-scheme AppUITests \
-destination "platform=iOS Simulator,id=${DEVICE_UDID}" \
-resultBundlePath "$PWD/artifacts/ui-tests.xcresult"
simctl boot가 반환되었다고 해서 시스템이 테스트를 실행할 준비를 마친 것은 아닙니다. 실제 동기화 지점은 bootstatus -b입니다. 병렬 작업에는 서로 다른 기기 UDID를 사용하여 두 작업이 동일한 시뮬레이터 데이터 디렉터리를 두고 경합하지 않도록 해야 합니다. 작업이 끝나면 기기를 종료할 수 있지만, 다른 작업이 실행 중일 때 전체 기기 정리를 수행해서는 안 됩니다.
세 계층의 증거 보관하기
문제를 조사할 때는 최소한 사전 점검 스크립트 출력, xcodebuild 원본 로그, .xcresult라는 세 계층의 자료를 보관하십시오. 테스트 호스트가 시작되지 않았다면 다음 프로세스를 기록하는 것도 도움이 됩니다.
ps -axo user,pid,ppid,command | \
grep -E 'XCTest|Simulator|CoreSimulator' | \
grep -v grep > artifacts/ui-processes.txt
이 정보는 명령이 최종적으로 실패했다는 사실만 알려 주는 것이 아니라, “테스트가 실제로 시뮬레이터에 진입했는가”라는 질문에도 답할 수 있게 해 줍니다.
사용자 GUI 도메인에서 작업 실행하기
안정적인 방법은 각 SSH 세션에서 UI 테스트를 직접 시작하는 것이 아니라, 로그인된 사용자 도메인의 LaunchAgent가 작업을 받도록 구성하는 것입니다. SSH는 작업 설명을 작성하거나 대기열을 트리거하는 역할만 맡고, LaunchAgent가 고정된 사용자, 작업 디렉터리와 환경 변수 아래에서 스크립트를 실행합니다.
LaunchAgent에는 WorkingDirectory를 명시적으로 설정하고, 표준 출력과 표준 오류를 작업 디렉터리에 기록해야 합니다. 도구 경로를 대화형 셸의 시작 파일에 의존하게 만들지 마십시오. 스크립트에서 PATH를 명시적으로 설정하고, xcode-select -p로 현재 개발자 도구 디렉터리를 기록하십시오. 이렇게 하면 “SSH에서 실행 가능”과 “GUI 자동화 실행 가능”을 서로 독립된 두 가지 계약으로 분리할 수 있습니다.
시스템 수준 LaunchDaemon에서 XCUITest를 직접 시작하는 방식은 권장하지 않으며, nohup을 GUI 세션에 진입하는 수단으로 사용해서도 안 됩니다. 이러한 방식은 프로세스가 계속 실행되게 할 수는 있지만, 프로세스가 속한 실행 도메인을 자동으로 변경하지는 않습니다.
작업 수락 전 최소 승인 검사 구성하기
노드가 UI 테스트를 수락하기 전에 다음 순서로 승인 검사를 수행할 수 있습니다.
- 콘솔 사용자와 작업 사용자가 일치합니다.
- 현재 작업에서
gui/<uid>를 조회할 수 있습니다. - WindowServer가 존재합니다.
xcode-select -p가 예상한 도구 디렉터리를 가리킵니다.- 대상 시뮬레이터 UDID가 존재하고
bootstatus를 완료할 수 있습니다. - 결과물 디렉터리에 쓰기가 가능하며, 이번 작업만을 위한 독립 경로를 사용합니다.
- 앱을 실행하고 첫 화면의 요소만 확인하는 가벼운 테스트를 수행합니다.
일곱 번째 단계가 실패하면 먼저 현장 상태를 보관한 뒤 해당 전용 시뮬레이터를 다시 생성할지 결정하십시오. 모든 기기를 삭제하거나 모든 캐시를 비우는 작업을 기본 복구 절차로 삼지 마십시오. 이렇게 하면 실행 도메인 문제가 가려질 뿐 아니라 동일한 시스템에서 실행 중인 다른 작업에도 영향을 줄 수 있습니다.
그래픽 세션 사전 점검을 파이프라인 진입점에 배치하면 XCUITest의 “실행 실패”는 더 이상 막연한 장애가 아닙니다. 작업이 콘솔 사용자, GUI 도메인, WindowServer 또는 시뮬레이터 중 어느 단계에서 실패했는지 명확히 보여 주므로, 이후 조사도 동일한 증거 자료를 바탕으로 진행할 수 있습니다.
자주 묻는 질문
SSH에서는 빌드되는데 XCUITest가 시작되지 않는 이유는 무엇인가요?
빌드는 명령줄 환경만으로 가능하지만 UI 테스트에는 로그인된 사용자, launchd GUI 도메인, WindowServer와 정상 부팅된 시뮬레이터가 추가로 필요합니다.
WindowServer 프로세스만 확인하면 충분한가요?
충분하지 않습니다. 콘솔 사용자가 loginwindow가 아닌지, gui/UID 도메인이 존재하는지, 테스트 실행 사용자와 일치하는지, 시뮬레이터 부팅이 끝났는지도 확인해야 합니다.
자동화 테스트는 올바른 GUI 세션에서 어떻게 실행하나요?
로그인된 사용자 도메인의 LaunchAgent가 작업을 받아 실행하도록 구성합니다. 시스템 데몬이나 임시 SSH 셸에서 UI 테스트를 직접 시작하지 않는 편이 안정적입니다.
다음 개발 작업을 위한 클라우드 Mac 구성
Oak M4, Oak M4 Plus 또는 Oak M4 Pro를 선택하고 팀의 위치에 따라 5개 물리 노드 중에서 대여 구성을 설정하세요.