- 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.
412 lines
11 KiB
Bash
Executable File
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 "$@"
|