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:
Adam Outler
2026-01-18 18:16:18 +00:00
parent cea3369b5e
commit ecea1d1fbd
46 changed files with 5195 additions and 1053 deletions

0
test/ui/__init__.py Normal file
View File

View 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():

View File

@@ -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)

View File

@@ -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"

View File

@@ -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:

View File

@@ -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",

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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
View 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"