Buy me a coffee!

GregJacobs.com

I’m excited to share my journey building a new npm package, cypress-dynamic-fixtures. This package extends Cypress by overwriting cy.fixture() to load dynamic JavaScript fixtures. In this post, I'll walk you through how we built it, the challenges we faced with ESM vs. CommonJS, and how we tackled module alias issues.


Quick Summary

  • Purpose: cypress-dynamic-fixtures extends Cypress to load .js fixtures dynamically.
  • Key Features:
    • Overwrites cy.fixture() for .js files
    • Gathers fixtures at runtime using Node (no Babel config required)
    • Easily integrates with CommonJS
    • Backwards compatible w/ Static .json
  • Challenges Overcome:
    • ESM vs. CommonJS compatibility
    • Handling module aliases in Node
  • Benefits:

    • Flexibility to compute or modify fixture data on the fly
    • Minimal user setup
  • Links:


Introduction

Cypress is an amazing tool for end-to-end testing, but one limitation is that its built-in cy.fixture() command only supports static JSON or text files. I needed a way to load dynamic JavaScript fixtures—data that can change or be computed at runtime. This inspired the creation of cypress-dynamic-fixtures.

cypress-dynamic-fixtures:

  • Overwrites cy.fixture() to handle .js files.
  • Scans your cypress/fixtures folder at runtime using Node.
  • Transforms file paths into keys using a dash-to-camel and slash-to-$ convention.
  • Provides a minimal, zero-config setup for users.

How It Works

Dynamic Fixture Loading

The package works by scanning your cypress/fixtures folder for .js files using the glob package. It then transforms each file’s path into a unique key (e.g., cards/card_valid_visa.js becomes cards$cardValidVisa) and stores the data in config.env.__ALL_JS_FIXTURES__.

When you call:

cy.fixture('cards/card_valid_visa.js')

The overwritten command retrieves the corresponding data object from that mapping.

Plugin and Command Overwrite

The package is divided into two main parts:

  1. Plugin (Node-side):

    Scans fixture files and registers them in Cypress’ environment. This part uses CommonJS modules.

  2. Command Overwrite (Browser-side):

    Overwrites cy.fixture() to check for a .js extension and return dynamic data if available.


Example: Sharing Dynamic Data with futureExpDates.js

One of the benefits of dynamic fixtures is the ability to share computed or changing data across multiple files. Suppose we have a utility that calculates a future expiration date:

// cypress/utils/futureExpDates.js
const moment = require('moment');

const futureMonth = moment().format('MM');
const futureYear = moment().add(2, 'years').format('YY');
const futureExpDate = `${futureMonth}/${futureYear}`;

module.exports = {
  month: futureMonth,
  year: futureYear,
  exp_date: futureExpDate,
};

Then, in any credit card fixture, we can import and spread that data:

// cypress/fixtures/cards/card_valid_visa.js
const futureExpDates = require('@/utils/futureExpDates');

module.exports = {
  name: 'Card Valid Visa',
  card_number: '4111111111111111',
  cvv: '111',
  zip_code: '12345',
  ...futureExpDates, // merges in the dynamic expiration date
};

By calling cy.fixture('cards/card_valid_visa.js') in your tests, you’ll automatically get updated expiration data each time. No more need to manually "bump" static fixture dates / data! 💪


Buy me a coffee!


Backwards Compatible

Backwards compatible

In your existing project, you can use cy.fixture() with static .json data just as before. Nothing breaks.

  • cypress/fixtures/static_data.json
{
    "staticDate": "2025-03-25"
}
  • ./existing_spec.js
Then('I expect a static date to exist', () => {
    cy.fixture('static_data') // NOTICE no file extension required (optional: static_data.json)
        .then((fixture) => cy.contains(fixture.staticDate).should('exist'));
});


TROUBLESHOOTING: Overcoming ESM vs. CommonJS

Overcoming ESM vs. CommonJS

One major hurdle was that my initial fixture files used ESM syntax (export default) but Cypress’s Node environment expects CommonJS (module.exports). I resolved this by converting the fixture files to CommonJS. For example:

Before (ESM):

// cypress/fixtures/cards/card_valid_visa.js
export default {
  name: 'Card Valid Visa',
  card_number: '4111111111111111',
  cvv: '111',
  zip_code: '12345',
  ...dynamicData, // Spread in Additional dynamic data...
};

After (CommonJS):

// cypress/fixtures/cards/card_valid_visa.js
module.exports = {
  name: 'Card Valid Visa',
  card_number: '4111111111111111',
  cvv: '111',
  zip_code: '12345',
  ...dynamicData, // Spread in Additional dynamic data...
};

This change ensured that Node’s require() could load the fixtures without syntax errors.



Incorporating Module Aliases

Incorporating Module Aliases

My project uses Babel aliases via babel-plugin-module-resolver to simplify imports. However, Babel aliases only work during transpilation—not at runtime. To resolve this, I integrated the module-alias package. This package allows you to register aliases at runtime so that Node can resolve them correctly.

For example, in your fixture files you might use:

const futureExpDates = require('@/utils/futureExpDates');

To make this work at runtime, add the following at the very top of your main entry file (e.g., in your cypress.config.js):

require('module-alias/register'); // NOTE: Registers runtime module aliases (Babel aliases only work during transpilation)
const { dynamicFixturePlugin } = require('cypress-dynamic-fixtures/plugin');

module.exports = {
    e2e: {
        setupNodeEvents(on, config) {
            return dynamicFixturePlugin(on, config);
        },
    },
};

This ensures that your alias definitions (set in the _moduleAliases field of your package.json) are recognized by Node when loading your dynamic fixtures.

{
    "_moduleAliases": {
        "@": "cypress"
    }
}


Conclusion

cypress-dynamic-fixtures simplifies the process of using dynamic JavaScript fixtures in your Cypress tests. By handling fixture loading at runtime and overcoming challenges with ESM vs. CommonJS and module aliasing, this package makes your testing workflow more flexible and powerful.

I hope this post provides a clear overview of how the package works and the hurdles we overcame along the way. Happy testing!

Feel free to post your feedback, leave criticism, or share any ideas for improvement — I'd love to hear from you!


GregJacobs.com

If you're interested in learning more about me as a developer, check out some of my projects on my website at:

www.GregJacobs.com


Buy me a coffee!