From 020820dc443bdef741c9d2991e16a2bc5f7cf82b Mon Sep 17 00:00:00 2001 From: skalthoff <32023561+skalthoff@users.noreply.github.com> Date: Wed, 31 Dec 2025 00:49:39 -0800 Subject: [PATCH] feat: Run Maestro Android tests using a single orchestrated flow file and collect project screenshots. --- scripts/maestro-android.js | 154 ++++++++++++------------------------- 1 file changed, 51 insertions(+), 103 deletions(-) diff --git a/scripts/maestro-android.js b/scripts/maestro-android.js index ea5ebe1a..67dffbd5 100644 --- a/scripts/maestro-android.js +++ b/scripts/maestro-android.js @@ -1,4 +1,4 @@ -const { execSync, exec, spawn } = require('child_process') +const { execSync, spawn } = require('child_process') const path = require('path') const fs = require('fs') @@ -6,46 +6,13 @@ const fs = require('fs') const [, , serverAddress, username] = process.argv if (!serverAddress || !username) { - console.error('Usage: node runMaestro.js ') + console.error('Usage: node maestro-android.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) -} +// Use the orchestrated flow file instead of individual tests +// flow-0.yaml clears state and runs all tests in the correct order +const FLOW_FILE = './maestro/flows/flow-0.yaml' function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) @@ -71,14 +38,14 @@ async function stopRecording(pid, videoName, deviceVideoPath) { } } -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` +async function runFullFlow(serverAddress, username) { + const videoName = 'maestro_full_test.mp4' + const deviceVideoPath = '/sdcard/maestro_full_test.mp4' - console.log(`\nšŸš€ Starting test ${testIndex + 1}/${FLOW_FILES.length}: ${relativePath}`) + console.log(`\nšŸš€ Running full Maestro test flow: ${FLOW_FILE}`) console.log(`šŸ“¹ Video will be saved as: ${videoName}`) + console.log(`šŸ”— Server: ${serverAddress}`) + console.log(`šŸ‘¤ Username: ${username}`) // Start screen recording const recording = spawn( @@ -92,21 +59,23 @@ async function runSingleTest(flowPath, serverAddress, username, testIndex) { const pid = recording.pid try { - const MAESTRO_PATH = path.join(process.env.HOME, '.maestro', 'bin', 'maestro') + const MAESTRO_PATH = process.env.HOME + '/.maestro/bin/maestro' - const command = `${MAESTRO_PATH} test ${flowPath} \ + const command = `${MAESTRO_PATH} test ${FLOW_FILE} \ --env server_address=${serverAddress} \ --env username=${username}` - const output = execSync(command, { stdio: 'inherit', env: process.env }) - console.log(`āœ… Test ${testIndex + 1} (${relativePath}) completed successfully`) + console.log(`\nšŸŽ­ Executing: maestro test ${FLOW_FILE}`) + execSync(command, { stdio: 'inherit', env: process.env }) + + console.log('āœ… Full test flow completed successfully!') await stopRecording(pid, videoName, deviceVideoPath) - return { success: true, flowName, relativePath, videoName } + return { success: true } } catch (error) { + console.error('āŒ Test flow failed:', error.message) await stopRecording(pid, videoName, deviceVideoPath) - console.error(`āŒ Test ${testIndex + 1} (${relativePath}) failed: ${error.message}`) - return { success: false, flowName, relativePath, videoName, error: error.message } + return { success: false, error: error.message } } } @@ -120,66 +89,22 @@ async function runSingleTest(flowPath, serverAddress, username, testIndex) { 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) + // Wait for app to launch + await sleep(3000) - const results = [] + const result = await runFullFlow(serverAddress, username) - 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`) - - // Collect screenshots from .maestro/screenshots + // Collect screenshots const screenshotDir = './.maestro/screenshots' const screenshotOutputDir = './screenshots-output' if (fs.existsSync(screenshotDir)) { console.log('\nšŸ“ø Collecting screenshots...') try { - // Create output directory if it doesn't exist if (!fs.existsSync(screenshotOutputDir)) { fs.mkdirSync(screenshotOutputDir, { recursive: true }) } - // Copy all screenshots to output directory const screenshots = fs.readdirSync(screenshotDir) screenshots.forEach((file) => { const srcPath = path.join(screenshotDir, file) @@ -193,14 +118,37 @@ async function runSingleTest(flowPath, serverAddress, username, testIndex) { console.error('āŒ Failed to collect screenshots:', err.message) } } else { - console.log('\nšŸ“ø No screenshots directory found') + console.log('\nšŸ“ø No screenshots directory found at .maestro/screenshots') } - if (failed === 0) { - console.log('šŸŽ‰ All tests passed!') + // Also collect from project screenshots folder + const projectScreenshots = './screenshots' + if (fs.existsSync(projectScreenshots)) { + console.log('\nšŸ“ø Collecting project screenshots...') + try { + if (!fs.existsSync(screenshotOutputDir)) { + fs.mkdirSync(screenshotOutputDir, { recursive: true }) + } + + const screenshots = fs.readdirSync(projectScreenshots).filter((f) => f.endsWith('.png')) + screenshots.forEach((file) => { + const srcPath = path.join(projectScreenshots, file) + const destPath = path.join(screenshotOutputDir, file) + fs.copyFileSync(srcPath, destPath) + console.log(` šŸ“· ${file}`) + }) + + console.log(`āœ… Collected ${screenshots.length} screenshots from project folder`) + } catch (err) { + console.error('āŒ Failed to collect screenshots:', err.message) + } + } + + if (result.success) { + console.log('\nšŸŽ‰ All tests passed!') process.exit(0) } else { - console.log('āš ļø Some tests failed. Check the videos for details.') + console.log('\nāš ļø Tests failed. Check the video for details.') process.exit(1) } })()