UI test screenshots turn an XCUITest run into visual evidence of the app state. They help when a test needs to preserve the screen after a key transition, capture a state for review, or leave enough context for a failure investigation.

XCTest stores screenshots as attachments on a test case or named activity. XCUIScreen.main.screenshot() captures the current simulator screen, XCTAttachment(screenshot:) wraps it for the test report, and the attachment lifetime decides whether Xcode keeps the image after a passing run.

Use .keepAlways for screenshots that must survive successful runs, such as release evidence or documentation captures. Leave the default .deleteOnSuccess behavior for failure-only diagnostics so routine CI runs do not fill result bundles with images that nobody reviews.

Steps to capture screenshots in XCUITest:

  1. Add a screenshot attachment helper to the UI test target.
    ScreenshotAttachment.swift
    import XCTest
     
    extension XCTActivity {
        func addScreenshot(
            named name: String,
            lifetime: XCTAttachment.Lifetime = .keepAlways
        ) {
            let screenshot = XCUIScreen.main.screenshot()
            let attachment = XCTAttachment(screenshot: screenshot)
            attachment.name = name
            attachment.lifetime = lifetime
            add(attachment)
        }
    }

    XCTActivity keeps the screenshot beside the named substep in the Xcode test report. Pass .deleteOnSuccess only when the screenshot is meant for failed runs.

  2. Attach a screenshot after the UI reaches the state that needs evidence.
    ScreenshotUITests.swift
    import XCTest
     
    final class ScreenshotUITests: XCTestCase {
        private var app: XCUIApplication!
     
        override func setUpWithError() throws {
            continueAfterFailure = false
            app = XCUIApplication()
            app.launch()
        }
     
        func testCapturesWelcome() throws {
            app.buttons["onboarding-continue"].tap()
     
            let welcomeTitle = app.staticTexts["welcome-title"]
            XCTAssertTrue(
                welcomeTitle.waitForExistence(timeout: 5),
                "Welcome title did not appear after onboarding"
            )
     
            XCTContext.runActivity(named: "Welcome screen after onboarding") { activity in
                activity.addScreenshot(named: "welcome-screen")
            }
        }
    }

    Capture after the assertion that proves the intended screen is present. A screenshot taken before the wait may preserve a loading state instead of the UI checkpoint.

  3. Create a result directory for the run.
    $ mkdir -p TestResults
  4. Remove stale screenshot result artifacts from earlier runs.
    $ rm -rf TestResults/ScreenshotCapture.xcresult TestResults/ScreenshotAttachments

    Remove only known result paths inside the project or CI workspace. Do not point cleanup commands at shared DerivedData or source directories.

  5. Run the focused UI test with a named result bundle.
    $ xcodebuild test \
      -workspace MyApp.xcworkspace \
      -scheme MyApp \
      -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
      -only-testing:MyAppUITests/ScreenshotUITests/testCapturesWelcome \
      -resultBundlePath TestResults/ScreenshotCapture.xcresult
    Test Suite 'Selected tests' started.
    Test Case '-[MyAppUITests.ScreenshotUITests testCapturesWelcome]' started.
    Test Case '-[MyAppUITests.ScreenshotUITests testCapturesWelcome]' passed (3.427 seconds).
    ** TEST SUCCEEDED **

    Use -project MyApp.xcodeproj instead of -workspace MyApp.xcworkspace when the scheme belongs to a standalone project. A full Xcode installation with an iOS Simulator runtime is required; Command Line Tools alone cannot run XCUITest.
    Related: How to save XCUITest result bundles

  6. Open the result bundle in Xcode.
    $ open TestResults/ScreenshotCapture.xcresult

    Select ScreenshotUITests/testCapturesWelcome() and expand Welcome screen after onboarding. The attachment should appear as welcome-screen.

  7. Export the saved attachments from the result bundle.
    $ xcrun xcresulttool export attachments \
      --path TestResults/ScreenshotCapture.xcresult \
      --output-path TestResults/ScreenshotAttachments

    xcresulttool writes exported files plus manifest.json. The manifest maps UUID-style exported filenames back to their human-readable attachment names.

  8. Confirm that the exported attachments include the screenshot.
    $ ls TestResults/ScreenshotAttachments
    welcome-screen_0_2F8A0EBC-4D28-4C52-8E2D-91B7F3AFD0E1.png
    manifest.json