mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2026-04-23 08:31:16 -04:00
279 lines
10 KiB
Python
279 lines
10 KiB
Python
"""
|
|
E2E tests for verifying the app works correctly when served from different base paths.
|
|
This catches issues with React Router and asset loading when deployed to subpaths.
|
|
"""
|
|
import os
|
|
import time
|
|
import pytest
|
|
from selenium import webdriver
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.chrome.options import Options
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
|
|
|
|
def get_base_url():
|
|
"""Get the base URL from environment or use default."""
|
|
return os.getenv("FOSSFLOW_TEST_URL", "http://localhost:3000")
|
|
|
|
|
|
def get_base_path():
|
|
"""Get the base path from environment."""
|
|
return os.getenv("FOSSFLOW_BASE_PATH", "/")
|
|
|
|
|
|
def get_webdriver_url():
|
|
"""Get the WebDriver URL from environment or use default."""
|
|
return os.getenv("WEBDRIVER_URL", "http://localhost:4444")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def driver():
|
|
"""Create a Chrome WebDriver instance for each test."""
|
|
chrome_options = Options()
|
|
chrome_options.add_argument("--headless=new")
|
|
chrome_options.add_argument("--no-sandbox")
|
|
chrome_options.add_argument("--disable-dev-shm-usage")
|
|
chrome_options.add_argument("--enable-webgl")
|
|
chrome_options.add_argument("--use-gl=swiftshader")
|
|
chrome_options.add_argument("--enable-accelerated-2d-canvas")
|
|
chrome_options.add_argument("--window-size=1920,1080")
|
|
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
|
chrome_options.set_capability('goog:loggingPrefs', {'browser': 'ALL'})
|
|
|
|
webdriver_url = get_webdriver_url()
|
|
|
|
driver = webdriver.Remote(
|
|
command_executor=webdriver_url,
|
|
options=chrome_options
|
|
)
|
|
|
|
driver.implicitly_wait(10)
|
|
|
|
yield driver
|
|
|
|
driver.quit()
|
|
|
|
|
|
def test_app_loads_at_base_path(driver):
|
|
"""Test that the app loads successfully at the configured base path."""
|
|
base_url = get_base_url()
|
|
base_path = get_base_path()
|
|
|
|
print(f"\nTesting app at base URL: {base_url}")
|
|
print(f"Base path: {base_path}")
|
|
|
|
# Navigate to the app
|
|
driver.get(base_url)
|
|
|
|
# Wait for React to mount
|
|
time.sleep(3)
|
|
|
|
# Verify we're at the correct URL
|
|
current_url = driver.current_url
|
|
print(f"Current URL: {current_url}")
|
|
|
|
# The URL should contain our base path
|
|
if base_path != "/":
|
|
assert base_path in current_url, f"Expected base path '{base_path}' in URL '{current_url}'"
|
|
|
|
# Check that the React app has mounted
|
|
root = driver.find_element(By.ID, "root")
|
|
assert root is not None, "React root element should exist"
|
|
|
|
root_content = driver.execute_script("return document.getElementById('root').innerHTML.length;")
|
|
assert root_content > 0, "React should have rendered content"
|
|
|
|
print("✓ App loaded successfully at base path")
|
|
|
|
|
|
def test_static_assets_load_correctly(driver):
|
|
"""Test that CSS, JS, and other static assets load from the correct path."""
|
|
base_url = get_base_url()
|
|
base_path = get_base_path()
|
|
|
|
driver.get(base_url)
|
|
time.sleep(3)
|
|
|
|
# Check for any failed resource loads in network
|
|
failed_resources = driver.execute_script("""
|
|
const perf = performance.getEntriesByType('resource');
|
|
const failed = perf.filter(entry => {
|
|
// Check for failed loads (status 404, 403, 500, etc.)
|
|
// Note: transferSize === 0 might indicate CORS issues or failed loads
|
|
return entry.transferSize === 0 && !entry.name.includes('data:');
|
|
});
|
|
return failed.map(r => ({
|
|
name: r.name,
|
|
type: r.initiatorType
|
|
}));
|
|
""")
|
|
|
|
if failed_resources:
|
|
print(f"\n⚠ Found {len(failed_resources)} potentially failed resource loads:")
|
|
for resource in failed_resources[:10]:
|
|
print(f" - {resource['type']}: {resource['name']}")
|
|
|
|
# Check that main JS bundle loaded
|
|
js_loaded = driver.execute_script("""
|
|
const scripts = Array.from(document.getElementsByTagName('script'));
|
|
return scripts.some(s => s.src && !s.src.includes('data:'));
|
|
""")
|
|
assert js_loaded, "JavaScript bundles should be loaded"
|
|
print("✓ JavaScript bundles loaded")
|
|
|
|
# Check that CSS loaded
|
|
css_loaded = driver.execute_script("""
|
|
const links = Array.from(document.getElementsByTagName('link'));
|
|
const hasCSS = links.some(l => l.rel === 'stylesheet' && l.href);
|
|
const hasStyles = document.getElementsByTagName('style').length > 0;
|
|
return hasCSS || hasStyles;
|
|
""")
|
|
assert css_loaded, "CSS should be loaded"
|
|
print("✓ CSS loaded")
|
|
|
|
# Check console for errors about failed loads
|
|
logs = driver.get_log('browser')
|
|
errors = [log for log in logs if 'Failed to load resource' in log.get('message', '') or '404' in log.get('message', '')]
|
|
|
|
if errors:
|
|
print(f"\n⚠ Found {len(errors)} resource loading errors in console:")
|
|
for error in errors[:5]:
|
|
print(f" {error['message'][:100]}")
|
|
|
|
# Don't fail the test but warn about errors
|
|
if len(errors) > 5:
|
|
pytest.fail(f"Too many resource loading errors ({len(errors)}). Check asset paths.")
|
|
|
|
print("✓ Static assets loaded correctly")
|
|
|
|
|
|
def test_react_router_navigation_works(driver):
|
|
"""Test that React Router navigation works correctly with the base path."""
|
|
base_url = get_base_url()
|
|
base_path = get_base_path()
|
|
|
|
driver.get(base_url)
|
|
time.sleep(3)
|
|
|
|
# Get initial URL
|
|
initial_url = driver.current_url
|
|
print(f"\nInitial URL: {initial_url}")
|
|
|
|
# Try navigating to a different route using React Router
|
|
# Note: This assumes the app has navigation. Adjust based on actual routes.
|
|
navigation_result = driver.execute_script("""
|
|
// Check if React Router is available
|
|
const hasRouter = window.React && window.ReactDOM;
|
|
|
|
// Try to find any links or buttons that might trigger navigation
|
|
const links = document.querySelectorAll('a[href^="/"], a[href^="./"], a[href^="#"]');
|
|
const buttons = document.querySelectorAll('button');
|
|
|
|
return {
|
|
hasReactApp: !!document.querySelector('#root').children.length,
|
|
linkCount: links.length,
|
|
buttonCount: buttons.length,
|
|
currentPath: window.location.pathname
|
|
};
|
|
""")
|
|
|
|
print(f"Navigation check:")
|
|
print(f" Has React App: {navigation_result['hasReactApp']}")
|
|
print(f" Links found: {navigation_result['linkCount']}")
|
|
print(f" Buttons found: {navigation_result['buttonCount']}")
|
|
print(f" Current path: {navigation_result['currentPath']}")
|
|
|
|
# Verify the current path matches our expected base path structure
|
|
current_path = navigation_result['currentPath']
|
|
if base_path != "/" and not current_path.startswith(base_path.rstrip('/')):
|
|
pytest.fail(f"Current path '{current_path}' doesn't start with base path '{base_path}'")
|
|
|
|
print("✓ React Router configured correctly for base path")
|
|
|
|
|
|
def test_router_basename_detection(driver):
|
|
"""Test that the React Router basename is correctly detected from the URL."""
|
|
base_url = get_base_url()
|
|
base_path = get_base_path()
|
|
|
|
driver.get(base_url)
|
|
time.sleep(3)
|
|
|
|
# Check what basename React Router is using
|
|
# This executes the same logic as in App.tsx
|
|
detected_basename = driver.execute_script(r"""
|
|
// This replicates the basename detection logic from App.tsx
|
|
const pathname = window.location.pathname;
|
|
const basename = pathname.replace(/\/display\/.*$/, '').replace(/\/$/, '') || '/';
|
|
return basename;
|
|
""")
|
|
|
|
print(f"\nBasename detection:")
|
|
print(f" Expected base path: {base_path}")
|
|
print(f" Detected basename: {detected_basename}")
|
|
print(f" Current pathname: {driver.execute_script('return window.location.pathname')}")
|
|
|
|
# The detected basename should match our base path (normalized)
|
|
expected = base_path.rstrip('/') or '/'
|
|
detected = detected_basename.rstrip('/') or '/'
|
|
|
|
if expected != detected:
|
|
print(f"⚠ Warning: Basename mismatch - expected '{expected}', detected '{detected}'")
|
|
# This might be okay if the app handles it correctly
|
|
# Don't fail immediately, but check if the app still works
|
|
|
|
# Verify the app actually rendered despite the mismatch
|
|
app_rendered = driver.execute_script("""
|
|
return document.querySelector('.fossflow-container') !== null ||
|
|
document.querySelector('#root').children.length > 0;
|
|
""")
|
|
|
|
if not app_rendered:
|
|
pytest.fail(f"App didn't render with basename mismatch. Expected '{expected}', got '{detected}'")
|
|
|
|
print("✓ Router basename detection working correctly")
|
|
|
|
|
|
def test_no_console_errors_at_base_path(driver):
|
|
"""Ensure there are no critical JavaScript errors when loaded at base path."""
|
|
base_url = get_base_url()
|
|
base_path = get_base_path()
|
|
|
|
driver.get(base_url)
|
|
time.sleep(3)
|
|
|
|
# Get console logs
|
|
logs = driver.get_log('browser')
|
|
|
|
# Filter for severe errors
|
|
severe_errors = [log for log in logs if log['level'] == 'SEVERE']
|
|
|
|
# Common errors to ignore (that might not be real issues)
|
|
ignored_patterns = [
|
|
'favicon.ico', # Missing favicon is okay
|
|
'manifest.json', # Missing manifest is okay for basic functionality
|
|
]
|
|
|
|
critical_errors = []
|
|
for error in severe_errors:
|
|
message = error.get('message', '')
|
|
if not any(pattern in message for pattern in ignored_patterns):
|
|
critical_errors.append(error)
|
|
|
|
if critical_errors:
|
|
print(f"\n⚠ Found {len(critical_errors)} critical console errors:")
|
|
for error in critical_errors[:5]:
|
|
print(f" {error['message'][:150]}")
|
|
|
|
# Check for specific routing-related errors
|
|
routing_errors = [e for e in critical_errors if 'Router' in e['message'] or 'basename' in e['message']]
|
|
if routing_errors:
|
|
pytest.fail(f"Found React Router errors: {routing_errors[0]['message']}")
|
|
|
|
else:
|
|
print("✓ No critical console errors found")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v", "-s"]) |