"""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: #