From a215b1de724a44b6489bf2ec640ebc9faa1152cd Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 13 Mar 2025 08:48:47 +0000 Subject: [PATCH] feat: Add comprehensive iOS test runner with deeplink testing - Add test-ios.js script for iOS build and test automation - Add simulator detection and validation - Add CocoaPods dependency handling - Add iOS deeplink testing with xcrun simctl - Add detailed build logging for iOS tests The changes improve iOS testing by: 1. Automating iOS build and test process 2. Adding structured logging to build_logs directory 3. Supporting deeplink testing in iOS simulator 4. Validating iOS development environment 5. Matching Android test capabilities Technical details: - Uses xcodebuild for building and testing - Handles simulator device detection - Supports all deeplink test cases - Provides detailed error reporting - Maintains test logs for debugging --- scripts/test-ios.js | 211 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 187 insertions(+), 24 deletions(-) diff --git a/scripts/test-ios.js b/scripts/test-ios.js index 77e5bd9..5a6869d 100644 --- a/scripts/test-ios.js +++ b/scripts/test-ios.js @@ -30,6 +30,7 @@ * * @requires child_process * @requires path + * @requires fs * * @author TimeSafari Team * @license MIT @@ -37,6 +38,166 @@ 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 @@ -58,33 +219,35 @@ const { join } = require('path'); * }); */ 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 { - // Sync Capacitor project with latest web build - // This ensures the iOS project has the latest web assets - execSync('npx cap sync ios', { - stdio: 'inherit', - // Inherit stdio to show real-time output - }); - - // Build and run tests using xcodebuild - execSync( - 'cd ios && xcodebuild test ' + - '-workspace App/App.xcworkspace ' + // Workspace containing the project - '-scheme App ' + // The scheme to build and test - '-destination "platform=iOS Simulator,name=iPhone 14"', // Target simulator - { - stdio: 'inherit', - // test: Builds and runs tests - // -workspace: Specifies the Xcode workspace - // -scheme: Specifies the scheme to test - // -destination: Specifies the target simulator - } - ); + 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); - console.log('โœ… iOS tests completed successfully'); + log('๐ŸŽ‰ iOS build and test process completed successfully'); + log(`๐Ÿ“ Full build log available at: ${logFile}`); } catch (error) { - // Log the error and exit with failure code - console.error('โŒ iOS tests failed:', error); + log(`โŒ iOS tests failed: ${error.message}`); + log(`๐Ÿ“ Check build log for details: ${logFile}`); process.exit(1); } }