WellNuo/.maestro/run-full-e2e.sh
Sergei b5ab28aa3e Add bulk sensor operations API
Implemented comprehensive bulk operations for BLE sensor management to improve
efficiency when working with multiple sensors simultaneously.

Features Added:
- bulkDisconnect: Disconnect multiple sensors at once
- bulkReboot: Reboot multiple sensors sequentially
- bulkSetWiFi: Configure WiFi for multiple sensors with progress tracking

Implementation Details:
- Added BulkOperationResult and BulkWiFiResult types to track operation outcomes
- Implemented bulk operations in both RealBLEManager and MockBLEManager
- Exposed bulk operations through BLEContext for easy UI integration
- Sequential processing ensures reliable operation completion
- Progress callbacks for real-time UI updates during bulk operations

Testing:
- Added comprehensive test suite with 14 test cases
- Tests cover success scenarios, error handling, and edge cases
- All tests passing with appropriate timeout configurations
- Verified both individual and sequential bulk operations

Technical Notes:
- Bulk operations maintain device connection state consistency
- Error handling allows graceful continuation despite individual failures
- MockBLEManager includes realistic delays for testing
- Integration with existing BLE service architecture preserved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 16:40:36 -08:00

416 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: 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 "$@"