const { test, expect } = require('@playwright/test'); const PRODUCTION_URL = 'https://colinknapp.com'; const LOCAL_URL = 'http://localhost:8080'; async function getPageUrl(page) { try { // Try production first await page.goto(PRODUCTION_URL, { timeout: 60000 }); return PRODUCTION_URL; } catch (error) { console.log('Production site not available, falling back to local'); await page.goto(LOCAL_URL, { timeout: 60000 }); return LOCAL_URL; } } test.describe('Security Headers Tests', () => { test('should have all required security headers', async ({ page, request }) => { const url = await getPageUrl(page); console.log(`Running security header tests against ${url}`); // Get headers directly from the main page const response = await request.get(url); const headers = response.headers(); // Check Content Security Policy const csp = headers['content-security-policy']; expect(csp).toBeTruthy(); expect(csp).toContain("default-src 'none'"); expect(csp).toContain("script-src 'self'"); expect(csp).toContain("style-src 'self'"); expect(csp).toContain("img-src 'self' data:"); expect(csp).toContain("font-src 'self' data:"); expect(csp).toContain("connect-src 'self'"); expect(csp).toContain("object-src 'none'"); expect(csp).toContain("frame-ancestors 'none'"); expect(csp).toContain("base-uri 'none'"); expect(csp).toContain("form-action 'none'"); // Check other security headers expect(headers['x-content-type-options']).toBe('nosniff'); expect(headers['x-frame-options']).toBe('DENY'); expect(headers['referrer-policy']).toBe('strict-origin-when-cross-origin'); }); test('should have correct Subresource Integrity (SRI) attributes', async ({ page }) => { const url = await getPageUrl(page); console.log(`Running SRI tests against ${url}`); await page.goto(url, { timeout: 60000 }); await page.waitForLoadState('networkidle'); // Check stylesheet const stylesheet = await page.locator('link[rel="stylesheet"]').first(); const stylesheetIntegrity = await stylesheet.getAttribute('integrity'); const stylesheetCrossorigin = await stylesheet.getAttribute('crossorigin'); expect(stylesheetIntegrity).toBeTruthy(); expect(stylesheetCrossorigin).toBe('anonymous'); // Check script const script = await page.locator('script[src]').first(); const scriptIntegrity = await script.getAttribute('integrity'); const scriptCrossorigin = await script.getAttribute('crossorigin'); expect(scriptIntegrity).toBeTruthy(); expect(scriptCrossorigin).toBe('anonymous'); }); test('should have correct caching headers for static assets', async ({ request }) => { const url = await getPageUrl({ goto: async () => {} }); console.log(`Running caching header tests against ${url}`); const baseUrl = url.replace(/\/$/, ''); // Check styles.css const stylesResponse = await request.get(`${baseUrl}/styles.css`); const stylesCacheControl = stylesResponse.headers()['cache-control']; expect(stylesCacheControl).toContain('public'); expect(stylesCacheControl).toContain('max-age='); // Check theme.js const scriptResponse = await request.get(`${baseUrl}/theme.js`); const scriptCacheControl = scriptResponse.headers()['cache-control']; expect(scriptCacheControl).toContain('public'); expect(scriptCacheControl).toContain('max-age='); }); });