Cypress

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:

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):

Using a Different Framework?

Next Steps