bottest/tests/e2e/platform_flow.rs

429 lines
12 KiB
Rust
Raw Permalink Normal View History

//! Complete E2E test for General Bots platform flow
//!
//! Tests the full user journey:
//! 1. Platform loading (UI assets)
//! 2. BotServer initialization
//! 3. User login
//! 4. Chat interaction
//! 5. User logout
use bottest::prelude::*;
use bottest::web::{Browser, Locator};
use std::time::Duration;
use super::{check_webdriver_available, should_run_e2e_tests, E2ETestContext};
/// Step 1: Verify platform loads
/// - Check UI is served
/// - Verify health endpoint responds
/// - Confirm database migrations completed
pub async fn verify_platform_loading(ctx: &E2ETestContext) -> anyhow::Result<()> {
let client = reqwest::Client::new();
// Check health endpoint
let health_url = format!("{}/health", ctx.base_url());
let health_resp = client.get(&health_url).send().await?;
assert!(
health_resp.status().is_success(),
"Health check failed with status: {}",
health_resp.status()
);
println!("✓ Platform health check passed");
// Verify API is responsive
let api_url = format!("{}/api/v1", ctx.base_url());
let api_resp = client.get(&api_url).send().await?;
assert!(
api_resp.status().is_success() || api_resp.status().as_u16() == 401,
"API endpoint failed with status: {}",
api_resp.status()
);
println!("✓ Platform API responsive");
Ok(())
}
/// Step 2: Verify BotServer is running and initialized
/// - Check service discovery
/// - Verify configuration loaded
/// - Confirm database connection
pub async fn verify_botserver_running(ctx: &E2ETestContext) -> anyhow::Result<()> {
let client = reqwest::Client::new();
// Check if server is actually running
assert!(ctx.server.is_running(), "BotServer process is not running");
println!("✓ BotServer process running");
// Verify server info endpoint
let info_url = format!("{}/api/v1/server/info", ctx.base_url());
match client.get(&info_url).send().await {
Ok(resp) => {
if resp.status().is_success() {
let body = resp.text().await?;
assert!(!body.is_empty(), "Server info response is empty");
println!(
"✓ BotServer initialized with info: {}",
body.chars().take(100).collect::<String>()
);
} else {
println!(
"⚠ Server info endpoint returned {}, continuing anyway",
resp.status()
);
}
}
Err(e) => {
println!(
"⚠ Could not reach server info endpoint: {}, continuing anyway",
e
);
}
}
println!("✓ BotServer is running and initialized");
Ok(())
}
/// Step 3: User login flow
/// - Navigate to login page
/// - Enter test credentials
/// - Verify session created
/// - Confirm redirect to dashboard
pub async fn test_user_login(browser: &Browser, ctx: &E2ETestContext) -> anyhow::Result<()> {
let login_url = format!("{}/login", ctx.base_url());
// Navigate to login page
browser.goto(&login_url).await?;
println!("✓ Navigated to login page: {}", login_url);
// Wait for login form to be visible
browser
.wait_for(Locator::css("input[type='email']"))
.await?;
println!("✓ Login form loaded");
// Fill in test credentials
let test_email = "test@example.com";
let test_password = "TestPassword123!";
browser
.fill(Locator::css("input[type='email']"), test_email)
.await?;
println!("✓ Entered email: {}", test_email);
browser
.fill(Locator::css("input[type='password']"), test_password)
.await?;
println!("✓ Entered password");
// Submit login form
browser.click(Locator::css("button[type='submit']")).await?;
println!("✓ Clicked login button");
// Wait a bit for redirect
tokio::time::sleep(Duration::from_secs(2)).await;
// Get current URL
let current_url = browser.current_url().await?;
// Check we're not on login page anymore
assert!(
!current_url.contains("/login"),
"Still on login page after login attempt. URL: {}",
current_url
);
println!("✓ Redirected from login page to: {}", current_url);
// Verify we can see dashboard or chat area
browser
.wait_for(Locator::css(
"[data-testid='chat-area'], [data-testid='dashboard'], main",
))
.await?;
println!("✓ Dashboard or chat area visible");
Ok(())
}
/// Step 4: Chat interaction
/// - Open chat window
/// - Send test message
/// - Receive bot response
/// - Verify message persisted
pub async fn test_chat_interaction(browser: &Browser, ctx: &E2ETestContext) -> anyhow::Result<()> {
// Ensure we're on chat page
let chat_url = format!("{}/chat", ctx.base_url());
browser.goto(&chat_url).await?;
println!("✓ Navigated to chat page");
// Wait for chat interface to load
browser
.wait_for(Locator::css(
"[data-testid='message-input'], textarea.chat-input, input.message",
))
.await?;
println!("✓ Chat interface loaded");
// Send test message
let test_message = "Hello, I need help";
browser
.fill(
Locator::css("textarea.chat-input, input.message"),
test_message,
)
.await?;
println!("✓ Typed message: {}", test_message);
// Click send button
let send_result = browser
.click(Locator::css(
"button[data-testid='send-button'], button.send-btn",
))
.await;
if send_result.is_err() {
// Try pressing Enter as alternative - find the input and send Enter key
let input = browser
.find(Locator::css("textarea.chat-input, input.message"))
.await?;
input.send_keys("\n").await?;
println!("✓ Sent message with Enter key");
} else {
println!("✓ Clicked send button");
}
// Wait for message to appear in chat history
browser
.wait_for(Locator::css(
"[data-testid='message-item'], .message-bubble, [class*='message']",
))
.await?;
println!("✓ Message appeared in chat");
// Wait for bot response
browser
.wait_for(Locator::css(
"[data-testid='bot-response'], .bot-message, [class*='bot']",
))
.await?;
println!("✓ Received bot response");
// Get response text
let response_text = browser
.text(Locator::css(
"[data-testid='bot-response'], .bot-message, [class*='bot']",
))
.await
.ok();
if let Some(text) = response_text {
println!(
"✓ Bot response: {}",
text.chars().take(100).collect::<String>()
);
}
Ok(())
}
/// Step 5: User logout flow
/// - Click logout button
/// - Verify session invalidated
/// - Confirm redirect to login
/// - Verify cannot access protected routes
pub async fn test_user_logout(browser: &Browser, ctx: &E2ETestContext) -> anyhow::Result<()> {
// Find and click logout button
let logout_selectors = vec![
"button[data-testid='logout-btn']",
"button.logout",
"[data-testid='user-menu'] button[data-testid='logout']",
"a[href*='logout']",
];
let mut logout_found = false;
for selector in logout_selectors {
if browser.click(Locator::css(selector)).await.is_ok() {
println!("✓ Clicked logout button: {}", selector);
logout_found = true;
break;
}
}
if !logout_found {
println!("⚠ Could not find logout button, attempting navigation to logout URL");
let logout_url = format!("{}/logout", ctx.base_url());
browser.goto(&logout_url).await?;
}
// Wait for redirect to login
tokio::time::sleep(Duration::from_secs(2)).await;
let current_url = browser.current_url().await?;
assert!(
current_url.contains("/login") || current_url.contains("/auth"),
"Not redirected to login page after logout. URL: {}",
current_url
);
println!("✓ Redirected to login page after logout: {}", current_url);
// Verify we cannot access protected routes
let chat_url = format!("{}/chat", ctx.base_url());
browser.goto(&chat_url).await?;
tokio::time::sleep(Duration::from_secs(1)).await;
let check_url = browser.current_url().await?;
assert!(
check_url.contains("/login") || check_url.contains("/auth"),
"Should be redirected to login when accessing protected route after logout. URL: {}",
check_url
);
println!("✓ Protected routes properly redirect to login");
Ok(())
}
/// Complete platform flow test
///
/// This test validates the entire user journey:
/// 1. Platform loads successfully
/// 2. BotServer is initialized and running
/// 3. User can login with credentials
/// 4. User can interact with chat
/// 5. User can logout and lose access
#[tokio::test]
async fn test_complete_platform_flow_login_chat_logout() {
if !should_run_e2e_tests() {
eprintln!("Skipping: E2E tests disabled (set SKIP_E2E_TESTS env var to disable)");
return;
}
if !check_webdriver_available().await {
eprintln!("Skipping: WebDriver not available at configured URL");
return;
}
println!("\n=== Starting Complete Platform Flow Test ===\n");
// Setup context
let ctx = match E2ETestContext::setup_with_browser().await {
Ok(ctx) => ctx,
Err(e) => {
eprintln!("Failed to setup E2E context: {}", e);
return;
}
};
if !ctx.has_browser() {
eprintln!("Browser not available");
return;
}
let browser = ctx.browser.as_ref().unwrap();
// Test each phase
println!("\n--- Phase 1: Platform Loading ---");
if let Err(e) = verify_platform_loading(&ctx).await {
eprintln!("Platform loading test failed: {}", e);
return;
}
println!("\n--- Phase 2: BotServer Initialization ---");
if let Err(e) = verify_botserver_running(&ctx).await {
eprintln!("BotServer initialization test failed: {}", e);
return;
}
println!("\n--- Phase 3: User Login ---");
if let Err(e) = test_user_login(browser, &ctx).await {
eprintln!("Login test failed: {}", e);
return;
}
println!("\n--- Phase 4: Chat Interaction ---");
if let Err(e) = test_chat_interaction(browser, &ctx).await {
eprintln!("Chat interaction test failed: {}", e);
// Don't return here - try to logout anyway
}
println!("\n--- Phase 5: User Logout ---");
if let Err(e) = test_user_logout(browser, &ctx).await {
eprintln!("Logout test failed: {}", e);
return;
}
println!("\n=== Complete Platform Flow Test PASSED ===\n");
ctx.close().await;
}
/// Simpler test for basic platform loading without browser
#[tokio::test]
async fn test_platform_loading_http_only() {
if !should_run_e2e_tests() {
eprintln!("Skipping: E2E tests disabled");
return;
}
println!("\n=== Testing Platform Loading (HTTP Only) ===\n");
let ctx = match E2ETestContext::setup().await {
Ok(ctx) => ctx,
Err(e) => {
eprintln!("Failed to setup context: {}", e);
return;
}
};
if let Err(e) = verify_platform_loading(&ctx).await {
eprintln!("Platform loading failed: {}", e);
return;
}
println!("\n✓ Platform Loading Test PASSED\n");
ctx.close().await;
}
/// Test BotServer startup and health
#[tokio::test]
async fn test_botserver_startup() {
if !should_run_e2e_tests() {
eprintln!("Skipping: E2E tests disabled");
return;
}
println!("\n=== Testing BotServer Startup ===\n");
let ctx = match E2ETestContext::setup().await {
Ok(ctx) => ctx,
Err(e) => {
eprintln!("Skipping: Failed to setup context: {}", e);
return;
}
};
// Skip if botserver isn't running (binary not found or failed to start)
if !ctx.server.is_running() {
eprintln!("Skipping: BotServer not running (BOTSERVER_BIN not set or binary not found)");
ctx.close().await;
return;
}
if let Err(e) = verify_botserver_running(&ctx).await {
eprintln!("BotServer test failed: {}", e);
ctx.close().await;
return;
}
println!("\n✓ BotServer Startup Test PASSED\n");
ctx.close().await;
}