A draft editor works in portrait, but rotating to landscape clears the text. Or the save button remains in the view hierarchy, yet the keyboard covers it. A build on a cloud Mac will not catch either problem: the UI test needs to perform the full “type — rotate — save — rotate back” sequence. Here is a repeatable way to check an iOS app with a draft-editing screen.
Define What Must Survive Rotation
Before testing, give the editor, save button, and save result stable accessibilityIdentifier values: draftEditor, saveDraft, and savedStatus. Define these identifiers in the app; do not locate elements by localized button labels. The test contract is that rotation must not clear unsaved input, the save button must be tappable in landscape, a result message must appear after saving, and that message must remain visible after returning to portrait.
existsonly tells you that an element remains in the accessibility tree, not that a user can reach it. A rotation test should also checkisHittableand actually tap the button.
First, confirm that the app supports the orientation you intend to test. If landscape is disabled in the project’s supported device orientations, a failed rotation is not a layout defect. Do not reuse unverified expectations across phones and tablets, either: split-view layouts, keyboard sizes, and safe areas may differ.
Pin the Simulator and Execution Environment
In the graphical session on an XcodeOps cloud Mac, run xcrun simctl list devices available. Choose a phone simulator under an installed runtime and record its UDID. In CI, do not match on device name alone: identically named devices may belong to different runtimes. Pass the UDID to the job as SIMULATOR_UDID, and check again before launch that it is still listed as available.
For a comparable baseline, keep the selected Xcode path, simulator UDID, app test data, and test entry point fixed. Record the output of xcodebuild -version and xcrun simctl list devices available with the job; do not mix results from another simulator into that baseline. UI tests also require an available graphical login session. SSH connectivity alone does not guarantee that the test process can control the simulator window.
Write One UI Test for the Full Round Trip
Add the following test to the project’s UI Test target. This example assumes the editor is visible immediately after launch and that the app provides the three identifiers above. If reaching the editor requires navigation, perform it explicitly before typing rather than relying on a screen left open by a previous run.
import XCTest
final class RotationTests: XCTestCase {
func testDraftSurvivesRotation() {
let device = XCUIDevice.shared
device.orientation = .portrait
defer { device.orientation = .portrait }
let app = XCUIApplication()
app.launch()
let editor = app.textViews["draftEditor"]
XCTAssertTrue(editor.waitForExistence(timeout: 10))
editor.tap()
editor.typeText("Rotation check 42")
device.orientation = .landscapeLeft
XCTAssertTrue(
NSPredicate(format: "value == %@", "Rotation check 42")
.evaluate(with: editor)
)
let save = app.buttons["saveDraft"]
XCTAssertTrue(save.waitForExistence(timeout: 10))
XCTAssertTrue(save.isHittable)
save.tap()
let status = app.staticTexts["savedStatus"]
XCTAssertTrue(status.waitForExistence(timeout: 10))
device.orientation = .portrait
XCTAssertTrue(status.waitForExistence(timeout: 10))
}
}
The defer attempts to restore portrait orientation for the next test; it does not replace setting an initial state in each test. If the app recreates the editor view on rotation, an element query can usually resolve again, but do not cache coordinates or a frame obtained from that query. For asynchronous saves, merely waiting for a message to appear is not enough. Ideally, the app should expose an unambiguous success state whose value you can assert, rather than treating “saving” as completion.
Run from the Command Line and Preserve Evidence
From the project directory, set the project path, shared Scheme, and previously recorded UDID. The following commands exit immediately if a required variable is empty and write test results to a new result bundle directory. Use a different directory name before running them again.
: "${PROJECT:?Set the Xcode project path}"
: "${SCHEME:?Set a shared test scheme}"
: "${SIMULATOR_UDID:?Set an available simulator UDID}"
xcodebuild test \
-project "$PROJECT" \
-scheme "$SCHEME" \
-destination "platform=iOS Simulator,id=$SIMULATOR_UDID" \
-only-testing:AppUITests/RotationTests/testDraftSurvivesRotation \
-resultBundlePath "$PWD/rotation-run.xcresult"
AppUITests must match the project’s UI Test target name. If the test fails, keep the .xcresult and use the failing step, screenshots, and test logs to determine whether input was reset or the button was obstructed. Do not rely on the last line of terminal output alone. If a script needs to read the results, first run xcrun xcresulttool help to check the subcommands available in the current Xcode version, then choose the appropriate structured output.
Triage the Symptom Before Adding Waits
| Symptom | Check first | Fix |
|---|---|---|
| Draft clears after rotating to landscape | Whether rotation recreates the view and who owns its state | Keep editing state somewhere that survives view recreation |
| Button exists but cannot be tapped | Keyboard, overlays, safe areas, and scroll container | Fix constraints or provide an explicit way to dismiss the keyboard |
| Failure occurs only in CI | Graphical session, simulator runtime, and test data | Pin the execution target and ensure each job starts from the same state |
| Message disappears after returning to portrait | Whether the message is only a brief animation | Check a persistent save result or read the data again |
Do not add a fixed sleep as soon as a landscape test fails. If the UI has a real transition, wait for a specific element to reach a verifiable state. If the keyboard permanently covers the button, waiting longer will not make it tappable. For an issue limited to a particular screen size, add a second simulator with a different screen size rather than putting every device into one test. Stabilize the round trip on one pinned target first, then expand coverage so failure records remain comparable.
Frequently asked questions
Can a simulator validate an iOS rotation layout?
It can validate view state and whether controls are tappable. Recheck sensor behavior and physical handling on a device.
Why does a rotation test fail when the button exists?
Existence does not imply tappability. Check for keyboard or overlay obstruction, then inspect safe-area constraints and view recreation.
Choose a cloud Mac for your next task
Choose a Mac mini based on memory or build workload. Three models are available to rent by the day, week, month, or quarter. Check real-time node availability in the console.