WellNuo/.maestro/run-full-e2e.sh
Sergei 8af7a11cd9 Fix WiFi credentials cache implementation in SecureStore
- Fix saveWiFiPassword to use encrypted passwords map instead of decrypted
- Fix getWiFiPassword to decrypt from encrypted storage
- Fix test expectations for migration and encryption functions
- Remove unused error variables to fix linting warnings
- All 27 tests now passing with proper encryption/decryption flow

The WiFi credentials cache feature was already implemented but had bugs
where encrypted and decrypted password maps were being mixed. This commit
ensures proper encryption is maintained throughout the storage lifecycle.
2026-01-31 15:55:24 -08:00

412 lines
11 KiB
Bash
Executable File

#!/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: Dashboard Verification
log "=== Phase 7: 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 "$@"