From 7815149d7dde314150e8a787a43a38f8bce92ac7 Mon Sep 17 00:00:00 2001 From: Restioson Date: Wed, 6 Nov 2024 13:08:15 +0200 Subject: [PATCH 1/2] refactor(ci): split Selenium tests into separate job This prepares the ground for Selenium tests to be run in a Matrix strategy with multiple different configurations (with JS, without JS, Firefox, Chrome, etc). --- .github/workflows/testing.yml | 39 +++++++++++++++++++++++++++++- app/general/tests/test_frontend.py | 2 ++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 9590f0b..12dd25b 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -60,7 +60,7 @@ jobs: cp .env.testing app/.env cd app/ mkdir -p static_files - python manage.py test + python manage.py test --exclude-tag=selenium env: DJANGO_SETTINGS_MODULE: app.settings DATABASE_URL: postgres://sadilar:sadilar@localhost:5432/test_db @@ -68,6 +68,43 @@ jobs: run: | cd app/ python manage.py check + selenium: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: sadilar + POSTGRES_PASSWORD: sadilar + POSTGRES_DB: test_db_1 + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: 3.12 + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-test.txt + sudo apt-get install -y gettext + - name: Create logging folder + run: | + sudo mkdir -p /logging + sudo chown runner:runner /logging + - name: Run Selenium Tests + run: | + cp .env.testing app/.env + cd app/ + mkdir -p static_files + python manage.py test --tag=selenium + env: + DJANGO_SETTINGS_MODULE: app.settings + DATABASE_URL: postgres://sadilar:sadilar@localhost:5432/test_db lighthouse: runs-on: ubuntu-latest # operating system your code will run on services: diff --git a/app/general/tests/test_frontend.py b/app/general/tests/test_frontend.py index 97779cd..adf1eac 100644 --- a/app/general/tests/test_frontend.py +++ b/app/general/tests/test_frontend.py @@ -1,4 +1,5 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from django.test import tag from selenium.common import TimeoutException from selenium.webdriver.chrome.webdriver import Options, WebDriver from selenium.webdriver.common.by import By @@ -8,6 +9,7 @@ WAIT_TIMEOUT = 5 +@tag("selenium") class TestFrontend(StaticLiveServerTestCase): @classmethod def setUpClass(cls): From ce0bdf4fe7f620cc9e5e8d9f12b1d59a89372864 Mon Sep 17 00:00:00 2001 From: Restioson Date: Wed, 6 Nov 2024 13:47:21 +0200 Subject: [PATCH 2/2] test(selenium): run tests across multiple {browser, js} combinations By passing the browser type and whether JS is enabled via environment variables, we can run the Selenium frontend tests on all combinations of targetted browsers and JS/no-JS in the CI pipeline. For now, we are not targetting Safari for this, as the GitHub Actions MacOS workers don't suppose `services`, which we use to run the postgres database. This will be left for a follow-up (see issue #145). --- .github/workflows/testing.yml | 10 ++++- Makefile | 2 +- app/general/tests/files/test_js.html | 9 ++++ app/general/tests/test_frontend.py | 61 ++++++++++++++++++++++++---- 4 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 app/general/tests/files/test_js.html diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 12dd25b..aa54b99 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -69,7 +69,13 @@ jobs: cd app/ python manage.py check selenium: - runs-on: ubuntu-latest + strategy: + matrix: + browser-and-os: + - [firefox, ubuntu-latest] + - [chrome, ubuntu-latest] + js: [js-enabled, js-disabled] + runs-on: ${{ matrix.browser-and-os[1] }} services: postgres: image: postgres:16 @@ -101,7 +107,7 @@ jobs: cp .env.testing app/.env cd app/ mkdir -p static_files - python manage.py test --tag=selenium + BROWSER=${{ matrix.browser-and-os[0] }} ENABLE_JS=${{ matrix.js }} python manage.py test --tag=selenium env: DJANGO_SETTINGS_MODULE: app.settings DATABASE_URL: postgres://sadilar:sadilar@localhost:5432/test_db diff --git a/Makefile b/Makefile index b4aa8e6..c0551aa 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,7 @@ create-schema: @docker compose run --rm web python manage.py graph_models -a -o schema/schema.png test: - @docker compose run --rm web python manage.py test $(module) + @docker compose run --rm -e BROWSER -e JS_ENABLED web python manage.py test $(module) ruff-check: @docker compose run --rm web ruff check . diff --git a/app/general/tests/files/test_js.html b/app/general/tests/files/test_js.html new file mode 100644 index 0000000..610a63e --- /dev/null +++ b/app/general/tests/files/test_js.html @@ -0,0 +1,9 @@ + + + + + + + diff --git a/app/general/tests/test_frontend.py b/app/general/tests/test_frontend.py index adf1eac..3b23190 100644 --- a/app/general/tests/test_frontend.py +++ b/app/general/tests/test_frontend.py @@ -1,8 +1,21 @@ +import os + from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.test import tag from selenium.common import TimeoutException -from selenium.webdriver.chrome.webdriver import Options, WebDriver +from selenium.webdriver.chrome.webdriver import ( + Options as ChromeOptions, +) +from selenium.webdriver.chrome.webdriver import ( + WebDriver as ChromeWebDriver, +) from selenium.webdriver.common.by import By +from selenium.webdriver.firefox.webdriver import ( + Options as FirefoxOptions, +) +from selenium.webdriver.firefox.webdriver import ( + WebDriver as FirefoxWebDriver, +) from selenium.webdriver.support.wait import WebDriverWait # Wait timeout in seconds @@ -13,13 +26,39 @@ class TestFrontend(StaticLiveServerTestCase): @classmethod def setUpClass(cls): - opts = Options() - opts.add_argument("--headless") - opts.add_argument("--no-sandbox") - opts.add_argument("--disable-dev-shm-usage") - opts.add_argument("--window-size=1920,1080") - cls.driver = WebDriver(opts) + browser = os.environ.get("BROWSER", "chrome").lower() + cls.js_enabled = os.environ.get("JS_ENABLED", "js-enabled") == "js-enabled" + + print( + f"Running Selenium tests on {browser}. JS is {'enabled' if cls.js_enabled else 'disabled'}." + ) + + if browser == "chrome": + opts = ChromeOptions() + opts.add_argument("--headless=new") + opts.add_argument("--no-sandbox") + opts.add_argument("--disable-dev-shm-usage") + opts.add_argument("--window-size=1920,1080") + + if not cls.js_enabled: + # Taken from https://stackoverflow.com/a/73961818 + prefs = dict() + prefs["webkit.webprefs.javascript_enabled"] = False + prefs["profile.content_settings.exceptions.javascript.*.setting"] = 2 + prefs["profile.default_content_setting_values.javascript"] = 2 + prefs["profile.managed_default_content_settings.javascript"] = 2 + opts.add_experimental_option("prefs", prefs) + opts.add_argument("--disable-javascript") + + cls.driver = ChromeWebDriver(opts) + elif browser == "firefox": + options = FirefoxOptions() + options.add_argument("-headless") + options.set_preference("javascript.enabled", cls.js_enabled) + cls.driver = FirefoxWebDriver(options=options) + cls.driver.implicitly_wait(WAIT_TIMEOUT) + super().setUpClass() @classmethod @@ -27,6 +66,14 @@ def tearDownClass(cls): cls.driver.quit() super().tearDownClass() + # Checks that JS is properly disabled if passed through env var, else check if enabled + def test_js_enabled_or_disabled(self): + test_dir = os.getenv("TESTING_DIR", "/app/general/tests/files") + self.driver.get(f"file://{test_dir}/test_js.html") + self.assertEqual( + len(self.driver.find_elements(By.ID, "js-enabled")), 1 if self.js_enabled else 0 + ) + def test_no_404s(self): # Sanity check in case we ever change the 404 title self.driver.get(f"{self.live_server_url}/blabla404")