Cypress: Doubt: load multiple fixtures once per Cypress execution

Created on 17 Jun 2019  ·  10Comments  ·  Source: cypress-io/cypress

I have 3 sites, and each site has 3 envs, each with their config.json and fixtures.json. However, part of the fixture is common, like routes which contains endpoints. Following the DRY pattern, I refactored the common bits to a routes.json.

Screenshot 2019-06-17 at 18 37 26

I basically want to load multiple fixtures...

  • avoiding "pyramid of doom of fixtures", as in this example
  • if possible, once per Cypress execution (it's a bit of a waste on a before()), unlike this example

Ideally I would be able to access each fixture file independently like:

cy.visit(routes.account.edit)
...
cy.get(selectorNotification).should("contain", keywords.dialog.success)

Given a file like:

// routes.json
{
  "account": {
    "edit": "/account/edit"
  },
  "users": {
    "list": "/users",
    "search": "/user/search"
  },
  ...
} 

I've tried a mix of cy.fixture, Cypress.config, fs.readFile, cy.readFile to no avail and now I'm too biased to think out of the box.

Please, can you advise me a solution? I feel like I'm over-engineering it.

question

Most helpful comment

This works:

const configHelper = new ConfigHelper()
let routes, keywords, fixtures
before(() => {
  cy.fixture(configHelper.getRoutesPath()).then(c => {
    routes = c
  })
  cy.fixture(configHelper.getKeywordsPath()).then(c => {
    keywords = c
  })
  cy.fixture(configHelper.getFixturesPath()).then(c => {
    fixtures = c
  })
})

describe("Example component under test", function() {
  it("expects a certain behaviour", function() {
    expect(routes).to.be.not.undefined
    expect(keywords).to.be.not.undefined
    expect(fixtures).to.be.not.undefined
  })
})

But it's annoying having to copy that whole block of boilerplate to every test file.

All 10 comments

This works:

const configHelper = new ConfigHelper()
let routes, keywords, fixtures
before(() => {
  cy.fixture(configHelper.getRoutesPath()).then(c => {
    routes = c
  })
  cy.fixture(configHelper.getKeywordsPath()).then(c => {
    keywords = c
  })
  cy.fixture(configHelper.getFixturesPath()).then(c => {
    fixtures = c
  })
})

describe("Example component under test", function() {
  it("expects a certain behaviour", function() {
    expect(routes).to.be.not.undefined
    expect(keywords).to.be.not.undefined
    expect(fixtures).to.be.not.undefined
  })
})

But it's annoying having to copy that whole block of boilerplate to every test file.

Why can't you use this solution (load once, add to test context before each) https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/fundamentals__fixtures/cypress/integration/load-fixtures-spec.js#L100

Or even better, if you use JSON fixtures just require them from the spec, they will be bundled, see https://github.com/cypress-io/cypress-example-recipes/commit/949c65adbd8fb38a4c23012b1625db6f160d6420

Thanks for replying ;)

_This solution (load once, add to test context before each)_

That's what I'm doing in my comment above. It loads the fixtures once per test file, ie. 10x test files -> 10x load fixtures. What I wanted was to load once per cypress execution, ie. 10x test files -> 1x load fixtures.


_if you use JSON fixtures just require them from the spec_

wow, that works? 😮 const city = require('../site/env/routes.json') that's it?
My only problem is that the path is dynamic based on 2 env vars. So it would look like `const routes =

require(`../config/${Cypress.env("REALM")}/routes.json`)

Edit: It's impossible to have dynamic imports.


Any other idea?
I think this is a reasonable use case, having multiple envs and sites/translations of the same site.

Well require should work, also you can load them once using before and just attach to Cypress object and they will stay there. I would not do this though because it makes things magical and unclear and harder to debug later for someone who is not familiar with this trick later

Sent from my iPhone

On Jun 18, 2019, at 10:34, Diogo Nunes notifications@github.com wrote:

Thanks for replying ;)

This solution (load once, add to test context before each)

That's what I'm doing in my comment above. It loads the fixtures once per test file, ie. 10x test files -> 10x load fixtures. What I wanted was to load once per cypress execution, ie. 10x test files -> 1x load fixtures.

if you use JSON fixtures just require them from the spec

wow, that works? 😮 const city = require('../site/env/routes.json') that's it?
My only problem is that the path is dynamic based on 2 env vars. So it would look like `const routes =

require(../config/${Cypress.env("REALM")}/routes.json)
Edit: It's impossible to have dynamic imports.

Any other idea?
I think this is a reasonable use case, having multiple envs and sites/translations of the same site.


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

Sorry for the dumb question, but how do you "attach to the Cypress object"?

Cypress.myFixture = require(“path/to/fixture/files”)

Sent from my iPhone

On Jun 21, 2019, at 11:19, Diogo Nunes notifications@github.com wrote:

Sorry for the dumb question, but how do you "attach to the Cypress object"?


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

Doesn't work: Error: Cannot find module 'cypress/config/imovirtualpt/routes.json'

before(() => {
  const routesPath = ConfigHelper.getRoutesPath()
  Cypress.routes = require("cypress/config/" + routesPath)
})

describe("Example component under test", function() {
  it("expects a certain behaviour", function() {
    expect(Cypress.routes).to.be.not.undefined
  })
})

I guess I'll have to settle with the solution at https://github.com/cypress-io/cypress/issues/4483#issuecomment-503014007

Another solution could be to collect fixtures in a Promise
The paths parameter is an array of paths to fixtures

const fixtures = (paths) => Promise.all(paths.map(p => cy.fixture(p)))

Use like this:

fixtures(['path/1', 'path/2']).then(filesContents => {
  console.log(filesContents)
})

Another solution could be to collect fixtures in a Promise
The paths parameter is an array of paths to fixtures

const fixtures = (paths) => Promise.all(paths.map(p => cy.fixture(p)))

Use like this:

fixtures(['path/1', 'path/2']).then(filesContents => {
  console.log(filesContents)
})

Thank you so much for this!
I managed to build a multi-upload command based on this input, which takes an array of fixture-paths and names and upload multiple files in a single array.

In case anyone needs it:
```js
Cypress.Commands.add('uploadMultiple', ({localPaths = [], fileNames = [], mimeType = 'image/png'}) => {
const fixtures = (paths) => Promise.all(paths.map(p => cy.fixture(p)))
const encoding = 'base64'

fixtures(localPaths).then(filesContents => {
const payload = fileNames.map((fileName, i) => (
{
fileContent: filesContents[i],
fileName,
encoding,
mimeType
}))
cy.get('input[multiple]')
.upload(payload)
})
})
```

Next step is to determine the right MimeType based on the fileName extension, but that should not be too difficult.

@deventually @IMightGitIt running Promise.all on cy.fixture doesn't seem to work for me. Is there anything else expected but not mentioned in this piece of code?

Was this page helpful?
0 / 5 - 0 ratings