Skip to content

Commit de49479

Browse files
authored
fix: Selenium tests via scroll_and_click (ietf-tools#8150)
* fix: selenium tests scroll_and_click * fix: reduce default timeout to 5 seconds * fix: also use scroll_and_click on test_upcoming_materials_modal * fix: remove conditional check on restoring scroll CSS * fix: restore conditional check on restoring scroll CSS * chore: code comments and adding jstest.py to coverage ignore
1 parent d530e9a commit de49479

3 files changed

Lines changed: 52 additions & 10 deletions

File tree

ietf/meeting/tests_js.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,9 @@ def test_edit_meeting_schedule(self):
249249
self.assertTrue(s1_element.is_displayed()) # should still be displayed
250250
self.assertIn('hidden-parent', s1_element.get_attribute('class'),
251251
'Session should be hidden when parent disabled')
252-
s1_element.click() # try to select
252+
253+
self.scroll_and_click((By.CSS_SELECTOR, '#session{}'.format(s1.pk)))
254+
253255
self.assertNotIn('selected', s1_element.get_attribute('class'),
254256
'Session should not be selectable when parent disabled')
255257

@@ -299,9 +301,9 @@ def test_edit_meeting_schedule(self):
299301
'Session s1 should have moved to second meeting day')
300302

301303
# swap timeslot column - put session in a differently-timed timeslot
302-
self.driver.find_element(By.CSS_SELECTOR,
304+
self.scroll_and_click((By.CSS_SELECTOR,
303305
'.day .swap-timeslot-col[data-timeslot-pk="{}"]'.format(slot1b.pk)
304-
).click() # open modal on the second timeslot for room1
306+
)) # open modal on the second timeslot for room1
305307
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, "#swap-timeslot-col-modal").is_displayed())
306308
self.driver.find_element(By.CSS_SELECTOR,
307309
'#swap-timeslot-col-modal input[name="target_timeslot"][value="{}"]'.format(slot4.pk)
@@ -1373,13 +1375,8 @@ def test_upcoming_materials_modal(self):
13731375
self.assertFalse(modal_div.is_displayed())
13741376

13751377
# Click the 'materials' button
1376-
open_modal_button = self.wait.until(
1377-
expected_conditions.element_to_be_clickable(
1378-
(By.CSS_SELECTOR, '[data-bs-target="#modal-%s"]' % slug)
1379-
),
1380-
'Modal open button not found or not clickable',
1381-
)
1382-
open_modal_button.click()
1378+
open_modal_button_locator = (By.CSS_SELECTOR, '[data-bs-target="#modal-%s"]' % slug)
1379+
self.scroll_and_click(open_modal_button_locator)
13831380
self.wait.until(
13841381
expected_conditions.visibility_of(modal_div),
13851382
'Modal did not become visible after clicking open button',

ietf/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ def skip_unreadable_post(record):
598598
"ietf/review/import_from_review_tool.py",
599599
"ietf/utils/patch.py",
600600
"ietf/utils/test_data.py",
601+
"ietf/utils/jstest.py",
601602
]
602603

603604
# These are code line regex patterns

ietf/utils/jstest.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from selenium import webdriver
1313
from selenium.webdriver.firefox.service import Service
1414
from selenium.webdriver.firefox.options import Options
15+
from selenium.webdriver.support.ui import WebDriverWait
16+
from selenium.webdriver.support import expected_conditions
1517
from selenium.webdriver.common.by import By
1618
except ImportError as e:
1719
skip_selenium = True
@@ -87,6 +89,48 @@ def scroll_to_element(self, element):
8789
# actions = ActionChains(self.driver)
8890
# actions.move_to_element(element).perform()
8991

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}"')
90134

91135
class presence_of_element_child_by_css_selector:
92136
"""Wait for presence of a child of a WebElement matching a CSS selector

0 commit comments

Comments
 (0)