You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

256 lines
8.2 KiB

/**
* @fileoverview iOS test runner for Capacitor-based mobile app
*
* This script handles the build and testing of the iOS app using Xcode's
* command-line tools. It ensures the app is properly synced with the latest
* web build and runs the test suite on a specified iOS simulator.
*
* Process flow:
* 1. Sync Capacitor project with latest web build
* 2. Build app for iOS simulator
* 3. Run XCTest suite
*
* Prerequisites:
* - macOS operating system
* - Xcode installed with command line tools
* - iOS simulator available
* - Capacitor iOS platform added to project
* - Valid iOS development certificates
*
* Exit codes:
* - 0: Tests completed successfully
* - 1: Build or test failure
*
* @example
* // Run directly
* node scripts/test-ios.js
*
* // Run via npm script
* npm run test:ios
*
* @requires child_process
* @requires path
* @requires fs
*
* @author TimeSafari Team
* @license MIT
*/
const { execSync } = require('child_process');
const { join } = require('path');
const { existsSync, mkdirSync, appendFileSync, readFileSync } = require('fs');
// Format date as YYYY-MM-DD-HHMMSS
const getLogFileName = () => {
const now = new Date();
const date = now.toISOString().split('T')[0];
const time = now.toTimeString().split(' ')[0].replace(/:/g, '');
return `build_logs/ios-build-${date}-${time}.log`;
};
// Create logger function
const createLogger = (logFile) => {
return (message) => {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
console.log(message);
appendFileSync(logFile, logMessage);
};
};
// Check for iOS simulator
const checkSimulator = async (log) => {
log('🔍 Checking for iOS simulator...');
const simulators = execSync('xcrun simctl list devices available').toString();
const bootedDevices = simulators.split('\n')
.filter(line => line.includes('Booted'))
.map(line => line.match(/(.*?)\s+\((.*?)\)/)?.[1])
.filter(Boolean);
if (bootedDevices.length === 0) {
throw new Error('No iOS simulator running. Please start a simulator first.');
}
log(`📱 Found ${bootedDevices.length} simulator(s): ${bootedDevices.join(', ')}`);
return bootedDevices;
};
// Verify Xcode installation
const verifyXcodeInstallation = (log) => {
log('🔍 Checking Xcode installation...');
try {
execSync('xcode-select -p');
log('✅ Xcode command line tools found');
} catch (error) {
throw new Error('Xcode command line tools not found. Please install Xcode first.');
}
};
// Generate test data using generate_data.ts
const generateTestData = async (log) => {
log('🔄 Generating test data...');
try {
execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
log('✅ Test data generated successfully');
} catch (error) {
throw new Error(`Failed to generate test data: ${error.message}`);
}
};
// Build web assets
const buildWebAssets = async (log) => {
log('🌐 Building web assets...');
execSync('rm -rf dist', { stdio: 'inherit' });
execSync('npm run build:web', { stdio: 'inherit' });
execSync('npm run build:capacitor', { stdio: 'inherit' });
log('✅ Web assets built successfully');
};
// Configure iOS project
const configureIosProject = async (log) => {
log('📱 Syncing Capacitor project...');
execSync('npx cap sync ios', { stdio: 'inherit' });
log('✅ Capacitor sync completed');
log('⚙️ Installing CocoaPods dependencies...');
execSync('cd ios/App && pod install', { stdio: 'inherit' });
log('✅ CocoaPods installation completed');
};
// Build and test iOS project
const buildAndTestIos = async (log) => {
log('🏗️ Building iOS project...');
execSync('cd ios/App && xcodebuild clean -workspace App.xcworkspace -scheme App', { stdio: 'inherit' });
log('✅ Xcode clean completed');
execSync('cd ios/App && xcodebuild build-for-testing -workspace App.xcworkspace -scheme App -destination "platform=iOS Simulator,name=iPhone 14"', { stdio: 'inherit' });
log('✅ Xcode build completed');
log('🧪 Running iOS tests...');
execSync('cd ios/App && xcodebuild test-without-building -workspace App.xcworkspace -scheme App -destination "platform=iOS Simulator,name=iPhone 14"', { stdio: 'inherit' });
log('✅ iOS tests completed');
};
// Run the app
const runIosApp = async (log) => {
log('📱 Running app in simulator...');
execSync('npx cap run ios', { stdio: 'inherit' });
log('✅ App launched successfully');
};
// Run deeplink tests
const runDeeplinkTests = async (log) => {
log('🔗 Starting deeplink tests...');
try {
// Load test data
const testEnv = JSON.parse(readFileSync('.generated/test-env.json', 'utf8'));
const claimDetails = JSON.parse(readFileSync('.generated/claim_details.json', 'utf8'));
const contacts = JSON.parse(readFileSync('.generated/contacts.json', 'utf8'));
// Test URLs
const deeplinkTests = [
{
url: `timesafari://claim/${claimDetails.claim_id}`,
description: 'Claim view'
},
{
url: `timesafari://claim-cert/${claimDetails.claim_id}`,
description: 'Claim certificate view'
},
{
url: `timesafari://claim-add-raw/${claimDetails.claim_id}`,
description: 'Raw claim addition'
},
{
url: 'timesafari://did/test',
description: 'DID view with test identifier'
},
{
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
description: 'DID view with contact DID'
},
{
url: `timesafari://contact-edit/${testEnv.CONTACT1_DID}`,
description: 'Contact editing'
},
{
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
description: 'Contacts import'
}
];
// Execute each test
for (const test of deeplinkTests) {
log(`\n🔗 Testing deeplink: ${test.description}`);
log(`URL: ${test.url}`);
execSync(`xcrun simctl openurl booted "${test.url}"`);
log(`✅ Successfully executed: ${test.description}`);
// Wait between tests
await new Promise(resolve => setTimeout(resolve, 5000));
}
log('✅ All deeplink tests completed successfully');
} catch (error) {
log('❌ Deeplink tests failed');
throw error;
}
};
/**
* Runs the complete iOS test suite including build and testing
*
* The function performs the following steps:
* 1. Syncs the Capacitor project with latest web build
* 2. Builds and tests the app using xcodebuild
*
* Note: This function requires a running iOS simulator. The test will
* fail if no simulator is available or if it's not in a booted state.
*
* @async
* @throws {Error} If any step in the build or test process fails
*
* @example
* runIosTests().catch(error => {
* console.error('Test execution failed:', error);
* process.exit(1);
* });
*/
async function runIosTests() {
// Create build_logs directory if it doesn't exist
if (!existsSync('build_logs')) {
mkdirSync('build_logs');
}
const logFile = getLogFileName();
const log = createLogger(logFile);
try {
log('🚀 Starting iOS build and test process...');
// Generate test data first
await generateTestData(log);
await checkSimulator(log);
verifyXcodeInstallation(log);
await buildWebAssets(log);
await configureIosProject(log);
await buildAndTestIos(log);
await runIosApp(log);
// Run deeplink tests after app is installed
await runDeeplinkTests(log);
log('🎉 iOS build and test process completed successfully');
log(`📝 Full build log available at: ${logFile}`);
} catch (error) {
log(`❌ iOS tests failed: ${error.message}`);
log(`📝 Check build log for details: ${logFile}`);
process.exit(1);
}
}
// Execute the test suite
runIosTests();