Article

Adding Attachments to Tests and Activities

Use attachments to store a test’s output data for later analysis.

Overview

Tests often produce output such as files, images, screenshots, and data during their execution. You can store this test output for later analysis by wrapping it in an XCTAttachment instance and adding it to the current test or activity. You can then view these attachments within Xcode’s test results browser alongside the test or activity that created them.

Figure 1

Viewing an attached screenshot in Xcode’s test results browser.

The output of a UI test, as viewed in Xcode’s test results browser. One of the items in the output is a screenshot of a window. A contextual menu is displayed showing an option to view a Quick Look of the screenshot.

XCTAttachment provides several convenience initializers to create attachments for common data types such as images, screenshots, files, folders, strings, and custom objects that conform to NSSecureCoding or can be encoded to a property list. Alternatively, you can attach custom test output by creating an attachment from raw data in memory.

You associate an attachment with a test by calling the add(_:) method on an instance of a type that conforms to the XCTActivity protocol. XCTestCase automatically conforms to XCTActivity, so you can add an attachment to the current test by calling add(_:) from within any test method.

Listing 1

Attaching a screenshot of an app’s first window to the current test.

class MainWindowScreenshotTests: XCTestCase {
    func testTakeScreenshotOfMainWindow() {
        let app = XCUIApplication()
        app.launch()
        let screenshot = app.windows.firstMatch.screenshot()
        let attachment = XCTAttachment(screenshot: screenshot)
        attachment.lifetime = .keepAlways
        add(attachment)
    }
}

Alternatively, you can create and run a custom activity by calling the XCTContext runActivityNamed:block: class method from anywhere in your testing code. This method runs a user-provided block of test code, passing an XCTActivity instance to the block. Call add(_:) on this XCTActivity instance to add an attachment to the activity instead of the overall test.

Every attachment you create has a uniformTypeIdentifier property containing a Uniform Type Identifier (UTI) that identifies the type of content stored in the attachment. Many of XCTAttachment's convenience initializers determine an appropriate UTI to use from their provided content. Alternatively, you can provide a manual UTI value to represent a custom data format used in your app.

Listing 2

Adding attachments in response to a background data download

func testDownloadAndAttachWebData() {
    
    // Create an expectation for a background download task.
    let expectation = XCTestExpectation(description: "Download apple.com home page")
    
    // Create a URL for a web page to be downloaded.
    let url = URL(string: "https://apple.com")!
    
    // Create a background task to download the web page.
    let dataTask = URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
        
        // Process the download task's response, adding some of its output as attachments.
        
        if let error = error {
            
            XCTFail("Data failed to download with an error: \(error.localizedDescription).")
            
        } else if let httpURLResponse = urlResponse as? HTTPURLResponse, httpURLResponse.statusCode != 200 {
            
            // Attach the response's header fields as an XML property list.
            let headers = XCTAttachment(plistObject: httpURLResponse.allHeaderFields)
            headers.name = "Headers"
            self.add(headers)
            
            XCTFail("Unexpected status code: \(httpURLResponse.statusCode).")
            
        } else if let data = data {
            
            // Attach the retrieved HTML data with an appropriate UTI.
            let html = XCTAttachment(data: data, uniformTypeIdentifier: "public.html")
            html.name = "HTML"
            // Keep the HTML attachment even when the test succeeds.
            html.lifetime = .keepAlways
            self.add(html)
            
        }
        
        // Fulfill the expectation to indicate that the background task has finished.
        expectation.fulfill()
        
    }
    
    // Start the download task.
    dataTask.resume()
    
    // Wait until the expectation is fulfilled, with a timeout of 10 seconds.
    wait(for: [expectation], timeout: 10.0)
    
}

Listing 2 performs an integration test of a web page download. The download takes place on a background thread, as described in Testing Asynchronous Operations with Expectations.

Based on the results of the download, the code above produces one of three outcomes:

  1. If the data task returns an error, the test fails and the error’s description is logged as part of the failure.

  2. If the data task succeeds but has an HTTP response status code of anything other than 200 OK, the response's header fields are added to the test as an XML property list attachment before the test fails, to assist with debugging the problem.

  3. If the data task succeeds and retrieves some data, the retrieved data is added to the test as an attachment with a UTI of "public.html", enabling Xcode to provide an appropriate Quick Look for the data within the Xcode test results browser. The attachment's lifetime property is set to XCTAttachment.Lifetime.keepAlways to store the HTML data even though the test succeeds.

Attachment Lifetime

By default, a test’s attachments are discarded when the test passes successfully, to save on storage space. To persist an attachment even when its test passes, set the attachment’s lifetime property to XCTAttachment.Lifetime.keepAlways rather than the default value of XCTAttachment.Lifetime.deleteOnSuccess. Alternatively, change the default lifetime for an entire test scheme by unchecking the “Attachments: Delete when each test succeeds” option in the “Options” tab of the scheme’s “Test” scheme action.

Figure 2

Changing the default lifetime for all attachments.

Shows the “Delete when each test succeeds” attachments option in Xcode’s Scheme Editor, within the “Options” tab of the scheme’s “Test” scheme action.

See Also

Attachments

class XCTAttachment

Data from a test method’s execution, such as a file, image, screenshot, data blob, or zip archive.