feat: Add comprehensive Android test logging and build tracking
- Add build_logs directory for tracking Android build output - Add Android generated assets to gitignore - Improve test logging with timestamps and build context - Add detailed error reporting for build and test failures - Add proper cleanup of Android build artifacts The changes improve Android testing by: 1. Tracking build history in build_logs directory 2. Providing detailed build context for debugging 3. Cleaning up generated assets properly 4. Improving error reporting and traceability 5. Adding structured logging for test execution Added: - build_logs/ directory for build history - Android assets cleanup in .gitignore - Detailed build and test logging
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -59,3 +59,9 @@ android/fastlane/test_output
|
|||||||
.env.default
|
.env.default
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
|
# Build logs
|
||||||
|
build_logs/
|
||||||
|
|
||||||
|
# Android generated assets
|
||||||
|
android/app/src/main/assets/public/assets/
|
||||||
|
|
||||||
|
|||||||
1
android/app/.gitignore
vendored
1
android/app/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/build/*
|
/build/*
|
||||||
!/build/.npmkeep
|
!/build/.npmkeep
|
||||||
|
src/main/assets/public/assets/
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package app.timesafari.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
assertEquals("app.timesafari.app", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,3 +20,6 @@ org.gradle.jvmargs=-Xmx1536m
|
|||||||
# Android operating system, and which are packaged with your app's APK
|
# Android operating system, and which are packaged with your app's APK
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
android.suppressUnsupportedCompileSdk=34
|
||||||
|
android.suppressUnsupportedCompileSdk=34
|
||||||
|
android.suppressUnsupportedCompileSdk=34
|
||||||
|
|||||||
@@ -37,14 +37,208 @@
|
|||||||
|
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const { join } = require('path');
|
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
|
* Runs the complete Android test suite including build, installation, and testing
|
||||||
*
|
*
|
||||||
* The function performs the following steps:
|
* The function performs the following steps:
|
||||||
* 1. Syncs the Capacitor project with latest web build
|
* 1. Checks for connected devices/emulators
|
||||||
* 2. Builds and installs debug version of the app
|
* 2. Ensures correct Java version is used
|
||||||
* 3. Runs instrumented Android tests
|
* 3. Checks if app is already installed
|
||||||
|
* 4. Syncs the Capacitor project with latest build
|
||||||
|
* 5. Builds and runs instrumented Android tests
|
||||||
*
|
*
|
||||||
* @async
|
* @async
|
||||||
* @throws {Error} If any step in the build or test process fails
|
* @throws {Error} If any step in the build or test process fails
|
||||||
@@ -56,33 +250,35 @@ const { join } = require('path');
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
async function runAndroidTests() {
|
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 {
|
try {
|
||||||
// Sync Capacitor project with latest web build
|
log('🚀 Starting Android build and test process...');
|
||||||
// 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
|
// Generate test data first
|
||||||
// Uses Gradle wrapper to ensure consistent build environment
|
await generateTestData(log);
|
||||||
execSync('cd android && ./gradlew assembleDebug installDebug', {
|
|
||||||
stdio: 'inherit',
|
|
||||||
// assembleDebug: Creates debug APK
|
|
||||||
// installDebug: Installs APK on connected device
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run the instrumented Android tests
|
await checkConnectedDevices(log);
|
||||||
// These are the tests defined in android/app/src/androidTest
|
const env = { ...process.env, ...verifyJavaInstallation(log) };
|
||||||
execSync('cd android && ./gradlew connectedAndroidTest', {
|
await buildWebAssets(log);
|
||||||
stdio: 'inherit',
|
await configureAndroidProject(log);
|
||||||
// connectedAndroidTest: Runs tests on connected device
|
await buildAndTestAndroid(log, env);
|
||||||
});
|
await runAndroidApp(log, env);
|
||||||
|
|
||||||
console.log('✅ Android tests completed successfully');
|
// 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) {
|
} catch (error) {
|
||||||
// Log the error and exit with failure code
|
log(`❌ Android tests failed: ${error.message}`);
|
||||||
console.error('❌ Android tests failed:', error);
|
log(`📝 Check build log for details: ${logFile}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user