DEV Community

mohamed Said Ibrahim
mohamed Said Ibrahim

Posted on • Originally published at Medium

🚀 How I Built a Robust Cypress.io

🚀 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
Enter fullscreen mode Exit fullscreen mode

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,  
});

Enter fullscreen mode Exit fullscreen mode

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();  
});
Enter fullscreen mode Exit fullscreen mode

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');  
  });  
});
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

🧨 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
Enter fullscreen mode Exit fullscreen mode

❌ 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');
Enter fullscreen mode Exit fullscreen mode

❌ 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();
Enter fullscreen mode Exit fullscreen mode

📌 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  
}
Enter fullscreen mode Exit fullscreen mode
  • 🔄 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.

Top comments (0)