How to use Cypress for Angular E2E tests
End-to-end testing verifies that your Angular application works correctly from the user’s perspective, testing complete user flows through the application. As the creator of CoreUI with over 12 years of Angular experience since 2014, I’ve implemented Cypress for E2E testing in enterprise applications. Cypress provides fast, reliable browser automation with excellent debugging capabilities and automatic waiting for elements. This approach ensures your application works correctly across real user scenarios without flaky tests.
Use Cypress to write end-to-end tests for Angular applications with browser automation and user interaction testing.
Install Cypress:
npm install --save-dev cypress
npx cypress open
Configure cypress.config.ts:
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:4200',
viewportWidth: 1280,
viewportHeight: 720,
video: false,
screenshotOnRunFailure: true
}
})
Add to package.json:
{
"scripts": {
"cy:open": "cypress open",
"cy:run": "cypress run",
"e2e": "start-server-and-test start http://localhost:4200 cy:run"
}
}
Basic E2E test:
// cypress/e2e/login.cy.js
describe('Login Page', () => {
beforeEach(() => {
cy.visit('/login')
})
it('displays login form', () => {
cy.get('h1').should('contain', 'Login')
cy.get('input[type="email"]').should('be.visible')
cy.get('input[type="password"]').should('be.visible')
cy.get('button[type="submit"]').should('contain', 'Login')
})
it('logs in successfully', () => {
cy.get('input[type="email"]').type('[email protected]')
cy.get('input[type="password"]').type('password123')
cy.get('button[type="submit"]').click()
cy.url().should('include', '/dashboard')
cy.get('.welcome-message').should('contain', 'Welcome back')
})
it('shows error for invalid credentials', () => {
cy.get('input[type="email"]').type('[email protected]')
cy.get('input[type="password"]').type('wrongpass')
cy.get('button[type="submit"]').click()
cy.get('.error-message').should('contain', 'Invalid credentials')
cy.url().should('include', '/login')
})
it('validates required fields', () => {
cy.get('button[type="submit"]').click()
cy.get('input[type="email"]:invalid').should('exist')
cy.get('input[type="password"]:invalid').should('exist')
})
})
Testing Angular components:
// cypress/e2e/users.cy.js
describe('User Management', () => {
beforeEach(() => {
cy.login('[email protected]', 'password123')
cy.visit('/users')
})
it('displays user list', () => {
cy.get('.user-table').should('be.visible')
cy.get('.user-row').should('have.length.at.least', 1)
})
it('creates new user', () => {
cy.get('[data-cy="add-user-btn"]').click()
cy.get('input[name="name"]').type('John Doe')
cy.get('input[name="email"]').type('[email protected]')
cy.get('select[name="role"]').select('User')
cy.get('button[type="submit"]').click()
cy.get('.success-message').should('contain', 'User created')
cy.get('.user-table').should('contain', 'John Doe')
})
it('edits existing user', () => {
cy.get('.user-row').first().find('[data-cy="edit-btn"]').click()
cy.get('input[name="name"]').clear().type('Jane Smith')
cy.get('button[type="submit"]').click()
cy.get('.success-message').should('contain', 'User updated')
cy.get('.user-table').should('contain', 'Jane Smith')
})
it('deletes user', () => {
cy.get('.user-row').first().find('[data-cy="delete-btn"]').click()
cy.get('[data-cy="confirm-delete"]').click()
cy.get('.success-message').should('contain', 'User deleted')
})
it('searches users', () => {
cy.get('[data-cy="search-input"]').type('john')
cy.get('.user-row').should('have.length', 1)
cy.get('.user-table').should('contain', 'john')
})
})
Custom commands (cypress/support/commands.ts):
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>
logout(): Chainable<void>
}
}
}
Cypress.Commands.add('login', (email: string, password: string) => {
cy.session([email, password], () => {
cy.visit('/login')
cy.get('input[type="email"]').type(email)
cy.get('input[type="password"]').type(password)
cy.get('button[type="submit"]').click()
cy.url().should('include', '/dashboard')
})
})
Cypress.Commands.add('logout', () => {
cy.get('[data-cy="user-menu"]').click()
cy.get('[data-cy="logout-btn"]').click()
})
Testing API requests:
it('intercepts API calls', () => {
cy.intercept('GET', '/api/users', {
fixture: 'users.json'
}).as('getUsers')
cy.visit('/users')
cy.wait('@getUsers')
cy.get('.user-row').should('have.length', 5)
})
it('handles API errors', () => {
cy.intercept('POST', '/api/users', {
statusCode: 500,
body: { error: 'Server error' }
})
cy.get('[data-cy="add-user-btn"]').click()
cy.get('input[name="name"]').type('Test User')
cy.get('button[type="submit"]').click()
cy.get('.error-message').should('contain', 'Server error')
})
Best Practice Note
Cypress automatically waits for elements to appear and be actionable—no manual waits needed. Use data-cy attributes for selectors to separate test logic from styling. Custom commands like cy.login() reduce repetition and improve readability. Cypress sessions cache authentication state for faster tests. Intercept API calls to test error handling and loading states. Use fixtures for consistent test data. This is how we test CoreUI Angular applications—comprehensive Cypress E2E suites testing real user flows, ensuring production applications work correctly from end to end.



