<!--
{
  "documentType" : "article",
  "framework" : "XCTest",
  "identifier" : "/documentation/XCTest/grouping-tests-into-substeps-with-activities",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Grouping Tests into Substeps with Activities"
}
-->

# Grouping Tests into Substeps with Activities

Simplify test reports by creating activities that organize substeps within complex test methods.

## Discussion

Use activities to break longer test methods, such as UI tests and integration tests, into smaller named substeps.

Each activity wraps a block of code, giving the code a name. You can nest and call activities within other activities. Xcode test reports organize results by activity name, making test reports for complex, multistep tests easier to understand.

For long test methods, especially in UI tests that contain lots of steps, simplify your test methods by refactoring them into utility methods or substeps with activities.

### Organize Long Test Methods into Substeps

Identify the substeps that you want to group into named activities. For example, you might break down a login UI test into three substeps: opening the login window, entering a password, and closing the login window. Then, create named activities for each substep you identify.

```objc
- (void)testLogin {
    [self openLoginWindow];
    [self enterPasswordAndUserName:@"member"];
    [self closeLoginWindow];
}

- (void)openLoginWindow {
    [XCTContext runActivityNamed:@"Open login window" block:^(id<XCTActivity> activity) {
        XCUIElement *loginButton = self.app.buttons[@"Login"];
        XCTAssertTrue(loginButton.exists, @"Login button is missing.");
        XCTAssertTrue(loginButton.isHittable, @"Login button is not hittable.");
        XCTAssertFalse(self.app.staticTexts[@"Logged In"].exists, @"Logged In label is visible and should not be.");
        [loginButton tap];
        
        XCUIElement *loginLabel = self.app.staticTexts[@"Login:"];
        BOOL loginLabelAppeared = [loginLabel waitForExistenceWithTimeout:3.0];
        XCTAssertTrue(loginLabelAppeared, @"Login label is missing.");
    }];
}

- (void)enterPasswordAndUserName:(NSString *)userName {
    [XCTContext runActivityNamed:@"Enter password" block:^(id<XCTActivity> activity) {
        XCUIElement *userNameTextField = self.app.textFields[@"user name"];
        [userNameTextField tap];
        [userNameTextField typeText:userName];
        
        XCUIElement *passwordSecureTextField = self.app.secureTextFields[@"password"];
        [passwordSecureTextField tap];
        [passwordSecureTextField typeText:@"password"];
        
        //Dismiss keyboard.
        [[[self.app childrenMatchingType:XCUIElementTypeWindow] firstMatch] tap];
    }];
}

- (void)closeLoginWindow {
    [XCTContext runActivityNamed:@"Close login window" block:^(id<XCTActivity> activity) {
        XCUIElement *submitLoginButton = self.app.buttons[@"Submit"];
        XCTAssertTrue(submitLoginButton.exists, @"Submit button is missing.");
        XCTAssertTrue(submitLoginButton.isHittable, @"Submit button is not hittable.");
        [submitLoginButton tap];

        BOOL loggedInLabelAppeared = [self.app.staticTexts[@"Logged In"] waitForExistenceWithTimeout:3.0];
        XCTAssertTrue(loggedInLabelAppeared, @"Logged In label is missing.");
    }];
}
```

Activities run against the current testing context, represented by [`XCTContext`](/documentation/XCTest/XCTContext). To run a block of code as an activity, call the [`runActivityNamed:block:`](/documentation/XCTest/XCTContext/runActivityNamed:block:) class method on `XCTContext`, passing your test code as the block to execute.

### Build Utility Methods from Common Test Substeps

Convert common test substeps into self-contained utility methods for reuse in multiple tests with activities. For example, if you have three UI tests that each require the user to be logged in, extract the login process into a utility method that wraps the process inside an activity called `Login`, and call the utility method from within each test method. The login activity appears in the Xcode test report for each test method that calls it.

```objc
- (void)testAdminLoginFeatures {
    BOOL loginResult = [self loginForUserName:@"admin"];
    XCTAssertTrue(loginResult);
    
    XCTAssertTrue(self.app.buttons[@"Admin Features"].exists, @"Missing Admin Features button.");
    XCTAssertFalse(self.app.buttons[@"Member Features"].exists, @"Member Features button is visible and should not be.");
}

- (void)testMemberLoginFeatures {
    BOOL loginResult = [self loginForUserName:@"member"];
    XCTAssertTrue(loginResult);

    XCTAssertFalse(self.app.buttons[@"Admin Features"].exists, @"Admin Features button is visible and should not be.");
    XCTAssertTrue(self.app.buttons[@"Member Features"].exists, @"Missing Member Features button.");
}

- (void)testGuestLoginFeatures {
    BOOL loginResult = [self loginForUserName:@"guest"];
    if (loginResult == YES) {
        XCTAssertFalse(self.app.buttons[@"Admin Features"].exists, @"Admin Features button is visible and should not be.");
        XCTAssertFalse(self.app.buttons[@"Member Features"].exists, @"Member Features button is visible and should not be.");
    } else {
        XCTSkip(@"Guest logins are still not working, skip this test.");
    }
}

- (BOOL)loginForUserName:(NSString *) userName {
    __block BOOL loginSuccessful = NO;
    [XCTContext runActivityNamed:@"Login" block:^(id<XCTActivity> activity) {
        [self performLoginUITestsForUserName:userName];
        loginSuccessful = self.app.staticTexts[@"Logged In"].exists;
        if (!loginSuccessful) {
            XCUIScreenshot *screenshot = [self.app.windows.firstMatch screenshot];
            XCTAttachment *attachment = [XCTAttachment attachmentWithScreenshot:screenshot];
            attachment.lifetime = XCTAttachmentLifetimeKeepAlways;
            [activity addAttachment:attachment];
        }
    }];
    return loginSuccessful;
}
```

If the login fails, the login activity adds a screenshot as an attachment for later investigation. For more information, see [Adding Attachments to Tests, Activities, and Issues](/documentation/XCTest/adding-attachments-to-tests-activities-and-issues).

You can use `XCTContext` anywhere within your test target, not just within test methods on an [`XCTestCase`](/documentation/XCTest/XCTestCase) subclass. This enables you to define activities in your own utility code, such as in custom methods on subclasses of <doc://com.apple.documentation/documentation/XCUIAutomation/XCUIApplication> or <doc://com.apple.documentation/documentation/XCUIAutomation/XCUIElement>.

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)