cypress: Fixtures do not support JavaScript in .js files

Current behavior:

Any attempt to use valid ES5 (e.g. var, require) let alone ES6 (import, export, const) results in syntax errors, e.g.:

Error: 'admin.js' is not a valid JavaScript object.SyntaxError: Unexpected token var

Because this error occurred during a 'before each' hook we are skipping the remaining tests in the current suite: 'when login is valid'

The only “javascripty” construct that seems to be allowed is module.exports =.

Desired behavior:

.js fixtures should support valid JS syntax (ES6, preferably).

How to reproduce:

Create a fixture containing valid ES6 syntax

const user = 'Joe'

export default {
  user,
}

Then use it:

  cy.fixture('auth.js')

Result:

Error: 'auth.js' is not a valid JavaScript object.
auth.js:3
export default {
^
ParseError: Unexpected token

Create a fixture using valid ES5 code

var user = 'Joe';

module.exports = {
  user: user
};

Result:

Error: 'auth.js' is not a valid JavaScript object.SyntaxError: Unexpected token var
  • Operating System: Darwin Kernel Version 15.6.0: Mon Nov 13 21:58:35 PST 2017; root:xnu-3248.72.11~1/RELEASE_X86_64
  • Cypress Version: 1.4.2
  • Browser Version: Electron 53, Chrome 64

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 39
  • Comments: 32 (6 by maintainers)

Commits related to this issue

Most upvoted comments

It’d be lovely if *.js fixtures were simply executed like any normal JavaScript, and they were expected to export the same fixture data that might otherwise be JSON encoded in a *.json fixture. Prime use case: using faker in a .js fixture to generate data.

It looks like this works, not ideal but might help people out.

cy.readFile('fixtures/3rd-party-scripts/3rdparty/js/foo.js', 'utf8' ).then((stubResponse) => {
    cy.intercept('https://3rdparty.com/js/foo.js', (req) => {
      req.reply(stubResponse)
    }).as('3rd-party-foo')
  })
  
  ...
  
    cy.wait('@3rd-party-foo')
  

I added a command to do this on the project I’m working on:

Cypress.Commands.add('mockThirdPartyJS', (url, fixturePath, _as) => {
  return cy.readFile(
    `fixtures/${fixturePath}`,
    'utf8'
  ).then((stubResponse) => {
    cy.intercept(url, (req) => {
      req.reply(stubResponse)
    }).as(_as);
  })
})

Then usage is:

cy.mockThirdPartyJS(
    'https://some-provider.com/a-script.js', 
    'some-provider/a-mocked-script.js', 
    'some-provider_a-script')
)

...

cy.wait('@some-provider_a-script')

Maybe we need to update the document to state that js fixture is not what people normally expect. I am quite surprised that this fixture works:

module.exports = {
  a: 1
}

but not this (with semicolon automatically added by my editor setting)

module.exports = {
  a: 1
};

This throws Error: 'myFixture' is not a valid JavaScript object.SyntaxError: Unexpected token ;

any updates on this?

Thanks @bahmutov for mentioning cy.task.

I just started with Cypress and I wanted to prevent the issue that @SLOBYYYY had. To do that, I created javascript files that exports an object and I get it with the following task:

plugins/index.js

module.exports = (on, config) => {
  on('task', {
    fixture (fixtures) {
      const result = {}

      if (Array.isArray(fixtures)) {
        fixtures.forEach(fixture => {
          result[fixture] = require('../fixtures/elements/' + fixture + '.js')
        })
      } else {
        result[fixtures] = require('../fixtures/elements/' + fixtures + '.js')
      }

      return result
    }
  })

  return config
}

So in your tests you can use it like this

// Getting one fixture
cy.task('fixture', 'user').then(f => {
      console.log(f) // outputs {user: {}}
})

// Getting multiple fixtures
cy.task('fixture', ['user', 'product']).then(f => {
      console.log(f) // outputs { user: {}, product: {]}
})

This way you can also use libraries like faker in your .js files. You can adjust the task so that it fits your project.

Hey We really need to use js in our fixtures for such simple things (I. e. generate today date) or use Faker!

Any update on the estimated timeline or roadmap to fix this? cc: @brian-mann @jennifer-shehane

Thanks in advance ❤️

Seems like it would help a ton of people if there was a mechanism to load a fixture as-is without doing whatever special magic Cypress is attempting to do with .js files. Maybe raw: true or transpile: false?

Like so:

      cy.intercept("/myjs.js", {
        fixture: "stubjs.js",
        raw: true,
        headers: {
          "content-type": "application/javascript",
        },
      });

Hello! Any news about this bug? I will much appreciate the effort in a fix, our tests are getting bigger and bigger and are hard to maintain without this functionality.

@bahmutov thanks for your guidance! What’s the benefit of using fixtures over just importing mock objects directly into your test files ?

This is my solution:

export const interceptFingerprintJS = () => {
    const replaceAndIntercept = (script) => {
        script = script.replace(/%fingerprintId%/, fingerprintId);
        cy.intercept(`/assets/vendor/f/f.min.js`, { body: script }).as('fingerprintJs');
    };
    global.myCypressCache = global.myCypressCache || {};
    let cachedFile = global.myCypressCache.fingerprint;
    if (!cachedFile) {
        cy.readFile('cypress/fixtures/fingerprint.js').then((script) => {
            global.myCypressCache.fingerprint = script;
            replaceAndIntercept(script);
        });
    } else {
        replaceAndIntercept(cachedFile);
    }
};

Primitive caching included, the downside is using global scope, but should be fine.

Alternative solution is to embed script in test:

cy.intercept(`/assets/vendor/file.js`, {
    body: `alert('my script lives here')`,
}).as('fileJs');

The downside is no formatting/linting/etc.

I was able to get factory.ts & faker working within a fixture with the following:

// fixtures/someFile.ts

const MyFactory = Factory.Sync.makeFactory<--- some TS type --->({
  id: faker.datatype.string(),
  name: faker.random.arrayElement<string>(--- some array ---),
  types: faker.random.arrayElements<string>(--- some array ---, 2),
});

export const myFixture: <--- some TS type ---> = {
  types: faker.random.arrayElements<string>(--- some array ---, 2),
  data: [
    MyFactory.build(),
    MyFactory.build(),
    MyFactory.build(),
    MyFactory.build(),
  ],
};

then in my spec file:

import { myFixture } from '../fixtures';

describe('Some Module', () => {
   beforeEach(() => {
       cy.intercept({ method: 'POST', path: '/some/api/endpoint' }, myFixture).as('someAlias');
   })
   ...
})

Hope this helps someone. 👍

Maybe we need to update the document to state that js fixture is not what people normally expect. I am quite surprised that this fixture works:

module.exports = {
  a: 1
}

but not this (with semicolon automatically added by my editor setting)

module.exports = {
  a: 1
};

This throws Error: 'myFixture' is not a valid JavaScript object.SyntaxError: Unexpected token ;

Took me an hour to find 😐

Yep, same here. I actually resorted to having a __fixtures__ directory inside integration/{moduleName} directory. What is the use case for the fixtures you need? I am trying to mock some responses, so for now it seems better to use them directly as a value to cy.route options parameter.

import {response} from "__fixtures__/my-response.js";

cy.route({
  method: "GET", // Route all GET requests
  url: "https://api.my-app.com/**",
  response: response.body,
});

But, I am not really sure will this break something in the future, I haven’t researched it fully yet. Does anyone use response mocks like this?

I could not get the task option to work, as my .js fixture files had ES6 imports that were constantly failing. In the end, I just did a standard import of each fixture file at the top of each spec file and then cloned the data in a beforeEach.

import props from '../../../fixtures/Models/Helpers/props';
import _ from 'lodash';

....

	beforeEach(function() {
		cy.then(function() {
			return _.cloneDeep(props);
		}).as('props');
	});

The fixture was then available in each test as this.props.

Take a look at cy.task - it can be used to generate any object in node context, including fixture json files that you could load later. Or use cy.writeFile to do the same. Or even JavaScript right from the tests and instead of fixture stub in cy.route use the created object.

Sent from my iPhone

On Jan 27, 2019, at 18:56, SLOBYYYY notifications@github.com wrote:

Any news about this? It’s really hard to maintain our fixtures since they are quite big with only slight changes here and there. One change in any of them and you have to update all of them.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

@sandro-pasquali forgot to mention that you can interop between the browser + node context seamlessly with cy.task. You could effectively design or come up with your own fixture implementation without using cy.fixture at all. Just defer to a cy.task, pass in arguments, and then dynamically call into whatever fixture or node.js file you want to do.

This is effectively the approach I described above as the 2nd option. The only downside is that you’ll have to write your node code for version 8.2.1 and it will not support typescript at all at the moment