Repository: NationalCentreTruthReconciliation/Secure-Record-Transfer
Pull request #541: Re-instate E2E tests on Pull Request
Author: login: yenaing-oo
Created: 2025-05-01T15:19:35Z | Merged: 2025-05-01T21:45:08Z

Closes https://github.com/NationalCentreTruthReconciliation/Secure-Record-Transfer/issues/532


Diff:
diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml
index 93b811445..d4ff7c72d 100644
--- a/.github/workflows/django-tests.yml
+++ b/.github/workflows/django-tests.yml
@@ -1,4 +1,4 @@
-name: Run Django Tests with pytest
+name: Run Django Unit Tests
 
 on:
   pull_request:
@@ -7,7 +7,7 @@ on:
     branches: [master]
 
 jobs:
-  test:
+  unit-tests:
     runs-on: ubuntu-latest
 
     strategy:
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
new file mode 100644
index 000000000..db1cfb1ad
--- /dev/null
+++ b/.github/workflows/e2e-tests.yml
@@ -0,0 +1,44 @@
+name: Run E2E Tests
+
+on:
+  pull_request:
+    branches: [master]
+  push:
+    branches: [master]
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+jobs:
+  e2e-tests:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        python-version: ["3.10"]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install pipx and Poetry
+        run: |
+          python -m pip install --upgrade pip
+          pip install pipx
+          pipx install poetry
+
+      - name: Install dependencies
+        run: poetry install --extras "dev"
+
+      - name: Setup Chrome for Selenium
+        uses: browser-actions/setup-chrome@latest
+
+      - name: Run E2E tests
+        env:
+          SELENIUM_TESTS_HEADLESS_MODE: True
+        run: poetry run pytest -m e2e
diff --git a/app/app/settings/base.py b/app/app/settings/base.py
index 991abb329..344b8b91a 100644
--- a/app/app/settings/base.py
+++ b/app/app/settings/base.py
@@ -99,7 +99,7 @@
 ]
 
 LOGIN_REDIRECT_URL = "/"
-LOGIN_URL = '/account/login/'
+LOGIN_URL = "/account/login/"
 
 AUTH_USER_MODEL = "recordtransfer.User"
 
@@ -297,4 +297,6 @@
 
 FIXTURE_DIRS = [
     os.path.join(BASE_DIR, "fixtures"),
-]
\ No newline at end of file
+]
+
+SELENIUM_TESTS_HEADLESS_MODE = config("SELENIUM_TESTS_HEADLESS_MODE", default=False, cast=bool)
diff --git a/app/app/settings/test.py b/app/app/settings/test.py
index 93205633f..dedb7fec2 100644
--- a/app/app/settings/test.py
+++ b/app/app/settings/test.py
@@ -103,6 +103,9 @@
     },
 }
 
+# Disable ClamAV scanning in tests
+CLAMAV_ENABLED = False
+
 BASE_DIR = tempfile.mkdtemp()
 MEDIA_ROOT = str(Path(BASE_DIR) / "media")
 TEMP_STORAGE_FOLDER = str(Path(MEDIA_ROOT) / "temp")
diff --git a/app/recordtransfer/tests/e2e/test_e2e.py b/app/recordtransfer/tests/e2e/test_e2e.py
index cb5761398..70d9ff24f 100644
--- a/app/recordtransfer/tests/e2e/test_e2e.py
+++ b/app/recordtransfer/tests/e2e/test_e2e.py
@@ -2,6 +2,7 @@
 from typing import ClassVar
 
 from caais.models import RightsType, SourceRole, SourceType
+from django.conf import settings
 from django.contrib.staticfiles.testing import StaticLiveServerTestCase
 from django.test import tag
 from selenium import webdriver
@@ -85,9 +86,11 @@ def setUp(self) -> None:
         chrome_options = webdriver.ChromeOptions()
         chrome_options.add_argument("--disable-autofill")
         chrome_options.add_argument("--disable-save-password-bubble")
-        # chrome_options.add_argument("--headless")
+        if settings.SELENIUM_TESTS_HEADLESS_MODE:
+            chrome_options.add_argument("--headless")
         chrome_options.add_argument("--no-sandbox")
         chrome_options.add_argument("--disable-dev-shm-usage")
+        chrome_options.add_argument("--guest")
         prefs = {"autofill.profile_enabled": False}
         chrome_options.add_experimental_option("prefs", prefs)
 
diff --git a/docs/settings/index.rst b/docs/settings/index.rst
index cbb53d4da..b4180a2c2 100644
--- a/docs/settings/index.rst
+++ b/docs/settings/index.rst
@@ -78,12 +78,12 @@ USE_DATE_WIDGETS
     If set to True, a date picker widget is used for date fields. If set to False, input text
     fields with an input mask are used instead.
 
-  **.env Example:**
+    **.env Example:**
 
-  ::
+    ::
 
-      #file: .env
-      USE_DATE_WIDGETS=false
+        #file: .env
+        USE_DATE_WIDGETS=false
 
 
 Services
@@ -1483,3 +1483,28 @@ CAAIS_DEFAULT_CREATION_NOTE
 
         # file .env
         CAAIS_DEFAULT_CREATION_NOTE='Defaults filled automatically by application.'
+
+Testing
+-------
+
+SELENIUM_TESTS_HEADLESS_MODE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+    *Controls whether Selenium tests run in headless mode*
+
+    .. table::
+
+        =======  =========
+        Default  Type
+        =======  =========
+        False    boolean
+        =======  =========
+
+    When set to ``True``, Selenium tests will run in headless mode (without a visible browser
+    window). This is useful for CI/CD environments or when running tests in the background. When
+    ``False``, browser windows will be visible during test execution.
+
+    ::
+
+        # file .env
+        SELENIUM_TESTS_HEADLESS_MODE=True
\ No newline at end of file


Reviews:
user: login: danloveg
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T16:01:34Z
user: login: yenaing-oo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:10:47Z
user: login: yenaing-oo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:10:50Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T16:33:48Z
user: login: yenaing-oo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T17:20:04Z
user: login: yenaing-oo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T17:20:17Z
user: login: danloveg
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-01T21:44:51Z

Review comments:
path: .github/workflows/django-tests.yml
user: login: danloveg
body: What do you think about making a separate action that runs these tests? I like the idea of having the end-to-end tests run separately from unit tests. I'm coming at this from a DRY perspective
created_at: 2025-05-01T15:57:22Z
author_association: COLLABORATOR
diff_hunk: @@ -85,7 +85,7 @@ def setUp(self) -> None:
         chrome_options = webdriver.ChromeOptions()
         chrome_options.add_argument("--disable-autofill")
         chrome_options.add_argument("--disable-save-password-bubble")
-        # chrome_options.add_argument("--headless")
+        chrome_options.add_argument("--headless")
path: app/recordtransfer/tests/e2e/test_e2e.py
user: login: danloveg
body: Is there a way to specify this argument conditionally? I like seeing the browser when these tests run locally but having to comment this line out when I want to do that doesn't sound great.

If there's no way to conditionally include this argument with `pytest`, maybe it's possible to conditionally include this argument when some environment variable is set?

```python
if os.getenv("SELENIUM_TESTS_HEADLESS_MODE"):
    chrome_options.add_argument("--headless")
```

Or something like that. Thoughts?
created_at: 2025-05-01T16:01:27Z
author_association: COLLABORATOR
path: .github/workflows/django-tests.yml
user: login: yenaing-oo
body: Even though the current way is more efficient, I agree that it is best to keep the two concerns separate. I'll make a separate action for that.
created_at: 2025-05-01T16:10:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -85,7 +85,7 @@ def setUp(self) -> None:
         chrome_options = webdriver.ChromeOptions()
         chrome_options.add_argument("--disable-autofill")
         chrome_options.add_argument("--disable-save-password-bubble")
-        # chrome_options.add_argument("--headless")
+        chrome_options.add_argument("--headless")
path: app/recordtransfer/tests/e2e/test_e2e.py
user: login: yenaing-oo
body: I think using an environment variable is the cleanest approach, rather than adding a custom flag and parsing it from the pytest command. Thanks for the suggestion.
created_at: 2025-05-01T16:10:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+name: Run E2E Tests
+
+on:
+  pull_request:
+    branches: [master]
+  push:
+    branches: [master]
+  workflow_dispatch:
+
+jobs:
+  e2e-tests:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        python-version: ["3.10"]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v3
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install pipx and Poetry
+        run: |
+          python -m pip install --upgrade pip
+          pip install pipx
+          pipx install poetry
+
+      - name: Install dependencies
+        run: poetry install --extras "dev"
+
+      - name: Setup Chrome for Selenium
+        uses: browser-actions/setup-chrome@latest
+
+      - name: Run E2E tests
+        run: poetry run pytest -k "e2e"
path: .github/workflows/e2e-tests.yml
user: login: github-advanced-security[bot]
body: ## Workflow does not contain permissions

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

[Show more details](https://github.com/NationalCentreTruthReconciliation/Secure-Record-Transfer/security/code-scanning/29)
created_at: 2025-05-01T16:33:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -85,7 +85,7 @@ def setUp(self) -> None:
         chrome_options = webdriver.ChromeOptions()
         chrome_options.add_argument("--disable-autofill")
         chrome_options.add_argument("--disable-save-password-bubble")
-        # chrome_options.add_argument("--headless")
+        chrome_options.add_argument("--headless")
path: app/recordtransfer/tests/e2e/test_e2e.py
user: login: yenaing-oo
body: Addressed by: https://github.com/NationalCentreTruthReconciliation/Secure-Record-Transfer/pull/541/commits/cd000ad881dbc161e344d23db53adb2ea7d6938f
created_at: 2025-05-01T17:20:04Z
author_association: CONTRIBUTOR
path: .github/workflows/django-tests.yml
user: login: yenaing-oo
body: Addressed by: https://github.com/NationalCentreTruthReconciliation/Secure-Record-Transfer/pull/541/commits/6f3e392ea939186828a47e92bccf0f3233078e70
created_at: 2025-05-01T17:20:17Z
author_association: CONTRIBUTOR

PR comments:
user: login: yenaing-oo
created_at: 2025-05-01T15:34:50Z
body: I changed the settings for the Chrome driver to run in headless mode [here](https://github.com/NationalCentreTruthReconciliation/Secure-Record-Transfer/pull/541/commits/01b49bf8bef17acc3690bb0adc503bed39739aaf), which means the browser window will not be visible. This setting required to run our E2E tests with solely a Chrome driver, although this also means we would have to unset this setting during local development if we wish to view the tests being executed on our browser.
author_association: CONTRIBUTOR
user: login: yenaing-oo
created_at: 2025-05-01T17:23:50Z
body: @danloveg 
Could you double-check that the E2E tests aren't flaky? I re-ran that workflow a couple of times to make sure.
author_association: CONTRIBUTOR
user: login: danloveg
created_at: 2025-05-01T18:49:28Z
body: > @danloveg Could you double-check that the E2E tests aren't flaky? I re-ran that workflow a couple of times to make sure.

They don't look to be flaky in the workflow, but they are flaky when I run them locally. For some reason, the file upload takes forever because it's waiting for the server to respond.

![weirdly long wait](https://github.com/user-attachments/assets/63ad0e59-5560-4d62-a051-9476431c3763)

I can look more into why this happens. Uploading the file seems to complete really quick but then it stalls for a good 15 seconds before the file upload looks complete (i.e., the file upload progress indicator gets stuck at 100%)

Also, I got a popup that the password was included in a data breach and the second test got stuck at that popup. Can you create a more secure password for the test user so that popup doesn't appear?
author_association: COLLABORATOR