Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
205: Accessibility Identifiers for Testing
I've seen this exact scenario play out in almost every professional project I've touched. You spend a few hours writing a suite of UI tests that pass perfectly. You feel great. Then, a designer asks you to change "Get Started" to "Let's Go!" on the onboarding screen. Suddenly, half your test suite is red, and you're spending your afternoon hunting down string mismatches in your test files instead of building features.
// This test works today...
func testOnboardingFlow() {
let app = XCUIApplication()
app.launch()
// We are searching for the button by its visible text
let startButton = app.buttons["Get Started"]
XCTAssertTrue(startButton.exists)
startButton.tap()
}
The Fragile Link Between UI and Tests
The problem here is that the test is coupled to the content of the UI. In the example above, app.buttons["Get Started"] tells XCTest to look for a button whose label is exactly "Get Started". This seems intuitive, but it's a trap. Content changes for a dozen reasons: A/B testing, localization for different languages, or just a simple wording tweak. When you use the label as your primary hook, your tests aren't actually testing the logic—they're testing the copywriting.
If you change that button to "Let's Go!", the test fails. Not because the app is broken, but because the "hook" you used to find the button vanished. I call these "brittle tests," and they are the primary reason developers eventually stop writing UI tests altogether because the maintenance overhead becomes a nightmare.
Decoupling Logic with Accessibility Identifiers
The solution is to use accessibilityIdentifier. Unlike the accessibilityLabel (which is what VoiceOver reads aloud to users), the accessibilityIdentifier is a developer-facing string. It's invisible to the user and doesn't change regardless of the language the app is running in. It acts as a permanent "ID tag" for the element.
Here is how I would rewrite that button and the corresponding test to make it bulletproof:
// In your View code
let startButton = UIButton()
startButton.setTitle("Get Started", for: .normal)
// This is the secret sauce:
startButton.accessibilityIdentifier = "onboarding_start_button"
// In your Test code
func testOnboardingFlow() {
let app = XCUIApplication()
app.launch()
// Now we search for the identifier, not the text
let startButton = app.buttons["onboarding_start_button"]
XCTAssertTrue(startButton.exists)
startButton.tap()
}
Now, the designer can change the button text to "Click Me!", "Start Journey", or "¡Empecemos!" and the test will still pass. You've successfully decoupled the what (the button's purpose) from the how (the button's appearance). Just remember: always use a consistent naming convention for these identifiers—like screenName_elementPurpose_elementType—so you don't end up with a chaotic mess of random strings as your app grows.
📋 Practical Task
Hardening the Checkout Screen Tests
You have a checkout screen where the "Pay Now" button is causing tests to fail because the marketing team keeps changing the text to "Complete Purchase" and "Secure Checkout".
Your Task:
- Create a
UIButtoncalledpaymentButton. - Set its initial title to "Complete Purchase".
- Assign it a unique
accessibilityIdentifierthat follows a professional naming convention (e.g.,checkout_pay_button). - Write a mock XCTest snippet that attempts to find and tap this button using the identifier rather than the title.
There are no comments for now.