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