1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import modify_settings, override_settings
from django.utils.csp import CSP
from django.utils.translation import gettext as _
from .base import PlaywrightTestCase
# Make unittest ignore frames in this module when reporting failures.
__unittest = True
@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 AdminPlaywrightTestCase(PlaywrightTestCase, StaticLiveServerTestCase):
available_apps = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
]
@classmethod
def setUpClass(cls):
super().setUpClass()
# Track CSP violations via SecurityPolicyViolationEvent.
cls._csp_violations = []
cls._browser_context.expose_function(
"cspviolation",
lambda violation: cls._csp_violations.append(violation),
)
cls._browser_context.add_init_script("""
document.addEventListener('securitypolicyviolation', (e) => {
window.cspviolation({
blockedURI: e.blockedURI,
violatedDirective: e.violatedDirective,
effectiveDirective: e.effectiveDirective,
originalPolicy: e.originalPolicy,
documentURI: e.documentURI,
});
});
""")
def tearDown(self):
try:
# WebKit injects inline style during screenshot(), which triggers
# a style-src-elem CSP violation.
violations = [
violation
for violation in self._csp_violations
if not (
self.browser == "webkit"
and violation["effectiveDirective"] == "style-src-elem"
and violation["blockedURI"] == "inline"
)
]
# Ensure that no CSP violations were logged in the browser.
self.assertEqual(violations, [])
finally:
self._csp_violations.clear()
super().tearDown()
def admin_login(self, username, password, login_url="/admin/"):
"""
Log in to the admin.
"""
self.page.goto(f"{self.live_server_url}{login_url}")
self.page.locator('[name="username"]').fill(username)
self.page.locator('[name="password"]').fill(password)
login_text = _("Log in")
self.page.get_by_role("button", name=login_text).click()
self.page.wait_for_url(f"{self.live_server_url}{login_url}")
def click_and_expect_popup_to_close(self, locator):
"""
Click a button that closes the popup and wait until the popup is
closed.
"""
from playwright.sync_api import Error
popup = locator.page
with popup.expect_event("close"):
try:
locator.click(no_wait_after=True)
except Error:
# The popup may close before Playwright finishes the click.
# See https://github.com/microsoft/playwright/issues/26900
if not popup.is_closed():
raise
def _assertOptionsValues(self, options_selector, values):
options = self.page.locator(options_selector)
if values:
actual_values = []
for option in options.all():
actual_values.append(option.get_attribute("value"))
self.assertEqual(values, actual_values)
else:
# Wait until no options match the selector, as expected after a
# DOM update that clears the <select>.
self.expect(options).to_have_count(0)
def assertSelectOptions(self, selector, values):
"""
Assert that the <SELECT> widget identified by `selector` has the
options with the given `values`.
"""
self._assertOptionsValues("%s > option" % selector, values)
def assertSelectedOptions(self, selector, values):
"""
Assert that the <SELECT> widget identified by `selector` has the
selected options with the given `values`.
"""
self._assertOptionsValues("%s > option:checked" % selector, values)
|