@ -37,14 +37,208 @@
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/android-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 connected Android devices
const checkConnectedDevices = async ( log ) => {
log ( '🔍 Checking for Android devices...' ) ;
const devices = execSync ( 'adb devices' ) . toString ( ) ;
const connectedDevices = devices . split ( '\n' )
. slice ( 1 )
. filter ( line => line . includes ( 'device' ) )
. map ( line => line . split ( '\t' ) [ 0 ] )
. filter ( Boolean ) ;
if ( connectedDevices . length === 0 ) {
throw new Error ( 'No Android devices or emulators connected. Please connect a device or start an emulator.' ) ;
}
log ( ` 📱 Found ${ connectedDevices . length } device(s): ${ connectedDevices . join ( ', ' ) } ` ) ;
return connectedDevices ;
} ;
// Verify Java installation
const verifyJavaInstallation = ( log ) => {
log ( '🔍 Checking Java version...' ) ;
const javaHome = '/usr/lib/jvm/java-17-openjdk' ;
if ( ! existsSync ( javaHome ) ) {
throw new Error ( ` Required Java 17 not found at ${ javaHome } . Please install OpenJDK 17. ` ) ;
}
log ( '✅ Java 17 found' ) ;
return { JAVA_HOME : javaHome } ;
} ;
// 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 } ` ) ;
}
} ;
// Parse shell environment file
const parseEnvFile = ( filePath ) => {
const content = readFileSync ( filePath , 'utf8' ) ;
const env = { } ;
content . split ( '\n' ) . forEach ( line => {
const match = line . match ( /^export\s+(\w+)="(.+)"$/ ) ;
if ( match ) {
env [ match [ 1 ] ] = match [ 2 ] ;
}
} ) ;
return env ;
} ;
// Run individual deeplink test
const executeDeeplink = async ( url , description , log ) => {
log ( ` \n 🔗 Testing deeplink: ${ description } ` ) ;
log ( ` URL: ${ url } ` ) ;
try {
// Stop the app before executing the deep link
execSync ( 'adb shell am force-stop app.timesafari.app' ) ;
await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ; // Wait 1s
execSync ( ` adb shell am start -W -a android.intent.action.VIEW -d " ${ url } " -c android.intent.category.BROWSABLE ` ) ;
log ( ` ✅ Successfully executed: ${ description } ` ) ;
// Wait between deeplink tests
await new Promise ( resolve => setTimeout ( resolve , 5000 ) ) ; // Wait 5s
} catch ( error ) {
log ( ` ❌ Failed to execute deeplink: ${ description } ` ) ;
log ( ` Error: ${ error . message } ` ) ;
throw error ;
}
} ;
// Run all deeplink tests
const runDeeplinkTests = async ( log ) => {
log ( '🔗 Starting deeplink tests...' ) ;
try {
// Load test data
const testEnv = parseEnvFile ( '.generated/test-env.sh' ) ;
const claimDetails = JSON . parse ( readFileSync ( '.generated/claim_details.json' , 'utf8' ) ) ;
const contacts = JSON . parse ( readFileSync ( '.generated/contacts.json' , 'utf8' ) ) ;
// Test each deeplink
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 ) {
await executeDeeplink ( test . url , test . description , log ) ;
}
log ( '✅ All deeplink tests completed successfully' ) ;
} catch ( error ) {
log ( '❌ Deeplink tests failed' ) ;
throw error ;
}
} ;
// 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 Android project
const configureAndroidProject = async ( log ) => {
log ( '📱 Syncing Capacitor project...' ) ;
execSync ( 'npx cap sync android' , { stdio : 'inherit' } ) ;
log ( '✅ Capacitor sync completed' ) ;
log ( '⚙️ Configuring Gradle properties...' ) ;
const gradleProps = 'android/gradle.properties' ;
if ( ! existsSync ( gradleProps ) || ! execSync ( ` grep -q "android.suppressUnsupportedCompileSdk=34" ${ gradleProps } ` ) ) {
execSync ( 'echo "android.suppressUnsupportedCompileSdk=34" >> android/gradle.properties' ) ;
log ( '✅ Added SDK suppression to gradle.properties' ) ;
}
} ;
// Build and test Android project
const buildAndTestAndroid = async ( log , env ) => {
log ( '🏗️ Building Android project...' ) ;
execSync ( 'cd android && ./gradlew clean' , { stdio : 'inherit' , env } ) ;
log ( '✅ Gradle clean completed' ) ;
execSync ( 'cd android && ./gradlew build' , { stdio : 'inherit' , env } ) ;
log ( '✅ Gradle build completed' ) ;
log ( '🧪 Running Android tests...' ) ;
execSync ( 'cd android && ./gradlew connectedAndroidTest' , { stdio : 'inherit' , env } ) ;
log ( '✅ Android tests completed' ) ;
} ;
// Run the app
const runAndroidApp = async ( log , env ) => {
log ( '📱 Running app on device...' ) ;
execSync ( 'npx cap run android' , { stdio : 'inherit' , env } ) ;
log ( '✅ App launched successfully' ) ;
} ;
/ * *
* Runs the complete Android test suite including build , installation , and testing
*
* The function performs the following steps :
* 1. Syncs the Capacitor project with latest web build
* 2. Builds and installs debug version of the app
* 3. Runs instrumented Android tests
* 1. Checks for connected devices / emulators
* 2. Ensures correct Java version is used
* 3. Checks if app is already installed
* 4. Syncs the Capacitor project with latest build
* 5. Builds and runs instrumented Android tests
*
* @ async
* @ throws { Error } If any step in the build or test process fails
@ -56,33 +250,35 @@ const { join } = require('path');
* } ) ;
* /
async function runAndroidTests ( ) {
// 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 Android project has the latest web assets
execSync ( 'npx cap sync android' , {
stdio : 'inherit' ,
// Inherit stdio to show real-time output
} ) ;
// Build and install debug version of the app
// Uses Gradle wrapper to ensure consistent build environment
execSync ( 'cd android && ./gradlew assembleDebug installDebug' , {
stdio : 'inherit' ,
// assembleDebug: Creates debug APK
// installDebug: Installs APK on connected device
} ) ;
// Run the instrumented Android tests
// These are the tests defined in android/app/src/androidTest
execSync ( 'cd android && ./gradlew connectedAndroidTest' , {
stdio : 'inherit' ,
// connectedAndroidTest: Runs tests on connected device
} ) ;
console . log ( '✅ Android tests completed successfully' ) ;
log ( '🚀 Starting Android build and test process...' ) ;
// Generate test data first
await generateTestData ( log ) ;
await checkConnectedDevices ( log ) ;
const env = { ... process . env , ... verifyJavaInstallation ( log ) } ;
await buildWebAssets ( log ) ;
await configureAndroidProject ( log ) ;
await buildAndTestAndroid ( log , env ) ;
await runAndroidApp ( log , env ) ;
// Run deeplink tests after app is installed
await runDeeplinkTests ( log ) ;
log ( '🎉 Android 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 ( '❌ Android tests failed:' , error ) ;
log ( ` ❌ Android tests failed: ${ error . message } ` ) ;
log ( ` 📝 Check build log for details: ${ logFile } ` ) ;
process . exit ( 1 ) ;
}
}