Skip to content

Commit c1f87fd

Browse files
authored
[fix:tests] Fixed flaky selenium tests
* [tests] Use new helpers from openwisp-utils * [ci] Show geckolog on failures * [fix] Fixed invalid HTML in template partial chart.html * [ci] Skip selenium tests in sample app and udp testing
1 parent ae44b12 commit c1f87fd

File tree

5 files changed

+76
-132
lines changed

5 files changed

+76
-132
lines changed

.github/workflows/ci.yml

+11-2
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,19 @@ jobs:
9393
if: ${{ !cancelled() && steps.deps.conclusion == 'success' }}
9494
run: |
9595
coverage run runtests.py
96-
SAMPLE_APP=1 coverage run runtests.py
97-
TIMESERIES_UDP=1 coverage run runtests.py
96+
# the next test runs focus on specific backend features, therefore
97+
# we can skip selenium tests to improve speed and test consistency
98+
SAMPLE_APP=1 coverage run runtests.py --exclude-tag=selenium_tests
99+
TIMESERIES_UDP=1 coverage run runtests.py --exclude-tag=selenium_tests
98100
coverage combine
99101
coverage xml
102+
env:
103+
SELENIUM_HEADLESS: 1
104+
GECKO_LOG: 1
105+
106+
- name: Show gecko web driver log on failures
107+
if: ${{ failure() }}
108+
run: cat geckodriver.log
100109

101110
- name: Upload Coverage
102111
if: ${{ success() }}

.jshintrc

-20
This file was deleted.

docs/developer/installation.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ Launch development server:
100100
101101
You can access the admin interface at ``http://127.0.0.1:8000/admin/``.
102102

103-
Run tests with:
103+
Run tests with (make sure you have the :ref:`selenium dependencies
104+
<selenium_dependencies>` installed locally first):
104105

105106
.. code-block:: shell
106107

openwisp_monitoring/monitoring/templates/monitoring/paritals/chart.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</a>
1313
<a id="ow-chart-export" class="button export">{% trans 'export data' %}</a>
1414
</span>
15-
<span style="display:none";>
15+
<span style="display:none">
1616
<select name="org-selector" id="org-selector">
1717
<option></option>
1818
</select>
+62-108
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from time import sleep
12
from unittest.mock import patch
23

34
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
5+
from django.test import tag
46
from django.urls.base import reverse
57
from reversion.models import Version
6-
from selenium.common.exceptions import TimeoutException, UnexpectedAlertPresentException
8+
from selenium.common.exceptions import TimeoutException
79
from selenium.webdriver.common.by import By
810
from selenium.webdriver.support import expected_conditions as EC
911
from selenium.webdriver.support.ui import WebDriverWait
@@ -17,9 +19,7 @@
1719
from openwisp_monitoring.monitoring.configuration import DEFAULT_DASHBOARD_TRAFFIC_CHART
1820
from openwisp_monitoring.monitoring.migrations import create_general_metrics
1921
from openwisp_utils.admin_theme.dashboard import DASHBOARD_TEMPLATES
20-
from openwisp_utils.test_selenium_mixins import (
21-
SeleniumTestMixin as BaseSeleniumTestMixin,
22-
)
22+
from openwisp_utils.tests import SeleniumTestMixin as BaseSeleniumTestMixin
2323

2424
Device = load_model('config', 'Device')
2525
DeviceConnection = load_model('connection', 'DeviceConnection')
@@ -68,13 +68,16 @@ def tearDownClass(cls):
6868
DASHBOARD_TEMPLATES[0][1] = cls._dashboard_map_context
6969
DASHBOARD_TEMPLATES[55][1] = cls._dashboard_timeseries_context
7070

71-
def setUp(self):
72-
self.admin = self._create_admin(
73-
username=self.admin_username, password=self.admin_password
74-
)
75-
super().setUp()
71+
def login(self, username=None, password=None, driver=None):
72+
super().login(username, password, driver)
73+
# Workaround for JS logic in chart-utils.js
74+
# which fails to perform a XHR request
75+
# during automated tests, it seems that the
76+
# lack of pause causes the request to fail randomly
77+
sleep(0.5)
7678

7779

80+
@tag('selenium_tests')
7881
class TestDeviceConnectionInlineAdmin(
7982
SeleniumTestMixin,
8083
TestDeviceMonitoringMixin,
@@ -83,25 +86,6 @@ class TestDeviceConnectionInlineAdmin(
8386
):
8487
config_app_label = 'config'
8588

86-
def tearDown(self):
87-
# Accept unsaved changes alert to allow other tests to run
88-
try:
89-
self.web_driver.refresh()
90-
except UnexpectedAlertPresentException:
91-
self.web_driver.switch_to_alert().accept()
92-
else:
93-
try:
94-
WebDriverWait(self.web_driver, 1).until(EC.alert_is_present())
95-
except TimeoutException:
96-
pass
97-
else:
98-
self.web_driver.switch_to_alert().accept()
99-
self.web_driver.refresh()
100-
WebDriverWait(self.web_driver, 2).until(
101-
EC.visibility_of_element_located((By.XPATH, '//*[@id="site-name"]'))
102-
)
103-
super().tearDown()
104-
10589
def test_restoring_deleted_device(self):
10690
org = self._get_org()
10791
self._create_credentials(auto_add=True, organization=org)
@@ -128,24 +112,26 @@ def test_restoring_deleted_device(self):
128112
self.open(
129113
reverse(f'admin:{self.config_app_label}_device_change', args=[device.id])
130114
)
131-
self.web_driver.find_element(
132-
By.XPATH, '//*[@id="device_form"]/div/div[1]/input[1]'
115+
self.hide_loading_overlay()
116+
self.wait_for(
117+
'element_to_be_clickable',
118+
By.XPATH,
119+
'//*[@id="device_form"]/div/div[1]/input[1]',
133120
).click()
134121
try:
135122
WebDriverWait(self.web_driver, 5).until(
136123
EC.url_to_be(f'{self.live_server_url}/admin/config/device/')
137124
)
138125
except TimeoutException:
139126
self.fail('Failed saving device')
140-
141127
# Delete the device
142128
device.deactivate()
143129
device.config.set_status_deactivated()
144130
self.open(
145131
reverse(f'admin:{self.config_app_label}_device_delete', args=[device.id])
146132
)
147-
self.web_driver.find_element(
148-
by=By.CSS_SELECTOR, value='#content form input[type="submit"]'
133+
self.find_element(
134+
By.CSS_SELECTOR, '#content form input[type="submit"]', timeout=5
149135
).click()
150136
self.assertEqual(Device.objects.count(), 0)
151137
self.assertEqual(DeviceConnection.objects.count(), 0)
@@ -160,7 +146,7 @@ def test_restoring_deleted_device(self):
160146
f'admin:{self.config_app_label}_device_recover', args=[version_obj.id]
161147
)
162148
)
163-
self.web_driver.find_element(
149+
self.find_element(
164150
By.XPATH, '//*[@id="device_form"]/div/div[1]/input[1]'
165151
).click()
166152
try:
@@ -181,6 +167,7 @@ def test_restoring_deleted_device(self):
181167
self.assertEqual(Chart.objects.filter(id__in=device_chart_ids).count(), 3)
182168

183169

170+
@tag('selenium_tests')
184171
class TestDashboardCharts(
185172
SeleniumTestMixin, TestDeviceMonitoringMixin, StaticLiveServerTestCase
186173
):
@@ -195,73 +182,43 @@ def setUp(self):
195182
@patch.dict(DEFAULT_DASHBOARD_TRAFFIC_CHART, {'__all__': ['wlan0', 'wlan1']})
196183
def test_dashboard_timeseries_charts(self):
197184
self.login()
198-
try:
199-
WebDriverWait(self.web_driver, 5).until(
200-
EC.visibility_of_element_located(
201-
(By.CSS_SELECTOR, '#ow-chart-inner-container')
202-
)
203-
)
204-
except TimeoutException:
205-
self.fail('Timeseries chart container not found on dashboard')
206-
try:
207-
WebDriverWait(self.web_driver, 5).until(
208-
EC.visibility_of_element_located((By.CSS_SELECTOR, '#ow-chart-utils'))
209-
)
210-
except TimeoutException:
211-
self.fail('Timeseries chart time filter not found on dashboard')
212-
213-
try:
214-
WebDriverWait(self.web_driver, 5).until(
215-
EC.visibility_of_element_located(
216-
(By.CSS_SELECTOR, '#ow-chart-fallback')
217-
)
218-
)
219-
except TimeoutException:
220-
self.fail('Fallback message for charts did not render')
221-
else:
222-
self.assertIn(
223-
'Insufficient data for selected time period.',
224-
self.web_driver.find_element(
225-
By.CSS_SELECTOR, '#ow-chart-fallback'
226-
).get_attribute('innerHTML'),
227-
)
185+
self.wait_for_visibility(
186+
By.CSS_SELECTOR, '#ow-chart-inner-container', timeout=5
187+
)
188+
self.wait_for_visibility(By.CSS_SELECTOR, '#ow-chart-utils', timeout=5)
189+
self.wait_for_visibility(By.CSS_SELECTOR, '#ow-chart-fallback', timeout=5)
190+
self.assertIn(
191+
'Insufficient data for selected time period.',
192+
self.find_element(By.CSS_SELECTOR, '#ow-chart-fallback').get_attribute(
193+
'innerHTML'
194+
),
195+
)
228196
self.create_test_data()
229197
self.web_driver.refresh()
230-
try:
231-
WebDriverWait(self.web_driver, 20).until(
232-
EC.visibility_of_element_located(
233-
(By.CSS_SELECTOR, '#ow-chart-contents')
234-
)
235-
)
236-
WebDriverWait(self.web_driver, 20).until(
237-
EC.visibility_of_element_located((By.CSS_SELECTOR, '#chart-0'))
238-
)
239-
WebDriverWait(self.web_driver, 60).until(
240-
EC.visibility_of_element_located((By.CSS_SELECTOR, '#chart-1'))
241-
)
242-
except TimeoutException:
243-
self.fail('Timeseries charts did not render')
244-
198+
self.wait_for_visibility(By.CSS_SELECTOR, '#ow-chart-contents', timeout=10)
199+
self.wait_for_visibility(By.CSS_SELECTOR, '#chart-0', timeout=10)
200+
self.wait_for_visibility(By.CSS_SELECTOR, '#chart-1', timeout=10)
245201
self.assertIn(
246202
'General WiFi Clients',
247-
self.web_driver.find_element(
248-
By.CSS_SELECTOR, '#chart-0 > h3'
249-
).get_attribute('innerHTML'),
203+
self.find_element(By.CSS_SELECTOR, '#chart-0 > h3').get_attribute(
204+
'innerHTML'
205+
),
250206
)
251207
self.assertIn(
252208
'General Traffic',
253-
self.web_driver.find_element(
254-
By.CSS_SELECTOR, '#chart-1 > h3'
255-
).get_attribute('innerHTML'),
209+
self.find_element(By.CSS_SELECTOR, '#chart-1 > h3').get_attribute(
210+
'innerHTML'
211+
),
256212
)
257213
self.assertIn(
258214
'Open WiFi session list',
259-
self.web_driver.find_element(
215+
self.find_element(
260216
By.CSS_SELECTOR, '#chart-0-quick-link-container'
261217
).get_attribute('innerHTML'),
262218
)
263219

264220

221+
@tag('selenium_tests')
265222
class TestWifiSessionInlineAdmin(
266223
SeleniumTestMixin,
267224
TestWifiClientSessionMixin,
@@ -275,12 +232,9 @@ def test_device_wifi_session_inline_change(self):
275232
self.login()
276233
path = f'admin:{self.config_app_label}_device_change'
277234
self.open(reverse(path, args=[device.pk]))
235+
self.hide_loading_overlay()
278236
# Make sure the wifi session inline doesn't exist
279-
WebDriverWait(self.web_driver, 2).until(
280-
EC.invisibility_of_element_located(
281-
(By.CSS_SELECTOR, '#wifisession_set-group')
282-
)
283-
)
237+
self.wait_for_invisibility(By.CSS_SELECTOR, '#wifisession_set-group')
284238
# We are still on the device change page,
285239
# and now we will create new wifi sessions
286240
ws1 = self._create_wifi_session(device=device)
@@ -289,15 +243,15 @@ def test_device_wifi_session_inline_change(self):
289243
device=device, wifi_client=wc2, ssid='Test Wifi Session'
290244
)
291245
# Now press the 'Save' button on the device change page
292-
self.web_driver.find_element(
246+
self.find_element(
293247
By.XPATH, '//*[@id="device_form"]/div/div[1]/input[3]'
294248
).click()
295249
# Make sure the wifi session tab now
296250
# exists with the correct wifi sessions
297-
wifi_session_inline = self.web_driver.find_element(
298-
By.XPATH, '//*[@id="tabs-container"]/ul/li[7]/a'
299-
)
300-
wifi_session_inline.click()
251+
self.hide_loading_overlay()
252+
self.wait_for(
253+
'element_to_be_clickable', By.XPATH, '//*[@id="tabs-container"]/ul/li[7]/a'
254+
).click()
301255
wifi_session_inline_form_error = (
302256
'ManagementForm data is missing '
303257
'or has been tampered with. Missing fields: '
@@ -307,26 +261,26 @@ def test_device_wifi_session_inline_change(self):
307261
# were encountered after saving
308262
self.assertNotIn(
309263
wifi_session_inline_form_error,
310-
self.web_driver.find_element(
311-
By.CSS_SELECTOR, '#wifisession_set-group'
312-
).get_attribute('innerHTML'),
264+
self.find_element(By.CSS_SELECTOR, '#wifisession_set-group').get_attribute(
265+
'innerHTML'
266+
),
313267
)
314268
# Make sure all wifi sessions are present
315269
self.assertIn(
316270
f'{ws1.ssid}',
317-
self.web_driver.find_element(
318-
By.CSS_SELECTOR, '#wifisession_set-group'
319-
).get_attribute('innerHTML'),
271+
self.find_element(By.CSS_SELECTOR, '#wifisession_set-group').get_attribute(
272+
'innerHTML'
273+
),
320274
)
321275
self.assertIn(
322276
f'{ws2.ssid}',
323-
self.web_driver.find_element(
324-
By.CSS_SELECTOR, '#wifisession_set-group'
325-
).get_attribute('innerHTML'),
277+
self.find_element(By.CSS_SELECTOR, '#wifisession_set-group').get_attribute(
278+
'innerHTML'
279+
),
326280
)
327281
self.assertIn(
328282
'View Full History of WiFi Sessions',
329-
self.web_driver.find_element(
330-
By.CSS_SELECTOR, '#wifisession_set-group'
331-
).get_attribute('innerHTML'),
283+
self.find_element(By.CSS_SELECTOR, '#wifisession_set-group').get_attribute(
284+
'innerHTML'
285+
),
332286
)

0 commit comments

Comments
 (0)