From ff0ff98d427982b7225df59f454a86bdf66251d6 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 25 Jun 2025 17:22:46 -0300 Subject: [PATCH] Refs #15727 -- Updated AdminSeleniumTestCase to use ContentSecurityPolicyMiddleware. Replaced the custom CSP middleware previously used in the admin's AdminSeleniumTestCase with the official ContentSecurityPolicyMiddleware. This change ensures alignment with Django's built-in CSP support. Also updates the test logic to inspect browser console logs to assert that no CSP violations are triggered during Selenium admin tests. --- django/contrib/admin/tests.py | 30 +++++++++++++++++++----------- django/test/selenium.py | 19 ++++++++++++++++++- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py index f64a4c47f8..3982142330 100644 --- a/django/contrib/admin/tests.py +++ b/django/contrib/admin/tests.py @@ -1,24 +1,27 @@ from contextlib import contextmanager from django.contrib.staticfiles.testing import StaticLiveServerTestCase -from django.test import modify_settings +from django.test import modify_settings, override_settings from django.test.selenium import SeleniumTestCase -from django.utils.deprecation import MiddlewareMixin +from django.utils.csp import CSP from django.utils.translation import gettext as _ # Make unittest ignore frames in this module when reporting failures. __unittest = True -class CSPMiddleware(MiddlewareMixin): - """The admin's JavaScript should be compatible with CSP.""" - - def process_response(self, request, response): - response.headers["Content-Security-Policy"] = "default-src 'self'" - return response - - -@modify_settings(MIDDLEWARE={"append": "django.contrib.admin.tests.CSPMiddleware"}) +@modify_settings( + MIDDLEWARE={"append": "django.middleware.csp.ContentSecurityPolicyMiddleware"} +) +@override_settings( + SECURE_CSP={ + "default-src": [CSP.NONE], + "connect-src": [CSP.SELF], + "img-src": [CSP.SELF], + "script-src": [CSP.SELF], + "style-src": [CSP.SELF], + }, +) class AdminSeleniumTestCase(SeleniumTestCase, StaticLiveServerTestCase): available_apps = [ "django.contrib.admin", @@ -28,6 +31,11 @@ class AdminSeleniumTestCase(SeleniumTestCase, StaticLiveServerTestCase): "django.contrib.sites", ] + def tearDown(self): + # Ensure that no CSP violations were logged in the browser. + self.assertEqual(self.get_browser_logs(source="security"), []) + super().tearDown() + def wait_until(self, callback, timeout=10): """ Block the execution of the tests until the specified callback returns a diff --git a/django/test/selenium.py b/django/test/selenium.py index 15ee3002ec..be8f4a815f 100644 --- a/django/test/selenium.py +++ b/django/test/selenium.py @@ -77,7 +77,11 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)): def get_capability(cls, browser): from selenium.webdriver.common.desired_capabilities import DesiredCapabilities - return getattr(DesiredCapabilities, browser.upper()) + caps = getattr(DesiredCapabilities, browser.upper()) + if browser == "chrome": + caps["goog:loggingPrefs"] = {"browser": "ALL"} + + return caps def create_options(self): options = self.import_options(self.browser)() @@ -237,6 +241,19 @@ class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase): path.parent.mkdir(exist_ok=True, parents=True) self.selenium.save_screenshot(path) + def get_browser_logs(self, source=None, level="ALL"): + """Return Chrome console logs filtered by level and optionally source.""" + try: + logs = self.selenium.get_log("browser") + except AttributeError: + logs = [] + return [ + log + for log in logs + if (level == "ALL" or log["level"] == level) + and (source is None or log["source"] == source) + ] + @classmethod def _quit_selenium(cls): # quit() the WebDriver before attempting to terminate and join the