calculator.127local.net/tests/test_mobile.py

345 lines
14 KiB
Python
Raw Permalink Normal View History

2025-09-01 18:37:33 -07:00
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
class TestMobileResponsiveness:
"""Test mobile responsiveness and navigation functionality"""
def test_mobile_nav_toggle_button_exists(self, calculator_page):
"""Test that mobile navigation toggle button is present"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
assert nav_toggle.is_displayed()
# Check it has the correct class
assert "nav-toggle" in nav_toggle.get_attribute("class")
# Check it has the hamburger icon
svg = nav_toggle.find_element(By.TAG_NAME, "svg")
assert svg.is_displayed()
def test_mobile_nav_toggle_functionality(self, calculator_page):
"""Test that mobile navigation toggle works correctly"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
sidenav = calculator_page.find_element(By.ID, "nav")
# Debug: check if element is actually clickable
print(f"Nav toggle displayed: {nav_toggle.is_displayed()}")
print(f"Nav toggle enabled: {nav_toggle.is_enabled()}")
print(f"Nav toggle location: {nav_toggle.location}")
print(f"Nav toggle size: {nav_toggle.size}")
# Initially, sidenav should not have mobile-active class
assert "mobile-active" not in sidenav.get_attribute("class")
# Wait for element to be clickable
WebDriverWait(calculator_page, 10).until(
EC.element_to_be_clickable((By.ID, "navToggle"))
)
# Click the toggle button using JavaScript if regular click fails
try:
nav_toggle.click()
except Exception as e:
print(f"Regular click failed: {e}")
calculator_page.execute_script("arguments[0].click();", nav_toggle)
# Wait for the mobile-active class to be added
WebDriverWait(calculator_page, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
# Click again to close
try:
nav_toggle.click()
except Exception as e:
print(f"Regular click failed on close: {e}")
calculator_page.execute_script("arguments[0].click();", nav_toggle)
# Wait for the mobile-active class to be removed
WebDriverWait(calculator_page, 5).until_not(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
def test_mobile_nav_closes_on_outside_click(self, calculator_page):
"""Test that mobile navigation closes when clicking outside"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
sidenav = calculator_page.find_element(By.ID, "nav")
# Open mobile nav using JavaScript
calculator_page.execute_script("arguments[0].click();", nav_toggle)
# Wait for nav to open
WebDriverWait(calculator_page, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
# Click on the body element (outside nav) using JavaScript
calculator_page.execute_script("document.body.click();")
# Wait for nav to close
WebDriverWait(calculator_page, 5).until_not(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
def test_mobile_nav_closes_on_nav_link_click(self, calculator_page):
"""Test that mobile navigation closes when clicking a navigation link"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
sidenav = calculator_page.find_element(By.ID, "nav")
# Open mobile nav using JavaScript
calculator_page.execute_script("arguments[0].click();", nav_toggle)
# Wait for nav to open
WebDriverWait(calculator_page, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
# Click on a navigation link
nav_link = sidenav.find_element(By.CSS_SELECTOR, "a[data-calc='raid']")
nav_link.click()
# Wait for nav to close
WebDriverWait(calculator_page, 5).until_not(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
def test_mobile_nav_sticky_positioning(self, calculator_page):
"""Test that navigation bar stays at top and content doesn't scroll under it"""
# Get the navigation bar
nav_bar = calculator_page.find_element(By.CLASS_NAME, "bar")
# Check that it has sticky positioning
position = nav_bar.value_of_css_property("position")
assert position == "sticky"
# Check that it has a high z-index
z_index = nav_bar.value_of_css_property("z-index")
assert int(z_index) >= 10
# Check that it has a minimum height
min_height = nav_bar.value_of_css_property("min-height")
assert min_height == "70px"
def test_mobile_responsive_layout(self, calculator_page):
"""Test that layout changes appropriately on mobile"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
# Get the layout container
layout = calculator_page.find_element(By.CLASS_NAME, "layout")
# Check that it has proper grid layout
display = layout.value_of_css_property("display")
assert display == "grid"
# Check that it has responsive grid template
grid_template = layout.value_of_css_property("grid-template-columns")
# Should be responsive - on mobile it will be 1fr, on desktop 240px 1fr
# The actual value might be computed differently, so just check it's a valid grid value
assert "px" in grid_template or "fr" in grid_template
def test_mobile_friendly_inputs(self, calculator_page):
"""Test that inputs are mobile-friendly"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
# Navigate to a calculator with inputs
calculator_page.get("http://localhost:8008/subnet")
# Wait for calculator to load
WebDriverWait(calculator_page, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='ipAddress']"))
)
# Check input styling
ip_input = calculator_page.find_element(By.CSS_SELECTOR, "input[name='ipAddress']")
# Check padding (should be 12px on mobile)
padding = ip_input.value_of_css_property("padding")
assert "12px" in padding
# Check font size (should be 16px to prevent zoom on iOS)
font_size = ip_input.value_of_css_property("font-size")
assert "16px" in font_size
def test_mobile_table_overflow(self, calculator_page):
"""Test that tables have horizontal scroll on mobile"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
# Navigate to subnet calculator which has tables
calculator_page.get("http://localhost:8008/subnet")
# Wait for calculator to load
WebDriverWait(calculator_page, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='ipAddress']"))
)
# Enter an IP address to generate the table
ip_input = calculator_page.find_element(By.CSS_SELECTOR, "input[name='ipAddress']")
ip_input.clear()
ip_input.send_keys("192.168.1.1")
# Wait for table to appear
WebDriverWait(calculator_page, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "table"))
)
# Check that the result container has overflow handling
result_container = calculator_page.find_element(By.CLASS_NAME, "result")
overflow_x = result_container.value_of_css_property("overflow-x")
# Should have auto or scroll overflow on mobile
assert overflow_x in ["auto", "scroll"]
def test_mobile_footer_layout(self, calculator_page):
"""Test that footer is mobile-friendly"""
footer_content = calculator_page.find_element(By.CLASS_NAME, "footer-content")
# Check that footer content has proper flexbox layout
display = footer_content.value_of_css_property("display")
assert display == "flex"
# Check that source link is properly positioned
source_link = calculator_page.find_element(By.CLASS_NAME, "source-link")
assert source_link.is_displayed()
assert "https://code.disobey.net/whilb/calculator.127local.net" in source_link.get_attribute("href")
def test_mobile_nav_theme_toggle_buttons(self, calculator_page):
"""Test that both nav toggle and theme toggle buttons are accessible"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
theme_toggle = calculator_page.find_element(By.ID, "themeToggle")
# Both buttons should be visible
assert nav_toggle.is_displayed()
assert theme_toggle.is_displayed()
# Both should be clickable
assert nav_toggle.is_enabled()
assert theme_toggle.is_enabled()
# Check button styling
for button in [nav_toggle, theme_toggle]:
cursor = button.value_of_css_property("cursor")
assert cursor == "pointer"
def test_mobile_nav_accessibility(self, calculator_page):
"""Test mobile navigation accessibility features"""
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
sidenav = calculator_page.find_element(By.ID, "nav")
# Check aria-label on toggle button
aria_label = nav_toggle.get_attribute("aria-label")
assert aria_label == "Toggle navigation"
# Check that sidenav has proper role (should be navigation)
role = sidenav.get_attribute("role")
# If no explicit role, check that it's semantically correct
if not role:
# Should contain navigation links
nav_links = sidenav.find_elements(By.CSS_SELECTOR, "a[data-calc]")
assert len(nav_links) > 0
def test_mobile_nav_calculator_integration(self, calculator_page):
"""Test that mobile navigation works properly with calculator functionality"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
# Navigate to subnet calculator
calculator_page.get("http://localhost:8008/subnet")
# Wait for calculator to load
WebDriverWait(calculator_page, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='ipAddress']"))
)
# Open mobile navigation using JavaScript
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
calculator_page.execute_script("arguments[0].click();", nav_toggle)
# Wait for nav to open
WebDriverWait(calculator_page, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
# Navigate to a different calculator via mobile nav
nav_link = calculator_page.find_element(By.CSS_SELECTOR, "a[data-calc='currency']")
nav_link.click()
# Wait for nav to close and currency calculator to load
WebDriverWait(calculator_page, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='amount']"))
)
# Verify we're on the currency calculator
currency_input = calculator_page.find_element(By.CSS_SELECTOR, "input[name='amount']")
assert currency_input.is_displayed()
# Verify nav is closed
sidenav = calculator_page.find_element(By.ID, "nav")
assert "mobile-active" not in sidenav.get_attribute("class")
def test_mobile_nav_scroll_behavior(self, calculator_page):
"""Test that mobile navigation doesn't interfere with page scrolling"""
# Set mobile viewport
calculator_page.set_window_size(375, 667)
# Navigate to a calculator with long content
calculator_page.get("http://localhost:8008/subnet")
# Wait for calculator to load
WebDriverWait(calculator_page, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='ipAddress']"))
)
# Enter an IP address to generate content
ip_input = calculator_page.find_element(By.CSS_SELECTOR, "input[name='ipAddress']")
ip_input.clear()
ip_input.send_keys("10.0.0.1")
# Wait for results to appear
WebDriverWait(calculator_page, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "result"))
)
# Open mobile navigation using JavaScript
nav_toggle = calculator_page.find_element(By.ID, "navToggle")
calculator_page.execute_script("arguments[0].click();", nav_toggle)
# Wait for nav to open
WebDriverWait(calculator_page, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)
# Try to scroll the page
calculator_page.execute_script("window.scrollTo(0, 100)")
# Verify navigation is still open and functional
sidenav = calculator_page.find_element(By.ID, "nav")
assert "mobile-active" in sidenav.get_attribute("class")
# Close navigation using JavaScript
calculator_page.execute_script("arguments[0].click();", nav_toggle)
# Verify navigation closes
WebDriverWait(calculator_page, 5).until_not(
EC.presence_of_element_located((By.CSS_SELECTOR, ".sidenav.mobile-active"))
)