Repository: openwisp/openwisp-controller (language: python)

Issue:
[bug] Flaky Selenium Test: test_unsaved_changes
Flaky test:

```
======================================================================
FAIL: test_unsaved_changes (openwisp_controller.config.tests.test_selenium.TestDeviceAdmin) [Alert should be displayed after making changes]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/config/tests/test_selenium.py", line 152, in test_unsaved_changes
    WebDriverWait(self.web_driver, 1).until(EC.alert_is_present())
  File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/selenium/webdriver/support/wait.py", line 105, in until
    raise TimeoutException(message, screen, stacktrace)
selenium.common.exceptions.TimeoutException: Message: 


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/unittest/case.py", line 60, in testPartExecutor
    yield
  File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/unittest/case.py", line 582, in subTest
    yield
  File "/home/runner/work/openwisp-controller/openwisp-controller/openwisp_controller/config/tests/test_selenium.py", line 154, in test_unsaved_changes
    self.fail('Timed out wating for unsaved changes alert')
  File "/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/unittest/case.py", line 753, in fail
    raise self.failureException(msg)
AssertionError: Timed out wating for unsaved changes alert
```


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 28dfbd168..c94dbedec 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,7 +13,7 @@ on:
 jobs:
   build:
     name: Python==${{ matrix.python-version }} | ${{ matrix.django-version }}
-    runs-on: ubuntu-24.04
+    runs-on: ubuntu-22.04
 
     services:
       redis:
@@ -50,6 +50,22 @@ jobs:
       uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
+        cache: 'pip'
+        cache-dependency-path: |
+          **/requirements*.txt
+
+    - uses: browser-actions/setup-chrome@v1
+      # Using a fixed version, see here for more information on why:
+      # https://github.com/openwisp/openwisp-controller/issues/902#issuecomment-2266219715
+      # TODO: find a solution to allow using recent versions
+      with:
+        chrome-version: 125
+        install-chromedriver: true
+      id: setup-chrome
+
+    - run: |
+        ${{ steps.setup-chrome.outputs.chrome-path }} --version
+        chromedriver --version
 
     - name: Install Dependencies
       id: deps
@@ -80,6 +96,7 @@ jobs:
         coverage xml
       env:
         SELENIUM_HEADLESS: 1
+        CHROME_BIN: ${{ steps.setup-chrome.outputs.chrome-path }}
 
     - name: Upload Coverage
       if: ${{ success() }}


Test patch:
diff --git a/openwisp_controller/config/tests/test_selenium.py b/openwisp_controller/config/tests/test_selenium.py
index 8b83d739f..ec360392b 100644
--- a/openwisp_controller/config/tests/test_selenium.py
+++ b/openwisp_controller/config/tests/test_selenium.py
@@ -144,13 +144,21 @@ def test_unsaved_changes(self):
                 self.fail('Unsaved changes alert displayed without any change')
 
         with self.subTest('Alert should be displayed after making changes'):
+            # simulate hand gestures
+            self.web_driver.find_element(by=By.TAG_NAME, value='body').click()
+            self.web_driver.find_element(by=By.NAME, value='name').click()
+            # set name
             self.web_driver.find_element(by=By.NAME, value='name').send_keys(
                 'new.device.name'
             )
+            # simulate hand gestures
+            self.web_driver.find_element(by=By.TAG_NAME, value='body').click()
             self.web_driver.refresh()
             try:
                 WebDriverWait(self.web_driver, 1).until(EC.alert_is_present())
             except TimeoutException:
+                for entry in self.web_driver.get_log('browser'):
+                    print(entry)
                 self.fail('Timed out wating for unsaved changes alert')
             else:
                 alert = Alert(self.web_driver)