13.12.2025, By Stephan Schwab
What if every product demo you gave also served as a quality gate in your CI/CD pipeline? Cypress, traditionally positioned as an end-to-end testing tool, can be repurposed to create executable demonstrations that both showcase application features to stakeholders and validate critical user journeys. This approach delivers double value from a single investment while keeping your testing pyramid properly balanced.
Most teams encounter Cypress as “yet another UI testing framework.” Install it, write some selectors, click some buttons, assert some outcomes. The documentation emphasizes testing, the tutorials focus on testing, and so teams dutifully add Cypress tests to their suites — often duplicating coverage they already have at lower levels.
There is a more powerful way to think about Cypress: as a tool for creating executable demonstrations that happen to also function as tests.
Before diving into the approach, let’s revisit the testing pyramid — a concept that remains as relevant today as when Mike Cohn introduced it. The pyramid suggests that the bulk of your automated tests should be fast, isolated unit tests at the base. Above that, a smaller layer of integration tests validates that components work together. At the very top, a thin layer of end-to-end (E2E) tests confirms that critical user journeys work through the complete system.
/\
/ \ E2E Tests (few, slow, expensive)
/----\
/ \ Integration Tests (moderate)
/--------\
/ \ Unit Tests (many, fast, cheap)
--------------
The pyramid shape is intentional. E2E tests are slow, flaky, and expensive to maintain. They require the full application stack to be running. A single selector change can break dozens of tests. Teams that invert this pyramid — writing more E2E tests than unit tests — often find themselves drowning in test maintenance while still shipping bugs.
So why use Cypress at all? Because those few E2E tests at the top of the pyramid serve a purpose that unit and integration tests cannot: they validate that the application works as a user would experience it, through a real browser, with all the JavaScript, CSS, and network interactions intact.
The key is to be strategic about what you test at this level.
Consider the typical rhythm of a development team. Features get built, and then someone needs to demo them — to product owners, stakeholders, or during sprint reviews. These demos follow a script: “First, I’ll log in as an admin user. Then I’ll navigate to the settings page. Watch as I update the notification preferences. See how the confirmation message appears?”
What if that demo script were executable code?
describe('Notification Preferences Feature Demo', () => {
it('allows an admin to update notification settings', () => {
// Authenticate as an admin user
cy.login('admin@example.com', 'securepassword')
// Navigate to the settings area
cy.get('[data-cy="settings-menu"]').click()
cy.get('[data-cy="notifications-tab"]').click()
// Update email notification preferences
cy.get('[data-cy="email-notifications-toggle"]')
.should('be.visible')
.click()
// Adjust notification frequency
cy.get('[data-cy="frequency-dropdown"]').select('Weekly')
// Save changes
cy.get('[data-cy="save-preferences"]').click()
// Verify the confirmation appears
cy.get('[data-cy="success-message"]')
.should('be.visible')
.and('contain', 'Preferences saved successfully')
})
})
This code does three things simultaneously:
For stakeholder presentations, run the tests in headless mode with cypress run — Cypress automatically records videos that you can pause and narrate at your own pace. Alternatively, add cy.pause() commands at key moments in your code; when running in interactive mode with cypress open, execution stops at each pause point, letting you explain what’s happening before clicking “Resume” to continue. Either way, the same code that serves your demo also runs in your pipeline to ensure this critical user journey hasn’t regressed.
The mental shift is subtle but important. When you set out to “write a test,” you focus on coverage, edge cases, and assertions. When you set out to “write a demo,” you focus on the user journey, the narrative, and the visible outcomes.
This changes how you structure the code:
describe('Order Fulfillment Workflow', () => {
beforeEach(() => {
// Set up the scenario: a customer has placed an order
cy.task('seedOrder', { status: 'pending', items: 3 })
cy.login('warehouse@example.com')
})
it('demonstrates the complete fulfillment process', () => {
// Warehouse staff sees pending orders on their dashboard
cy.visit('/fulfillment/dashboard')
cy.get('[data-cy="pending-orders"]')
.should('contain', '1 order awaiting fulfillment')
// They open the order details
cy.get('[data-cy="order-row"]').first().click()
cy.get('[data-cy="order-items"]')
.find('li')
.should('have.length', 3)
// Each item gets scanned and marked as picked
cy.get('[data-cy="scan-item-input"]').type('SKU-001{enter}')
cy.get('[data-cy="picked-count"]').should('contain', '1 of 3')
cy.get('[data-cy="scan-item-input"]').type('SKU-002{enter}')
cy.get('[data-cy="scan-item-input"]').type('SKU-003{enter}')
cy.get('[data-cy="picked-count"]').should('contain', '3 of 3')
// Order is marked ready for shipping
cy.get('[data-cy="complete-fulfillment"]').click()
cy.get('[data-cy="order-status"]')
.should('contain', 'Ready for Shipping')
})
})
Notice the comments. They read like a demo script, not test documentation. When you present this to stakeholders, you can literally read the comments aloud while Cypress executes the steps.
Resist the temptation to select elements by CSS classes or complex DOM paths. Data attributes like data-cy are explicit, stable, and communicate intent:
// Fragile - breaks when styling changes
cy.get('.btn.btn-primary.submit-form')
// Robust - survives refactoring
cy.get('[data-cy="submit-order"]')
Your demos will share common patterns. Encapsulate them:
// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
cy.visit('/login')
cy.get('[data-cy="email-input"]').type(email)
cy.get('[data-cy="password-input"]').type(password)
cy.get('[data-cy="login-button"]').click()
cy.url().should('not.include', '/login')
})
Cypress.Commands.add('addToCart', (productId) => {
cy.visit(`/products/${productId}`)
cy.get('[data-cy="add-to-cart"]').click()
cy.get('[data-cy="cart-notification"]').should('be.visible')
})
Now your demos read even more cleanly:
it('demonstrates a complete purchase flow', () => {
cy.login('customer@example.com', 'password')
cy.addToCart('product-123')
cy.addToCart('product-456')
cy.visit('/checkout')
// ...continue the journey
})
Remember the pyramid. You’re not trying to test every permutation at the E2E level. Aim for a handful of demos that cover the critical user journeys — the paths that, if broken, would make the application unusable or cost the business significant money.
A typical application might have:
That’s perhaps 5-10 Cypress demos total, not 500 E2E tests.
In your CI/CD configuration, these demos become a quality gate:
# Example GitHub Actions workflow
cypress-demos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cypress-io/github-action@v6
with:
start: npm run start:ci
wait-on: 'http://localhost:3000'
spec: cypress/e2e/demos/**/*.cy.js
When a demo fails in the pipeline, it means a critical user journey is broken. This is a much stronger signal than “a test failed.” It forces a conversation: “The order fulfillment demo is failing — we cannot ship this release until warehouse staff can complete orders.”
The traditional approach treats demos and tests as separate activities. Someone writes Cypress tests for the pipeline. Someone else prepares PowerPoint slides and manually clicks through the application for stakeholders. These activities happen independently, consuming time and often falling out of sync.
The executable demo approach collapses these into one artifact. The demo you show stakeholders is the same code that guards your production releases. When the application changes, updating the demo updates the test. When stakeholders ask “can you show me that feature again?”, you run the demo — and you’ve just verified it still works.
This isn’t about abandoning proper testing. Your unit tests still cover the edge cases. Your integration tests still verify component interactions. The Cypress demos sit at the apex of your pyramid, ensuring the critical paths work end-to-end while serving double duty as stakeholder communication tools.
The next time you reach for Cypress, ask yourself: am I writing a test, or am I scripting a demo? The answer might change how you approach the entire exercise.
Let's talk about your real situation. Want to accelerate delivery, remove technical blockers, or validate whether an idea deserves more investment? Book a short conversation (20 min): I listen to your context and give 1–2 practical recommendations—no pitch, no obligation. If it fits, we continue; if not, you leave with clarity. Confidential and direct.
Prefer email? Write me: sns@caimito.net