E2E Testing with Cypress

LightNap uses Cypress for end-to-end testing, enabling automated testing of complete user workflows from the browser perspective. This guide covers writing, configuring, and running E2E tests.

Test Configuration

E2E tests are configured in cypress.config.ts:

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:4200',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    supportFile: 'cypress/support/e2e.ts',
    viewportWidth: 1280,
    viewportHeight: 720,
    defaultCommandTimeout: 10000,
    requestTimeout: 15000,
    responseTimeout: 15000,
  }
})

Key settings:

  • baseUrl: Application URL for tests
  • Timeouts: Generous timeouts for reliable execution
  • Viewport: Consistent screen size across tests

Writing E2E Tests

Basic Test Structure

describe('Feature Name', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('should perform user action', () => {
    // Test implementation
  });
});

Common Patterns

Authentication Testing

it('should login successfully', () => {
  cy.visit('/identity/login');
  cy.get('[data-cy="login-username"]').type('E2eRegularUser');
  cy.get('[data-cy="login-password"]').type('P@ssw0rd');
  cy.get('[data-cy="login-submit"]').click();

  cy.url().should('not.include', '/identity/login');
  cy.get('[data-cy="user-menu"]').should('be.visible');
});

Form Testing

it('should validate form inputs', () => {
  cy.visit('/some-form');

  // Test required field
  cy.get('[data-cy="submit-btn"]').click();
  cy.get('[data-cy="error-message"]').should('contain', 'Required field');

  // Fill form and submit
  cy.get('[data-cy="name-input"]').type('Test Name');
  cy.get('[data-cy="submit-btn"]').click();
  cy.get('[data-cy="success-message"]').should('be.visible');
});
it('should navigate between pages', () => {
  cy.visit('/');
  cy.get('[data-cy="nav-link"]').click();
  cy.url().should('include', '/target-page');
  cy.get('[data-cy="page-title"]').should('contain', 'Target Page');
});

Custom Commands

LightNap provides reusable Cypress commands in cypress/support/commands.ts:

// Authentication helpers
cy.logInRegularUser();
cy.logInAdministrator();
cy.logInContentEditor();
cy.logout();
cy.isLoggedIn();
cy.shouldBeLoggedIn();
cy.shouldBeLoggedOut();

// Mock setup
cy.setupContentMocks();
cy.setupAdminMocks();

Using Custom Commands

it('should allow admin to access restricted area', () => {
  cy.logInAdministrator();
  cy.visit('/admin/users');
  cy.get('[data-cy="manage-users-panel"]').should('be.visible');
});

Running E2E Tests

With Mocks

npm run e2e:mocks

Runs headless tests with REST API mocking enabled.

Interactive Mode

npm run e2e:mocks:open

Opens Cypress Test Runner with mocks for interactive test development.

Against Live Backend

To run tests against a live backend, you need to start both the frontend and backend servers.

Starting the Backend

Start the backend in E2E mode, which seeds test users and content:

npm run e2e:backend

This runs the backend with the E2e launch profile, which:

  • Uses the E2E environment configuration
  • Seeds test users with credentials:
    • E2eRegularUser / P@ssw0rd (regular user)
    • E2eAdmin / P@ssw0rd (administrator)
    • E2eContentEditor / P@ssw0rd (content editor)
  • Seeds E2E-specific test content

Starting the Frontend

In a separate terminal, start the frontend development server:

npm run start

Running Tests

Once both servers are running, execute tests against the live backend:

# Headless mode
npm run e2e

# Interactive mode
npm run e2e:open

The E2E environment uses backend seeding to automatically configure test users and content. This ensures a consistent test environment across different machines and CI/CD pipelines.

CI Mode

npm run e2e:ci

Runs tests in CI mode with recording and parallel execution for Cypress Dashboard. This task now runs against a live backend by default (no mocking) to get realistic end-to-end coverage.

If you prefer to run CI-style tests against mocked responses, use the e2e:mocks script or set the useMocks env variable explicitly in your CI pipeline. For example:

npx cross-env useMocks=true npm run e2e:ci

Test Credentials

When running tests against a live backend, use these seeded credentials:

User Username Password Roles
Regular User E2eRegularUser P@ssw0rd -
Administrator E2eAdmin P@ssw0rd Administrator
Content Editor E2eContentEditor P@ssw0rd ContentEditor

These credentials are automatically seeded when the backend runs in E2E mode.

The backend now includes an appsettings.E2e.json file that seeds a set of test users and simple content for E2E runs; this is consumed when you start the backend with the E2e launch profile (see Documentation -> Getting Started -> Application Configuration).

Test Organization

File Structure

cypress/
├── e2e/
│   ├── authentication.cy.ts
│   ├── content.cy.ts
│   ├── admin.cy.ts
│   └── ...
├── support/
│   ├── commands.ts
│   ├── e2e.ts
│   └── mock-api.ts
└── fixtures/
    └── example.json

Test Categories

  • authentication.cy.ts: Login, registration, password reset
  • content.cy.ts: CMS functionality, page management
  • admin.cy.ts: Administrative features, user management
  • profile.cy.ts: User profile, settings, notifications

Mocking and Fixtures

API Mocking

Use cypress/support/mock-api.ts for API interception:

cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');

Environment-Based Mocking

# Run with mocks
npm run e2e:mocks:open

Configure mocks in cypress.config.ts:

env: {
  useMocks: false
}

Mock vs Live Backend

The custom commands automatically handle differences between mocked and live backends:

  • With mocks: Uses Cypress sessions to cache login state for faster test execution
  • Live backend: Skips session caching to properly handle refresh token cookie updates

Best Practices

Test Data Management

  • Use fixtures for static data
  • Seed test data programmatically when needed
  • Clean up data between tests
  • Use the E2E environment for consistent test data

Selector Strategy

  • Prefer data-cy attributes over CSS selectors
  • Avoid brittle selectors like .class:nth-child(3)
  • Use descriptive data attributes

Test Isolation

  • Each test should be independent
  • Use beforeEach for common setup
  • Avoid test interdependencies

Performance

  • Use API mocking for faster execution
  • Group related tests in the same spec file
  • Parallel execution in CI

Debugging

  • Use cy.debug() and cy.pause() during development
  • Check screenshots/videos on failures
  • Use cy.log() for debugging output

Troubleshooting

Common Issues

Tests fail intermittently

  • Increase timeouts in cypress.config.ts
  • Use cy.wait() for async operations
  • Check for race conditions

Element not found

  • Verify data-cy attributes exist
  • Check if element is rendered after navigation
  • Use cy.get() with timeout options

API calls not intercepted

  • Ensure intercept is set before the action
  • Check URL patterns match exactly
  • Use cy.intercept() with wildcards

Slow test execution

  • Enable API mocking
  • Reduce viewport size if not needed
  • Use cy.intercept() to stub slow endpoints

Flaky tests

  • Avoid fixed waits, use assertions instead
  • Ensure proper element loading
  • Check for async operations completion

Backend connection issues

  • Verify backend is running with npm run e2e:backend
  • Check that backend is using port 7266 (default E2E configuration)
  • Ensure frontend proxy is configured correctly