"""Unit tests for galaxy.selenium.has_driver module.""" from typing import cast import pytest from selenium.common.exceptions import ( NoSuchElementException, TimeoutException as SeleniumTimeoutException, ) from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver from galaxy.navigation.components import Target from galaxy.selenium.availability import ( PLAYWRIGHT_BROWSER_NOT_AVAILABLE_MESSAGE, SELENIUM_BROWSER_NOT_AVAILABLE_MESSAGE, ) from galaxy.selenium.has_driver import ( exception_indicates_click_intercepted, exception_indicates_not_clickable, exception_indicates_stale_element, HasDriver, ) from galaxy.selenium.has_driver_protocol import ( fixed_timeout_handler, HasDriverProtocol, TimeoutCallback, ) from galaxy.selenium.has_driver_proxy import HasDriverProxyImpl from galaxy.selenium.has_playwright_driver import ( HasPlaywrightDriver, PlaywrightResources, PlaywrightTimeoutException, ) from .util import ( check_playwright_cached, check_selenium_cached, ) class SimpleTarget(Target): """Simple concrete implementation of Target for testing.""" def __init__(self, element_locator: tuple, description: str): """ Initialize target with locator and description. Args: element_locator: Tuple of (By, locator_string) for Selenium description: Human-readable description """ self._element_locator = element_locator self._description = description @property def description(self) -> str: """Return description.""" return self._description @property def element_locator(self): """Return Selenium locator tuple.""" return self._element_locator @property def component_locator(self): """Return component locator (not used in these tests).""" raise NotImplementedError("component_locator not needed for these tests") class TestHasDriverImpl(HasDriver): """ Concrete implementation of HasDriver for testing. HasDriver is an abstract mixin that requires a driver and timeout implementation. """ def __init__(self, driver: WebDriver, default_timeout: float = 10.0): """ Initialize test implementation. Args: driver: Selenium WebDriver instance default_timeout: Default timeout for waits """ self.driver = driver self.default_timeout = default_timeout @property def timeout_handler(self) -> TimeoutCallback: """Return timeout value.""" return fixed_timeout_handler(self.default_timeout) class TestHasPlaywrightDriverImpl(HasPlaywrightDriver): """ Concrete implementation of HasPlaywrightDriver for testing. HasPlaywrightDriver is an abstract mixin that requires PlaywrightResources and timeout implementation. """ def __init__(self, playwright_resources: PlaywrightResources, default_timeout: float = 10.0): """ Initialize test implementation. Args: playwright_resources: PlaywrightResources containing playwright, browser, and page default_timeout: Default timeout for waits """ self._playwright_resources = playwright_resources self.default_timeout = default_timeout self._current_frame = None @property def timeout_handler(self) -> TimeoutCallback: """Return timeout value.""" return fixed_timeout_handler(self.default_timeout) @pytest.fixture( params=[ pytest.param( "selenium", marks=pytest.mark.skipif( not check_selenium_cached(), reason=SELENIUM_BROWSER_NOT_AVAILABLE_MESSAGE, ), ), pytest.param( "playwright", marks=pytest.mark.skipif( not check_playwright_cached(), reason=PLAYWRIGHT_BROWSER_NOT_AVAILABLE_MESSAGE, ), ), pytest.param( "proxy-selenium", marks=pytest.mark.skipif( not check_selenium_cached(), reason=SELENIUM_BROWSER_NOT_AVAILABLE_MESSAGE, ), ), ] ) def has_driver_instance(request, driver, playwright_resources) -> HasDriverProtocol: """ Create a HasDriver, HasPlaywrightDriver, or proxied instance for testing. This fixture is parametrized to test all three approaches: - Direct Selenium (HasDriver) - skipped if Chrome not available - Direct Playwright (HasPlaywrightDriver) - skipped if Chromium not available - Proxied Selenium (HasDriverProxy wrapping HasDriver) - skipped if Chrome not available Args: request: Pytest request object driver: Selenium WebDriver fixture playwright_resources: PlaywrightResources fixture """ if request.param == "selenium": return cast(HasDriverProtocol, TestHasDriverImpl(driver)) elif request.param == "playwright": return cast(HasDriverProtocol, TestHasPlaywrightDriverImpl(playwright_resources)) else: # proxy-selenium selenium_impl = cast(HasDriverProtocol, TestHasDriverImpl(driver)) return HasDriverProxyImpl(selenium_impl) class TestElementFinding: """Tests for element finding methods.""" def test_assert_xpath(self, has_driver_instance, base_url: str) -> None: """Test finding element by XPath assertion.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.assert_xpath("//h1[@id='header']") def test_assert_xpath_fails_when_not_found(self, has_driver_instance, base_url: str) -> None: """Test assert_xpath raises when element not found.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") with pytest.raises((NoSuchElementException, AssertionError)): has_driver_instance.assert_xpath("//div[@id='nonexistent']") def test_assert_selector(self, has_driver_instance, base_url: str) -> None: """Test finding element by CSS selector assertion.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.assert_selector("#test-div") def test_assert_selector_fails_when_not_found(self, has_driver_instance, base_url: str) -> None: """Test assert_selector raises when element not found.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") with pytest.raises((NoSuchElementException, AssertionError)): has_driver_instance.assert_selector("#nonexistent") def test_find_element_by_id(self, has_driver_instance, base_url: str) -> None: """Test finding element by ID.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("test-div") assert element.text == "Test Div" def test_find_element_by_xpath(self, has_driver_instance, base_url: str) -> None: """Test finding element by XPath.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_xpath("//p[@class='test-paragraph']") assert element.text == "Test Paragraph" def test_find_element_by_selector(self, has_driver_instance, base_url: str) -> None: """Test finding element by CSS selector.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_selector("[data-testid='test-span']") assert element.text == "Test Span" def test_find_element_by_link_text(self, has_driver_instance, base_url: str) -> None: """Test finding element by link text.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_link_text("Test Link") assert element.get_attribute("id") == "test-link" def test_find_elements_with_target(self, has_driver_instance, base_url: str) -> None: """Test finding multiple elements using Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.CLASS_NAME, "item"), description="list items") elements = has_driver_instance.find_elements(target) assert len(elements) == 3 def test_find_elements_by_selector(self, has_driver_instance, base_url: str) -> None: """Test finding multiple elements by CSS selector.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") elements = has_driver_instance.find_elements_by_selector(".item") assert len(elements) == 3 # Verify we got actual elements assert all(hasattr(el, "text") for el in elements) def test_find_elements_by_selector_no_matches(self, has_driver_instance, base_url: str) -> None: """Test finding elements when selector matches nothing.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") elements = has_driver_instance.find_elements_by_selector(".nonexistent-class") assert len(elements) == 0 assert elements == [] def test_find_elements_by_selector_single_match(self, has_driver_instance, base_url: str) -> None: """Test finding elements when selector matches single element.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") elements = has_driver_instance.find_elements_by_selector("#test-div") assert len(elements) == 1 assert elements[0].text == "Test Div" def test_find_element_with_target(self, has_driver_instance, base_url: str) -> None: """Test finding single element using Target (no waiting).""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "test-div"), description="test div") element = has_driver_instance.find_element(target) assert element.text == "Test Div" def test_find_element_with_target_by_class(self, has_driver_instance, base_url: str) -> None: """Test find_element returns first element when multiple matches exist.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.CLASS_NAME, "item"), description="first item") element = has_driver_instance.find_element(target) # Should get the first item assert element.text in ["Item 1", "Item 2", "Item 3"] def test_find_element_with_target_fails_when_not_found(self, has_driver_instance, base_url: str) -> None: """Test find_element raises exception when element not found.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "nonexistent"), description="nonexistent element") with pytest.raises((NoSuchElementException, Exception)): has_driver_instance.find_element(target) class TestVisibilityAndPresence: """Tests for visibility and presence checking methods.""" def test_selector_is_displayed_visible_element(self, has_driver_instance, base_url): """Test checking if visible element is displayed.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") assert has_driver_instance.selector_is_displayed("#visible-element") def test_selector_is_displayed_hidden_element(self, has_driver_instance, base_url): """Test checking if hidden element is not displayed.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") assert not has_driver_instance.selector_is_displayed("#hidden-element") def test_is_displayed_with_target(self, has_driver_instance, base_url): """Test is_displayed with Target selector.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "visible-element"), description="visible element") assert has_driver_instance.is_displayed(target) def test_assert_selector_absent_or_hidden(self, has_driver_instance, base_url): """Test asserting element is absent or hidden.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.assert_selector_absent_or_hidden("#hidden-element") def test_assert_absent_or_hidden_with_target(self, has_driver_instance, base_url): """Test assert_absent_or_hidden with Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "hidden-element"), description="hidden element") has_driver_instance.assert_absent_or_hidden(target) def test_assert_selector_absent(self, has_driver_instance, base_url): """Test asserting element is completely absent from DOM.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.assert_selector_absent("#nonexistent-element") def test_assert_absent_with_target(self, has_driver_instance, base_url): """Test assert_absent with Target when element doesn't exist.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "nonexistent"), description="nonexistent element") has_driver_instance.assert_absent(target) def test_element_absent_returns_true(self, has_driver_instance, base_url): """Test element_absent returns True when element not in DOM.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "nonexistent"), description="nonexistent element") assert has_driver_instance.element_absent(target) def test_element_absent_returns_false(self, has_driver_instance, base_url): """Test element_absent returns False when element exists.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "test-div"), description="test div") assert not has_driver_instance.element_absent(target) def test_assert_disabled(self, has_driver_instance, base_url): """Test asserting element is disabled.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "disabled-button"), description="disabled button") has_driver_instance.assert_disabled(target) class TestWaitMethods: """Tests for wait methods.""" def test_wait_for_xpath(self, has_driver_instance, base_url): """Test waiting for element by XPath.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.wait_for_xpath("//h1[@id='header']") assert element.text == "Test Page" def test_wait_for_xpath_visible(self, has_driver_instance, base_url): """Test waiting for visible element by XPath.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.wait_for_xpath_visible("//div[@id='visible-element']") assert element.is_displayed() def test_wait_for_selector(self, has_driver_instance, base_url): """Test waiting for element by CSS selector.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.wait_for_selector("#test-div") assert element.text == "Test Div" def test_wait_for_present_with_target(self, has_driver_instance, base_url): """Test wait_for_present with Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "test-div"), description="test div") element = has_driver_instance.wait_for_present(target) assert element is not None def test_wait_for_visible_with_target(self, has_driver_instance, base_url): """Test wait_for_visible with Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "visible-element"), description="visible element") element = has_driver_instance.wait_for_visible(target) assert element.is_displayed() def test_wait_for_selector_visible(self, has_driver_instance, base_url): """Test waiting for visible element by CSS selector.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.wait_for_selector_visible("#visible-element") assert element.is_displayed() def test_wait_for_selector_clickable(self, has_driver_instance, base_url): """Test waiting for clickable element by CSS selector.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.wait_for_selector_clickable("#clickable-button") assert element.is_enabled() def test_wait_for_clickable_with_target(self, has_driver_instance, base_url): """Test wait_for_clickable with Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "clickable-button"), description="clickable button") element = has_driver_instance.wait_for_clickable(target) assert element.is_enabled() def test_wait_for_selector_absent(self, has_driver_instance, base_url): """Test waiting for element to be absent.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Element doesn't exist, so wait should succeed immediately has_driver_instance.wait_for_selector_absent("#nonexistent-element") def test_wait_for_absent_with_target(self, has_driver_instance, base_url): """Test wait_for_absent with Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "nonexistent"), description="nonexistent element") has_driver_instance.wait_for_absent(target) def test_wait_for_selector_absent_or_hidden(self, has_driver_instance, base_url): """Test waiting for element to be absent or hidden.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.wait_for_selector_absent_or_hidden("#hidden-element") def test_wait_for_absent_or_hidden_with_target(self, has_driver_instance, base_url): """Test wait_for_absent_or_hidden with Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "hidden-element"), description="hidden element") has_driver_instance.wait_for_absent_or_hidden(target) def test_wait_for_id(self, has_driver_instance, base_url): """Test waiting for element by ID.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.wait_for_id("test-div") assert element.get_attribute("id") == "test-div" def test_wait_for_element_count_of_at_least(self, has_driver_instance, base_url): """Test waiting for at least N elements.""" # Skip for Playwright since _wait_on_condition_count is not implemented if hasattr(has_driver_instance, "page"): pytest.skip("wait_for_element_count_of_at_least not implemented for Playwright") has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.CLASS_NAME, "item"), description="list items") has_driver_instance.wait_for_element_count_of_at_least(target, 3) elements = has_driver_instance.find_elements(target) assert len(elements) >= 3 def test_wait_timeout_with_custom_timeout(self, has_driver_instance, base_url): """Test that custom timeout is used.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Expect either SeleniumTimeoutException or PlaywrightTimeoutException with pytest.raises((SeleniumTimeoutException, PlaywrightTimeoutException)): has_driver_instance.wait_for_selector("#nonexistent", timeout=1) def test_wait_for_delayed_element_becomes_visible(self, has_driver_instance, base_url): """Test waiting for element that becomes visible after delay.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Element becomes visible after 1 second element = has_driver_instance.wait_for_selector_visible("#delayed-element", timeout=3) assert element.is_displayed() class TestClickAndInteraction: """Tests for click and interaction methods.""" def test_click_xpath(self, has_driver_instance, base_url): """Test clicking element by XPath.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.click_xpath("//button[@id='clickable-button']") button = has_driver_instance.find_element_by_id("clickable-button") assert button.text == "Clicked!" def test_click_selector(self, has_driver_instance, base_url): """Test clicking element by CSS selector.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.click_selector("#clickable-button") button = has_driver_instance.find_element_by_id("clickable-button") assert button.text == "Clicked!" def test_click_label(self, has_driver_instance, base_url): """Test clicking link by text.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.click_label("Test Link") # Link was clicked (href="#" so stays on same page) def test_click_with_target(self, has_driver_instance, base_url): """Test click method with Target.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = SimpleTarget(element_locator=(By.ID, "clickable-button"), description="clickable button") has_driver_instance.click(target) button = has_driver_instance.find_element_by_id("clickable-button") assert button.text == "Clicked!" class TestFormInteraction: """Tests for form interaction methods.""" def test_fill_form(self, has_driver_instance, base_url): """Test filling form fields.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") form = has_driver_instance.find_element_by_id("test-form") form_data = {"username": "testuser", "password": "testpass", "email": "test@example.com"} has_driver_instance.fill(form, form_data) # Verify fields were filled username = has_driver_instance.find_element_by_id("username") assert username.get_attribute("value") == "testuser" password = has_driver_instance.find_element_by_id("password") assert password.get_attribute("value") == "testpass" email = has_driver_instance.find_element_by_id("email") assert email.get_attribute("value") == "test@example.com" def test_click_submit(self, has_driver_instance, base_url): """Test clicking submit button on form.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") form = has_driver_instance.find_element_by_id("test-form") has_driver_instance.click_submit(form) # Verify form was submitted (result div appears) result = has_driver_instance.wait_for_id("form-result", timeout=2) assert result.text == "Form submitted!" class TestInputValueAbstraction: """Tests for get_input_value abstraction.""" def test_get_input_value_basic(self, has_driver_instance, base_url): """Test getting input value from a text input.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") username_input = has_driver_instance.find_element_by_id("username") # Set a value username_input.send_keys("testuser") # Get the value using the abstraction value = has_driver_instance.get_input_value(username_input) assert value == "testuser" def test_get_input_value_empty(self, has_driver_instance, base_url): """Test getting input value from an empty input.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") username_input = has_driver_instance.find_element_by_id("username") # Get value from empty input value = has_driver_instance.get_input_value(username_input) assert value == "" def test_get_input_value_after_js_modification(self, has_driver_instance, base_url): """Test getting input value after JavaScript modification.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") username_input = has_driver_instance.find_element_by_id("username") # Set value using JavaScript has_driver_instance.set_element_value(username_input, "jsvalue") # Get the value - this is the problematic case for Playwright value = has_driver_instance.get_input_value(username_input) assert value == "jsvalue" def test_get_input_value_password_field(self, has_driver_instance, base_url): """Test getting value from password input.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") password_input = has_driver_instance.find_element_by_id("password") # Set a value password_input.send_keys("secret123") # Get the value value = has_driver_instance.get_input_value(password_input) assert value == "secret123" def test_get_input_value_email_field(self, has_driver_instance, base_url): """Test getting value from email input.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") email_input = has_driver_instance.find_element_by_id("email") # Set a value email_input.send_keys("test@example.com") # Get the value value = has_driver_instance.get_input_value(email_input) assert value == "test@example.com" class TestSelectByValue: """Tests for select_by_value method.""" def test_select_by_value_basic(self, has_driver_instance, base_url): """Test basic select by value functionality.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Select an option by value using Target fruit_select = SimpleTarget(element_locator=(By.CSS_SELECTOR, "#fruit-select"), description="fruit select") has_driver_instance.select_by_value(fruit_select, "banana") # Verify the selection select_element = has_driver_instance.find_element_by_id("fruit-select") selected_value = select_element.get_attribute("value") assert selected_value == "banana" def test_select_by_value_multiple_options(self, has_driver_instance, base_url): """Test selecting different options sequentially.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") select_element = has_driver_instance.find_element_by_id("fruit-select") fruit_select = SimpleTarget(element_locator=(By.CSS_SELECTOR, "#fruit-select"), description="fruit select") # Select first option has_driver_instance.select_by_value(fruit_select, "apple") assert select_element.get_attribute("value") == "apple" # Select a different option has_driver_instance.select_by_value(fruit_select, "cherry") assert select_element.get_attribute("value") == "cherry" # Select another option has_driver_instance.select_by_value(fruit_select, "durian") assert select_element.get_attribute("value") == "durian" def test_select_by_value_with_tuple(self, has_driver_instance, base_url): """Test select by value using a (locator_type, value) tuple.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Select using tuple (By.CSS_SELECTOR, selector) has_driver_instance.select_by_value((By.CSS_SELECTOR, "#fruit-select"), "cherry") # Verify the selection select_element = has_driver_instance.find_element_by_id("fruit-select") assert select_element.get_attribute("value") == "cherry" def test_select_by_value_with_id_tuple(self, has_driver_instance, base_url): """Test select by value using a (By.ID, value) tuple.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Select using tuple (By.ID, id) has_driver_instance.select_by_value((By.ID, "fruit-select"), "durian") # Verify the selection select_element = has_driver_instance.find_element_by_id("fruit-select") assert select_element.get_attribute("value") == "durian" class TestFindElementWithTuple: """Tests for find_element with tuple-based locators.""" def test_find_element_with_css_selector_tuple(self, has_driver_instance, base_url): """Test finding element with (By.CSS_SELECTOR, selector) tuple.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Find element using tuple element = has_driver_instance.find_element((By.CSS_SELECTOR, "#test-div")) assert element.text == "Test Div" def test_find_element_with_id_tuple(self, has_driver_instance, base_url): """Test finding element with (By.ID, id) tuple.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Find element using tuple element = has_driver_instance.find_element((By.ID, "test-div")) assert element.text == "Test Div" def test_find_element_with_xpath_tuple(self, has_driver_instance, base_url): """Test finding element with (By.XPATH, xpath) tuple.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Find element using tuple element = has_driver_instance.find_element((By.XPATH, "//div[@id='test-div']")) assert element.text == "Test Div" class TestActionChainsAndKeys: """Tests for action chains and key sending methods.""" def test_action_chains(self, has_driver_instance, base_url): """Test creating action chains.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") chains = has_driver_instance.action_chains() assert chains is not None def test_drag_and_drop(self, has_driver_instance, base_url): """Test drag and drop functionality.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # TODO: Add actual draggable elements to basic.html for proper testing # For example, add: #
Drag me
#
Drop here
# Then verify with JavaScript that droptarget contains draggable after drag_and_drop # e.g., assert has_driver_instance.execute_script("return document.getElementById('droptarget').contains(document.getElementById('draggable'))") # For now, just verify the method can be called without error source = has_driver_instance.find_element_by_id("test-div") target = has_driver_instance.find_element_by_id("visible-element") # Call drag_and_drop - it should not raise an exception # (even though these aren't actually draggable elements, the JS will execute) has_driver_instance.drag_and_drop(source, target) def test_move_to_and_click(self, has_driver_instance, base_url): """Test moving to element and clicking via ActionChains.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") button = has_driver_instance.find_element_by_id("clickable-button") # Use move_to_and_click has_driver_instance.move_to_and_click(button) # Verify button was clicked assert button.text == "Clicked!" def test_hover(self, has_driver_instance, base_url): """Test hovering over an element.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") hover_target = has_driver_instance.find_element_by_id("hover-target") hover_indicator = has_driver_instance.find_element_by_id("hover-indicator") # Verify indicator is initially hidden assert not hover_indicator.is_displayed() # Hover over the target element has_driver_instance.hover(hover_target) # Verify the hover made the indicator visible (using CSS :hover + sibling selector) assert hover_indicator.is_displayed() def test_send_enter(self, has_driver_instance, base_url): """Test sending ENTER key.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("enter-element") assert not element.is_displayed() element = has_driver_instance.find_element_by_id("username") element.click() has_driver_instance.send_enter(element) element = has_driver_instance.find_element_by_id("enter-element") assert element.is_displayed() def test_send_escape(self, has_driver_instance, base_url): """Test sending ESCAPE key.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("username") element.click() has_driver_instance.send_escape(element) # Key was sent (no exception) def test_send_backspace(self, has_driver_instance, base_url): """Test sending BACKSPACE key.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("username") element.send_keys("test") has_driver_instance.send_backspace(element) # Verify one character was deleted value = has_driver_instance.get_input_value(element) assert value == "tes" def test_aggressive_clear(self, has_driver_instance, base_url): """Test aggressive_clear() method for clearing input fields.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("username") # Type some text element.send_keys("test value that needs clearing") # Use aggressive_clear has_driver_instance.aggressive_clear(element) # Verify the field is cleared assert element.get_attribute("value") == "" def test_fire_mousedown(self, has_driver_instance, base_url): """Test firing mousedown event on an element.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") target = has_driver_instance.find_element_by_id("mousedown-target") indicator = has_driver_instance.find_element_by_id("mousedown-indicator") assert not indicator.is_displayed() has_driver_instance.fire_mousedown(target) indicator = has_driver_instance.find_element_by_id("mousedown-indicator") assert indicator.is_displayed() class TestFrameSwitching: """Tests for frame switching functionality.""" def test_switch_to_frame_by_name(self, has_driver_instance, base_url): """Test switching to iframe by name.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.switch_to_frame("frame") # Verify we're in the frame by finding frame-specific element frame_header = has_driver_instance.find_element_by_id("frame-header") assert frame_header.text == "Inside Frame" # Switch back using new method has_driver_instance.switch_to_default_content() def test_switch_to_frame_by_id(self, has_driver_instance, base_url): """Test switching to iframe by id (frame has name='frame' and id could be different).""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # The frame has name="frame" - the method should work for both name and id has_driver_instance.switch_to_frame("frame") # Verify we're in the frame frame_header = has_driver_instance.find_element_by_id("frame-header") assert frame_header.text == "Inside Frame" # Switch back has_driver_instance.switch_to_default_content() def test_switch_to_frame_by_element(self, has_driver_instance, base_url): """Test switching to iframe by element reference.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Find the frame element first frame_element = has_driver_instance.find_element_by_selector("[name='frame']") # Switch to it by element has_driver_instance.switch_to_frame(frame_element) # Verify we're in the frame frame_header = has_driver_instance.find_element_by_id("frame-header") assert frame_header.text == "Inside Frame" # Switch back has_driver_instance.switch_to_default_content() def test_switch_to_default_content(self, has_driver_instance, base_url): """Test switching back to default content.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Switch to frame has_driver_instance.switch_to_frame("frame") # Verify we're in the frame frame_header = has_driver_instance.find_element_by_id("frame-header") assert frame_header.text == "Inside Frame" # Switch back to default content has_driver_instance.switch_to_default_content() # Verify we're back in main content (frame-header shouldn't be accessible now) header = has_driver_instance.find_element_by_id("header") assert header.text == "Test Page" class TestAlertHandling: """Tests for alert handling.""" def test_accept_alert(self, has_driver_instance, base_url): """Test accepting browser alert using context manager.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Use context manager to handle alert with has_driver_instance.accept_alert(): has_driver_instance.click_selector("#alert-button") # Alert is automatically accepted here # Verify we're back on the main page and can interact with it header = has_driver_instance.find_element_by_id("header") assert header.text == "Test Page" class TestCookieManagement: """Tests for cookie management.""" def test_get_cookies(self, has_driver_instance, base_url): """Test getting all cookies.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Get all cookies (test server sets test_cookie automatically) cookies = has_driver_instance.get_cookies() # Verify cookies is a list assert isinstance(cookies, list) assert len(cookies) > 0 # Verify our test cookie is in the list cookie_names = [cookie["name"] for cookie in cookies] assert "test_cookie" in cookie_names # Find and verify the test cookie value test_cookie = next(cookie for cookie in cookies if cookie["name"] == "test_cookie") assert test_cookie["value"] == "test_value" class TestUtilityMethods: """Tests for utility methods.""" def test_navigate_to(self, has_driver_instance: HasDriverProtocol, base_url: str, request) -> None: """Test navigating to a URL and changing pages.""" # Navigate to first page has_driver_instance.navigate_to(f"{base_url}/basic.html") assert "basic.html" in has_driver_instance.current_url # Verify first page loaded by checking for expected element header = has_driver_instance.find_element_by_id("header") assert header.text == "Test Page" # Navigate to second page has_driver_instance.navigate_to(f"{base_url}/frame.html") assert "frame.html" in has_driver_instance.current_url # Verify we actually navigated to a different page frame_header = has_driver_instance.find_element_by_id("frame-header") assert frame_header.text == "Inside Frame" # Verify the original element is no longer present target = SimpleTarget((By.ID, "header"), "header element") elements = has_driver_instance.find_elements(target) assert len(elements) == 0 def test_re_get_with_query_params_adds_question_mark(self, has_driver_instance, base_url): """Test adding query params to URL without existing params.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.re_get_with_query_params("foo=bar") assert "?foo=bar" in has_driver_instance.current_url def test_re_get_with_query_params_appends_to_existing(self, has_driver_instance, base_url): """Test adding query params to URL with existing params.""" has_driver_instance.navigate_to(f"{base_url}/basic.html?existing=param") has_driver_instance.re_get_with_query_params("foo=bar") current_url = has_driver_instance.current_url assert "existing=param" in current_url assert "foo=bar" in current_url def test_prepend_timeout_message(self, has_driver_instance, request): """Test prepending message to timeout exception.""" backend = request.node.callspec.params.get("has_driver_instance") if backend in ("selenium", "proxy-selenium"): original_selenium_exc = SeleniumTimeoutException(msg="original message") new_selenium_exception = has_driver_instance.prepend_timeout_message(original_selenium_exc, "New prefix:") assert "New prefix:" in new_selenium_exception.msg assert "original message" in new_selenium_exception.msg else: # playwright original_playwright_exc = PlaywrightTimeoutException("original message") new_playwright_exception = has_driver_instance.prepend_timeout_message( original_playwright_exc, "New prefix:" ) assert "New prefix:" in str(new_playwright_exception) assert "original message" in str(new_playwright_exception) class TestJavaScriptExecution: """Tests for JavaScript execution methods.""" def test_execute_script_returns_value(self, has_driver_instance, base_url): """Test executing JavaScript and returning value.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") result = has_driver_instance.execute_script("return 42;") assert result == 42 def test_execute_script_with_arguments(self, has_driver_instance, base_url): """Test executing JavaScript with arguments.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") result = has_driver_instance.execute_script("return arguments[0] + arguments[1];", 10, 20) assert result == 30 def test_execute_script_with_element(self, has_driver_instance, base_url): """Test executing JavaScript with element argument.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("test-div") result = has_driver_instance.execute_script("return arguments[0].textContent;", element) assert result == "Test Div" def test_set_local_storage(self, has_driver_instance, base_url): """Test setting localStorage value.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") has_driver_instance.set_local_storage("testKey", '"testValue"') # Verify localStorage was set result = has_driver_instance.execute_script('return window.localStorage.getItem("testKey");') assert result == "testValue" def test_remove_local_storage(self, has_driver_instance, base_url): """Test removing localStorage value.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # First set a value has_driver_instance.execute_script('window.localStorage.setItem("testKey", "testValue");') # Now remove it has_driver_instance.remove_local_storage("testKey") # Verify it was removed result = has_driver_instance.execute_script('return window.localStorage.getItem("testKey");') assert result is None def test_scroll_into_view(self, has_driver_instance, base_url): """Test scrolling element into view.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("test-div") # Scroll into view (should not raise exception) has_driver_instance.scroll_into_view(element) # Verify element is visible assert element.is_displayed() def test_set_element_value(self, has_driver_instance, base_url): """Test setting element value directly.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") input_element = has_driver_instance.find_element_by_id("username") # Set value using JavaScript has_driver_instance.set_element_value(input_element, "newvalue") # Verify value was set using the abstraction value = has_driver_instance.get_input_value(input_element) assert value == "newvalue" def test_execute_script_click(self, has_driver_instance, base_url): """Test clicking element via JavaScript.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") button = has_driver_instance.find_element_by_id("clickable-button") # Click using JavaScript has_driver_instance.execute_script_click(button) # Verify button was clicked assert button.text == "Clicked!" class TestExceptionHelpers: """Tests for exception helper functions.""" def test_exception_indicates_click_intercepted(self): """Test detecting click intercepted exceptions.""" exc = Exception("Element click intercepted") assert exception_indicates_click_intercepted(exc) exc = Exception("Something else") assert not exception_indicates_click_intercepted(exc) def test_exception_indicates_not_clickable(self): """Test detecting not clickable exceptions.""" exc = Exception("Element is not clickable") assert exception_indicates_not_clickable(exc) exc = Exception("Something else") assert not exception_indicates_not_clickable(exc) def test_exception_indicates_stale_element(self): """Test detecting stale element exceptions.""" exc = Exception("stale element reference") assert exception_indicates_stale_element(exc) exc = Exception("Something else") assert not exception_indicates_stale_element(exc) class TestScreenshots: """Tests for screenshot functionality.""" def test_save_screenshot(self, has_driver_instance, base_url, tmp_path): """Test saving a screenshot to a file.""" import os has_driver_instance.navigate_to(f"{base_url}/basic.html") # Save screenshot to temp file screenshot_path = str(tmp_path / "test_screenshot.png") has_driver_instance.save_screenshot(screenshot_path) # Verify file was created assert os.path.exists(screenshot_path) # Verify it's a PNG file (check magic bytes) with open(screenshot_path, "rb") as f: header = f.read(8) # PNG magic bytes: 89 50 4E 47 0D 0A 1A 0A assert header == b"\x89PNG\r\n\x1a\n" # Verify file has content assert os.path.getsize(screenshot_path) > 0 def test_get_screenshot_as_png(self, has_driver_instance, base_url): """Test getting a screenshot as PNG bytes.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") # Get screenshot as bytes screenshot_bytes = has_driver_instance.get_screenshot_as_png() # Verify it's bytes assert isinstance(screenshot_bytes, bytes) # Verify it's non-empty assert len(screenshot_bytes) > 0 # Verify it's a PNG file (check magic bytes) # PNG magic bytes: 89 50 4E 47 0D 0A 1A 0A assert screenshot_bytes[:8] == b"\x89PNG\r\n\x1a\n" class TestAccessibility: """Tests for axe_eval accessibility testing.""" def test_axe_eval_basic(self, has_driver_instance, base_url): """Test basic axe_eval functionality returns results.""" has_driver_instance.navigate_to(f"{base_url}/accessibility.html") results = has_driver_instance.axe_eval() # Should have some violations in our test page violations = results.violations() assert len(violations) > 0 def test_axe_eval_with_context(self, has_driver_instance, base_url): """Test axe_eval with context parameter to limit scope.""" has_driver_instance.navigate_to(f"{base_url}/accessibility.html") # Run axe on just the good section results = has_driver_instance.axe_eval(context="#good-section") # Good section should have fewer/no violations violations = results.violations() # We expect fewer violations when scoped to good section assert isinstance(violations, list) def test_axe_eval_assert_passes(self, has_driver_instance, base_url): """Test axe_eval assert_passes for specific rules.""" has_driver_instance.navigate_to(f"{base_url}/accessibility.html") results = has_driver_instance.axe_eval() # Test that we can check if a specific rule passed # document-title should pass on our test page (we have a element) results.assert_passes("document-title") def test_axe_eval_assert_does_not_violate(self, has_driver_instance, base_url): """Test axe_eval assert_does_not_violate for specific rules.""" has_driver_instance.navigate_to(f"{base_url}/accessibility.html") results = has_driver_instance.axe_eval(context="#good-section") # Good section should not violate certain rules # This is a sanity check - if this fails, our test fixture needs adjustment try: results.assert_does_not_violate("aria-roles") except AssertionError: pytest.fail("Good section should not have aria-roles violations") def test_axe_eval_violations_with_impact(self, has_driver_instance, base_url): """Test filtering violations by impact level.""" has_driver_instance.navigate_to(f"{base_url}/accessibility.html") results = has_driver_instance.axe_eval() # Get violations with at least moderate impact moderate_violations = results.violations_with_impact_of_at_least("moderate") all_violations = results.violations() # moderate+ violations should be subset of all violations assert len(moderate_violations) <= len(all_violations) assert isinstance(moderate_violations, list) def test_axe_eval_with_axe_skip(self, driver, base_url): """Test that axe_eval returns NullAxeResults when axe_skip is True.""" # Create a test instance with axe_skip=True class TestHasDriverSkipAxe(TestHasDriverImpl): axe_skip = True instance = TestHasDriverSkipAxe(driver) instance.navigate_to(f"{base_url}/accessibility.html") results = instance.axe_eval() # Should return NullAxeResults which has no violations violations = results.violations() assert len(violations) == 0 # All assertions should pass silently with NullAxeResults results.assert_passes("any-rule") results.assert_does_not_violate("any-rule") results.assert_no_violations_with_impact_of_at_least("critical") class TestPageSource: """Test page_source property.""" def test_page_source_contains_content(self, has_driver_instance, base_url): """Test that page_source contains actual page content.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") source = has_driver_instance.page_source # Should contain the test div with known ID assert 'id="test-div"' in source # Should contain some visible text content assert "Test Div" in source def test_page_source_updates_after_navigation(self, has_driver_instance, base_url): """Test that page_source updates when navigating to different pages.""" # Navigate to first page has_driver_instance.navigate_to(f"{base_url}/basic.html") source1 = has_driver_instance.page_source # Navigate to different page has_driver_instance.navigate_to(f"{base_url}/accessibility.html") source2 = has_driver_instance.page_source # Sources should be different assert source1 != source2 # Second source should contain content from accessibility page assert "good-section" in source2 or "bad-section" in source2 class TestPageTitle: """Test page_title property.""" def test_page_title_basic(self, has_driver_instance, base_url): """Test that page_title returns the page title.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") title = has_driver_instance.page_title # Should match the title from basic.html assert title == "Basic Test Page" def test_page_title_updates_after_navigation(self, has_driver_instance, base_url): """Test that page_title updates when navigating to different pages.""" # Navigate to first page has_driver_instance.navigate_to(f"{base_url}/basic.html") title1 = has_driver_instance.page_title # Navigate to different page has_driver_instance.navigate_to(f"{base_url}/accessibility.html") title2 = has_driver_instance.page_title # Titles should be different assert title1 != title2 # Verify titles match expected values assert title1 == "Basic Test Page" assert title2 == "Accessibility Test Page" class TestCSSProperties: """Test CSS property retrieval via value_of_css_property.""" def test_value_of_css_property_display(self, has_driver_instance, base_url): """Test getting the display CSS property of a visible element.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("visible-element") # Get the display property display_value = element.value_of_css_property("display") # Should be a valid display value (not "none" for visible element) assert display_value != "" assert display_value != "none" def test_value_of_css_property_color(self, has_driver_instance, base_url): """Test getting the color CSS property.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("test-div") # Get the color property color_value = element.value_of_css_property("color") # Should return some color value (format may vary between browsers) assert color_value != "" # Color could be in various formats: rgb(), rgba(), hex, or named color assert isinstance(color_value, str) def test_value_of_css_property_hidden_element(self, has_driver_instance, base_url): """Test getting CSS property of a hidden element.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("hidden-element") # Get the display property of hidden element display_value = element.value_of_css_property("display") # Hidden element should have display: none assert display_value == "none" def test_value_of_css_property_font_size(self, has_driver_instance, base_url): """Test getting the font-size CSS property.""" has_driver_instance.navigate_to(f"{base_url}/basic.html") element = has_driver_instance.find_element_by_id("header") # Get the font-size property font_size = element.value_of_css_property("font-size") # Should return a font size (typically in px) assert font_size != "" # Font size should contain numeric value assert any(char.isdigit() for char in font_size)