Developing E2E Tests for Website (Cypress)
Cypress—most popular E2E testing tool for web applications. Runs tests directly in browser, has built-in time-travel debugger, automatic element waiting and network request interception.
Installation
npm install -D cypress
npx cypress open # first run—creates directory structure
Project Structure
cypress/
├── e2e/ # test specifications
├── fixtures/ # static test data
├── support/
│ ├── commands.ts # custom commands
│ └── e2e.ts # global configuration
└── cypress.config.ts
Basic Test: Login
// cypress/e2e/auth/login.cy.ts
describe('Login', () => {
beforeEach(() => {
cy.visit('/login');
});
it('successfully logs in with valid credentials', () => {
cy.get('[data-cy="email-input"]').type('[email protected]');
cy.get('[data-cy="password-input"]').type('password123');
cy.get('[data-cy="login-btn"]').click();
cy.url().should('include', '/dashboard');
cy.get('[data-cy="user-name"]').should('contain', 'John');
});
it('shows error on invalid credentials', () => {
cy.get('[data-cy="email-input"]').type('[email protected]');
cy.get('[data-cy="password-input"]').type('wrongpass');
cy.get('[data-cy="login-btn"]').click();
cy.get('[data-cy="error-message"]')
.should('be.visible')
.and('contain', 'Invalid email or password');
});
});
Intercepting API Requests
// Mock API—test doesn't depend on backend
it('displays products from API', () => {
cy.intercept('GET', '/api/products*', {
fixture: 'products.json',
statusCode: 200,
}).as('getProducts');
cy.visit('/products');
cy.wait('@getProducts');
cy.get('[data-cy="product-card"]').should('have.length', 10);
});
// Check request
it('sends correct payload on form submit', () => {
cy.intercept('POST', '/api/orders', (req) => {
expect(req.body).to.deep.include({ productId: 1, quantity: 2 });
req.reply({ statusCode: 201, body: { orderId: 100 } });
}).as('createOrder');
// ... actions
cy.wait('@createOrder');
cy.get('[data-cy="success-message"]').should('be.visible');
});
Custom Commands
// cypress/support/commands.ts
Cypress.Commands.add('login', (email = '[email protected]', password = 'password') => {
cy.request('POST', '/api/auth/login', { email, password })
.then(({ body }) => {
localStorage.setItem('auth_token', body.token);
});
cy.visit('/');
});
Cypress.Commands.add('createUser', (overrides = {}) => {
return cy.request('POST', '/api/test/users', {
name: 'Test User',
email: `test-${Date.now()}@example.com`,
...overrides,
}).its('body');
});
// Usage
before(() => cy.login());
Testing Forms
it('validates registration form', () => {
cy.visit('/register');
cy.get('[data-cy="submit-btn"]').click();
// Check error messages
cy.get('[data-cy="name-error"]').should('contain', 'Required field');
cy.get('[data-cy="email-error"]').should('contain', 'Required field');
// Fill form
cy.get('[data-cy="name"]').type('John Doe');
cy.get('[data-cy="email"]').type('invalid-email');
cy.get('[data-cy="submit-btn"]').click();
cy.get('[data-cy="email-error"]').should('contain', 'Invalid email');
cy.get('[data-cy="email"]').clear().type('[email protected]');
cy.get('[data-cy="password"]').type('SecurePass123!');
cy.get('[data-cy="submit-btn"]').click();
cy.url().should('include', '/dashboard');
});
CI/CD Integration
# .github/workflows/e2e.yml
name: E2E Tests
on: [push]
jobs:
cypress:
runs-on: ubuntu-latest
services:
app:
image: myapp:latest
ports: ['3000:3000']
steps:
- uses: actions/checkout@v3
- uses: cypress-io/github-action@v6
with:
start: npm run start:ci
wait-on: 'http://localhost:3000'
record: true
parallel: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
Timeline
Setup Cypress + 20–40 scenarios for typical commercial site: 5–8 days.







