AI Skill Report Card

Writing iOS Automated Tests

A-85·Oct 7, 2026·Source: Web
15 / 15
SWIFT
import XCTest final class LoginFlowUITests: XCTestCase { var app: XCUIApplication! override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launchArguments = ["UI-Testing"] app.launch() } func testSuccessfulLogin() throws { let emailField = app.textFields["emailTextField"] let passwordField = app.secureTextFields["passwordTextField"] let loginButton = app.buttons["loginButton"] XCTAssertTrue(emailField.waitForExistence(timeout: 5)) emailField.tap() emailField.typeText("qa@example.com") passwordField.tap() passwordField.typeText("ValidPass123!") loginButton.tap() let homeTitle = app.staticTexts["homeScreenTitle"] XCTAssertTrue(homeTitle.waitForExistence(timeout: 5)) } }

Run with cmd+U in Xcode or xcodebuild test -scheme YourScheme -destination 'platform=iOS Simulator,name=iPhone 15'.

Recommendation▾
Add an example showing a flaky test anti-pattern vs fixed version for contrast
14 / 15

Progress:

  • Step 1: Identify the manual test case and its preconditions, steps, and expected result
  • Step 2: Confirm accessibility identifiers exist on relevant UI elements (request from dev if missing)
  • Step 3: Choose test type — XCTest (unit/logic) vs XCUITest (UI/flow)
  • Step 4: Write setUp/tearDown to establish consistent state (launch args, mock data, reset state)
  • Step 5: Translate each manual step into an action + assertion pair
  • Step 6: Add explicit waits (waitForExistence) instead of sleep()
  • Step 7: Run test 3x locally to check for flakiness
  • Step 8: Add to test plan / CI scheme and tag (smoke, regression, etc.)

Step 1: Map manual case to automated structure

Take the manual test case and break it into Given/When/Then:

  • Given: preconditions (logged out, cart has item, airplane mode on)
  • When: user actions (tap, swipe, type, scroll)
  • Then: expected outcome (element appears, value changes, navigation occurs)

Step 2: Ensure testability

Request accessibility identifiers from developers rather than relying on label text (which changes with localization):

SWIFT
// Dev side loginButton.accessibilityIdentifier = "loginButton"

If devs can't add identifiers immediately, fall back to app.buttons["Log In"] temporarily and flag as tech debt.

Step 3: Unit vs UI test decision

Use XCTest (unit) when...Use XCUITest (UI) when...
Testing a function, validator, or modelTesting a user-facing flow
Testing API/network response handling (mocked)Testing navigation, gestures, visual state
Fast feedback needed, no simulator requiredVerifying end-to-end user journeys

Step 4: Structure for reliability

  • One behavior per test method — don't chain unrelated assertions
  • Use launchArguments/launchEnvironment to inject test mode (mock APIs, seeded data)
  • Reset app state in setUpWithError, not relying on test execution order
Recommendation▾
Include guidance on handling async/await or Combine-based network mocking in XCUITest context
18 / 20

Example 1: Input: Manual test case — "Verify user can add item to cart and see updated badge count" Output:

SWIFT
func testAddItemUpdatesCartBadge() throws { let product = app.cells["product_123"] XCTAssertTrue(product.waitForExistence(timeout: 5)) product.tap() app.buttons["addToCartButton"].tap() let cartBadge = app.staticTexts["cartBadgeCount"] XCTAssertTrue(cartBadge.waitForExistence(timeout: 5)) XCTAssertEqual(cartBadge.label, "1") }

Example 2: Input: Manual test case — "Verify form validation shows error for invalid email" Output:

SWIFT
func testInvalidEmailShowsError() throws { let emailField = app.textFields["emailTextField"] emailField.tap() emailField.typeText("not-an-email") app.buttons["submitButton"].tap() let errorLabel = app.staticTexts["emailErrorLabel"] XCTAssertTrue(errorLabel.waitForExistence(timeout: 3)) XCTAssertEqual(errorLabel.label, "Please enter a valid email address") }

Example 3: Input: Business logic — "Discount should apply only to orders over $50" Output:

SWIFT
func testDiscountAppliesAboveThreshold() { let order = Order(subtotal: 60.0) XCTAssertEqual(order.applyDiscount(), 54.0) // 10% off let smallOrder = Order(subtotal: 30.0) XCTAssertEqual(smallOrder.applyDiscount(), 30.0) // no discount }
Recommendation▾
Consider adding a brief section on CI integration specifics (fastlane, GitHub Actions) for completeness
  • Use waitForExistence(timeout:) for every assertion on a newly-appearing element — never assume instant rendering
  • Inject mock/stubbed network responses via launch environment so tests don't depend on live backends
  • Name tests descriptively: test<Scenario>_<ExpectedResult>
  • Group tests into suites by feature (LoginTests, CheckoutTests) and tag smoke vs regression
  • Keep a Page Object layer (helper structs wrapping XCUIApplication queries) so UI changes require updating one file, not every test
  • Run new tests repeatedly (5-10x) before merging to catch flakiness early
  • Capture screenshots on failure using XCTAttachment for easier debugging in CI
  • Don't use sleep() for timing — it's slow and still flaky; always use waitForExistence
  • Don't rely on visible text labels for element lookup if localization is in scope — use identifiers
  • Don't let tests depend on execution order or shared mutable state between test methods
  • Don't test against production/live data — always use a seeded/mock environment
  • Don't write one giant test covering an entire user journey — split into focused, independently-diagnosable tests
  • Don't ignore intermittent failures as "just flaky" — investigate; it's often a missing wait condition
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
15/15
Workflow
14/15
Examples
18/20
Completeness
18/20
Format
15/15
Conciseness
13/15