Skip to content

Commit 5ff3953

Browse files
committed
test: catch new CSP violations before they land
1 parent a06d5fc commit 5ff3953

File tree

2 files changed

+41
-1
lines changed

2 files changed

+41
-1
lines changed

test/e2e/security-headers.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,15 @@ test.describe('security headers', () => {
1818

1919
expect(headers['content-security-policy']).toBeFalsy()
2020
})
21+
22+
// Navigate key pages and assert no CSP violations are logged.
23+
// This catches new external resources that weren't added to the CSP.
24+
const PAGES = ['/', '/package/nuxt', '/search?q=vue', '/compare'] as const
25+
26+
for (const path of PAGES) {
27+
test(`no CSP violations on ${path}`, async ({ goto, cspViolations }) => {
28+
await goto(path, { waitUntil: 'hydration' })
29+
expect(cspViolations).toEqual([])
30+
})
31+
}
2132
})

test/e2e/test-utils.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ function isHydrationMismatch(message: ConsoleMessage): boolean {
7474
return HYDRATION_MISMATCH_PATTERNS.some(pattern => text.includes(pattern))
7575
}
7676

77+
/**
78+
* Detect Content-Security-Policy violations logged to the console.
79+
*
80+
* Browsers log CSP violations as console errors with a distinctive prefix.
81+
* Catching these in e2e tests ensures new external resources are added to the
82+
* CSP before they land in production.
83+
*/
84+
function isCspViolation(message: ConsoleMessage): boolean {
85+
if (message.type() !== 'error') return false
86+
const text = message.text()
87+
return text.includes('Content-Security-Policy') || text.includes('content security policy')
88+
}
89+
7790
/**
7891
* Extended test fixture with automatic external API mocking and hydration mismatch detection.
7992
*
@@ -83,7 +96,11 @@ function isHydrationMismatch(message: ConsoleMessage): boolean {
8396
* Hydration mismatches are detected via Vue's console.error output, which is always
8497
* emitted in production builds when server-rendered HTML doesn't match client expectations.
8598
*/
86-
export const test = base.extend<{ mockExternalApis: void; hydrationErrors: string[] }>({
99+
export const test = base.extend<{
100+
mockExternalApis: void
101+
hydrationErrors: string[]
102+
cspViolations: string[]
103+
}>({
87104
mockExternalApis: [
88105
async ({ page }, use) => {
89106
await setupRouteMocking(page)
@@ -103,6 +120,18 @@ export const test = base.extend<{ mockExternalApis: void; hydrationErrors: strin
103120

104121
await use(errors)
105122
},
123+
124+
cspViolations: async ({ page }, use) => {
125+
const violations: string[] = []
126+
127+
page.on('console', message => {
128+
if (isCspViolation(message)) {
129+
violations.push(message.text())
130+
}
131+
})
132+
133+
await use(violations)
134+
},
106135
})
107136

108137
export { expect }

0 commit comments

Comments
 (0)