fix: Selenium tests via scroll_and_click (#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
This commit is contained in:
Matthew Holloway 2024-11-05 15:03:21 +00:00 committed by GitHub
parent d530e9aaba
commit de494790b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 52 additions and 10 deletions

View file

@ -249,7 +249,9 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
self.assertTrue(s1_element.is_displayed()) # should still be displayed self.assertTrue(s1_element.is_displayed()) # should still be displayed
self.assertIn('hidden-parent', s1_element.get_attribute('class'), self.assertIn('hidden-parent', s1_element.get_attribute('class'),
'Session should be hidden when parent disabled') 'Session should be hidden when parent disabled')
s1_element.click() # try to select
self.scroll_and_click((By.CSS_SELECTOR, '#session{}'.format(s1.pk)))
self.assertNotIn('selected', s1_element.get_attribute('class'), self.assertNotIn('selected', s1_element.get_attribute('class'),
'Session should not be selectable when parent disabled') 'Session should not be selectable when parent disabled')
@ -299,9 +301,9 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
'Session s1 should have moved to second meeting day') 'Session s1 should have moved to second meeting day')
# swap timeslot column - put session in a differently-timed timeslot # swap timeslot column - put session in a differently-timed timeslot
self.driver.find_element(By.CSS_SELECTOR, self.scroll_and_click((By.CSS_SELECTOR,
'.day .swap-timeslot-col[data-timeslot-pk="{}"]'.format(slot1b.pk) '.day .swap-timeslot-col[data-timeslot-pk="{}"]'.format(slot1b.pk)
).click() # open modal on the second timeslot for room1 )) # open modal on the second timeslot for room1
self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, "#swap-timeslot-col-modal").is_displayed()) self.assertTrue(self.driver.find_element(By.CSS_SELECTOR, "#swap-timeslot-col-modal").is_displayed())
self.driver.find_element(By.CSS_SELECTOR, self.driver.find_element(By.CSS_SELECTOR,
'#swap-timeslot-col-modal input[name="target_timeslot"][value="{}"]'.format(slot4.pk) '#swap-timeslot-col-modal input[name="target_timeslot"][value="{}"]'.format(slot4.pk)
@ -1373,13 +1375,8 @@ class InterimTests(IetfSeleniumTestCase):
self.assertFalse(modal_div.is_displayed()) self.assertFalse(modal_div.is_displayed())
# Click the 'materials' button # Click the 'materials' button
open_modal_button = self.wait.until( open_modal_button_locator = (By.CSS_SELECTOR, '[data-bs-target="#modal-%s"]' % slug)
expected_conditions.element_to_be_clickable( self.scroll_and_click(open_modal_button_locator)
(By.CSS_SELECTOR, '[data-bs-target="#modal-%s"]' % slug)
),
'Modal open button not found or not clickable',
)
open_modal_button.click()
self.wait.until( self.wait.until(
expected_conditions.visibility_of(modal_div), expected_conditions.visibility_of(modal_div),
'Modal did not become visible after clicking open button', 'Modal did not become visible after clicking open button',

View file

@ -598,6 +598,7 @@ TEST_CODE_COVERAGE_EXCLUDE_FILES = [
"ietf/review/import_from_review_tool.py", "ietf/review/import_from_review_tool.py",
"ietf/utils/patch.py", "ietf/utils/patch.py",
"ietf/utils/test_data.py", "ietf/utils/test_data.py",
"ietf/utils/jstest.py",
] ]
# These are code line regex patterns # These are code line regex patterns

View file

@ -12,6 +12,8 @@ try:
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.firefox.service import Service from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
except ImportError as e: except ImportError as e:
skip_selenium = True skip_selenium = True
@ -87,6 +89,48 @@ class IetfSeleniumTestCase(IetfLiveServerTestCase):
# actions = ActionChains(self.driver) # actions = ActionChains(self.driver)
# actions.move_to_element(element).perform() # actions.move_to_element(element).perform()
def scroll_and_click(self, element_locator, timeout_seconds=5):
"""
Selenium has restrictions around clicking elements outside the viewport, so
this wrapper encapsulates the boilerplate of forcing scrolling and clicking.
:param element_locator: A two item tuple of a Selenium locator eg `(By.CSS_SELECTOR, '#something')`
"""
# so that we can restore the state of the webpage after clicking
original_html_scroll_behaviour_to_restore = self.driver.execute_script('return document.documentElement.style.scrollBehavior')
original_html_overflow_to_restore = self.driver.execute_script('return document.documentElement.style.overflow')
original_body_scroll_behaviour_to_restore = self.driver.execute_script('return document.body.style.scrollBehavior')
original_body_overflow_to_restore = self.driver.execute_script('return document.body.style.overflow')
self.driver.execute_script('document.documentElement.style.scrollBehavior = "auto"')
self.driver.execute_script('document.documentElement.style.overflow = "auto"')
self.driver.execute_script('document.body.style.scrollBehavior = "auto"')
self.driver.execute_script('document.body.style.overflow = "auto"')
element = self.driver.find_element(element_locator[0], element_locator[1])
self.scroll_to_element(element)
# Note that Selenium itself seems to have multiple definitions of 'clickable'.
# You might expect that the following wait for the 'element_to_be_clickable'
# would confirm that the following .click() would succeed but it doesn't.
# That's why the preceeding code attempts to force scrolling to bring the
# element into the viewport to allow clicking.
WebDriverWait(self.driver, timeout_seconds).until(expected_conditions.element_to_be_clickable(element_locator))
element.click()
if original_html_scroll_behaviour_to_restore:
self.driver.execute_script(f'document.documentElement.style.scrollBehavior = "{original_html_scroll_behaviour_to_restore}"')
if original_html_overflow_to_restore:
self.driver.execute_script(f'document.documentElement.style.overflow = "{original_html_overflow_to_restore}"')
if original_body_scroll_behaviour_to_restore:
self.driver.execute_script(f'document.body.style.scrollBehavior = "{original_body_scroll_behaviour_to_restore}"')
if original_body_overflow_to_restore:
self.driver.execute_script(f'document.body.style.overflow = "{original_body_overflow_to_restore}"')
class presence_of_element_child_by_css_selector: class presence_of_element_child_by_css_selector:
"""Wait for presence of a child of a WebElement matching a CSS selector """Wait for presence of a child of a WebElement matching a CSS selector