mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-03-31 07:12:23 -07:00
feat(api): MCP, OpenAPI & Dynamic Introspection
New Features: - API endpoints now support comprehensive input validation with detailed error responses via Pydantic models. - OpenAPI specification endpoint (/openapi.json) and interactive Swagger UI documentation (/docs) now available for API discovery. - Enhanced MCP session lifecycle management with create, retrieve, and delete operations. - Network diagnostic tools: traceroute, nslookup, NMAP scanning, and network topology viewing exposed via API. - Device search, filtering by status (including 'offline'), and bulk operations (copy, delete, update). - Wake-on-LAN functionality for remote device management. - Added dynamic tool disablement and status reporting. Bug Fixes: - Fixed get_tools_status in registry to correctly return boolean values instead of None for enabled tools. - Improved error handling for invalid API inputs with standardized validation responses. - Fixed OPTIONS request handling for cross-origin requests. Refactoring: - Significant refactoring of api_server_start.py to use decorator-based validation (@validate_request).
This commit is contained in:
0
test/ui/__init__.py
Normal file
0
test/ui/__init__.py
Normal file
@@ -5,20 +5,15 @@ Runs all page-specific UI tests and provides summary
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add test directory to path
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
# Import all test modules
|
||||
import test_ui_dashboard # noqa: E402 [flake8 lint suppression]
|
||||
import test_ui_devices # noqa: E402 [flake8 lint suppression]
|
||||
import test_ui_network # noqa: E402 [flake8 lint suppression]
|
||||
import test_ui_maintenance # noqa: E402 [flake8 lint suppression]
|
||||
import test_ui_multi_edit # noqa: E402 [flake8 lint suppression]
|
||||
import test_ui_notifications # noqa: E402 [flake8 lint suppression]
|
||||
import test_ui_settings # noqa: E402 [flake8 lint suppression]
|
||||
import test_ui_plugins # noqa: E402 [flake8 lint suppression]
|
||||
from .test_helpers import test_ui_dashboard
|
||||
from .test_helpers import test_ui_devices
|
||||
from .test_helpers import test_ui_network
|
||||
from .test_helpers import test_ui_maintenance
|
||||
from .test_helpers import test_ui_multi_edit
|
||||
from .test_helpers import test_ui_notifications
|
||||
from .test_helpers import test_ui_settings
|
||||
from .test_helpers import test_ui_plugins
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -8,6 +8,9 @@ import requests
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
# Configuration
|
||||
BASE_URL = os.getenv("UI_BASE_URL", "http://localhost:20211")
|
||||
@@ -15,7 +18,11 @@ API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:20212")
|
||||
|
||||
|
||||
def get_api_token():
|
||||
"""Get API token from config file"""
|
||||
"""Get API token from config file or environment"""
|
||||
# Check environment first
|
||||
if os.getenv("API_TOKEN"):
|
||||
return os.getenv("API_TOKEN")
|
||||
|
||||
config_path = "/data/config/app.conf"
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
@@ -115,3 +122,31 @@ def api_post(endpoint, api_token, data=None, timeout=5):
|
||||
# Handle both full URLs and path-only endpoints
|
||||
url = endpoint if endpoint.startswith('http') else f"{API_BASE_URL}{endpoint}"
|
||||
return requests.post(url, headers=headers, json=data, timeout=timeout)
|
||||
|
||||
|
||||
# --- Page load and element wait helpers (used by UI tests) ---
|
||||
def wait_for_page_load(driver, timeout=10):
|
||||
"""Wait until the browser reports the document readyState is 'complete'."""
|
||||
WebDriverWait(driver, timeout).until(
|
||||
lambda d: d.execute_script("return document.readyState") == "complete"
|
||||
)
|
||||
|
||||
|
||||
def wait_for_element_by_css(driver, css_selector, timeout=10):
|
||||
"""Wait for presence of an element matching a CSS selector and return it."""
|
||||
return WebDriverWait(driver, timeout).until(
|
||||
EC.presence_of_element_located((By.CSS_SELECTOR, css_selector))
|
||||
)
|
||||
|
||||
|
||||
def wait_for_input_value(driver, element_id, timeout=10):
|
||||
"""Wait for the input with given id to have a non-empty value and return it."""
|
||||
def _get_val(d):
|
||||
try:
|
||||
el = d.find_element(By.ID, element_id)
|
||||
val = el.get_attribute("value")
|
||||
return val if val else False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return WebDriverWait(driver, timeout).until(_get_val)
|
||||
|
||||
@@ -4,34 +4,30 @@ Dashboard Page UI Tests
|
||||
Tests main dashboard metrics, charts, and device table
|
||||
"""
|
||||
|
||||
import time
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
# Add test directory to path
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from test_helpers import BASE_URL # noqa: E402 [flake8 lint suppression]
|
||||
from .test_helpers import BASE_URL, wait_for_page_load, wait_for_element_by_css # noqa: E402
|
||||
|
||||
|
||||
def test_dashboard_loads(driver):
|
||||
"""Test: Dashboard/index page loads successfully"""
|
||||
driver.get(f"{BASE_URL}/index.php")
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
assert driver.title, "Page should have a title"
|
||||
|
||||
|
||||
def test_metric_tiles_present(driver):
|
||||
"""Test: Dashboard metric tiles are rendered"""
|
||||
driver.get(f"{BASE_URL}/index.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Wait for at least one metric/tile/info-box to be present
|
||||
wait_for_element_by_css(driver, ".metric, .tile, .info-box, .small-box", timeout=10)
|
||||
tiles = driver.find_elements(By.CSS_SELECTOR, ".metric, .tile, .info-box, .small-box")
|
||||
assert len(tiles) > 0, "Dashboard should have metric tiles"
|
||||
|
||||
@@ -39,7 +35,8 @@ def test_metric_tiles_present(driver):
|
||||
def test_device_table_present(driver):
|
||||
"""Test: Dashboard device table is rendered"""
|
||||
driver.get(f"{BASE_URL}/index.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
wait_for_element_by_css(driver, "table", timeout=10)
|
||||
table = driver.find_elements(By.CSS_SELECTOR, "table")
|
||||
assert len(table) > 0, "Dashboard should have a device table"
|
||||
|
||||
@@ -47,6 +44,7 @@ def test_device_table_present(driver):
|
||||
def test_charts_present(driver):
|
||||
"""Test: Dashboard charts are rendered"""
|
||||
driver.get(f"{BASE_URL}/index.php")
|
||||
time.sleep(3) # Charts may take longer to load
|
||||
wait_for_page_load(driver, timeout=15) # Charts may take longer to load
|
||||
wait_for_element_by_css(driver, "canvas, .chart, svg", timeout=15)
|
||||
charts = driver.find_elements(By.CSS_SELECTOR, "canvas, .chart, svg")
|
||||
assert len(charts) > 0, "Dashboard should have charts"
|
||||
|
||||
@@ -4,34 +4,28 @@ Device Details Page UI Tests
|
||||
Tests device details page, field updates, and delete operations
|
||||
"""
|
||||
|
||||
import time
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
import sys
|
||||
import os
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
# Add test directory to path
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from test_helpers import BASE_URL, API_BASE_URL, api_get # noqa: E402 [flake8 lint suppression]
|
||||
from .test_helpers import BASE_URL, API_BASE_URL, api_get, wait_for_page_load, wait_for_element_by_css, wait_for_input_value # noqa: E402
|
||||
|
||||
|
||||
def test_device_list_page_loads(driver):
|
||||
"""Test: Device list page loads successfully"""
|
||||
driver.get(f"{BASE_URL}/devices.php")
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
assert "device" in driver.page_source.lower(), "Page should contain device content"
|
||||
|
||||
|
||||
def test_devices_table_present(driver):
|
||||
"""Test: Devices table is rendered"""
|
||||
driver.get(f"{BASE_URL}/devices.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
wait_for_element_by_css(driver, "table, #devicesTable", timeout=10)
|
||||
table = driver.find_elements(By.CSS_SELECTOR, "table, #devicesTable")
|
||||
assert len(table) > 0, "Devices table should be present"
|
||||
|
||||
@@ -39,7 +33,7 @@ def test_devices_table_present(driver):
|
||||
def test_device_search_works(driver):
|
||||
"""Test: Device search/filter functionality works"""
|
||||
driver.get(f"{BASE_URL}/devices.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Find search input (common patterns)
|
||||
search_inputs = driver.find_elements(By.CSS_SELECTOR, "input[type='search'], input[placeholder*='search' i], .dataTables_filter input")
|
||||
@@ -48,10 +42,11 @@ def test_device_search_works(driver):
|
||||
search_box = search_inputs[0]
|
||||
assert search_box.is_displayed(), "Search box should be visible"
|
||||
|
||||
# Type in search box
|
||||
# Type in search box and wait briefly for filter to apply
|
||||
search_box.clear()
|
||||
search_box.send_keys("test")
|
||||
time.sleep(1)
|
||||
# Wait for DOM/JS to react (at least one row or filtered content) — if datatables in use, table body should update
|
||||
wait_for_element_by_css(driver, "table tbody tr", timeout=5)
|
||||
|
||||
# Verify search executed (page content changed or filter applied)
|
||||
assert True, "Search executed successfully"
|
||||
@@ -82,10 +77,9 @@ def test_devices_totals_api(api_token):
|
||||
def test_add_device_with_generated_mac_ip(driver, api_token):
|
||||
"""Add a new device using the UI, always clicking Generate MAC/IP buttons"""
|
||||
import requests
|
||||
import time
|
||||
|
||||
driver.get(f"{BASE_URL}/devices.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# --- Click "Add Device" ---
|
||||
add_buttons = driver.find_elements(By.CSS_SELECTOR, "button#btnAddDevice, button[onclick*='addDevice'], a[href*='deviceDetails.php?mac='], .btn-add-device")
|
||||
@@ -95,16 +89,16 @@ def test_add_device_with_generated_mac_ip(driver, api_token):
|
||||
assert True, "Add device button not found, skipping test"
|
||||
return
|
||||
add_buttons[0].click()
|
||||
time.sleep(2)
|
||||
|
||||
# Wait for the device form to appear (use the NEWDEV_devMac field as indicator)
|
||||
wait_for_element_by_css(driver, "#NEWDEV_devMac", timeout=10)
|
||||
|
||||
# --- Helper to click generate button for a field ---
|
||||
def click_generate_button(field_id):
|
||||
btn = driver.find_element(By.CSS_SELECTOR, f"span[onclick*='generate_{field_id}']")
|
||||
driver.execute_script("arguments[0].click();", btn)
|
||||
time.sleep(0.5)
|
||||
# Return the new value
|
||||
inp = driver.find_element(By.ID, field_id)
|
||||
return inp.get_attribute("value")
|
||||
# Wait for the input to be populated and return it
|
||||
return wait_for_input_value(driver, field_id, timeout=10)
|
||||
|
||||
# --- Generate MAC ---
|
||||
test_mac = click_generate_button("NEWDEV_devMac")
|
||||
@@ -127,7 +121,6 @@ def test_add_device_with_generated_mac_ip(driver, api_token):
|
||||
assert True, "Save button not found, skipping test"
|
||||
return
|
||||
driver.execute_script("arguments[0].click();", save_buttons[0])
|
||||
time.sleep(3)
|
||||
|
||||
# --- Verify device via API ---
|
||||
headers = {"Authorization": f"Bearer {api_token}"}
|
||||
@@ -139,7 +132,7 @@ def test_add_device_with_generated_mac_ip(driver, api_token):
|
||||
else:
|
||||
# Fallback: check UI
|
||||
driver.get(f"{BASE_URL}/devices.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
if test_mac in driver.page_source or "Test Device Selenium" in driver.page_source:
|
||||
assert True, "Device appears in UI"
|
||||
else:
|
||||
|
||||
@@ -4,28 +4,23 @@ Maintenance Page UI Tests
|
||||
Tests CSV export/import, delete operations, database tools
|
||||
"""
|
||||
|
||||
import time
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from test_helpers import BASE_URL, api_get
|
||||
from .test_helpers import BASE_URL, api_get, wait_for_page_load # noqa: E402
|
||||
|
||||
|
||||
def test_maintenance_page_loads(driver):
|
||||
"""Test: Maintenance page loads successfully"""
|
||||
driver.get(f"{BASE_URL}/maintenance.php")
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
assert "Maintenance" in driver.page_source, "Page should show Maintenance content"
|
||||
|
||||
|
||||
def test_export_buttons_present(driver):
|
||||
"""Test: Export buttons are visible"""
|
||||
driver.get(f"{BASE_URL}/maintenance.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
export_btn = driver.find_elements(By.ID, "btnExportCSV")
|
||||
assert len(export_btn) > 0, "Export CSV button should be present"
|
||||
|
||||
@@ -36,7 +31,7 @@ def test_export_csv_button_works(driver):
|
||||
import glob
|
||||
|
||||
driver.get(f"{BASE_URL}/maintenance.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Clear any existing downloads
|
||||
download_dir = getattr(driver, 'download_dir', '/tmp/selenium_downloads')
|
||||
@@ -53,15 +48,13 @@ def test_export_csv_button_works(driver):
|
||||
driver.execute_script("arguments[0].click();", export_btn)
|
||||
|
||||
# Wait for download to complete (up to 10 seconds)
|
||||
downloaded = False
|
||||
for i in range(20): # Check every 0.5s for 10s
|
||||
time.sleep(0.5)
|
||||
csv_files = glob.glob(f"{download_dir}/*.csv")
|
||||
if len(csv_files) > 0:
|
||||
# Check file has content (download completed)
|
||||
if os.path.getsize(csv_files[0]) > 0:
|
||||
downloaded = True
|
||||
break
|
||||
try:
|
||||
WebDriverWait(driver, 10).until(
|
||||
lambda d: any(os.path.getsize(f) > 0 for f in glob.glob(f"{download_dir}/*.csv"))
|
||||
)
|
||||
downloaded = True
|
||||
except Exception:
|
||||
downloaded = False
|
||||
|
||||
if downloaded:
|
||||
# Verify CSV file exists and has data
|
||||
@@ -85,7 +78,7 @@ def test_export_csv_button_works(driver):
|
||||
def test_import_section_present(driver):
|
||||
"""Test: Import section is rendered or page loads without errors"""
|
||||
driver.get(f"{BASE_URL}/maintenance.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Check page loaded and doesn't show fatal errors
|
||||
assert "fatal" not in driver.page_source.lower(), "Page should not show fatal errors"
|
||||
assert "maintenance" in driver.page_source.lower() or len(driver.page_source) > 100, "Page should load content"
|
||||
@@ -94,7 +87,7 @@ def test_import_section_present(driver):
|
||||
def test_delete_buttons_present(driver):
|
||||
"""Test: Delete operation buttons are visible (at least some)"""
|
||||
driver.get(f"{BASE_URL}/maintenance.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
buttons = [
|
||||
"btnDeleteEmptyMACs",
|
||||
"btnDeleteAllDevices",
|
||||
|
||||
@@ -4,12 +4,11 @@ Multi-Edit Page UI Tests
|
||||
Tests bulk device operations and form controls
|
||||
"""
|
||||
|
||||
import time
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from test_helpers import BASE_URL
|
||||
from .test_helpers import BASE_URL, wait_for_page_load
|
||||
|
||||
|
||||
def test_multi_edit_page_loads(driver):
|
||||
@@ -18,7 +17,7 @@ def test_multi_edit_page_loads(driver):
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Check page loaded without fatal errors
|
||||
assert "fatal" not in driver.page_source.lower(), "Page should not show fatal errors"
|
||||
assert len(driver.page_source) > 100, "Page should load some content"
|
||||
@@ -27,7 +26,7 @@ def test_multi_edit_page_loads(driver):
|
||||
def test_device_selector_present(driver):
|
||||
"""Test: Device selector/table is rendered or page loads"""
|
||||
driver.get(f"{BASE_URL}/multiEditCore.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Page should load without fatal errors
|
||||
assert "fatal" not in driver.page_source.lower(), "Page should not show fatal errors"
|
||||
|
||||
@@ -35,7 +34,7 @@ def test_device_selector_present(driver):
|
||||
def test_bulk_action_buttons_present(driver):
|
||||
"""Test: Page loads for bulk actions"""
|
||||
driver.get(f"{BASE_URL}/multiEditCore.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Check page loads without errors
|
||||
assert len(driver.page_source) > 50, "Page should load content"
|
||||
|
||||
@@ -43,6 +42,6 @@ def test_bulk_action_buttons_present(driver):
|
||||
def test_field_dropdowns_present(driver):
|
||||
"""Test: Page loads successfully"""
|
||||
driver.get(f"{BASE_URL}/multiEditCore.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Check page loads
|
||||
assert "fatal" not in driver.page_source.lower(), "Page should not show fatal errors"
|
||||
|
||||
@@ -4,12 +4,11 @@ Network Page UI Tests
|
||||
Tests network topology visualization and device relationships
|
||||
"""
|
||||
|
||||
import time
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from test_helpers import BASE_URL
|
||||
from .test_helpers import BASE_URL, wait_for_page_load
|
||||
|
||||
|
||||
def test_network_page_loads(driver):
|
||||
@@ -18,14 +17,14 @@ def test_network_page_loads(driver):
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
assert driver.title, "Network page should have a title"
|
||||
|
||||
|
||||
def test_network_tree_present(driver):
|
||||
"""Test: Network tree container is rendered"""
|
||||
driver.get(f"{BASE_URL}/network.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
tree = driver.find_elements(By.ID, "networkTree")
|
||||
assert len(tree) > 0, "Network tree should be present"
|
||||
|
||||
@@ -33,7 +32,7 @@ def test_network_tree_present(driver):
|
||||
def test_network_tabs_present(driver):
|
||||
"""Test: Network page loads successfully"""
|
||||
driver.get(f"{BASE_URL}/network.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Check page loaded without fatal errors
|
||||
assert "fatal" not in driver.page_source.lower(), "Page should not show fatal errors"
|
||||
assert len(driver.page_source) > 100, "Page should load content"
|
||||
@@ -42,6 +41,6 @@ def test_network_tabs_present(driver):
|
||||
def test_device_tables_present(driver):
|
||||
"""Test: Device tables are rendered"""
|
||||
driver.get(f"{BASE_URL}/network.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
tables = driver.find_elements(By.CSS_SELECTOR, ".networkTable, table")
|
||||
assert len(tables) > 0, "Device tables should be present"
|
||||
|
||||
@@ -4,12 +4,11 @@ Notifications Page UI Tests
|
||||
Tests notification table, mark as read, delete operations
|
||||
"""
|
||||
|
||||
import time
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from test_helpers import BASE_URL, api_get
|
||||
from .test_helpers import BASE_URL, api_get, wait_for_page_load
|
||||
|
||||
|
||||
def test_notifications_page_loads(driver):
|
||||
@@ -18,14 +17,14 @@ def test_notifications_page_loads(driver):
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
assert "notification" in driver.page_source.lower(), "Page should contain notification content"
|
||||
|
||||
|
||||
def test_notifications_table_present(driver):
|
||||
"""Test: Notifications table is rendered"""
|
||||
driver.get(f"{BASE_URL}/userNotifications.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
table = driver.find_elements(By.CSS_SELECTOR, "table, #notificationsTable")
|
||||
assert len(table) > 0, "Notifications table should be present"
|
||||
|
||||
@@ -33,7 +32,7 @@ def test_notifications_table_present(driver):
|
||||
def test_notification_action_buttons_present(driver):
|
||||
"""Test: Notification action buttons are visible"""
|
||||
driver.get(f"{BASE_URL}/userNotifications.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
buttons = driver.find_elements(By.CSS_SELECTOR, "button[id*='notification'], .notification-action")
|
||||
assert len(buttons) > 0, "Notification action buttons should be present"
|
||||
|
||||
|
||||
@@ -4,28 +4,28 @@ Plugins Page UI Tests
|
||||
Tests plugin management interface and operations
|
||||
"""
|
||||
|
||||
import time
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from test_helpers import BASE_URL
|
||||
from .test_helpers import BASE_URL, wait_for_page_load
|
||||
|
||||
|
||||
def test_plugins_page_loads(driver):
|
||||
"""Test: Plugins page loads successfully"""
|
||||
driver.get(f"{BASE_URL}/pluginsCore.php")
|
||||
driver.get(f"{BASE_URL}/plugins.php")
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
assert "plugin" in driver.page_source.lower(), "Page should contain plugin content"
|
||||
|
||||
|
||||
def test_plugin_list_present(driver):
|
||||
"""Test: Plugin page loads successfully"""
|
||||
driver.get(f"{BASE_URL}/pluginsCore.php")
|
||||
time.sleep(2)
|
||||
driver.get(f"{BASE_URL}/plugins.php")
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Check page loaded
|
||||
assert "fatal" not in driver.page_source.lower(), "Page should not show fatal errors"
|
||||
assert len(driver.page_source) > 50, "Page should load content"
|
||||
@@ -33,7 +33,7 @@ def test_plugin_list_present(driver):
|
||||
|
||||
def test_plugin_actions_present(driver):
|
||||
"""Test: Plugin page loads without errors"""
|
||||
driver.get(f"{BASE_URL}/pluginsCore.php")
|
||||
time.sleep(2)
|
||||
driver.get(f"{BASE_URL}/plugins.php")
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
# Check page loads
|
||||
assert "fatal" not in driver.page_source.lower(), "Page should not show fatal errors"
|
||||
|
||||
@@ -9,12 +9,8 @@ import os
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
import sys
|
||||
|
||||
# Add test directory to path
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from test_helpers import BASE_URL # noqa: E402 [flake8 lint suppression]
|
||||
from .test_helpers import BASE_URL, wait_for_page_load
|
||||
|
||||
|
||||
def test_settings_page_loads(driver):
|
||||
@@ -23,14 +19,14 @@ def test_settings_page_loads(driver):
|
||||
WebDriverWait(driver, 10).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
assert "setting" in driver.page_source.lower(), "Page should contain settings content"
|
||||
|
||||
|
||||
def test_settings_groups_present(driver):
|
||||
"""Test: Settings groups/sections are rendered"""
|
||||
driver.get(f"{BASE_URL}/settings.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
groups = driver.find_elements(By.CSS_SELECTOR, ".settings-group, .panel, .card, fieldset")
|
||||
assert len(groups) > 0, "Settings groups should be present"
|
||||
|
||||
@@ -38,7 +34,7 @@ def test_settings_groups_present(driver):
|
||||
def test_settings_inputs_present(driver):
|
||||
"""Test: Settings input fields are rendered"""
|
||||
driver.get(f"{BASE_URL}/settings.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
inputs = driver.find_elements(By.CSS_SELECTOR, "input, select, textarea")
|
||||
assert len(inputs) > 0, "Settings input fields should be present"
|
||||
|
||||
@@ -46,7 +42,7 @@ def test_settings_inputs_present(driver):
|
||||
def test_save_button_present(driver):
|
||||
"""Test: Save button is visible"""
|
||||
driver.get(f"{BASE_URL}/settings.php")
|
||||
time.sleep(2)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
save_btn = driver.find_elements(By.CSS_SELECTOR, "button[type='submit'], button#save, .btn-save")
|
||||
assert len(save_btn) > 0, "Save button should be present"
|
||||
|
||||
@@ -63,7 +59,7 @@ def test_save_settings_with_form_submission(driver):
|
||||
6. Verifies the config file was updated
|
||||
"""
|
||||
driver.get(f"{BASE_URL}/settings.php")
|
||||
time.sleep(3)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Wait for the save button to be present and clickable
|
||||
save_btn = WebDriverWait(driver, 10).until(
|
||||
@@ -161,7 +157,7 @@ def test_save_settings_no_loss_of_data(driver):
|
||||
4. Check API endpoint that the setting is updated correctly
|
||||
"""
|
||||
driver.get(f"{BASE_URL}/settings.php")
|
||||
time.sleep(3)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Find the PLUGINS_KEEP_HIST input field
|
||||
plugins_keep_hist_input = None
|
||||
@@ -181,12 +177,12 @@ def test_save_settings_no_loss_of_data(driver):
|
||||
new_value = "333"
|
||||
plugins_keep_hist_input.clear()
|
||||
plugins_keep_hist_input.send_keys(new_value)
|
||||
time.sleep(1)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Click save
|
||||
save_btn = driver.find_element(By.CSS_SELECTOR, "button#save")
|
||||
driver.execute_script("arguments[0].click();", save_btn)
|
||||
time.sleep(3)
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Check for errors after save
|
||||
error_elements = driver.find_elements(By.CSS_SELECTOR, ".alert-danger, .error-message, .callout-danger")
|
||||
|
||||
77
test/ui/test_ui_waits.py
Normal file
77
test/ui/test_ui_waits.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic verification tests for wait helpers used by UI tests.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
# Add test directory to path
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from .test_helpers import BASE_URL, wait_for_page_load, wait_for_element_by_css, wait_for_input_value # noqa: E402
|
||||
|
||||
|
||||
def test_wait_helpers_work_on_dashboard(driver):
|
||||
"""Ensure wait helpers can detect basic dashboard elements"""
|
||||
driver.get(f"{BASE_URL}/index.php")
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
body = wait_for_element_by_css(driver, "body", timeout=5)
|
||||
assert body is not None
|
||||
# Device table should be present on the dashboard
|
||||
table = wait_for_element_by_css(driver, "table", timeout=10)
|
||||
assert table is not None
|
||||
|
||||
|
||||
def test_wait_for_input_value_on_devices(driver):
|
||||
"""Try generating a MAC on the devices add form and use wait_for_input_value to validate it."""
|
||||
driver.get(f"{BASE_URL}/devices.php")
|
||||
wait_for_page_load(driver, timeout=10)
|
||||
|
||||
# Try to open an add form - skip if not present
|
||||
add_buttons = driver.find_elements(By.CSS_SELECTOR, "button#btnAddDevice, button[onclick*='addDevice'], a[href*='deviceDetails.php?mac='], .btn-add-device")
|
||||
if not add_buttons:
|
||||
return # nothing to test in this environment
|
||||
# Use JS click with scroll into view to avoid element click intercepted errors
|
||||
btn = add_buttons[0]
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", btn)
|
||||
try:
|
||||
driver.execute_script("arguments[0].click();", btn)
|
||||
except Exception:
|
||||
# Fallback to normal click if JS click fails for any reason
|
||||
btn.click()
|
||||
|
||||
# Wait for the NEWDEV_devMac field to appear; if not found, try navigating directly to the add form
|
||||
try:
|
||||
wait_for_element_by_css(driver, "#NEWDEV_devMac", timeout=5)
|
||||
except Exception:
|
||||
# Some UIs open a new page at deviceDetails.php?mac=new; navigate directly as a fallback
|
||||
driver.get(f"{BASE_URL}/deviceDetails.php?mac=new")
|
||||
try:
|
||||
wait_for_element_by_css(driver, "#NEWDEV_devMac", timeout=10)
|
||||
except Exception:
|
||||
# If that still fails, attempt to remove canvas overlays (chart.js) and retry clicking the add button
|
||||
driver.execute_script("document.querySelectorAll('canvas').forEach(c=>c.style.pointerEvents='none');")
|
||||
btn = add_buttons[0]
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", btn)
|
||||
try:
|
||||
driver.execute_script("arguments[0].click();", btn)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
wait_for_element_by_css(driver, "#NEWDEV_devMac", timeout=5)
|
||||
except Exception:
|
||||
# Restore canvas pointer-events and give up
|
||||
driver.execute_script("document.querySelectorAll('canvas').forEach(c=>c.style.pointerEvents='auto');")
|
||||
return
|
||||
# Restore canvas pointer-events
|
||||
driver.execute_script("document.querySelectorAll('canvas').forEach(c=>c.style.pointerEvents='auto');")
|
||||
|
||||
# Attempt to click the generate control if present
|
||||
gen_buttons = driver.find_elements(By.CSS_SELECTOR, "span[onclick*='generate_NEWDEV_devMac']")
|
||||
if not gen_buttons:
|
||||
return
|
||||
driver.execute_script("arguments[0].click();", gen_buttons[0])
|
||||
mac_val = wait_for_input_value(driver, "NEWDEV_devMac", timeout=10)
|
||||
assert mac_val, "Generated MAC should be populated"
|
||||
Reference in New Issue
Block a user