The Complete Cypress Course
Nine hands-on lessons that take you from zero to shipping E2E tests in CI — every step animated so you can see exactly what to do. 🎬
🚀 Start the course
Install & Setup Cypress
Get Cypress into your project and understand the files it creates — with a live demo of the whole install. 👇
Create your project
Make a folder and initialize npm inside it:
mkdir my-cypress-project
cd my-cypress-project
npm init -yInstall Cypress
Cypress is installed as a dev dependency — it ships its own Electron browser, no extra setup:
npm install cypress --save-devOpen the launcher
This opens the Cypress app. Choose E2E Testing → pick a browser → Cypress scaffolds your project:
npx cypress openMeet the scaffold
These are the files that appear — you'll use every one of them in this course:
Which command installs Cypress as a dev dependency?
Your First E2E Test
describe → it → cy.visit(). Four steps to your first green checkmark — watch it happen live.
Create a spec file
In cypress/e2e/, create an empty file with a .cy.js (or .cy.ts) extension:
Write the describe block
describe is your test suite — inside it you can write any number of it() test cases:
describe('My First Test', () => {
// it blocks and test logic go here
})Add your first test
cy.visit() is the Cypress command that navigates to any URL:
describe('My First Test', () => {
it('Visits a website', () => {
cy.visit('https://qa.imcseian.com/')
})
})Run it!
Save the file, then from your project root:
npx cypress openClick E2E Testing → choose your browser → click your spec to watch it run.
What does cy.visit() do?
Selectors & Actions
Find elements and interact with them — typing, clicking, and finding by text.
cy.get() — pick an element
Works with any CSS selector. Pro tip: prefer data-cy attributes — they never change when styles do:
Actions
.type() types into inputs, .clear() empties them, .click() clicks buttons & links. cy.contains() finds elements by visible text:
cy.get('#email').type('qa@tester.io')
cy.get('[data-cy="login"]').click()
cy.contains('Welcome back').should('be.visible')Which selector strategy is most stable for tests?
Assertions with .should()
Prove your app behaves. Chain as many assertions as you like with .and().
.should() + .and()
.should() adds an assertion; .and() chains another one on the same element:
cy.get('.title')
.should('be.visible')
.and('have.text', 'Welcome')Which method chains ANOTHER assertion onto the same element?
Auto-Waiting & Retries
The reason Cypress tests are not flaky: commands and assertions retry automatically.
How it works
cy.get() and .should() keep retrying for up to 4 seconds (by default) until the element exists / the assertion passes. No cy.wait(5000) hacks — that's an anti-pattern!
Custom timeout
Slow-loading element? Extend the timeout per command:
cy.get('.status', { timeout: 10000 })
.should('have.text', 'Success')cy.get() finds no element. What happens?
Fixtures & Test Data
Keep test data out of your code. Load JSON fixtures and loop through them data-driven style.
Create a fixture
Drop a JSON file into cypress/fixtures/:
{
"users": [
{ "email": "qa1@tester.io" },
{ "email": "qa2@tester.io" },
{ "email": "admin@corp.io" }
]
}Use it in a test
cy.fixture() loads the file; .then() gives you the data. You can also alias it with .as('users') and read it back with cy.get('@users'):
cy.fixture('users.json').then((users) => {
users.users.forEach((u) => {
cy.get('#email').clear().type(u.email)
})
})Where do fixture files live?
Custom Commands
Repeating the login flow in 12 tests? Wrap it once — use it everywhere as cy.login().
Register the command
Add it in cypress/support/commands.js — it's auto-loaded before every spec:
Cypress.Commands.add('login', () => {
cy.get('#email').type('qa@tester.io')
cy.get('.btn-login').click()
})Use it anywhere
One line replaces the whole flow — and it shows up nicely in the command log:
cy.login()Using TypeScript? Declare it: declare namespace Cypress { interface Chainable { login(): Chainable } }
Where do you register custom commands?
Network Stubbing — cy.intercept()
Catch requests before they hit the real API. Stub responses, test edge cases, go faster.
Stub a request
cy.intercept() catches the matching request and serves your fixture instead — the backend never sees it:
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
cy.visit('/dashboard')
cy.wait('@getUsers')
.its('response.statusCode')
.should('eq', 200)Why it's powerful
Simulate a 500 error, an empty list, or slow responses — without touching the backend:
cy.intercept('GET','/api/x',{fixture:'u.json'}) will…
Run Cypress in CI/CD
Tests are only useful if they run on every push. Headless mode + GitHub Actions = automated confidence.
Headless mode
cypress open is for development. cypress run runs headless — perfect for CI:
npx cypress runGitHub Actions workflow
Create .github/workflows/cypress.yml — the official action starts your app and runs every spec:
name: E2E Tests
on: [push]
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cypress-io/github-action@v6
with:
start: npm startOn failure, Cypress auto-saves screenshots (in cypress/screenshots) and videos (cypress/videos) — upload them as workflow artifacts for debugging.
Which command runs Cypress headless in CI?
Comments
Comments
Post a Comment