Cypress Security Testing
Add 250+ security checks to your Cypress E2E tests. Detect XSS vectors, CSP issues, insecure cookies, and more - using the same API you already know.
Quick Start with Cypress
1. Install QAstell
npm install qastell cypress
2. Add Security Auditing to Your Test
import { SecurityAuditor } from 'qastell/cypress';
it('security audit', () => {
cy.visit('https://example.com');
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
await auditor.assertNoViolations();
});
});
3. Run Your Test
npx cypress run
Key Difference: Unlike Playwright/Puppeteer which pass a page object, Cypress passes the browser's Window object via cy.window(). QAstell automatically detects this and uses direct DOM access.
Complete Cypress Example
Here's a comprehensive example showing security auditing with Cypress:
import { SecurityAuditor } from 'qastell/cypress';
describe('Security Audit', () => {
it('should pass basic security audit', () => {
cy.visit('https://example.com');
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
const results = await auditor.audit();
// Log summary
cy.log(`Total issues: ${results.summary.total}`);
cy.log(`Critical: ${results.summary.bySeverity.critical}`);
cy.log(`High: ${results.summary.bySeverity.high}`);
// Generate HTML report
const html = results.toHTML();
cy.writeFile('cypress/reports/security-report.html', html);
// Assert no violations
await auditor.assertNoViolations();
});
});
});
Cypress-Specific Features
How It Works
Cypress runs inside the browser, giving QAstell direct DOM access through the Window object. This is actually an advantage - QAstell can inspect the DOM synchronously without network overhead.
// Cypress runs inside the browser
cy.window().then(async (win) => {
// 'win' is the actual browser Window object
// QAstell uses win.document.querySelectorAll() directly
const auditor = new SecurityAuditor(win);
const results = await auditor.audit();
});
Audit After User Interactions
Run security audits after login, form submissions, or navigation:
it('should audit authenticated pages', () => {
cy.visit('https://example.com/login');
// Perform login
cy.get('#email').type('user@example.com');
cy.get('#password').type('password123');
cy.get('button[type="submit"]').click();
// Wait for navigation
cy.url().should('include', '/dashboard');
// Audit the authenticated page
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
await auditor.assertNoViolations();
});
});
Multi-Page Auditing
Audit multiple pages in a single test:
it('should audit multiple pages', () => {
const pages = [
'https://example.com/',
'https://example.com/about',
'https://example.com/contact',
];
pages.forEach((url) => {
cy.visit(url);
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
const results = await auditor.audit();
cy.log(`${url}: ${results.summary.total} issues`);
await auditor.assertNoViolations();
});
});
});
Category Filtering
Focus on specific security categories:
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
// Only check specific categories
const results = await auditor.audit({
include: ['forms', 'cookies', 'inline-handlers'],
});
// Or exclude categories
const results2 = await auditor.audit({
exclude: ['third-party', 'sri'],
});
});
Severity Thresholds
Gradually adopt security scanning:
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
await auditor.assertNoViolations({
thresholds: {
info: 999, // Allow info-level issues
low: 10, // Allow up to 10 low severity
medium: 0, // Fail on medium
high: 0, // Fail on high
critical: 0, // Fail on critical
},
});
});
Integration with Cypress Config
Set up the license globally in your Cypress support file:
// cypress/support/e2e.js (or e2e.ts)
import { initLicense } from 'qastell/cypress';
// Initialize license once when tests start
initLicense(Cypress.env('QASTELL_LICENSE'));
Pass the license key via environment variable in cypress.config.ts:
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// Pass environment variable to Cypress
config.env.QASTELL_LICENSE = process.env.QASTELL_LICENSE;
return config;
},
},
});
Or set it directly in your CI environment:
# Run Cypress with license
CYPRESS_QASTELL_LICENSE="your-key" npx cypress run
Force Framework Detection
If you're using custom window wrappers and auto-detection fails, you can force Cypress mode:
const auditor = new SecurityAuditor(win, { framework: 'cypress' });
// Verify the detected framework
console.log(auditor.getFramework()); // 'cypress'
Limitations
Note: Due to Cypress's architecture (running inside the browser), some features available in Playwright/Puppeteer have limitations:
- HTTP Response Headers - Not available. Use
cy.intercept()to capture headers if needed. - Cookies - Only non-HttpOnly cookies are visible via
document.cookie. For full cookie inspection, usecy.getCookies()separately.
These limitations affect approximately 5 header-based rules. All DOM-based security checks (245+ rules) work identically to other frameworks.
Workaround: Full Cookie Inspection with cy.getCookies()
QAstell can only see non-HttpOnly cookies via document.cookie. To check HttpOnly, Secure, or SameSite flags, add manual assertions using Cypress's native cy.getCookies():
it('should have secure cookies', () => {
cy.visit('https://example.com');
// ========================================
// QAstell: 245+ DOM-based security checks
// ========================================
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
await auditor.assertNoViolations();
});
// ========================================
// Manual: Cookie flags (httpOnly, secure)
// QAstell can't see these in Cypress
// ========================================
cy.getCookies().then((cookies) => {
cookies.forEach((cookie) => {
if (cookie.name.includes('session') || cookie.name.includes('auth')) {
expect(cookie.secure, `${cookie.name} should be Secure`).to.be.true;
expect(cookie.httpOnly, `${cookie.name} should be HttpOnly`).to.be.true;
expect(cookie.sameSite, `${cookie.name} should have SameSite`).to.match(/strict|lax/i);
}
});
});
});
Workaround: HTTP Headers with cy.intercept()
QAstell cannot access HTTP response headers in Cypress. To check security headers like CSP, X-Frame-Options, or HSTS, add manual assertions using cy.intercept():
it('should have security headers', () => {
let responseHeaders = {};
// ========================================
// Manual: Capture HTTP response headers
// Must be set up BEFORE cy.visit()
// ========================================
cy.intercept('GET', 'https://example.com/', (req) => {
req.continue((res) => {
responseHeaders = res.headers;
});
});
cy.visit('https://example.com');
// ========================================
// QAstell: 245+ DOM-based security checks
// ========================================
cy.window().then(async (win) => {
const auditor = new SecurityAuditor(win);
await auditor.assertNoViolations();
});
// ========================================
// Manual: Check security headers
// QAstell can't access headers in Cypress
// ========================================
cy.then(() => {
expect(responseHeaders['x-frame-options']).to.exist;
expect(responseHeaders['x-content-type-options']).to.equal('nosniff');
expect(responseHeaders['strict-transport-security']).to.exist;
});
});
What Gets Checked
QAstell runs 245+ security checks when using Cypress (same as WebDriver):
- Forms - CSRF tokens, autocomplete on sensitive fields, action URLs
- Cookies - Secure, SameSite flags (via document.cookie)
- Links - Missing rel="noopener", javascript: URLs
- DOM Security - Inline handlers, DOM clobbering, prototype pollution
- Secrets - API keys, tokens in HTML, comments, localStorage
- Mixed Content - HTTP resources on HTTPS pages
- And 40+ more categories...