Michele Titolo | Blog | Speaking

Test Logs in Xcode

Apple spent the week of WWDC touting the new testing features in Xcode 7. One thing they failed to mention both this year and last was the persistence of test results to disk. These files and folders are not officially documented, so if you decide to rely on them make sure to check a new version of Xcode doesn’t break anything. This post will be updated to reflect changes as needed.

If you run a set of tests in Xcode 6/7 or with xcodebuild test via the command line, a new folder inside of the app’s folder in DerivedData is created, called Test. It contains similar files to the other folders, but Xcode 7 adds some new things as well.

.xcactivitylog

An .xcactivitylog is a gzipped copy of the build log that’s generated any time a project is built. Each file is uniquely named with a UUID. Unfortunately for us, the UUID generated is unique per folder, so correlating a file in Build and Test cannot be done by file name alone. These appear to be segments of the output, not complete logs. At least one of these is generated per build.

TestSummaries.plist

This is the exciting part. This file contains the results of the tests in a machine readable format. At the root of this plist is 3 things: FormatVersion, RunDestination, and TestableSummaries.

FormatVersion

This is a string that is used to version the TestSummaries file format. Between Xcode 6 and 7, the version number did not change, though additional information is added to the files generated in 7. With this, we can assume that the version is not incremented when additions are made to the objects contained.

RunDestination

This contains information about what computer, device, and sdk were used to run the tests. All the items are fairly self-explanatory.

TestableSummaries

Each test target is an item in this array. As of Xcode 7, running test will automatically run all test targets associated with a project.

Within the Tests array there are records of the test, but they are quite buried. The top-level item distinguishes between All tests or Selected tests. The only difference between these two is how many levels of Subtests exist between the top level Test object and the individual test results.

In All tests the identifier hierarchy looks like so

All tests
	└── AppTests.xctest
		└── AppTests
			└── Individual tests   

For Selected tests the .xctest level is skipped.

Selected tests
	└── AppTests
		└── Individual tests   

Once we get down into individual tests, the Identifier is automatically generated based on the method name. so -(void)testExample becomes AppTests/testExample. When using Specta, the names are mangled slightly more.

Take this test for example:

describe(@"successful request", ^{
	 it(@"calls success block when request succeeds", ^{
	  ...
	 });
});

The TestIdentifier generated will look something like this: AppTests/test_successful_request__calls_success_block_when_request_succeeds.

Tests written in swift will always end with ().

Xcode 7 adds ActivitySummaries and FailureSummaries

For Unit test targets, the useful piece of information here is the reason the test fails. For UI test targets, this is where the steps to perform the UI tests are recorded, along with any extra information about them. When running a UI test, each action taken on a UI object generates one of these sub activities.

Take this test:

    func testOne() {
        let app = XCUIApplication()
        app.buttons["One"].tap()
        let aTextFieldTextField = app.textFields["A Text Field"]
        aTextFieldTextField.tap()
        aTextFieldTextField.typeText("asd")
    }

Sidenote: the test must tap a textField before it can enter text into it.

This test generates 4 activities, with sub activities auto-generated:

    t =     1.77s     Wait for app to idle
    t =     2.42s     Tap the "One" Button
    t =     2.43s         Wait for app to idle
    t =     2.68s         Find the "One" Button
    t =     2.72s         Dispatch the event
    t =     2.97s         Wait for app to idle
    t =     3.62s     Tap the "A Text Field" TextField
    t =     3.62s         Wait for app to idle
    t =     3.62s         Find the "A Text Field" TextField
    t =     3.64s         Dispatch the event
    t =     3.87s         Wait for app to idle
    t =     4.49s     Type 'asd' into the "A Text Field" TextField
    t =     4.49s         Wait for app to idle
    t =     4.49s         Find the "A Text Field" TextField
    t =     4.55s         Dispatch the event
    t =     4.66s         Wait for app to idle

The step we care about most is “Dispatch the event” because it contains the attachments generated from the tests.

Attachments

Xcode 7 introduced UI testing, a big part of which is screenshots of the tests as they happen. The Attachments object contains information about where those screenshots live. But there’s more! In addition to a .png of the screen during that particular test step, there’s also a snapshot. What is a snapshot? It’s a archive containing the entire view hierarchy on screen when the screenshot was taken as a binary plist.

Failures

When a test fails, a summary is added to the FailureSummaries key. This is just a shortcut to the item in ActivitySummaries which has almost the same information.

Xcode vs xcodebuild

xcodebuild

Thankfully for those of us who automate builds, xcodebuild test does the same thing that Xcode does, though it generates fewer files. Here’s what you’ll find in Logs after running on the command line:

├── Build
│   ├── Cache.db
│   └── FCDDA053-2DA1-4C1C-8716-B6C339CB5815.xcactivitylog
└── Test
    ├── 21ED62CB-4F14-4A87-805E-C594936F24E6_TestSummaries.plist
    └── Attachments
        ├── Screenshot_6CD674C6-4E0D-4DAC-BF66-016198C32780.png
        ├── Screenshot_8126D5ED-88B9-4F6D-8F38-A339F3A14AE5.png
        ├── Screenshot_94513284-AA2F-48DC-A9D7-C56A0EF9BC91.png
        ├── Screenshot_E6EBC6AC-7311-49C2-829D-AF9880141C4B.png
        ├── Snapshot_5638F5F5-C04C-4F5E-AE42-C517B6B1FCBC
        ├── Snapshot_57F67566-9E22-4005-B7D6-B822EF08CD2E
        ├── Snapshot_7AA349A8-CF54-4AC4-8139-A676167C8801
        └── Snapshot_A065B515-A4DE-431C-B72F-87952AA133A6

One caveat: The .xcactivitylog in the Build folder does not contain the full build log. It is missing the logs generated when tests are run.

Now this seems great, but remember this is inside of DervivedData so there will be many other files, so finding it programmatically is not easy. One solution is to grep the console output for the sessionIdentifier which is included in the XCTestConfiguration:

Found configuration <XCTestConfiguration: 0x7ff38a5894e0>
	                  testBundleURL:file:///Users/michele/Library/Developer/Xcode/DerivedData/Buttons-amswwgmmhfjuzcegszalugciwdsj/Build/Products/Debug-iphonesimulator/ButtonsUITests-Runner.app/PlugIns/ButtonsUITests.xctest
	              productModuleName:ButtonsUITests
	                    testsToSkip:(null)
	                     testsToRun:(null)
	             reportResultsToIDE:YES
	              sessionIdentifier:<__NSConcreteUUID 0x7ff38a58bf70> CC966A22-0E28-4880-B445-9E401BB24CDB
	     pathToXcodeReportingSocket:(null)
	      disablePerformanceMetrics:no
	treatMissingBaselinesAsFailures:no
	                baselineFileURL:(null)
	          targetApplicationPath:/Users/michele/Library/Developer/Xcode/DerivedData/Buttons-amswwgmmhfjuzcegszalugciwdsj/Build/Products/Debug-iphonesimulator/Buttons.app
	      targetApplicationBundleID:com.mrt.Buttons

Manually sorting through Xcode’s logs is The Hard Way™ and finally with Xcode 7 there is an easier way to get that information.

There is a new xcodebuild argument resultBundlePath which will save the .xcactivitylog, TestSummary.plist, and related files to a directory of your choosing. As of Xcode 7b1, this must be an absolute path.

$ xcodebuild test -scheme 'Buttons' -destination 'platform=iOS Simulator,name=iPhone 6,OS=9.0' -resultBundlePath '~/Desktop/build'

Outputs:

├── 1_Test
│   ├── Attachments
│   │   ├── Screenshot_1236AC16-AF8C-4E87-A748-4867EAB120FC.png
│   │   ├── Screenshot_52E63346-D71F-4A6B-ACA9-736AD54854F3.png
│   │   ├── Screenshot_7A8F0CD6-3981-47FD-BC2D-DC6B35863656.png
│   │   ├── Screenshot_E4792BBC-9717-4BD1-8CD0-D560E19A491D.png
│   │   ├── Snapshot_273B4980-BE03-4EA8-81B7-03F237772558
│   │   ├── Snapshot_86786328-081A-4314-ADC9-213648471F5D
│   │   ├── Snapshot_AA3A1ADC-84C8-4FCC-91A6-590431B89210
│   │   └── Snapshot_CDCA9CA4-847D-46DF-852F-8BCEF0C468AE
│   ├── action.xcactivitylog
│   ├── action_TestSummaries.plist
│   └── build.xcactivitylog
├── Attachments
│   ├── Screenshot_2750BC84-575E-4849-9A39-820FB8D8E998.png
│   ├── Screenshot_59E390A6-9B39-4FD5-AA13-6915C211F6F8.png
│   ├── Screenshot_8F1BB0EE-1146-4CE0-BB5B-1ED473A12173.png
│   ├── Screenshot_DF5341F9-2763-47C6-821B-2B157E88AEC7.png
│   ├── Snapshot_21D3F473-0F28-4F4A-97FD-F30263461422
│   ├── Snapshot_5AF107B1-7CA0-4B14-9EEB-3918E4C4B5E9
│   ├── Snapshot_5F74DD59-6225-40D7-955A-21B366A563BD
│   └── Snapshot_FD2ED335-1729-4489-8B7F-0777E73D4EE8
├── Info.plist
└── TestSummaries.plist

There is duplicate data here. The TestSummaries.plist at the root of the directory is version 1.1, but inside 1_Test is the correct version. This also adds both build and test .xcactivitylog files conveniently in one place. Using CI? This argument will make your life a whole lot easier.

Xcode

It is common knowledge that when Xcode builds a project, it does not do the same thing that xcodebuild does. Just like xcodebuild, building in Xcode creates a Test folder inside Logs, and saves summary plists and attachments. However, Xcode itself creates a lot more logs.

├── Build
│   ├── Cache.db
│   └── D0C828D2-21CA-4182-9A0E-C80DE1F8FA86.xcactivitylog
├── Debug
│   ├── 130CC81E-789E-4FDD-A156-DB75FBEBC770.xcactivitylog
│   ├── 44845FEA-0D87-4266-AD62-502E0F4C30B2.xcactivitylog
│   └── Cache.db
├── Issues
│   ├── 0F1BB6CD-F28B-48DB-9A09-B3948257D698.xcactivitylog
│   ├── 4AFC3EE7-12EC-4E4C-BDF9-2ED7BCA6887E.xcactivitylog
│   ├── C32CA334-AAF9-43B4-AB00-D10373391A08.xcactivitylog
│   └── Cache.db
└── Test
    ├── 9309CF06-A5BB-418C-91D5-299788C685A3.xcactivitylog
    ├── 9309CF06-A5BB-418C-91D5-299788C685A3_TestSummaries.plist
    ├── Attachments
    │   ├── Screenshot_556D54FE-235B-4A3E-A00E-C34ED00AF043.png
    │   ├── Screenshot_786546BC-98C4-4788-B7F1-9A66DFA0EF51.png
    │   ├── Screenshot_976F8BF7-C2A6-4E8F-8BFC-EE6A4FA6D78E.png
    │   ├── Screenshot_BC2D4BF7-DC31-459F-B726-84651CFF52A3.png
    │   ├── Snapshot_31A23466-8A83-42B6-8AD7-4CBD2FB34BB7
    │   ├── Snapshot_626616A0-436F-4D45-822B-41E21E7E80CF
    │   ├── Snapshot_73A33739-49A0-4D8E-9295-338BDAE08C5C
    │   └── Snapshot_83BF8F36-C1EF-45E0-B55E-3320F7FE55D7
    └── Cache.db

When opening up the multiple .xcactivitylog files, there is some overlap. It appears that Xcode will split the different sections of the build into different activity logs. Unfortunately, because the UUIDs are unique and not related, putting a full log back together from these pieces is fairly difficult.

Conclusion

These new files and flags will make automated builds even better with Xcode 7. Apple has finally rounded out the toolset for automating tests, and CI providers are already starting to offer builds using Xcode 7.

The sample project used in this post is available on Github.

Thanks

  • Lawrence Lomax for telling me about the new resultBundlePath option.
  • Brian Nickel for fixing the sample project’s storyboard overriding accessibilityLabel.

© 2023 Michele Titolo