Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixtures do not support JavaScript in .js files #1271

Open
dkreft opened this issue Feb 7, 2018 · 32 comments
Open

Fixtures do not support JavaScript in .js files #1271

dkreft opened this issue Feb 7, 2018 · 32 comments
Labels
prevent-stale mark an issue so it is ignored by stale[bot] topic: fixtures Fixture loading and usage type: bug type: feature New feature that does not currently exist

Comments

@dkreft
Copy link

dkreft commented Feb 7, 2018

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
@brian-mann
Copy link
Member

This is true, we likely need to rethink how we handle .js fixtures.

@tomatau

This comment has been minimized.

@jennifer-shehane jennifer-shehane added type: bug stage: ready for work The issue is reproducible and in scope labels Feb 16, 2018
@bensampaio

This comment has been minimized.

@zebulonj
Copy link

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.

@sandro-pasquali
Copy link

@brian-mann Sincere question: why are .js fixtures not treated as a typical .js files? Is there something about the context in a cy test which precludes that? Just trying to understand, and would love to help fix. Pretty normal (for me at least) to dynamically generate fixtures at runtime via .js. Kinda need this.

@brian-mann
Copy link
Member

@sandro-pasquali the reason is that there are two contexts - the browser context and node context.

Because of this, it's not as simple as what you'd expect. Fixtures are utilized inside of the browser, which means you use them like this... cy.fixture('foo.js', arg1, arg2, arg3) right?

That's fine except... how is the foo.jsevaluated? If it's evaluated in the browser context then that fixture file needs to already be part of the bundle that is served along with spec files. That way it could go through the normal spec preprocessor rules so you could use your typescript, webpack modules, etc on it. But wait: how does it become part of the browser bundle if you don't ever require(...) or import it directly in your spec files?

Alternatively, we could evaluate foo.js in the node context. That's possible, but then we run afoul of the same preprocessor rules. It would mean your specs are evaluated with preprocessors but then fixtures aren't. We might could add fixtures to those rules too which would would enable you to use typescript on there, but it really doesn't make sense in cases like with webpack because this foo.js isn't actually being served to the browser.

Another problem with the node context approach is that arguments would have to be JSON serializable. That's probably not that big of a problem but it means you cannot pass functions or instances or even undefined across the wire.

I'm probably more okay with opting to go with the node context approach as opposed to the browser.

There has been some dialog amongst our team to effectively rewrite the entire fixtures approach in Cypress because we feel it's been large superseded by better approaches and initially was sort of a "stop-gap" feature invented years ago.

@brian-mann
Copy link
Member

brian-mann commented Jun 3, 2018

@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

@simonhaenisch
Copy link

simonhaenisch commented Jun 27, 2018

I'm running into similar issues with something like this:

[
  { /* ... */ },
];

Error: 'notifications' is not a valid JavaScript object. SyntaxError: Unexpected token ;

Removing the trailing semicolon fixes it, however my prettier editor plugin auto-appends it on save.

I was expecting the fixtures to become part of the browser bundle and also tried export default [] first. I see what you mean with having to import them somewhere, however I wonder what the actual issue is with importing them?

This is more like default JS and more flexible anyway (passing the fixture as an object reference):

import notificationsFixture from '../fixtures/notifications';

describe('Notifications', () => {
  it('lists notifications', () => {
    cy.server();
    cy.route('GET', '/api/notifications', notificationsFixture);
    
    // or
    cy.fixture(notificationsFixture).as('notifications');
    cy.route('GET', '/api/notifications', '@notifications');

    // ...
  });
});

This could even become a backwards-compatible API change (i. e., check whether the fixture is a string or an object)?

Edit: I also don't get auto-reload of my tests when I change a fixture, which could be solved by this as well.

@chochihim
Copy link

chochihim commented Aug 10, 2018

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 ;

@szabolcsmaj
Copy link

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.

@bahmutov
Copy link
Contributor

bahmutov commented Jan 28, 2019 via email

@younesea
Copy link

younesea commented Apr 3, 2019

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.

@OneHatRepo
Copy link
Contributor

OneHatRepo commented May 16, 2019

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.

@liamhubers
Copy link

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 :|

@renatoruk
Copy link

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?

@mesqueeb
Copy link

What's the recommended way to import mock data into tests which rendered by triggering a function?
Eg. mockUser() or new Array(100).fill(null).map(mockUser)

Could I just create a TS file that exports these functions that create user mocks and import and execute the function inside cypress tests?

@bahmutov
Copy link
Contributor

bahmutov commented May 19, 2020 via email

@mesqueeb
Copy link

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

@sebastianjung
Copy link

@mesqueeb
I think manipulating the imported data in one test will leave the data manipulated in other tests. That's why cloning is important.

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.

@sebastianjung
Copy link

sebastianjung commented Aug 28, 2020

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.

@OneHatRepo
I am not able to access the data with this.props. Do i have to be in a specific scope or should i be able to use it within a it() closure?

Does the beforeEach() have to be inside the describe closure or is there any other advice you could possibly give?

Any help is very much appreciated!

@bahmutov bahmutov added the topic: fixtures Fixture loading and usage label Sep 15, 2020
@melibe23
Copy link

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.

@melibe23
Copy link

melibe23 commented Mar 9, 2021

This is killing me.
I need to use RegExp in a JSON due to this bug that Cypress has. Ok. But then, I found that match works with a literal RegExp but not with Object RegExp, and I don't know why. And I don't know what to do.

@supasympa
Copy link

supasympa commented Apr 13, 2021

Hi,

Is there any update on this?

I have a third party script that controls features within my application and I don't control how the external script works, it invokes a global method within the application itself (yuck - I know). The script is loaded by the main app and I'd like to intercept and replace the script in the tests.

I'd like to be able to do it something like this:

   cy.intercept('https://third-party.com/js/foo.js', {
      fixture: 'third-party-com/js/foo.js',
    }).as('3rd-party-foo')
...

    cy.wait('@3rd-party-foo')

Is there an alternative way to achieve this?

@supasympa
Copy link

supasympa commented Apr 13, 2021

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')

@MoKhajavi75
Copy link

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 update on this?
I need to add the fixture directory to .prettierignore 😐

@andrewagain
Copy link

andrewagain commented Jul 28, 2021

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",
        },
      });

@poponuts
Copy link

any updates on this?

@MoKhajavi75
Copy link

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 ❤️

@MoKhajavi75
Copy link

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.

There was some error when I tried this so here is my version (tested on [email protected]):

plugins/index.js:

module.exports = (on, config) => {
  on('task', {
    fixture({ name, address }) {
      const result = require(`../fixtures/${address}`);
      return { result, name };
    }
  });

  return config;
};

How to use it:

cy.task('fixture', {
  name: 'someName',
  address: 'someWhere/file.js'
}).then(function({ result, name }) {
  this[name] = result;
});

Finally:

cy.get('@something').should('contain.text', this.someName.x);

reinhrst added a commit to reinhrst/cypress-documentation that referenced this issue Jan 26, 2022
(Taking a stab at) adding documentation on `.js` fixture files. Current documentation mentions that `.js` fixture files are possible, but doesn't describe how to use them, leading to issues like cypress-io/cypress#1271 .

I do think the current `.js` fixture support should get a good rethink (as mentioned 4 years ago in that issue) , but in the meantime it should have some documentation on how to use it (or fully remove any mention of it from the documentation, making it "unsupported").
@aryankarim
Copy link

aryankarim commented Feb 2, 2022

I wanna import a JSON file outside fixtures via a JavaScript code. I tried requiring it into an array but it doesn't work.
e.g.
// fixtures/data.js
[require('../../data.json')]

is that even possible?

@cypress-bot cypress-bot bot added stage: backlog and removed stage: ready for work The issue is reproducible and in scope labels Apr 29, 2022
@hloughrey
Copy link

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. 👍

@Maxim-Mazurok
Copy link

Maxim-Mazurok commented May 31, 2022

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.

@nagash77 nagash77 added the prevent-stale mark an issue so it is ignored by stale[bot] label Apr 3, 2023
@jennifer-shehane jennifer-shehane added the type: feature New feature that does not currently exist label Oct 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
prevent-stale mark an issue so it is ignored by stale[bot] topic: fixtures Fixture loading and usage type: bug type: feature New feature that does not currently exist
Projects
None yet
Development

No branches or pull requests