🚀 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
andscreenshotOnRunFailure: 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.
Top comments (0)