const { execSync, exec, spawn } = require('child_process') const path = require('path') const fs = require('fs') // Read arguments from CLI const [, , serverAddress, username] = process.argv if (!serverAddress || !username) { console.error('Usage: node runMaestro.js ') process.exit(1) } // Function to recursively find all YAML files in maestro/tests directory function findYamlFiles(dir) { const files = [] function scanDirectory(currentDir) { const items = fs.readdirSync(currentDir) for (const item of items) { const fullPath = path.join(currentDir, item) const stat = fs.statSync(fullPath) if (stat.isDirectory()) { scanDirectory(fullPath) } else if (item.endsWith('.yaml') || item.endsWith('.yml')) { files.push(fullPath) } } } scanDirectory(dir) return files.sort() // Sort for consistent ordering } // Get all YAML files from maestro/tests directory const MAESTRO_TESTS_DIR = './maestro/tests' const FLOW_FILES = findYamlFiles(MAESTRO_TESTS_DIR) console.log(`šŸ” Found ${FLOW_FILES.length} YAML test files:`) FLOW_FILES.forEach((file, index) => { console.log(` ${index + 1}. ${file}`) }) if (FLOW_FILES.length === 0) { console.error('āŒ No YAML test files found in maestro/testsdirectory') process.exit(1) } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } async function stopRecording(pid, videoName, deviceVideoPath) { try { // Kill the adb screenrecord process process.kill(pid, 'SIGINT') // Wait 3 seconds for file to finalize await sleep(3000) // Pull the recorded file with custom name execSync(`adb pull ${deviceVideoPath} ${videoName}`, { stdio: 'inherit' }) // Optionally delete the file on device execSync(`adb shell rm ${deviceVideoPath}`) console.log(`āœ… Recording pulled and saved as ${videoName}`) } catch (err) { console.error('āŒ Failed to stop or pull recording:', err.message) } } async function runSingleTest(flowPath, serverAddress, username, testIndex) { const flowName = path.basename(flowPath, '.yaml') const relativePath = path.relative(MAESTRO_TESTS_DIR, flowPath) const videoName = `test_${testIndex}_${flowName}.mp4` const deviceVideoPath = `/sdcard/screen_${testIndex}_${flowName}.mp4` console.log(`\nšŸš€ Starting test ${testIndex + 1}/${FLOW_FILES.length}: ${relativePath}`) console.log(`šŸ“¹ Video will be saved as: ${videoName}`) // Start screen recording const recording = spawn( 'adb', ['shell', 'screenrecord', '--time-limit=1800', deviceVideoPath], { stdio: 'ignore', detached: true, }, ) const pid = recording.pid try { const MAESTRO_PATH = path.join(process.env.HOME, '.maestro', 'bin', 'maestro') const command = `${MAESTRO_PATH} test ${flowPath} \ --env server_address=${serverAddress} \ --env username=${username}` const output = execSync(command, { stdio: 'inherit', env: process.env }) console.log(`āœ… Test ${testIndex + 1} (${relativePath}) completed successfully`) await stopRecording(pid, videoName, deviceVideoPath) return { success: true, flowName, relativePath, videoName } } catch (error) { await stopRecording(pid, videoName, deviceVideoPath) console.error(`āŒ Test ${testIndex + 1} (${relativePath}) failed: ${error.message}`) return { success: false, flowName, relativePath, videoName, error: error.message } } } ;(async () => { console.log('šŸ“± Installing app...') execSync('adb install ./artifacts/app-universal-release.apk', { stdio: 'inherit', env: process.env, }) console.log('šŸš€ Launching app...') execSync(`adb shell monkey -p com.cosmonautical.jellify 1`, { stdio: 'inherit' }) // Wait a bit for app to launch await sleep(2000) const results = [] console.log(`\nšŸ”„ Starting test suite with ${FLOW_FILES.length} tests...`) for (let i = 0; i < FLOW_FILES.length; i++) { const flowPath = FLOW_FILES[i] // Check if flow file exists if (!fs.existsSync(flowPath)) { console.log(`āš ļø Skipping ${flowPath} - file not found`) continue } const result = await runSingleTest(flowPath, serverAddress, username, i) results.push(result) // Wait between tests to ensure clean state if (i < FLOW_FILES.length - 1) { console.log('ā³ Waiting 3 seconds before next test...') await sleep(3000) } } // Print summary console.log('\nšŸ“Š Test Results Summary:') console.log('========================') let passed = 0 let failed = 0 results.forEach((result, index) => { const status = result.success ? 'āœ… PASS' : 'āŒ FAIL' console.log(`${index + 1}. ${result.relativePath}: ${status}`) if (result.success) { passed++ } else { failed++ console.log(` Error: ${result.error}`) } console.log(` Video: ${result.videoName}`) }) console.log(`\nšŸ“ˆ Final Results: ${passed} passed, ${failed} failed`) if (failed === 0) { console.log('šŸŽ‰ All tests passed!') process.exit(0) } else { console.log('āš ļø Some tests failed. Check the videos for details.') process.exit(1) } })()