#!/bin/bash # WellNuo Full E2E Test Runner # Orchestrates Maestro tests with temp email for real OTP verification # # Usage: ./run-full-e2e.sh [--device SERIAL] [--skip-build] # # Requirements: # - Maestro installed (~/.maestro/bin/maestro) # - Android device connected via USB # - Node.js for temp email operations # - Release APK built set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" MAESTRO_BIN="${HOME}/.maestro/bin/maestro" RESULTS_DIR="${SCRIPT_DIR}/test-results/$(date +%Y-%m-%d_%H%M%S)" APK_PATH="${PROJECT_DIR}/android/app/build/outputs/apk/release/app-release.apk" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Parse arguments DEVICE_SERIAL="" SKIP_BUILD=false while [[ $# -gt 0 ]]; do case $1 in --device) DEVICE_SERIAL="$2" shift 2 ;; --skip-build) SKIP_BUILD=true shift ;; *) echo "Unknown option: $1" exit 1 ;; esac done log() { echo -e "${BLUE}[E2E]${NC} $1" } success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } error() { echo -e "${RED}[ERROR]${NC} $1" } warn() { echo -e "${YELLOW}[WARN]${NC} $1" } # Check prerequisites check_prerequisites() { log "Checking prerequisites..." # Check Maestro if [ ! -f "$MAESTRO_BIN" ]; then error "Maestro not found at $MAESTRO_BIN" echo "Install: curl -Ls 'https://get.maestro.mobile.dev' | bash" exit 1 fi # Check device if [ -z "$DEVICE_SERIAL" ]; then DEVICE_SERIAL=$(adb devices | grep -v "List" | grep "device$" | head -1 | awk '{print $1}') fi if [ -z "$DEVICE_SERIAL" ]; then error "No Android device connected" echo "Connect device via USB and enable USB debugging" exit 1 fi log "Using device: $DEVICE_SERIAL" export ANDROID_SERIAL="$DEVICE_SERIAL" # Keep screen on during tests adb -s "$DEVICE_SERIAL" shell settings put system screen_off_timeout 600000 adb -s "$DEVICE_SERIAL" shell settings put global stay_on_while_plugged_in 3 success "Prerequisites OK" } # Build release APK build_apk() { if [ "$SKIP_BUILD" = true ]; then log "Skipping build (--skip-build)" return fi log "Building release APK..." cd "$PROJECT_DIR/android" ./gradlew assembleRelease --quiet if [ ! -f "$APK_PATH" ]; then error "APK not found at $APK_PATH" exit 1 fi success "APK built: $APK_PATH" } # Install APK install_apk() { log "Installing APK on device..." adb -s "$DEVICE_SERIAL" install -r "$APK_PATH" 2>/dev/null || true success "APK installed" } # Create temp email (using Node.js script) create_temp_email() { log "Creating temporary email..." # Use temp-mail MCP to create email # This creates a simple Node script that uses the tempmail API TEMP_EMAIL_SCRIPT=$(mktemp) cat > "$TEMP_EMAIL_SCRIPT" << 'EMAILSCRIPT' const https = require('https'); // Simple temp email using mail.tm API async function createTempEmail() { return new Promise((resolve, reject) => { // Get available domains https.get('https://api.mail.tm/domains', (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { const domains = JSON.parse(data); const domain = domains['hydra:member'][0].domain; // Generate random email const username = 'wellnuo_test_' + Math.random().toString(36).substring(7); const email = `${username}@${domain}`; const password = 'TestPass123!'; // Create account const postData = JSON.stringify({ address: email, password }); const options = { hostname: 'api.mail.tm', path: '/accounts', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': postData.length } }; const req = https.request(options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { if (res.statusCode === 201) { // Get token const loginData = JSON.stringify({ address: email, password }); const loginOptions = { hostname: 'api.mail.tm', path: '/token', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': loginData.length } }; const loginReq = https.request(loginOptions, (loginRes) => { let loginBody = ''; loginRes.on('data', chunk => loginBody += chunk); loginRes.on('end', () => { const token = JSON.parse(loginBody).token; resolve({ email, password, token }); }); }); loginReq.write(loginData); loginReq.end(); } else { reject(new Error(`Failed to create email: ${res.statusCode}`)); } }); }); req.write(postData); req.end(); }); }); }); } createTempEmail() .then(result => console.log(JSON.stringify(result))) .catch(err => { console.error(err.message); process.exit(1); }); EMAILSCRIPT TEMP_EMAIL_JSON=$(node "$TEMP_EMAIL_SCRIPT" 2>/dev/null) rm "$TEMP_EMAIL_SCRIPT" if [ -z "$TEMP_EMAIL_JSON" ]; then error "Failed to create temp email" exit 1 fi TEMP_EMAIL=$(echo "$TEMP_EMAIL_JSON" | grep -o '"email":"[^"]*"' | cut -d'"' -f4) TEMP_TOKEN=$(echo "$TEMP_EMAIL_JSON" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) success "Temp email created: $TEMP_EMAIL" echo "$TEMP_EMAIL_JSON" > "$RESULTS_DIR/temp_email.json" } # Wait for OTP email and extract code get_otp_from_email() { log "Waiting for OTP email (up to 60 seconds)..." OTP_SCRIPT=$(mktemp) cat > "$OTP_SCRIPT" << 'OTPSCRIPT' const https = require('https'); const token = process.argv[2]; const maxAttempts = 12; // 12 * 5s = 60s let attempts = 0; function checkMessages() { return new Promise((resolve) => { const options = { hostname: 'api.mail.tm', path: '/messages', headers: { 'Authorization': `Bearer ${token}` } }; https.get(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const messages = JSON.parse(data)['hydra:member']; if (messages && messages.length > 0) { // Get first message const msgId = messages[0].id; // Fetch full message const msgOptions = { hostname: 'api.mail.tm', path: `/messages/${msgId}`, headers: { 'Authorization': `Bearer ${token}` } }; https.get(msgOptions, (msgRes) => { let msgData = ''; msgRes.on('data', chunk => msgData += chunk); msgRes.on('end', () => { const msg = JSON.parse(msgData); // Extract 6-digit OTP from email body const body = msg.text || msg.html || ''; const otpMatch = body.match(/\b(\d{6})\b/); if (otpMatch) { resolve(otpMatch[1]); } else { resolve(null); } }); }); } else { resolve(null); } } catch (e) { resolve(null); } }); }); }); } async function waitForOtp() { while (attempts < maxAttempts) { const otp = await checkMessages(); if (otp) { console.log(otp); process.exit(0); } attempts++; await new Promise(r => setTimeout(r, 5000)); } console.error('Timeout waiting for OTP'); process.exit(1); } waitForOtp(); OTPSCRIPT OTP_CODE=$(node "$OTP_SCRIPT" "$TEMP_TOKEN" 2>/dev/null) rm "$OTP_SCRIPT" if [ -z "$OTP_CODE" ] || [ "$OTP_CODE" = "Timeout waiting for OTP" ]; then error "Failed to get OTP code from email" exit 1 fi success "Got OTP code: $OTP_CODE" } # Run Maestro test with variable substitution run_maestro_test() { local test_file="$1" local test_name=$(basename "$test_file" .yaml) log "Running test: $test_name" # Create temp file with variables substituted local temp_test=$(mktemp) sed -e "s/\${EMAIL}/$TEMP_EMAIL/g" \ -e "s/\${OTP_CODE}/$OTP_CODE/g" \ "$test_file" > "$temp_test" # Run Maestro if "$MAESTRO_BIN" test "$temp_test" --output "$RESULTS_DIR/$test_name" 2>&1; then success "Test passed: $test_name" rm "$temp_test" return 0 else error "Test failed: $test_name" rm "$temp_test" return 1 fi } # Main test flow main() { echo "" echo "╔═══════════════════════════════════════════════════════════╗" echo "║ WellNuo Full E2E Test Suite ║" echo "╚═══════════════════════════════════════════════════════════╝" echo "" # Setup mkdir -p "$RESULTS_DIR" check_prerequisites # Build and install build_apk install_apk # Create temp email create_temp_email # Phase 1: Registration + Email entry log "=== Phase 1: Registration ===" if ! run_maestro_test "$SCRIPT_DIR/01-registration-flow.yaml"; then error "Registration flow failed" exit 1 fi # Get OTP from email get_otp_from_email # Phase 2: OTP Entry log "=== Phase 2: OTP Verification ===" if ! run_maestro_test "$SCRIPT_DIR/02-enter-otp.yaml"; then warn "OTP entry failed, may be existing user" fi # Phase 3: Enter Name (new users only) log "=== Phase 3: Enter Name ===" run_maestro_test "$SCRIPT_DIR/03-enter-name.yaml" || true # Phase 4: Add Beneficiary log "=== Phase 4: Add Beneficiary ===" run_maestro_test "$SCRIPT_DIR/04-add-beneficiary.yaml" || true # Phase 5: Purchase/Demo log "=== Phase 5: Purchase Decision ===" run_maestro_test "$SCRIPT_DIR/05-purchase-or-demo.yaml" || true # Phase 6: Activation log "=== Phase 6: Device Activation ===" run_maestro_test "$SCRIPT_DIR/06-activate-device.yaml" || true # Phase 7: Subscribe (Payment) log "=== Phase 7: Subscribe (Stripe Payment) ===" run_maestro_test "$SCRIPT_DIR/08-subscribe.yaml" || true # Phase 8: Dashboard Verification log "=== Phase 8: Dashboard Verification ===" if run_maestro_test "$SCRIPT_DIR/07-verify-dashboard.yaml"; then success "FULL E2E TEST PASSED!" else warn "Dashboard verification had issues" fi echo "" echo "╔═══════════════════════════════════════════════════════════╗" echo "║ TEST COMPLETE ║" echo "╚═══════════════════════════════════════════════════════════╝" echo "" log "Results saved to: $RESULTS_DIR" log "Screenshots: $RESULTS_DIR/*/screenshots/" # List screenshots find "$RESULTS_DIR" -name "*.png" -type f 2>/dev/null | head -20 } main "$@"