Summary
Cypress is a modern JavaScript-based end-to-end testing framework designed specifically for testing web applications. Unlike traditional testing tools, Cypress runs directly in the browser's execution loop, providing automatic waiting, real-time reloading, and time-travel debugging. It offers a complete testing solution with built-in assertions, mocking capabilities, and an intuitive API that makes writing and debugging tests faster and more reliable. This cheatsheet covers Cypress fundamentals, best practices, and advanced patterns for technical interviews.
Installation & Setup
# Install Cypress
npm install --save-dev cypress
# Open Cypress
npx cypress open
# Run tests headlessly
npx cypress run
Project Structure
cypress/
├── e2e/ # Test files
├── fixtures/ # Test data
├── support/ # Custom commands & utilities
└── downloads/ # Downloaded files during tests
Configuration (cypress.config.js)
module.exports = {
e2e: {
baseUrl: 'http://localhost:3000',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 4000,
requestTimeout: 5000,
responseTimeout: 30000
}
}
Basic Commands
Navigation
cy.visit('https://example.com')
cy.visit('/login') // Uses baseUrl
cy.go('back')
cy.go('forward')
cy.reload()
Element Selection
cy.get('.class') // By class
cy.get('#id') // By ID
cy.get('[data-cy=submit]') // By data attribute
cy.get('button').first() // First element
cy.get('li').last() // Last element
cy.get('tr').eq(2) // By index
Waiting
cy.wait(1000) // Wait 1 second (avoid if possible)
cy.wait('@apiCall') // Wait for intercepted request
cy.wait(['@call1', '@call2']) // Multiple waits
Selectors & Queries
Chaining Commands
cy.get('form')
.find('input[type="email"]')
.type('user@example.com')
cy.get('ul').children() // Direct children
cy.get('li').parent() // Parent element
cy.get('button').siblings() // Sibling elements
cy.get('div').closest('form') // Closest ancestor
Filtering
cy.get('button').filter('.active')
cy.get('li').not('.disabled')
cy.get('div').contains('Submit') // Contains text
cy.get('input').filter(':visible')
Aliases
cy.get('.my-element').as('myAlias')
cy.get('@myAlias').click()
Assertions
Common Assertions
// Existence
cy.get('button').should('exist')
cy.get('.error').should('not.exist')
// Visibility
cy.get('modal').should('be.visible')
cy.get('spinner').should('not.be.visible')
// Text content
cy.get('h1').should('have.text', 'Welcome')
cy.get('p').should('contain', 'Hello')
cy.get('div').should('not.contain', 'Error')
// Attributes
cy.get('input').should('have.attr', 'placeholder', 'Email')
cy.get('button').should('have.class', 'btn-primary')
cy.get('a').should('have.css', 'color', 'rgb(255, 0, 0)')
// State
cy.get('button').should('be.disabled')
cy.get('input').should('be.enabled')
cy.get('checkbox').should('be.checked')
cy.get('option').should('be.selected')
// Value
cy.get('input').should('have.value', 'test@example.com')
// Length
cy.get('li').should('have.length', 5)
cy.get('li').should('have.length.greaterThan', 3)
Multiple Assertions
cy.get('button')
.should('be.visible')
.and('be.enabled')
.and('contain', 'Submit')
Actions & Interactions
Form Interactions
// Typing
cy.get('input').type('Hello World')
cy.get('input').type('{enter}') // Special keys
cy.get('input').clear().type('New text')
// Clicking
cy.get('button').click()
cy.get('button').dblclick()
cy.get('button').rightclick()
cy.get('button').click({ force: true }) // Force click
// Selecting
cy.get('select').select('option-value')
cy.get('select').select(['opt1', 'opt2']) // Multiple
// Checkboxes & Radio
cy.get('input[type="checkbox"]').check()
cy.get('input[type="checkbox"]').uncheck()
cy.get('input[type="radio"]').check('value')
Mouse Actions
cy.get('element').trigger('mouseover')
cy.get('element').trigger('mousedown')
cy.get('draggable').trigger('dragstart')
Scrolling
cy.scrollTo(0, 500) // Scroll window
cy.get('.sidebar').scrollTo('bottom')
cy.get('button').scrollIntoView()
Hooks & Test Structure
describe('Test Suite', () => {
before(() => {
// Runs once before all tests
})
beforeEach(() => {
// Runs before each test
cy.visit('/')
})
afterEach(() => {
// Runs after each test
})
after(() => {
// Runs once after all tests
})
it('should do something', () => {
// Test case
})
it.only('runs only this test', () => {
// Focused test
})
it.skip('skips this test', () => {
// Skipped test
})
})
Custom Commands
Creating Custom Commands
// In cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
cy.get('#email').type(email)
cy.get('#password').type(password)
cy.get('button[type="submit"]').click()
})
// Usage
cy.login('user@example.com', 'password123')
Overwriting Commands
Cypress.Commands.overwrite('type', (originalFn, element, text, options) => {
if (options && options.sensitive) {
options.log = false
}
return originalFn(element, text, options)
})
Fixtures & Test Data
Using Fixtures
// cypress/fixtures/user.json
{
"email": "test@example.com",
"password": "password123"
}
// In test
cy.fixture('user').then((user) => {
cy.get('#email').type(user.email)
cy.get('#password').type(user.password)
})
// Or using beforeEach
beforeEach(function() {
cy.fixture('user').as('userData')
})
it('uses fixture data', function() {
cy.get('#email').type(this.userData.email)
})
Network Requests & Intercepting
Intercepting Requests (cy.intercept)
// Basic intercept
cy.intercept('GET', '/api/users', { fixture: 'users.json' })
// With alias
cy.intercept('POST', '/api/login').as('loginRequest')
cy.get('button').click()
cy.wait('@loginRequest')
// Dynamic response
cy.intercept('GET', '/api/user/*', (req) => {
req.reply({
statusCode: 200,
body: { name: 'Test User' }
})
})
// Modifying requests
cy.intercept('POST', '/api/data', (req) => {
req.body.timestamp = Date.now()
})
// Conditional stubbing
cy.intercept('GET', '/api/data', (req) => {
if (req.query.type === 'error') {
req.reply({ statusCode: 500 })
}
})
Assertions on Requests
cy.wait('@apiCall').then((interception) => {
expect(interception.request.body).to.have.property('email')
expect(interception.response.statusCode).to.eq(200)
})
Best Practices
1. Use Data Attributes
<button data-cy="submit-button">Submit</button>
cy.get('[data-cy=submit-button]').click()
2. Avoid Hard-Coded Waits
// ❌ Bad
cy.wait(3000)
// ✅ Good
cy.get('.spinner').should('not.exist')
cy.intercept('/api/data').as('getData')
cy.wait('@getData')
3. Keep Tests Independent
// Each test should be able to run in isolation
beforeEach(() => {
cy.visit('/')
// Reset state
})
4. Use Aliases for Reusability
cy.get('[data-cy=user-list]').as('userList')
cy.get('@userList').should('be.visible')
cy.get('@userList').find('li').should('have.length', 3)
5. Handle Flaky Tests
// Retry ability
cy.get('button', { timeout: 10000 }).should('be.visible')
// Retry configuration
{
retries: {
runMode: 2,
openMode: 0
}
}
Advanced Topics
Environment Variables
// cypress.config.js
module.exports = {
env: {
apiUrl: 'https://api.example.com',
auth_token: 'xyz123'
}
}
// Usage
cy.request(Cypress.env('apiUrl') + '/users')
Viewport Testing
// Test different screen sizes
cy.viewport(320, 568) // iPhone 5
cy.viewport('iphone-x')
cy.viewport(1280, 720)
// In config
viewportWidth: 1000,
viewportHeight: 660
File Upload
cy.get('input[type="file"]').selectFile('cypress/fixtures/image.png')
cy.get('input[type="file"]').selectFile({
contents: Cypress.Buffer.from('file contents'),
fileName: 'file.txt',
mimeType: 'text/plain'
})
Working with iframes
cy.get('iframe').then($iframe => {
const body = $iframe.contents().find('body')
cy.wrap(body).find('button').click()
})
Multiple Tabs/Windows
// Cypress runs in a single tab, so remove target="_blank"
cy.get('a[target="_blank"]').invoke('removeAttr', 'target').click()
Session Management
// Cache session across tests
Cypress.Commands.add('loginSession', (username, password) => {
cy.session([username, password], () => {
cy.visit('/login')
cy.get('#username').type(username)
cy.get('#password').type(password)
cy.get('button').click()
cy.url().should('include', '/dashboard')
})
})
Key Interview Concepts
Cypress vs Selenium Comparison
| Feature | Cypress | Selenium |
|---|---|---|
| Architecture | Runs in browser, same run-loop | Runs outside browser via driver |
| Language Support | JavaScript/TypeScript only | Multiple languages (Java, Python, etc.) |
| Waiting | Automatic smart waiting | Requires explicit waits |
| Debugging | Time travel, snapshots, real-time | Traditional debugging methods |
| Browser Support | Chrome-family browsers | All major browsers |
| Speed | Fast (in-browser execution) | Slower (network hops) |
| Multi-tab | Not supported | Fully supported |
| Setup | Simple, zero config | Complex driver management |
Asynchronous Command Handling
Key Concept: Cypress commands are enqueued and executed sequentially
// Commands are automatically chained
cy.get('button').click() // Enqueued
cy.get('.result').should('be.visible') // Waits for previous
// No async/await needed - Cypress manages promises internally
// Each command yields a subject to the next command
API Testing Strategies
// Direct API testing
cy.request({
method: 'POST',
url: '/api/users',
body: { name: 'John' },
headers: { 'Authorization': 'Bearer token' }
}).then((response) => {
expect(response.status).to.eq(201)
expect(response.body).to.have.property('id')
})
// Combining API and UI testing
cy.request('POST', '/api/seed-data') // Setup via API
cy.visit('/dashboard') // Test UI
Authentication Patterns
// Pattern 1: UI Login (comprehensive but slow)
Cypress.Commands.add('loginViaUI', (email, password) => {
cy.visit('/login')
cy.get('[data-cy=email]').type(email)
cy.get('[data-cy=password]').type(password)
cy.get('[data-cy=submit]').click()
cy.url().should('include', '/dashboard')
})
// Pattern 2: API Login (fast, recommended)
Cypress.Commands.add('loginViaAPI', (email, password) => {
cy.request('POST', '/api/login', { email, password })
.then((resp) => {
window.localStorage.setItem('authToken', resp.body.token)
cy.visit('/dashboard')
})
})
// Pattern 3: Session Caching (optimal for test suites)
Cypress.Commands.add('loginWithSession', (email, password) => {
cy.session([email, password], () => {
cy.request('POST', '/api/login', { email, password })
.then((resp) => {
window.localStorage.setItem('authToken', resp.body.token)
})
})
})
Debugging Techniques
// Interactive debugging
cy.pause() // Pause test execution
cy.debug() // Debugger breakpoint
// Logging & visibility
cy.log('Custom message') // Test runner log
cy.screenshot('debug-state') // Capture screenshot
cy.task('log', 'Server message') // Node console log
// Command log inspection
cy.get('button').then(($btn) => {
debugger // Browser DevTools breakpoint
console.log($btn)
})
Testing Patterns & Architectures
Page Object Model:
// pages/LoginPage.js
class LoginPage {
visit() {
cy.visit('/login')
}
fillEmail(email) {
cy.get('[data-cy=email]').type(email)
}
submit() {
cy.get('[data-cy=submit]').click()
}
}
// In test
const loginPage = new LoginPage()
loginPage.visit()
loginPage.fillEmail('test@example.com')
App Actions:
// Expose app methods to Cypress
window.app = {
login: (user) => { /* ... */ },
resetDb: () => { /* ... */ }
}
// In test
cy.window().its('app').invoke('login', user)
Quick Reference
Command Cheat Sheet
| Command | Purpose |
|---|---|
cy.visit() |
Navigate to URL |
cy.get() |
Select elements |
cy.contains() |
Find by text |
cy.type() |
Type into input |
cy.click() |
Click element |
cy.wait() |
Wait for time/alias |
cy.intercept() |
Mock network requests |
cy.fixture() |
Load test data |
cy.should() |
Make assertion |
cy.wrap() |
Wrap object for Cypress commands |
Assertion Cheat Sheet
| Assertion | Usage |
|---|---|
exist / not.exist |
Element existence |
be.visible / not.be.visible |
Visibility |
have.text |
Exact text match |
contain |
Partial text match |
have.value |
Input value |
have.attr |
Attribute value |
have.class |
CSS class |
be.checked |
Checkbox/radio state |
be.disabled / be.enabled |
Element state |
Common Gotchas
- Async handling: Cypress commands are queued, not promises
- Variable scope: Use aliases or
.then()for values - Element detachment: Re-query elements after DOM changes
- Cross-origin: Limited by browser security
- File downloads: Use cy.readFile() to verify downloads
Performance Optimization
Test Execution Speed
// Parallel execution configuration
{
"parallel": true,
"record": true,
"key": "your-record-key"
}
// Optimize test structure
beforeEach(() => {
cy.task('db:seed') // Fast DB setup
cy.intercept('/api/**', { fixture: 'api-responses' }) // Mock APIs
})
Memory Management
// Clear browser state between tests
afterEach(() => {
cy.window().then(win => {
win.sessionStorage.clear()
})
cy.clearCookies()
cy.clearLocalStorage()
})
Interview Preparation Checklist
- ✅ Core Commands: visit, get, type, click, should
- ✅ Selectors: CSS, data attributes, best practices
- ✅ Assertions: Chai assertions, custom matchers
- ✅ Network: intercept, fixtures, request mocking
- ✅ Async Handling: Command queueing, automatic waiting
- ✅ Custom Commands: Creating reusable test helpers
- ✅ Debugging: pause, debug, screenshots, videos
- ✅ Best Practices: Page objects, test isolation, data attributes
- ✅ CI/CD Integration: Headless mode, parallel execution
- ✅ Limitations: Cross-origin, multi-tab, file uploads
Key Takeaways: Master Cypress's unique architecture (in-browser execution), understand automatic waiting and retry-ability, practice writing maintainable tests with custom commands and page objects, and know the trade-offs compared to traditional testing tools like Selenium.