🚀 How I Built a Robust Cypress.io



This content originally appeared on DEV Community and was authored by mohamed Said Ibrahim

🚀 How I Built a Robust Cypress.io

“Behind every successful automation project, there’s a chaotic first commit.”

🚀 How I Built a Robust Cypress.io Automation Project from Scratch — A Real-World Engineer’s Journey to Excellence

“Behind every successful automation project, there’s a chaotic first commit.”

📍 Introduction

I remember it like it was yesterday. Our mobile-first web application was growing rapidly, and manual testing became a bottleneck. Regression bugs haunted us in every release, and the team was crying out for help. I knew it was time to act.

I decided to build a robust, scalable, and professional Cypress.io automation testing project — something maintainable, extendable, and powerful enough to cover everything from login flows to end-to-end business scenarios. What started as a simple plan turned into a real-world masterclass in Cypress, custom commands, POM, and solving unexpected errors.

This article is your map to follow that journey and build your own Cypress automation project like a pro.

Cypress

🧱 Step 1: Initialize the Project with NPM

First things first, I opened the terminal and ran:

mkdir cypress-floward-e2e  
cd cypress-floward-e2e  
npm init -y

This created the backbone of my project — the package.json file.

🧪 Step 2: Install Cypress

Since Cypress is web-focused and we had some hybrid mobile flows in WebView, I started with the essential web testing stack:

npm install cypress --save-dev

📦 Step 3: Install Required Dependencies

Next, I installed helpful plugins to supercharge my Cypress tests:


npm install \--save-dev cypress-file-upload
npm install \--save-dev cypress-axe
npm install \--save-dev cypress-xpath

👉 These plugins enabled me to:

  • Handle file uploads easily.
  • Test accessibility.
  • Use powerful XPath selectors when needed.

🗂 Step 4: Create a Clean Project Structure (POM)

To avoid the “spaghetti test code” syndrome, I structured my folders following the Page Object Model (POM):

cypress/

├── fixtures/ # Test data (JSON)

├── integration/ # Test specs

│ └── login/ # login.e2e.cy.js

├── support/

│ ├── commands.js # Custom Cypress commands

│ ├── index.js

│ └── pages/ # POM Files

│ └── LoginPage.js

├── plugins/

This structure made my tests readable, reusable, and scalable.

📄 Step 5: Customize Cypress Configuration

In cypress.config.js:

const { defineConfig } = require('cypress');  

module.exports = defineConfig({  
  e2e: {  
    baseUrl: 'https://floward.com/en-eg/cairo',  
    supportFile: 'cypress/support/index.js',  
    specPattern: 'cypress/integration/\*\*/\*.cy.{js,jsx,ts,tsx}',  
    setupNodeEvents(on, config) {  
      // Add plugins here  
    },  
  },  
  viewportWidth: 1440,  
  viewportHeight: 900,  
});

I also added environment-specific values using .env or cypress.env.json.

🧩 Step 6: Build Custom Commands

Inside cypress/support/commands.js:

Cypress.Commands.add('login', (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();  
});

Now in any test, I can just call:

cy.login(‘admin@floward.com‘, ‘securePassword!’);

💻 Step 7: Write Your First Real Test

login.e2e.cy.js:

import LoginPage from '../../support/pages/LoginPage';  

describe('Login Suite', () => {  
  it('logs in with valid credentials', () => {  
    LoginPage.visit();  
    LoginPage.fillEmail('admin@floward.com');  
    LoginPage.fillPassword('securePassword');  
    LoginPage.submit();  
    cy.contains('Welcome back').should('be.visible');  
  });  
});

LoginPage.js:

class LoginPage {  
  visit() {  
    cy.visit('/login');  
  }  

fillEmail(email) {  
    cy.get('\[data-cy=email\]').type(email);  
  }  
  fillPassword(password) {  
    cy.get('\[data-cy=password\]').type(password);  
  }  
  submit() {  
    cy.get('\[data-cy=submit\]').click();  
  }  
}  
export default new LoginPage();

🧨 Step 8: Conquering Real-World Errors (with Solutions)

❌ Cypress installation fails (Node version mismatch)

Fix:

 Update Node.js to the LTS version (18.x or 20.x) and clear the npm cache:

npm cache clean --force

❌ App crashes on WebView or iframe

Fix:

 Use the right context-switching commands or access the iframe body:

cy.get('iframe').its('0.contentDocument.body').should('not.be.empty');

❌ Element not found or detached

Fix:

 Use should('be.visible') or wait() smartly and avoid hardcoded delays:

cy.get('\[data-cy=submit\]').should('be.visible').click();

📌 Step 9: Bonus Power Moves for Real Teams

  • 🧪 Use data-cy attributes in HTML for reliable element selection.
  • 📸 Add screenshot/video reporting with video: true and screenshotOnRunFailure: true.
  • 🔁 Enable retries:
"retries": {  
  "runMode": 2,  
  "openMode": 0  
}
  • 🔄 Integrate with GitHub Actions or Jenkins CI.

🙌 Final Thoughts

Setting up Cypress wasn’t just about writing tests — it was about creating a test framework that could grow with the app. By using POM, smart selectors, and command abstraction, I reduced code duplication and improved reliability.

👉 This journey transformed our team from firefighting bugs to confidently shipping features.

📈 You are enaged with

  • Cypress testing tutorial
  • Cypress automation setup
  • Cypress POM structure
  • Cypress custom commands
  • Cypress installation errors
  • Cypress vs Appium
  • Build Cypress project from scratch

📚 Bonus: Want a GitHub Starter Repo or Video Series?

Let me know — I’ll send you the ready-made boilerplate to launch your Cypress automation today.

By Mohamed Said Ibrahim on July 5, 2025.

Exported from Medium on October 2, 2025.


This content originally appeared on DEV Community and was authored by mohamed Said Ibrahim