Search code examples
javascriptunit-testingcypress

Reading dynamic file name in cypress test spec


How can I parameterise a file name in a Cypress spec and load that file? I'm injecting other parameters via environment variables which works fine, but whatever is provided to require(...) does not get found as file (although the error message specifies the file name in the exact same way as if it was hardcoded).

describe('run test', () => {
  const data = require('../fixtures/my.json');
  it('foo', () {
   ...
  });

So in other words the above snipped behaves differently to:

describe('run test', () => {
  const file = Cypress.env('file');
  const data = require('../fixtures/' + file);
  data.forEach((entry) => {
    it(`foo testing ${entry}`, () {
     ... run a test based on `entry`
    });
  });

when calling cypress from the command line via $(npm bin)/cypress run ... --env file=my.json. In the example where the file parameter is provided, a file not found error message is shown (with the exact file path as for the hardcoded file name example). I believe this is because the require() doesn't get resolved at runtime (but before and thus cannot accept dynamic arguments), but I am not 100% certain and would also consider an alternative for reading the file.


Solution

  • This is the basic structure of the dynamic test case creation spec. It is populated by a combination of environment variables and a file. The elements in the environment variable ids specify the key for the individual test cases' parameters which are provided in a separate file (also specified via an environment variable)). The ids are serialised and delimited through an outside script.

    Call this script via:

    $(npm bin)/cypress run --config-file cypress.json --headless --env ids=foo@bar@blah,paramsFile=myParamfile.json
    

    features/test_xyz.js

    let paramsFile = Cypress.env('paramsFile');
    let ids = Cypress.env('ids');
    
    
    describe('My test', () => {
      let params;
    
      before(() => {
        cy.task('readFileMaybe', paramsFile).then(fileContent => {
          if (fileContent === null) {
            throw new Error(`Could not find file ${paramsFile}.`);
          }
          // params = {id1: testcaseParam2, id2: testcaseParam2, ...}
          // `testcaseParamX` are objects or arrays and can therefore not be passed
          // easily into the test case via environment variables.
          params = JSON.parse(fileContent);
        });
      });
    
      // `ids` is a string -> needs to be split
      // e.g. `ids.split(delim).forEach(...)` to actually run.
      ids.forEach(id => {
        // Create test case.
        it(`test case for ${id}`, () => {
          let param = params[id];
    
          // run assertions here...
        });
      });
    });
    

    plugins/index.js (reference)

    module.exports = (on, config) => {
      on('task', {
        // Needs to be done through a task, as we can't access `fs` in test case
        // (this task runs in Node which allows use of external libraries).
        readFileMaybe(filename) {
          // console.log('Loading ', filename);
          if (fs.existsSync(filename)) {
            return fs.readFileSync(filename, 'utf8');
          }
          // console.log('Could not find ', filename);
          return null;
        }
      });
    };
    

    This was extremely painful to get working. However, the ability to run commands outside of the Cypress context directly in Node is very powerful and can be used for similar problems, too. I hope others find this useful.