परीक्षण के लिए समर्पित हार्डवेयर क्यों?
साझा या स्थानीय मशीनों पर UI परीक्षण अस्थिरता, असंगति और बाधाएं पैदा करता है। एक समर्पित Mac mini M4 बिल्ड सर्वर इन समस्याओं को हल करता है।
सुसंगत परिणाम
CPU या RAM के लिए प्रतिस्पर्धा करने वाली कोई अन्य प्रक्रिया नहीं। परीक्षण हर बार एक साफ, नियंत्रित वातावरण में चलते हैं, जिससे संसाधन प्रतिस्पर्धा के कारण होने वाली अस्थिर परीक्षण विफलताएं समाप्त हो जाती हैं।
समानांतर निष्पादन
कई iOS Simulator इंस्टेंस पर एक साथ परीक्षण चलाएं। Mac mini M4 Pro (12-कोर CPU, 24GB) बिना प्रदर्शन गिरावट के 4-6 समानांतर Simulator इंस्टेंस संभालता है।
24/7 उपलब्धता
परीक्षण किसी भी समय चलते हैं: रात्रिकालीन रिग्रेशन सूट, मर्ज-पश्चात सत्यापन, या मांग पर। सर्वर हमेशा तैयार रहता है, किसी डेवलपर लैपटॉप की आवश्यकता नहीं।
समर्पित Mac पर XCTest
XCTest Apple का अंतर्निहित परीक्षण फ्रेमवर्क है, जो Xcode के साथ शामिल है। XCUITest इसे UI परीक्षण के लिए विस्तारित करता है। दोनों मूल रूप से चलते हैं और Xcode के अलावा किसी अतिरिक्त सेटअप की आवश्यकता नहीं होती।
कमांड लाइन से XCTest चलाना
# Use any device listed by: xcrun simctl list devices available
# Run all unit tests
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \
-resultBundlePath ./TestResults/UnitTests.xcresult
# Run only UI tests
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyAppUITests \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \
-resultBundlePath ./TestResults/UITests.xcresult
# Run a specific test class
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:MyAppTests/LoginTests
# Run a specific test method
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:MyAppTests/LoginTests/testSuccessfulLogin
कई गंतव्यों पर परीक्षण
# Test on multiple iPhone models simultaneously
# (each name must exist in: xcrun simctl list devices available;
# create a missing one with: xcrun simctl create "iPhone 16e" "iPhone 16e")
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro Max' \
-destination 'platform=iOS Simulator,name=iPhone 16e' \
-destination 'platform=iOS Simulator,name=iPad Pro 13-inch (M4)' \
-resultBundlePath ./TestResults/MultiDevice.xcresult
# List all available destinations
xcodebuild -showdestinations \
-workspace MyApp.xcworkspace \
-scheme MyApp
परिणाम बंडल और स्क्रीनशॉट
# Test results summary as JSON (Xcode 16 and later)
xcrun xcresulttool get test-results summary \
--path ./TestResults/UITests.xcresult
# Every test with its result
xcrun xcresulttool get test-results tests \
--path ./TestResults/UITests.xcresult
# Export test attachments (screenshots, videos)
xcrun xcresulttool export attachments \
--path ./TestResults/UITests.xcresult \
--output-path ./TestArtifacts
iOS के लिए Appium सेटअप
Appium एक ओपन-सोर्स ऑटोमेशन फ्रेमवर्क है जो आपको किसी भी भाषा (Python, JavaScript, Java, आदि) में परीक्षण लिखने और उन्हें सिम्युलेटर या डिवाइस पर iOS ऐप्स के विरुद्ध चलाने देता है। यह पर्दे के पीछे Apple के XCUITest ड्राइवर का उपयोग करता है।
अपने Mac सर्वर पर Appium इंस्टॉल करें
# Install Node.js via Homebrew
brew install node
# Install Appium globally (npm installs the latest release)
npm install -g appium
# Install the XCUITest driver for iOS
appium driver install xcuitest
# Verify installation
appium --version
appium driver list --installed
# Check the driver's requirements (Xcode, WebDriverAgent, etc.)
appium driver doctor xcuitest
# Start Appium server
appium server --address 127.0.0.1 --port 4723
वांछित क्षमताएं (Desired Capabilities) कॉन्फ़िगर करें
# Example capabilities (JSON format, Appium 2 and later)
# The Simulator is picked by name; add "appium:platformVersion"
# to pin a specific iOS version
{
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:deviceName": "iPhone 17",
"appium:app": "/path/to/MyApp.app",
"appium:noReset": false,
"appium:wdaStartupRetries": 3,
"appium:wdaStartupRetryInterval": 20000,
"appium:simulatorStartupTimeout": 120000
}
उदाहरण Appium परीक्षण (Python)
# Install Appium Python client
# pip install Appium-Python-Client
from appium import webdriver
from appium.options.ios import XCUITestOptions
from appium.webdriver.common.appiumby import AppiumBy
# Configure options
options = XCUITestOptions()
options.platform_name = "iOS"
options.device_name = "iPhone 17"
# Optional: options.platform_version pins a specific iOS version
options.app = "/path/to/MyApp.app"
# Connect to Appium server
driver = webdriver.Remote(
command_executor="http://127.0.0.1:4723",
options=options
)
try:
# Wait for app to load
driver.implicitly_wait(10)
# Find and tap login button
login_button = driver.find_element(
AppiumBy.ACCESSIBILITY_ID, "loginButton"
)
login_button.click()
# Enter username
username_field = driver.find_element(
AppiumBy.ACCESSIBILITY_ID, "usernameField"
)
username_field.send_keys("testuser@example.com")
# Enter password
password_field = driver.find_element(
AppiumBy.ACCESSIBILITY_ID, "passwordField"
)
password_field.send_keys("password123")
# Submit login
submit_button = driver.find_element(
AppiumBy.ACCESSIBILITY_ID, "submitButton"
)
submit_button.click()
# Verify welcome screen
welcome_label = driver.find_element(
AppiumBy.ACCESSIBILITY_ID, "welcomeLabel"
)
assert "Welcome" in welcome_label.text
print("Test PASSED: Login successful")
finally:
driver.quit()
उदाहरण Appium परीक्षण (JavaScript)
// npm install webdriverio @wdio/cli
const { remote } = require('webdriverio');
async function runTest() {
const driver = await remote({
protocol: 'http',
hostname: '127.0.0.1',
port: 4723,
path: '/',
capabilities: {
platformName: 'iOS',
'appium:automationName': 'XCUITest',
'appium:deviceName': 'iPhone 17',
'appium:app': '/path/to/MyApp.app'
}
});
try {
// Tap login button
const loginBtn = await driver.$('~loginButton');
await loginBtn.click();
// Enter credentials
const username = await driver.$('~usernameField');
await username.setValue('testuser@example.com');
const password = await driver.$('~passwordField');
await password.setValue('password123');
// Submit
const submit = await driver.$('~submitButton');
await submit.click();
// Verify
const welcome = await driver.$('~welcomeLabel');
const text = await welcome.getText();
console.assert(text.includes('Welcome'), 'Login test failed');
console.log('Test PASSED: Login successful');
} finally {
await driver.deleteSession();
}
}
runTest();
Safari के लिए Selenium
macOS में Safari और safaridriver पहले से शामिल होते हैं। किसी अतिरिक्त ब्राउज़र ड्राइवर डाउनलोड की आवश्यकता नहीं, अन्य प्लेटफ़ॉर्म पर Chrome या Firefox के विपरीत।
safaridriver सक्षम करें
# Enable the Safari WebDriver (one-time setup)
safaridriver --enable
# Verify safaridriver is working
safaridriver --version
# For headless-like automation, enable "Allow Remote Automation"
# in Safari > Settings > Advanced > Show Develop menu
# Then: Develop > Allow Remote Automation
# Or via command line:
defaults write com.apple.Safari AllowRemoteAutomation 1
Selenium Safari परीक्षण (Python)
# pip install selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Create Safari driver
driver = webdriver.Safari()
try:
# Navigate to your web app
driver.get("https://your-webapp.com")
# Wait for page to load
wait = WebDriverWait(driver, 10)
# Find and click a button
login_link = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "a.login-btn"))
)
login_link.click()
# Fill in form
email_input = wait.until(
EC.presence_of_element_located((By.ID, "email"))
)
email_input.send_keys("test@example.com")
password_input = driver.find_element(By.ID, "password")
password_input.send_keys("testpassword")
# Submit form
submit_btn = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
submit_btn.click()
# Verify redirect to dashboard
wait.until(EC.url_contains("/dashboard"))
assert "/dashboard" in driver.current_url
print("Test PASSED: Safari login flow works correctly")
finally:
driver.quit()
Selenium Safari परीक्षण (JavaScript)
// npm install selenium-webdriver
const { Builder, By, until } = require('selenium-webdriver');
async function safariTest() {
const driver = await new Builder()
.forBrowser('safari')
.build();
try {
await driver.get('https://your-webapp.com');
// Click login
const loginBtn = await driver.findElement(By.css('a.login-btn'));
await loginBtn.click();
// Fill form
const email = await driver.findElement(By.id('email'));
await email.sendKeys('test@example.com');
const password = await driver.findElement(By.id('password'));
await password.sendKeys('testpassword');
// Submit
const submit = await driver.findElement(
By.css("button[type='submit']")
);
await submit.click();
// Verify
await driver.wait(until.urlContains('/dashboard'), 10000);
const url = await driver.getCurrentUrl();
console.assert(url.includes('/dashboard'));
console.log('Test PASSED: Safari login flow works');
} finally {
await driver.quit();
}
}
safariTest();
समानांतर परीक्षण निष्पादन
परीक्षणों को समानांतर में चलाने से आपके कुल परीक्षण सूट के निष्पादन समय में नाटकीय कमी आती है। Mac Mini M4 Pro आराम से 4-6 Simulator इंस्टेंस एक साथ चला सकता है।
XCTest समानांतर परीक्षण
# Enable parallel testing with xcodebuild
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-parallel-testing-enabled YES \
-parallel-testing-worker-count 4 \
-resultBundlePath ./TestResults/Parallel.xcresult
# Parallel testing across multiple device types
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-destination 'platform=iOS Simulator,name=iPhone 16e' \
-destination 'platform=iOS Simulator,name=iPad Pro 13-inch (M4)' \
-parallel-testing-enabled YES \
-resultBundlePath ./TestResults/MultiDeviceParallel.xcresult
Simulator इंस्टेंस प्रबंधित करना
# List all available simulators
xcrun simctl list devices available
# Create custom simulator instances for testing
# (without a runtime argument, simctl uses the newest iOS runtime)
xcrun simctl create "Test-iPhone-1" "iPhone 17"
xcrun simctl create "Test-iPhone-2" "iPhone 17"
xcrun simctl create "Test-iPhone-3" "iPhone 17"
xcrun simctl create "Test-iPhone-4" "iPhone 17"
# Boot multiple simulators
xcrun simctl boot "Test-iPhone-1"
xcrun simctl boot "Test-iPhone-2"
xcrun simctl boot "Test-iPhone-3"
xcrun simctl boot "Test-iPhone-4"
# Check booted simulators
xcrun simctl list devices booted
# Shut down all simulators
xcrun simctl shutdown all
# Delete all test simulators
xcrun simctl delete "Test-iPhone-1"
xcrun simctl delete "Test-iPhone-2"
xcrun simctl delete "Test-iPhone-3"
xcrun simctl delete "Test-iPhone-4"
समानांतर परीक्षण प्रदर्शन
| कॉन्फ़िगरेशन | 200 UI परीक्षणों की अवधि | गति सुधार |
|---|---|---|
| 1 Simulator (क्रमिक) | 45 मिनट | बेसलाइन |
| 2 Simulator (समानांतर) | 24 मिनट | 1.9 गुना तेज़ |
| 4 Simulator (समानांतर) | 13 मिनट | 3.5 गुना तेज़ |
| 6 Simulator (समानांतर) | 10 मिनट | 4.5 गुना तेज़ |
नोट: परिणाम Mac Mini M4 Pro (14-कोर, 24GB RAM) पर मापे गए। समानांतर Simulator की इष्टतम संख्या आपकी परीक्षण जटिलता और मेमोरी आवश्यकताओं पर निर्भर करती है। अधिकांश UI परीक्षण सूट के लिए, 4 समानांतर वर्कर गति और स्थिरता का सबसे अच्छा संतुलन प्रदान करते हैं।
CI/CD एकीकरण
हर पुश, पुल रिक्वेस्ट, या निर्धारित अंतराल पर स्वचालित परीक्षण निष्पादन के लिए अपने स्वचालित परीक्षणों को अपनी CI/CD पाइपलाइन में एकीकृत करें।
स्वचालित परीक्षण के लिए GitHub Actions वर्कफ़्लो
# .github/workflows/ios-tests.yml
name: iOS Automated Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
# Nightly regression tests at 2 AM UTC
- cron: '0 2 * * *'
env:
# Xcode for every job, no sudo needed (path from: xcodes installed)
DEVELOPER_DIR: /Applications/Xcode-27.0.0.app/Contents/Developer
jobs:
unit-tests:
name: Unit Tests
runs-on: self-hosted
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Resolve dependencies
run: |
xcodebuild -resolvePackageDependencies \
-workspace MyApp.xcworkspace \
-scheme MyApp
- name: Run unit tests
run: |
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \
-parallel-testing-enabled YES \
-resultBundlePath ${{ runner.temp }}/UnitTests.xcresult
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: ${{ runner.temp }}/UnitTests.xcresult
ui-tests:
name: UI Tests
runs-on: self-hosted
timeout-minutes: 60
needs: unit-tests
steps:
- uses: actions/checkout@v4
- name: Boot simulators for parallel testing
run: |
xcrun simctl boot "iPhone 17" || true
xcrun simctl boot "iPhone 16e" || true
- name: Run UI tests in parallel
run: |
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyAppUITests \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-destination 'platform=iOS Simulator,name=iPhone 16e' \
-parallel-testing-enabled YES \
-parallel-testing-worker-count 4 \
-resultBundlePath ${{ runner.temp }}/UITests.xcresult
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: ui-test-results
path: ${{ runner.temp }}/UITests.xcresult
- name: Cleanup simulators
if: always()
run: xcrun simctl shutdown all
CI में Appium परीक्षण चलाना
# Add to your GitHub Actions workflow
- name: Start Appium server
run: |
appium server --address 127.0.0.1 --port 4723 &
sleep 5
curl http://127.0.0.1:4723/status
- name: Build app for testing
run: |
xcodebuild build-for-testing \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'generic/platform=iOS Simulator'
- name: Run Appium tests
run: |
cd tests/appium
pip install -r requirements.txt
pytest test_login.py test_checkout.py \
--junitxml=results.xml -v
- name: Stop Appium server
if: always()
run: pkill -f appium || true
परीक्षण रिपोर्टिंग
गुणवत्ता रुझानों को ट्रैक करने और विफल परीक्षणों की तुरंत पहचान करने के लिए परीक्षण रिपोर्ट उत्पन्न और संसाधित करें।
xcresult बंडल के साथ काम करना
# Test results summary as JSON (Xcode 16 and later)
xcrun xcresulttool get test-results summary \
--path ./TestResults.xcresult
# Results of every test, then the details of one of them
# (TEST_ID: an identifier printed by the tests command)
xcrun xcresulttool get test-results tests \
--path ./TestResults.xcresult
xcrun xcresulttool get test-results test-details \
--test-id "TEST_ID" \
--path ./TestResults.xcresult
# Export all attachments (screenshots, logs)
xcrun xcresulttool export attachments \
--path ./TestResults.xcresult \
--output-path ./artifacts
# Merge multiple xcresult bundles
xcrun xcresulttool merge \
./UnitTests.xcresult \
./UITests.xcresult \
--output-path ./MergedResults.xcresult
JUnit XML में बदलें
JUnit XML सार्वभौमिक प्रारूप है जो CI/CD प्लेटफ़ॉर्म, Slack एकीकरण और परीक्षण डैशबोर्ड द्वारा समर्थित है।
# Option 1: write JUnit XML while the tests run (xcbeautify)
brew install xcbeautify
set -o pipefail && xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-resultBundlePath ./TestResults/Tests.xcresult \
| xcbeautify --report junit --report-path ./reports
# Writes ./reports/junit.xml
# Option 2: convert existing .xcresult bundles with fastlane's
# trainer action (path: the folder that holds them)
fastlane run trainer path:"./TestResults" output_directory:"./reports"
# The generated JUnit XML works with:
# - GitHub Actions test summaries
# - Jenkins Test Result plugin
# - GitLab CI test reporting
# - Slack notifications via CI integrations
विफलता पर स्क्रीनशॉट
// In your XCUITest, add screenshot capture on failure:
// XCTestCase+Screenshots.swift
import XCTest
extension XCTestCase {
override func tearDown() {
if testRun?.hasSucceeded == false {
let screenshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = "Failure-\(name)"
attachment.lifetime = .keepAlways
add(attachment)
}
super.tearDown()
}
}
सर्वोत्तम प्रथाएं
परीक्षण पृथक्करण
प्रत्येक परीक्षण स्वतंत्र होना चाहिए और पिछले परीक्षणों की स्थिति पर निर्भर नहीं होना चाहिए। प्रत्येक परीक्षण से पहले ऐप की स्थिति रीसेट करें।
// In your XCUITest setUp() method:
override func setUp() {
super.setUp()
continueAfterFailure = false
let app = XCUIApplication()
app.launchArguments = ["--uitesting", "--reset-state"]
app.launchEnvironment = [
"DISABLE_ANIMATIONS": "1",
"UI_TEST_MODE": "true"
]
app.launch()
}
Simulator प्रबंधन
स्थिति लीक और डिस्क स्थान समस्याओं को रोकने के लिए परीक्षण रन के बीच सिम्युलेटर साफ़ करें।
#!/bin/bash
# cleanup-simulators.sh - Run before and after test suites
# Shutdown all running simulators
xcrun simctl shutdown all
# Erase all simulator content and settings
xcrun simctl erase all
# Delete unavailable simulators
xcrun simctl delete unavailable
# Clear DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData/*
# Clear simulator logs
rm -rf ~/Library/Logs/CoreSimulator/*
echo "Simulator cleanup complete"
गति के लिए एनिमेशन अक्षम करें
परीक्षण निष्पादन समय और अस्थिरता कम करने के लिए UI परीक्षण के दौरान अपने ऐप में एनिमेशन अक्षम करें।
// In your AppDelegate or App struct:
#if DEBUG
if CommandLine.arguments.contains("--uitesting") {
UIView.setAnimationsEnabled(false)
}
#endif
// Also disable Simulator animations via command line:
// Set Simulator > Debug > Slow Animations = OFF
defaults write com.apple.iphonesimulator SlowMotionAnimation -bool NO
अस्थिर परीक्षण पुनः प्रयास करें
कभी-कभार अस्थिर UI परीक्षणों को संभालने के लिए Xcode के अंतर्निहित परीक्षण पुनः-प्रयास तंत्र का उपयोग करें।
# Run each failing test up to 3 times in total
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyAppUITests \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-retry-tests-on-failure \
-test-iterations 3 \
-resultBundlePath ./TestResults.xcresult
डिस्क स्थान की निगरानी करें
सिम्युलेटर और परीक्षण आर्टिफैक्ट काफी डिस्क स्थान का उपयोग करते हैं। स्वचालित सफाई सेट करें।
# Add to crontab for daily cleanup at midnight
# crontab -e
0 0 * * * /usr/local/bin/cleanup-test-artifacts.sh
# cleanup-test-artifacts.sh
#!/bin/bash
# Remove test results older than 7 days
find ~/TestResults -name "*.xcresult" -mtime +7 -delete
# Remove DerivedData older than 3 days
find ~/Library/Developer/Xcode/DerivedData \
-maxdepth 1 -mtime +3 -exec rm -rf {} +
# Remove old simulator logs
find ~/Library/Logs/CoreSimulator -mtime +3 -delete
# Check remaining disk space
df -h / | tail -1
अक्सर पूछे जाने वाले प्रश्न
एक Mac Mini M4 Pro कितने समानांतर सिम्युलेटर संभाल सकता है?
क्या मैं VNC के बिना परीक्षण हेडलेस रूप से चला सकता हूं?
xcodebuild के माध्यम से बिना डिस्प्ले सत्र के लॉन्च किया जाता है तो iOS Simulator "हेडलेस" मोड में चलता है। परीक्षण निष्पादन के दौरान आपको VNC कनेक्ट करने की आवश्यकता नहीं है। VNC केवल विफल परीक्षणों को दृश्य रूप से डिबग करने के लिए उपयोगी है।मैं अस्थिर UI परीक्षणों को कैसे संभालूं?
sleep() के बजाय स्पष्ट प्रतीक्षा (explicit waits) का उपयोग करें। विफल परीक्षणों को स्वचालित रूप से पुनः प्रयास करने के लिए Xcode के -retry-tests-on-failure फ्लैग का उपयोग करें। एक समर्पित macOS सर्वर पर, संसाधन प्रतिस्पर्धा से होने वाली अस्थिरता समाप्त हो जाती है, जो साझा CI मशीनों पर अस्थिर परीक्षणों का सबसे सामान्य कारण है।क्या मैं एक दूरस्थ Mac से वास्तविक iOS डिवाइस पर परीक्षण कर सकता हूं?
विभिन्न iOS संस्करणों पर परीक्षण के बारे में क्या?
xcodebuild -downloadPlatform iOS का उपयोग करें, या पुराने रनटाइम Xcode > Settings > Components से डाउनलोड करें। फिर अपने परीक्षण गंतव्य में OS संस्करण निर्दिष्ट करें: -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.0'।