forked from ietf-tools/datatracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjstest.py
More file actions
179 lines (144 loc) · 7.47 KB
/
jstest.py
File metadata and controls
179 lines (144 loc) · 7.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# Copyright The IETF Trust 2014-2021, All Rights Reserved
# -*- coding: utf-8 -*-
import os
from django.conf import settings
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.urls import reverse as urlreverse
from unittest import skipIf
skip_selenium = False
skip_message = ""
try:
from selenium import webdriver
from selenium.webdriver.firefox.service import Service
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
except ImportError as e:
skip_selenium = True
skip_message = "Skipping selenium tests: %s" % e
from ietf.utils.pipe import pipe
from ietf.utils.test_runner import (
set_template_coverage,
set_url_coverage,
load_and_run_fixtures,
)
executable_name = 'geckodriver'
code, out, err = pipe('{} --version'.format(executable_name))
if code != 0:
skip_selenium = True
skip_message = "Skipping selenium tests: '{}' executable not found.".format(executable_name)
if skip_selenium:
print(" "+skip_message)
def start_web_driver():
service = Service(executable_path=f"/usr/bin/{executable_name}", log_output=f"{executable_name}.log", service_args=['--log-no-truncate'])
options = Options()
options.add_argument("--headless")
os.environ["MOZ_REMOTE_SETTINGS_DEVTOOLS"] = "1"
return webdriver.Firefox(service=service, options=options)
def selenium_enabled():
"""Are Selenium tests enabled?"""
return not skip_selenium
def ifSeleniumEnabled(func):
"""Only run test if Selenium testing is enabled"""
return skipIf(skip_selenium, skip_message)(func)
class IetfSeleniumTestCase(StaticLiveServerTestCase): # pragma: no cover
login_view = 'ietf.ietfauth.views.login'
@classmethod
def setUpClass(cls):
set_template_coverage(False)
set_url_coverage(False)
super().setUpClass()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
set_template_coverage(True)
set_url_coverage(True)
def setUp(self):
super().setUp()
# LiveServerTestCase uses TransactionTestCase which seems to
# somehow interfere with the fixture loading process in
# IetfTestRunner when running multiple tests (the first test
# is fine, in the next ones the fixtures have been wiped) -
# this is no doubt solvable somehow, but until then we simply
# recreate them here
from ietf.person.models import Person
if not Person.objects.exists():
load_and_run_fixtures(verbosity=0)
self.replaced_settings = dict()
if hasattr(settings, 'IDTRACKER_BASE_URL'):
self.replaced_settings['IDTRACKER_BASE_URL'] = settings.IDTRACKER_BASE_URL
settings.IDTRACKER_BASE_URL = self.live_server_url
self.driver = start_web_driver()
self.driver.set_window_size(1024,768)
def tearDown(self):
self.driver.close()
for k, v in self.replaced_settings.items():
setattr(settings, k, v)
super().tearDown()
def absreverse(self,*args,**kwargs):
return '%s%s'%(self.live_server_url, urlreverse(*args, **kwargs))
def debug_snapshot(self,filename='debug_this.png'):
self.driver.execute_script("document.body.bgColor = 'white';")
self.driver.save_screenshot(filename)
def login(self, username='plain'):
url = self.absreverse(self.login_view)
password = '%s+password' % username
self.driver.get(url)
self.driver.find_element(By.NAME, 'username').send_keys(username)
self.driver.find_element(By.NAME, 'password').send_keys(password)
self.driver.find_element(By.XPATH, '//*[@id="content"]//button[@type="submit"]').click()
def scroll_to_element(self, element):
"""Scroll an element into view"""
# Compute the offset to put the element in the center of the window
win_height = self.driver.get_window_rect()['height']
offset = element.rect['y'] + (element.rect['height'] - win_height) // 2
self.driver.execute_script(
'window.scroll({top: arguments[0], behavior: "instant"})',
offset,
)
# The ActionChains approach below seems to be fragile, hence he JS above.
# actions = ActionChains(self.driver)
# 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:
"""Wait for presence of a child of a WebElement matching a CSS selector
This is a condition class for use with WebDriverWait.
"""
def __init__(self, element, child_selector):
self.element = element
self.child_selector = child_selector
def __call__(self, driver):
child = self.element.find_element(By.CSS_SELECTOR, self.child_selector)
return child if child is not None else False