|
12 | 12 | from selenium import webdriver |
13 | 13 | from selenium.webdriver.firefox.service import Service |
14 | 14 | from selenium.webdriver.firefox.options import Options |
| 15 | + from selenium.webdriver.support.ui import WebDriverWait |
| 16 | + from selenium.webdriver.support import expected_conditions |
15 | 17 | from selenium.webdriver.common.by import By |
16 | 18 | except ImportError as e: |
17 | 19 | skip_selenium = True |
@@ -87,6 +89,48 @@ def scroll_to_element(self, element): |
87 | 89 | # actions = ActionChains(self.driver) |
88 | 90 | # actions.move_to_element(element).perform() |
89 | 91 |
|
| 92 | + def scroll_and_click(self, element_locator, timeout_seconds=5): |
| 93 | + """ |
| 94 | + Selenium has restrictions around clicking elements outside the viewport, so |
| 95 | + this wrapper encapsulates the boilerplate of forcing scrolling and clicking. |
| 96 | +
|
| 97 | + :param element_locator: A two item tuple of a Selenium locator eg `(By.CSS_SELECTOR, '#something')` |
| 98 | + """ |
| 99 | + |
| 100 | + # so that we can restore the state of the webpage after clicking |
| 101 | + original_html_scroll_behaviour_to_restore = self.driver.execute_script('return document.documentElement.style.scrollBehavior') |
| 102 | + original_html_overflow_to_restore = self.driver.execute_script('return document.documentElement.style.overflow') |
| 103 | + |
| 104 | + original_body_scroll_behaviour_to_restore = self.driver.execute_script('return document.body.style.scrollBehavior') |
| 105 | + original_body_overflow_to_restore = self.driver.execute_script('return document.body.style.overflow') |
| 106 | + |
| 107 | + self.driver.execute_script('document.documentElement.style.scrollBehavior = "auto"') |
| 108 | + self.driver.execute_script('document.documentElement.style.overflow = "auto"') |
| 109 | + |
| 110 | + self.driver.execute_script('document.body.style.scrollBehavior = "auto"') |
| 111 | + self.driver.execute_script('document.body.style.overflow = "auto"') |
| 112 | + |
| 113 | + element = self.driver.find_element(element_locator[0], element_locator[1]) |
| 114 | + self.scroll_to_element(element) |
| 115 | + |
| 116 | + # Note that Selenium itself seems to have multiple definitions of 'clickable'. |
| 117 | + # You might expect that the following wait for the 'element_to_be_clickable' |
| 118 | + # would confirm that the following .click() would succeed but it doesn't. |
| 119 | + # That's why the preceeding code attempts to force scrolling to bring the |
| 120 | + # element into the viewport to allow clicking. |
| 121 | + WebDriverWait(self.driver, timeout_seconds).until(expected_conditions.element_to_be_clickable(element_locator)) |
| 122 | + |
| 123 | + element.click() |
| 124 | + |
| 125 | + if original_html_scroll_behaviour_to_restore: |
| 126 | + self.driver.execute_script(f'document.documentElement.style.scrollBehavior = "{original_html_scroll_behaviour_to_restore}"') |
| 127 | + if original_html_overflow_to_restore: |
| 128 | + self.driver.execute_script(f'document.documentElement.style.overflow = "{original_html_overflow_to_restore}"') |
| 129 | + |
| 130 | + if original_body_scroll_behaviour_to_restore: |
| 131 | + self.driver.execute_script(f'document.body.style.scrollBehavior = "{original_body_scroll_behaviour_to_restore}"') |
| 132 | + if original_body_overflow_to_restore: |
| 133 | + self.driver.execute_script(f'document.body.style.overflow = "{original_body_overflow_to_restore}"') |
90 | 134 |
|
91 | 135 | class presence_of_element_child_by_css_selector: |
92 | 136 | """Wait for presence of a child of a WebElement matching a CSS selector |
|
0 commit comments