docs/ui-testing.md
xcodebuild test \
-project Telegram/Telegram.xcodeproj \
-scheme iOSAppUITestSuite \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=26.1'
Pick any available simulator. List them with xcrun simctl list devices available iPhone.
Tests launch the app with the --ui-test argument. When the app detects this flag:
telegram-ui-tests-data/ inside the app group container), completely separate from production data.This means every test begins with the app in its first-launch state: no accounts, no data, showing the welcome/auth screen.
Test files live in Telegram/Tests/Sources/. The test target is iOSAppUITestSuite.
Tests use Apple's XCUITest framework. Each test class extends XCTestCase and interacts with the app through XCUIApplication.
import XCTest
class MyFeatureTests: XCTestCase {
private var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments.append("--ui-test")
}
override func tearDownWithError() throws {
app = nil
}
func testSomething() throws {
app.launch()
// Find elements
let button = app.buttons["Start Messaging"]
// Wait for elements to appear
XCTAssert(button.waitForExistence(timeout: 5.0))
// Interact
button.tap()
// Assert
XCTAssert(app.textFields["Phone Number"].waitForExistence(timeout: 3.0))
}
}
Always pass --ui-test in launchArguments. Without it, the app uses production servers and the real database.
Wait for elements rather than assuming they exist immediately. Use element.waitForExistence(timeout:) before interacting.
Find elements by accessibility identifier, label, or type:
app.buttons["Continue"] // by label
app.textFields["Phone Number"] // by placeholder/label
app.staticTexts["Welcome"] // static text
app.navigationBars["Settings"] // navigation bar
Relaunch between tests if needed. Each app.launch() call with --ui-test wipes the database, so every test method that calls launch() gets a fresh app state.
Type text into fields:
let field = app.textFields["Phone Number"]
field.tap()
field.typeText("9996621234")
.swift file in Telegram/Tests/Sources/.iOSAppUITestSuite target via the Bazel build — no manual target membership changes needed.xcodebuild test command.The test environment uses 3 separate Telegram datacenters, completely independent from production.
Test logins are guarded behind a specialized OS environment. The simulator or device must be configured for the test environment before test accounts can authenticate.
Test phone numbers follow the format 99966XYYYY:
X is the DC number (1, 2, or 3)YYYY are random digitsThe country code for test numbers is 999, and the remaining digits are 66XYYYY.
Examples: +999 66 2 1234, +999 66 1 0000, +999 66 3 0001.
Test accounts do not receive real SMS. The confirmation code is the DC number repeated 5 times:
+999 661 YYYY) -> code 11111+999 662 YYYY) -> code 22222+999 663 YYYY) -> code 33333Test numbers are still subject to flood limits. If a number gets rate-limited, pick a different YYYY suffix.
Tests that exercise the sign-up flow create an account on the test servers. Re-running the same test with the same phone number will skip sign-up because the account already exists. To get a fresh sign-up screen, delete the account before running the test.
The app supports a --delete-test-account <phone> launch argument. When combined with --ui-test, the app logs into the test account, deletes it, and exits — no UI is shown. The verification code is derived automatically from the phone number (DC digit repeated 5 times).
In UI tests, use this by launching a separate app instance:
private func deleteTestAccount(phone: String) {
let cleanupApp = XCUIApplication()
cleanupApp.launchArguments += ["--ui-test", "--delete-test-account", phone]
cleanupApp.launch()
let terminated = cleanupApp.wait(for: .notRunning, timeout: 30)
XCTAssert(terminated, "test account cleanup did not complete in 30s")
}
Call deleteTestAccount(phone:) at the start of any test that needs a clean sign-up flow. The phone number format is 99966XYYYY (no + prefix needed).
Do not store any important or private information in test accounts. Anyone can use the simplified authorization mechanism to access them.