How CSP, HSTS, and X-Frame-Options become a fixed test assertion in the existing E2E suite, so a silent regression gets caught immediately
An HTTP security header such as the Content Security Policy or the X-Frame-Options header usually gets carefully configured once, often as part of a security audit, and then barely gets touched again. This exact calm turns into a trap the moment a later web server refactor, a new CDN, or a changed Nginx configuration accidentally shortens, removes, or misconfigures the header, since normal storefront operation gives nobody an immediate signal that a protection mechanism has silently vanished. Adding a fixed assertion for the most important security headers to the existing end-to-end test suite instead lets such a regression surface at the very next test run, long before it turns into a real security problem in production.
Table of Contents
- 1. Why security headers disappear silently without a test
- 2. Which headers deserve a fixed test assertion
- 3. A practical example with Cypress
- 4. The same approach with Playwright
- 5. Handling CSP in report-only mode
- 6. Accounting for different headers per page type
- 7. Integrating this into the CI pipeline
- 8. Common pitfalls with header tests
- 9. Security header tests at a glance
- 10. Summary
- 11. FAQ
1. Why security headers disappear silently without a test
HTTP security headers usually get configured somewhere far removed from the actual application code, say in the Nginx configuration, a reverse proxy, a CDN, or a middleware maintained separately from the Magento core. This spatial separation between header configuration and application logic means a header can vanish during an infrastructure overhaul, a CDN switch, or a simply forgotten configuration line, without any functional test of the application noticing, since the page itself keeps working perfectly for shoppers, both visually and functionally.
Without an automated check, the only safeguard is a manual glance at the response headers, usually only performed during audits, which in practice means months can pass between two such manual checks during which a missing header stays undetected. An automated test that runs on every deployment, or at least daily in the CI pipeline, closes exactly this gap by reliably re-checking header presence and header content on every run, regardless of whether anyone on the team actively remembers to.
This risk is especially critical for an online store, since customer data, payment information, and sessions directly depend on the protection level these headers provide: a missing or too loosely configured Content Security Policy opens the door to cross-site scripting attacks, while a missing HSTS header makes a downgrade to unencrypted HTTP easier, two scenarios that a simple, repeatable test reliably rules out.
2. Which headers deserve a fixed test assertion
Not every theoretically possible security header needs to be individually checked on every test run, but a small core group has established itself in practice as especially worth checking: the Content Security Policy (CSP) as the central defense against cross-site scripting, the Strict-Transport-Security header (HSTS) against protocol downgrades, X-Frame-Options or the corresponding CSP directive frame-ancestors against clickjacking, and X-Content-Type-Options against MIME sniffing attacks.
For each of these headers, it is worth checking not just plain presence but also the actual directive content, since a present but too loosely configured header offers barely more protection than a missing one. A CSP with the directive script-src * technically fulfills the requirement of setting a CSP header, but practically prevents not a single cross-site scripting attack, which is why the test should check the actual directive, not just the bare presence of the header.
3. A practical example with Cypress
In Cypress, accessing actual response headers works via cy.request(), since a regular cy.visit() call doesn't return the headers directly and merely loads the rendered page. The following test checks several headers on the Magento checkout page as an example, inside one clearly structured test function.
describe('security headers on the checkout page', () => {
it('sets CSP, HSTS, and X-Frame-Options correctly', () => {
cy.request('/checkout').then((response) => {
const headers = response.headers;
expect(headers).to.have.property('content-security-policy');
expect(headers['content-security-policy']).to.include("default-src 'self'");
expect(headers['content-security-policy']).to.not.include('script-src *');
expect(headers).to.have.property('strict-transport-security');
expect(headers['strict-transport-security']).to.include('max-age=');
expect(headers['strict-transport-security']).to.include('includeSubDomains');
expect(headers).to.have.property('x-frame-options');
expect(headers['x-frame-options']).to.eq('SAMEORIGIN');
expect(headers).to.have.property('x-content-type-options');
expect(headers['x-content-type-options']).to.eq('nosniff');
});
});
});
4. The same approach with Playwright
Playwright allows access to response headers both through page.goto(), which returns a response object, and through the standalone request API, without requiring a full browser render, which noticeably speeds up the test when only headers, not visible content, need checking.
import { test, expect } from '@playwright/test';
test('homepage returns expected security headers', async ({ request }) => {
const response = await request.get('/');
const headers = response.headers();
expect(headers['content-security-policy']).toContain("object-src 'none'");
expect(headers['x-frame-options'] ?? headers['content-security-policy']).toBeTruthy();
expect(headers['referrer-policy']).toBe('strict-origin-when-cross-origin');
expect(response.status()).toBe(200);
});
5. Handling CSP in report-only mode
Many teams first roll out a new or tightened Content Security Policy in report-only mode via the Content-Security-Policy-Report-Only header, where violations get logged but not actually blocked, so unwanted side effects can be safely observed in real operation before final activation, without risking functionality for actual shoppers in the meantime.
A test case for this transition phase should clearly distinguish between the two header names and explicitly check, depending on the current project state, which of the two is set, so that the corresponding test gets consciously adjusted once the planned switch from report-only to the actually enforcing header happens, instead of the test suite silently continuing to validate only the looser report-only header even though the header has meanwhile switched to the stricter, enforcing mode.
6. Accounting for different headers per page type
In a Magento store, the actually necessary headers can differ somewhat by page type: an embedded PayPal or Klarna payment page may need deliberately loosened frame-src or connect-src directives in the CSP, while the rest of the store page should be configured considerably more restrictively, meaning a single, blanket header test for the entire domain often fails to correctly reflect actual reality.
A small set of targeted test cases per relevant page type therefore makes more sense, say one dedicated test each for category page, product page, cart, checkout, and customer account, with each test case checking the specific header directives expected for exactly that page type, instead of projecting a single, generic expectation onto the entire store domain that doesn't match the different actual requirements of the individual page types.
7. Integrating this into the CI pipeline
For a missing header to actually surface early, the header tests should run not just locally but as a fixed part of the CI pipeline on every pull request, and additionally regularly against the production environment, since a misconfigured infrastructure change (say to the CDN or load balancer) might happen outside the actual application deployment and would therefore go completely undetected by pure pre-deployment tests.
A separate, daily smoke test against the real production domain, independent of the regular deployment cycle, reliably closes this gap by also catching header changes made by infrastructure teams outside the actual application code, say as part of a CDN configuration change the development team might not directly notice at all.
8. Common pitfalls with header tests
A widespread mistake is running the header test only against the homepage and wrongly concluding that all other pages are equally protected, even though different page types, as described in the previous section, can well have different header configurations, say because an embedded third-party payment page needs a different CSP.
Another common pitfall is an overly strict assertion checking exact string equality for the CSP, even though the order of individual CSP directives or uncritical extra values can occasionally change without the actual protective effect being affected. A more robust test instead deliberately checks whether specific, security-relevant directives are present, instead of comparing the entire string exactly character by character, which would make the test unnecessarily brittle against harmless, functionally irrelevant changes.
9. Security header tests at a glance
The table below summarizes the most important headers and their recommended test depth.
| Header | Protection goal | Recommended test depth |
|---|---|---|
| Content-Security-Policy | Cross-site scripting, data injection | Check directive content, no exact string comparison |
| Strict-Transport-Security | Protocol downgrade to HTTP | Check presence plus a minimum max-age value |
| X-Frame-Options / frame-ancestors | Clickjacking | Check the expected value per page type |
| X-Content-Type-Options | MIME sniffing | Plain presence and value check is sufficient |
| Referrer-Policy | Unwanted data leakage via referrer | Check the expected policy value |
Mironsoft
E2E test strategy, CI integration, and stable test suites
Test suites that actually find bugs instead of just blinking red?
We review existing E2E test suites for flakiness, missing test isolation, and inefficient CI runtimes, then build a test strategy that genuinely creates confidence instead of just checking a box.
Test Audit
Systematically uncovering flaky tests, testing pyramid gaps, and coverage blind spots.
CI Optimization
Building parallel execution, retry strategies, and fast feedback loops.
Cypress/Playwright Setup
Setting up robust E2E suites for Magento frontends from the ground up.
10. Summary
Security Header Tests: The Essentials at a Glance
Core idea
Add security headers as a fixed assertion to the existing E2E suite instead of only checking manually during audits.
Test depth
Check concrete directives, not just the bare presence of a header.
Page type relevance
Different page types like checkout or embedded payment pages need their own test cases.
CI integration
Run tests on every pull request plus daily against production.