forked from Rockachopa/Timmy-time-dashboard
This commit implements six major features: 1. Event Log System (src/swarm/event_log.py) - SQLite-based audit trail for all swarm events - Task lifecycle tracking (created, assigned, completed, failed) - Agent lifecycle tracking (joined, left, status changes) - Integrated with coordinator for automatic logging - Dashboard page at /swarm/events 2. Lightning Ledger (src/lightning/ledger.py) - Transaction tracking for Lightning Network payments - Balance calculations (incoming, outgoing, net, available) - Integrated with payment_handler for automatic logging - Dashboard page at /lightning/ledger 3. Semantic Memory / Vector Store (src/memory/vector_store.py) - Embedding-based similarity search for Echo agent - Fallback to keyword matching if sentence-transformers unavailable - Personal facts storage and retrieval - Dashboard page at /memory 4. Cascade Router Integration (src/timmy/cascade_adapter.py) - Automatic LLM failover between providers (Ollama → AirLLM → API) - Circuit breaker pattern for failing providers - Metrics tracking per provider (latency, error rates) - Dashboard status page at /router/status 5. Self-Upgrade Approval Queue (src/upgrades/) - State machine for self-modifications: proposed → approved/rejected → applied/failed - Human approval required before applying changes - Git integration for branch management - Dashboard queue at /self-modify/queue 6. Real-Time Activity Feed (src/events/broadcaster.py) - WebSocket-based live activity streaming - Bridges event_log to dashboard clients - Activity panel on /swarm/live Tests: - 101 unit tests passing - 4 new E2E test files for Selenium testing - Run with: SELENIUM_UI=1 pytest tests/functional/ -v --headed Documentation: - 6 ADRs (017-022) documenting architecture decisions - Implementation summary in docs/IMPLEMENTATION_SUMMARY.md - Architecture diagram in docs/architecture-v2.md
212 lines
7.0 KiB
Python
212 lines
7.0 KiB
Python
"""E2E tests for Real-Time Activity Feed.
|
|
|
|
RUN: pytest tests/functional/test_activity_feed_e2e.py -v --headed
|
|
"""
|
|
|
|
import os
|
|
import time
|
|
|
|
import pytest
|
|
from selenium import webdriver
|
|
from selenium.webdriver.chrome.options import Options
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
import httpx
|
|
|
|
from .conftest import DASHBOARD_URL
|
|
|
|
|
|
@pytest.fixture
|
|
def driver():
|
|
"""Non-headless Chrome so you can watch."""
|
|
opts = Options()
|
|
opts.add_argument("--no-sandbox")
|
|
opts.add_argument("--disable-dev-shm-usage")
|
|
opts.add_argument("--window-size=1400,900")
|
|
|
|
d = webdriver.Chrome(options=opts)
|
|
d.implicitly_wait(5)
|
|
yield d
|
|
d.quit()
|
|
|
|
|
|
class TestActivityFeedUI:
|
|
"""Real-time activity feed on dashboard."""
|
|
|
|
def test_activity_feed_exists_on_swarm_live(self, driver):
|
|
"""Swarm live page has activity feed panel."""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
# Look for activity feed
|
|
feed = driver.find_elements(
|
|
By.CSS_SELECTOR, ".activity-feed, .live-feed, .events-feed"
|
|
)
|
|
|
|
# Or look for activity header
|
|
headers = driver.find_elements(
|
|
By.XPATH, "//*[contains(text(), 'Activity') or contains(text(), 'Live')]"
|
|
)
|
|
|
|
assert feed or headers, "Should have activity feed panel"
|
|
|
|
def test_activity_feed_shows_events(self, driver):
|
|
"""Activity feed displays events."""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
time.sleep(2) # Let feed load
|
|
|
|
# Look for event items
|
|
events = driver.find_elements(By.CSS_SELECTOR, ".event-item, .activity-item")
|
|
|
|
# Or empty state
|
|
empty = driver.find_elements(By.XPATH, "//*[contains(text(), 'No activity')]")
|
|
|
|
assert events or empty, "Should show events or empty state"
|
|
|
|
def test_activity_feed_updates_in_realtime(self, driver):
|
|
"""Creating a task shows up in activity feed immediately.
|
|
|
|
This tests the WebSocket real-time update.
|
|
"""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
# Get initial event count
|
|
initial = len(driver.find_elements(By.CSS_SELECTOR, ".event-item"))
|
|
|
|
# Create a task via API (this should trigger event)
|
|
task_desc = f"Activity test {time.time()}"
|
|
try:
|
|
httpx.post(
|
|
f"{DASHBOARD_URL}/swarm/tasks",
|
|
data={"description": task_desc},
|
|
timeout=5
|
|
)
|
|
except Exception:
|
|
pass # Task may not complete, but event should still fire
|
|
|
|
# Wait for WebSocket update
|
|
time.sleep(3)
|
|
|
|
# Check for new event
|
|
current = len(driver.find_elements(By.CSS_SELECTOR, ".event-item"))
|
|
|
|
# Or check for task-related text
|
|
page_text = driver.find_element(By.TAG_NAME, "body").text.lower()
|
|
has_task_event = "task" in page_text and "created" in page_text
|
|
|
|
assert current > initial or has_task_event, "Should see new activity"
|
|
|
|
def test_activity_feed_shows_task_events(self, driver):
|
|
"""Task lifecycle events appear in feed."""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
time.sleep(2)
|
|
|
|
page_text = driver.find_element(By.TAG_NAME, "body").text.lower()
|
|
|
|
# Should see task-related events if any exist
|
|
task_related = any(x in page_text for x in [
|
|
"task.created", "task assigned", "task completed", "new task"
|
|
])
|
|
|
|
# Not a failure if no tasks exist, just check the feed is there
|
|
feed_exists = driver.find_elements(By.CSS_SELECTOR, ".activity-feed")
|
|
assert feed_exists, "Activity feed should exist"
|
|
|
|
def test_activity_feed_shows_agent_events(self, driver):
|
|
"""Agent join/leave events appear in feed."""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
time.sleep(2)
|
|
|
|
page_text = driver.find_element(By.TAG_NAME, "body").text.lower()
|
|
|
|
# Should see agent-related events if any exist
|
|
agent_related = any(x in page_text for x in [
|
|
"agent joined", "agent left", "agent status"
|
|
])
|
|
|
|
# Feed should exist regardless
|
|
feed = driver.find_elements(By.CSS_SELECTOR, ".activity-feed, .live-feed")
|
|
|
|
def test_activity_feed_shows_bid_events(self, driver):
|
|
"""Bid events appear in feed."""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
time.sleep(2)
|
|
|
|
page_text = driver.find_element(By.TAG_NAME, "body").text.lower()
|
|
|
|
# Look for bid-related text
|
|
bid_related = any(x in page_text for x in [
|
|
"bid", "sats", "auction"
|
|
])
|
|
|
|
def test_activity_feed_timestamps(self, driver):
|
|
"""Events show timestamps."""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
time.sleep(2)
|
|
|
|
# Look for time patterns
|
|
page_text = driver.find_element(By.TAG_NAME, "body").text
|
|
|
|
# Should have timestamps (HH:MM format)
|
|
import re
|
|
time_pattern = re.search(r'\d{1,2}:\d{2}', page_text)
|
|
|
|
# If there are events, they should have timestamps
|
|
events = driver.find_elements(By.CSS_SELECTOR, ".event-item")
|
|
if events:
|
|
assert time_pattern, "Events should have timestamps"
|
|
|
|
def test_activity_feed_icons(self, driver):
|
|
"""Different event types have different icons."""
|
|
driver.get(f"{DASHBOARD_URL}/swarm/live")
|
|
|
|
time.sleep(2)
|
|
|
|
# Look for icons or visual indicators
|
|
icons = driver.find_elements(By.CSS_SELECTOR, ".event-icon, .activity-icon, .icon")
|
|
|
|
# Not required but nice to have
|
|
|
|
|
|
class TestActivityFeedIntegration:
|
|
"""Activity feed integration with other features."""
|
|
|
|
def test_activity_appears_in_event_log(self, driver):
|
|
"""Activity feed events are also in event log page."""
|
|
# Create a task
|
|
try:
|
|
httpx.post(
|
|
f"{DASHBOARD_URL}/swarm/tasks",
|
|
data={"description": "Integration test task"},
|
|
timeout=5
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
time.sleep(2)
|
|
|
|
# Check event log
|
|
driver.get(f"{DASHBOARD_URL}/swarm/events")
|
|
|
|
page_text = driver.find_element(By.TAG_NAME, "body").text.lower()
|
|
assert "task" in page_text, "Event log should show task events"
|
|
|
|
def test_nav_to_swarm_live(self, driver):
|
|
"""Can navigate to swarm live page."""
|
|
driver.get(DASHBOARD_URL)
|
|
|
|
# Look for swarm/live link
|
|
live_link = driver.find_elements(
|
|
By.XPATH, "//a[contains(@href, '/swarm/live') or contains(text(), 'Live')]"
|
|
)
|
|
|
|
if live_link:
|
|
live_link[0].click()
|
|
time.sleep(1)
|
|
assert "/swarm/live" in driver.current_url
|