- Add daily-notification-test.sh for basic notification testing - Add daily-notification-test.py for Python-based testing - Add reboot-test.sh for automated reboot recovery testing - Include comprehensive error handling and logging - Add colored output for better user experience - Support for different testing scenarios and edge cases - Include ADB command validation and device connectivity checks Scripts provide: - Automated notification scheduling and verification - Reboot recovery testing with proper timing - Permission management testing - Comprehensive logging and error reporting - Cross-platform compatibility (bash and Python) These scripts enable automated testing of the complete notification system including boot receiver and app startup recovery mechanisms.
263 lines
9.6 KiB
Python
Executable File
263 lines
9.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
DailyNotification Plugin Automated Test Suite
|
||
Usage: python3 daily-notification-test.py [OPTIONS]
|
||
"""
|
||
|
||
import subprocess
|
||
import time
|
||
import json
|
||
import sys
|
||
import argparse
|
||
from typing import Optional, Dict, Any, List
|
||
from dataclasses import dataclass
|
||
from enum import Enum
|
||
|
||
class TestResult(Enum):
|
||
PASS = "PASS"
|
||
FAIL = "FAIL"
|
||
SKIP = "SKIP"
|
||
|
||
@dataclass
|
||
class TestCase:
|
||
name: str
|
||
description: str
|
||
result: TestResult
|
||
duration: float
|
||
error: Optional[str] = None
|
||
|
||
class DailyNotificationTester:
|
||
def __init__(self, package: str = "com.timesafari.dailynotification", verbose: bool = False):
|
||
self.package = package
|
||
self.activity = f"{package}/.MainActivity"
|
||
self.verbose = verbose
|
||
self.test_results: List[TestCase] = []
|
||
|
||
def log(self, message: str, level: str = "INFO"):
|
||
"""Log message with timestamp"""
|
||
timestamp = time.strftime("%H:%M:%S")
|
||
if level == "ERROR":
|
||
print(f"❌ [{timestamp}] {message}")
|
||
elif level == "SUCCESS":
|
||
print(f"✅ [{timestamp}] {message}")
|
||
elif level == "WARNING":
|
||
print(f"⚠️ [{timestamp}] {message}")
|
||
elif level == "INFO":
|
||
print(f"ℹ️ [{timestamp}] {message}")
|
||
else:
|
||
print(f"[{timestamp}] {message}")
|
||
|
||
def run_adb_command(self, command: str, capture_output: bool = True) -> subprocess.CompletedProcess:
|
||
"""Run ADB command and return result"""
|
||
full_command = f"adb {command}"
|
||
if self.verbose:
|
||
self.log(f"Running: {full_command}")
|
||
|
||
try:
|
||
return subprocess.run(
|
||
full_command,
|
||
shell=True,
|
||
capture_output=capture_output,
|
||
text=True,
|
||
timeout=30
|
||
)
|
||
except subprocess.TimeoutExpired:
|
||
self.log(f"Command timed out: {full_command}", "ERROR")
|
||
raise
|
||
|
||
def is_app_running(self) -> bool:
|
||
"""Check if app is currently running"""
|
||
result = self.run_adb_command(f'shell "ps | grep {self.package}"')
|
||
return result.returncode == 0 and self.package in result.stdout
|
||
|
||
def launch_app(self) -> bool:
|
||
"""Launch the app"""
|
||
self.log("Launching app...")
|
||
result = self.run_adb_command(f"shell am start -n {self.activity}")
|
||
time.sleep(3) # Wait for app to load
|
||
return self.is_app_running()
|
||
|
||
def send_to_background(self) -> bool:
|
||
"""Send app to background"""
|
||
self.log("Sending app to background...")
|
||
self.run_adb_command("shell input keyevent KEYCODE_HOME")
|
||
time.sleep(2)
|
||
return self.is_app_running()
|
||
|
||
def force_stop_app(self) -> bool:
|
||
"""Force stop the app"""
|
||
self.log("Force stopping app...")
|
||
self.run_adb_command(f"shell am force-stop {self.package}")
|
||
time.sleep(2)
|
||
return not self.is_app_running()
|
||
|
||
def check_notification_logs(self, timeout: int = 60) -> bool:
|
||
"""Check for notification success in logs"""
|
||
self.log(f"Checking notification logs (timeout: {timeout}s)...")
|
||
start_time = time.time()
|
||
|
||
while time.time() - start_time < timeout:
|
||
result = self.run_adb_command("logcat -d")
|
||
if "Notification displayed successfully" in result.stdout:
|
||
return True
|
||
time.sleep(5)
|
||
return False
|
||
|
||
def check_permissions(self) -> Dict[str, bool]:
|
||
"""Check app permissions"""
|
||
self.log("Checking app permissions...")
|
||
result = self.run_adb_command(f"shell dumpsys package {self.package}")
|
||
|
||
permissions = {
|
||
"notifications": "POST_NOTIFICATIONS" in result.stdout,
|
||
"exact_alarm": "SCHEDULE_EXACT_ALARM" in result.stdout,
|
||
"wake_lock": "WAKE_LOCK" in result.stdout
|
||
}
|
||
|
||
return permissions
|
||
|
||
def check_scheduled_alarms(self) -> bool:
|
||
"""Check if any alarms are scheduled for the app"""
|
||
self.log("Checking scheduled alarms...")
|
||
result = self.run_adb_command("shell \"dumpsys alarm | grep timesafari\"")
|
||
return result.returncode == 0 and self.package in result.stdout
|
||
|
||
def run_test(self, test_name: str, test_func, *args, **kwargs) -> TestCase:
|
||
"""Run a single test case"""
|
||
self.log(f"Running test: {test_name}")
|
||
start_time = time.time()
|
||
|
||
try:
|
||
result = test_func(*args, **kwargs)
|
||
duration = time.time() - start_time
|
||
|
||
if result:
|
||
self.log(f"Test passed: {test_name}", "SUCCESS")
|
||
return TestCase(test_name, "", TestResult.PASS, duration)
|
||
else:
|
||
self.log(f"Test failed: {test_name}", "ERROR")
|
||
return TestCase(test_name, "", TestResult.FAIL, duration, "Test returned False")
|
||
|
||
except Exception as e:
|
||
duration = time.time() - start_time
|
||
self.log(f"Test error: {test_name} - {str(e)}", "ERROR")
|
||
return TestCase(test_name, "", TestResult.FAIL, duration, str(e))
|
||
|
||
def run_test_suite(self) -> Dict[str, Any]:
|
||
"""Run complete test suite"""
|
||
self.log("🧪 Starting DailyNotification Test Suite")
|
||
self.log("=" * 50)
|
||
|
||
# Check ADB connection
|
||
result = self.run_adb_command("devices")
|
||
if "device" not in result.stdout:
|
||
self.log("No Android device connected via ADB", "ERROR")
|
||
sys.exit(1)
|
||
|
||
self.log("ADB device connected", "SUCCESS")
|
||
|
||
# Test 1: App Launch
|
||
test_case = self.run_test("App Launch", self.launch_app)
|
||
self.test_results.append(test_case)
|
||
|
||
# Test 2: Background Test
|
||
test_case = self.run_test("Background Operation", self.send_to_background)
|
||
self.test_results.append(test_case)
|
||
|
||
# Test 3: Force Stop Test
|
||
test_case = self.run_test("Force Stop", self.force_stop_app)
|
||
self.test_results.append(test_case)
|
||
|
||
# Test 4: Permission Check
|
||
def check_permissions_test():
|
||
permissions = self.check_permissions()
|
||
return any(permissions.values())
|
||
|
||
test_case = self.run_test("Permission Check", check_permissions_test)
|
||
self.test_results.append(test_case)
|
||
|
||
# Test 5: Alarm Check
|
||
test_case = self.run_test("Alarm Scheduling", self.check_scheduled_alarms)
|
||
self.test_results.append(test_case)
|
||
|
||
# Test 6: Notification Test (requires manual scheduling)
|
||
self.log("📱 Manual Notification Test")
|
||
self.log("⚠️ Manual step required: Schedule notification in app", "WARNING")
|
||
input("Press Enter when notification is scheduled...")
|
||
|
||
test_case = self.run_test("Notification Delivery", self.check_notification_logs, 120)
|
||
self.test_results.append(test_case)
|
||
|
||
return self.generate_report()
|
||
|
||
def generate_report(self) -> Dict[str, Any]:
|
||
"""Generate test report"""
|
||
total_tests = len(self.test_results)
|
||
passed_tests = sum(1 for test in self.test_results if test.result == TestResult.PASS)
|
||
failed_tests = sum(1 for test in self.test_results if test.result == TestResult.FAIL)
|
||
|
||
report = {
|
||
"summary": {
|
||
"total": total_tests,
|
||
"passed": passed_tests,
|
||
"failed": failed_tests,
|
||
"success_rate": (passed_tests / total_tests * 100) if total_tests > 0 else 0
|
||
},
|
||
"tests": [
|
||
{
|
||
"name": test.name,
|
||
"result": test.result.value,
|
||
"duration": test.duration,
|
||
"error": test.error
|
||
}
|
||
for test in self.test_results
|
||
]
|
||
}
|
||
|
||
return report
|
||
|
||
def print_report(self, report: Dict[str, Any]):
|
||
"""Print test report"""
|
||
self.log("\n📊 Test Results Summary:")
|
||
self.log("=" * 30)
|
||
|
||
summary = report["summary"]
|
||
self.log(f"Total Tests: {summary['total']}")
|
||
self.log(f"Passed: {summary['passed']}")
|
||
self.log(f"Failed: {summary['failed']}")
|
||
self.log(f"Success Rate: {summary['success_rate']:.1f}%")
|
||
|
||
self.log("\nDetailed Results:")
|
||
for test in report["tests"]:
|
||
status = "✅ PASS" if test["result"] == "PASS" else "❌ FAIL"
|
||
duration = f"({test['duration']:.1f}s)"
|
||
self.log(f"{test['name']}: {status} {duration}")
|
||
if test["error"]:
|
||
self.log(f" Error: {test['error']}", "ERROR")
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="DailyNotification Plugin Test Suite")
|
||
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
|
||
parser.add_argument("-p", "--package", default="com.timesafari.dailynotification", help="App package name")
|
||
parser.add_argument("-o", "--output", help="Output report to JSON file")
|
||
parser.add_argument("--timeout", type=int, default=120, help="Test timeout in seconds")
|
||
|
||
args = parser.parse_args()
|
||
|
||
tester = DailyNotificationTester(package=args.package, verbose=args.verbose)
|
||
report = tester.run_test_suite()
|
||
tester.print_report(report)
|
||
|
||
# Save report to file if specified
|
||
if args.output:
|
||
with open(args.output, 'w') as f:
|
||
json.dump(report, f, indent=2)
|
||
tester.log(f"Report saved to: {args.output}")
|
||
|
||
# Exit with error code if any test failed
|
||
if report["summary"]["failed"] > 0:
|
||
sys.exit(1)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|