Cypress-cucumber-preprocessor: To support hooks

Created on 9 Mar 2018  路  34Comments  路  Source: TheBrainFamily/cypress-cucumber-preprocessor

hooks are not supported feature yet

enhancement good first issue help wanted released

Most helpful comment

Hi @lgandecki

To make things clear, I have made few observations regarding the support for hooks.
can you correct those I am wrong.
I have created a hooks.js file in step_definitions folder and have placed the following code:

Here is my directory structure:

Cypress
      -Integrations
            -Folder 1
                 -step_definitions
                         -step_definitions1.js
                          - hooks.js
                  a.feature
            -Folder 2
                  -step_definitions
                         -step_definitions2.js
                   b.feature
before(() => {
    cy.log('I Run for each feature before all the scenarios');
});

beforeEach(() => {
    cy.log('I Run for each feature before Every Scenario');
});


afterEach(() => {
    cy.log('I Run for Each Feature After every Scenario');
});

after(() => {
    cy.log('I Run for Each Feature After all the scenarios');
}

Here are the observations:

  • Before block executed before all my scenario's [suppose if we select run all tests, it will be executed multiple times before all my tests start to run ]

  • BeforeEach block executed before each scenario

  • AfterEach block executed before each scenario
  • After block executed After all my scenario's
  • Even tough we placed hooks.js in Folder1, before, beforeEach, after, afterEach got executed when i am running b.feature

  • before, beforeEach, after, afterEach are global not limited to a feature

Missing functionality for hooks:

  • no way to restrict the hook to run only for a particular feature
  • no way to pass tags to hooks

All 34 comments

Is it possible to add a BeforeFeature hook? Such that whatever I specify in BeforeFeature will run before each feature file? (Such as clearing the test database)

@stevepkuo I don't think you shouldn be running things before feature itself, your scenario should be independent, and they should start with a clean state everytime. That's what you can use Background functionality for

How would the hooks work ? Would they be defined in the feature files ? In the steps file ? (but then the question is, where are they placed).

I'd be happy to attempt a PR but I'm not sure how we want to design this.

I think we would probably want to design it to resemble the cucumberjs hooks:

https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/hooks.md

So, in step definitions.
I鈥檓 not sure what are the rules for how the before would work - do we make them work on file basis?

A good attempt might be to install cucumberjs and play with their functionality and then try to reflect it here.

The beforeAll afterAll might be the easiest to start :-)

It seems that the hooks are "global" in the sense that we can't restrict them to a specific feature or scenario (which is normal I think). I think it might be better to define them in their own support file or folder with a new configuration option, similar to the ste_definitions folder.

Yeah, I think that makes sense actually.
But that's more of a pattern. If someone wants they should be able to put those anywhere.

actually, you can have tagged hook where you restrict hooks based on scope of tags

What is the advised if i need to have my database reset after every scenario? The scenario is independent each time, but in my case I rely on an external service that needs to be reset

is background step not suitable for that?

@lgandecki Although I forgot about the Background steps (thanks for remind myself!), I was trying to attach something like the beforeEach, to just wipe the api application after every Scenario.

It can be done through the Background steps as well I guess, just a preference if you want to have the api wipe/refresh as a step.

Do you have any opinion about this? Does CCP support Contexts BTW?

  @before-hook1
  @before-hook2
  @after-hook3
  Scenario: Do Something
    Given I do something
    When I do something else
    Then something else should happen

Could we support adding those kind of hooks inside the .feature files? It would be really helpful controlling what you want to run and when, so you don't repeat unnecessarily a large amount of code when you have a chain of actions that reuses a certain feature. Inside step_definitions or hooks folder we could have the implementation.

@lgandecki Hi! Global hooks should available for the next reasons:

  • global hook give the possibility to support DRY principle (when user want to do any interactions with his app before all or before each scenario/feature)
  • global hooks give the possibility to do any actions when any scenario have failed (like making screenshots or saving browser console logs anywhere)
  • global hooks give the possibility to make pre-post conditions through the API calls for saving time (it also could be in the background, but DRY will not be supported in that case).
    In cucumber all such functions were in the support/*.hooks.ts (or other) files. And it also could contain other settings for Cucumber, like setDefaultTimeout(N)

I am also not sure about questions whether the hooks are needed or not. The cucumber is just a DSL wrapper of actual test. In usual (mocha/chai) DSL wrapper you have before/after hooks, same way it could work for cucumber DSL.
As cucumber usually used for defining plain-language or business language, it's strange to use technical steps like

Given I want to mock backend for this test if the record mode is set

I would rather decide

Before(() => {
  cy.route(...) if !Cypress.env('RECORD_MODE')
})

vs.

Given("I want to mock backend for this test if the record mode is set", () => {
  cy.route(...) if !Cypress.env('RECORD_MODE')
});

@lgandecki is there any hint when it could be a reality?

I agree with @alexttransisland that something technical has no place in the Cucumber syntax. My co-worker is trying to transform his tests to Cucumber style now. He has a login step and putting that as Given when I am logged in and on the page doesn't make any sense.

Is there anybody that has done some work towards this already?

Yeah we're doing something similar as that logged in step @kayvanbree, but we've placed it in the Background section. How do other people handle this logged in requirement? Obviously placing it in the Background section means it will run before each scenario, but this causes so much redundant repetition. Also any ideas about achieving a global beforeAll and afterAll hook, for things like database seeding/clean up?

I did not know background was already supported. There's no docs for it. I created #116 for that.

Although I like the background section, but I would rather use it for simple feature-specific stuff, like navigating to the correct page. Seeding/resetting the database just doesn't belong in a feature file.

@leksinteractive what do you mean it creates redundant repetitions? Do you want to leak state between different scenarios? Could you show me an example?

@kayvanbree good catch. If you have a moment I'd appreciate a PR with the docs update.
Could you tell me how would you do seeding/resetting the db in a pure cucumber project? using hooks, based on tags? but that's sill in a feature file..

@lgandecki the usual way to use cucumber in other languages is env file where you could do all the pre-setup needed. Something like env.js or cucumberEnv.js with

before(() => {
  dbHelper.connect('...')
  dbHelper.seed('...')
})

beforeEach(() => {
  if (Cypress.env('RECORD_MODE')) {
    cy.recordServerResponses()
    cy.recordRoutes()
  } else {
    cy.readRoutes(fixtureName)
  }

  cy.viewport('iphone-6')

  cy.visit('whatever')
})

afterEach(() => {
  dbHelper.cleaniseData() //remove test data generated by previous test but don't touch the seed
})

after(() => {
  if (Cypress.env('RECORD_MODE')) {
    cy.writeRecordedFixtures(fixtureName)
  }
})

and then in cucumber feature you just execute your test or do additional setup per requirements

@alexttransisland
I'm pretty sure you can do this with this package as well, have you tried?
https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Hooks

@lgandecki Of course I can do this if we talk about mocha/chai, but here we talk about Cucumber DSL. And there is no way to do this for cucumber features. (run same hooks)

I still don't understand. Just put them in one of your step definitions files and they should work just fine :) I guess we could add a configurable file (like env.js) that would be imported automatically before all tests.
I personally prefer to create a js file and import it in the step definitions file that require a particular configuration (with before/after steps)

btw, this cucumber is not fully a cucumber, it does use mocha beneath, so you can use all the mocha hooks. Your scenario becomes an it() block, and your feature is describe() block.

@lgandecki great. It simply worked
so actual beforeFeature() block becomes a simple before block for the file which contains first step for cucumber feature. 馃
Thanks!

No problem.. Happy it worked!

@lgandecki @alexttransisland should it be documented that you can use the mocha hooks? That might be useful for newcomers.

(I created PR #117 fro the background section docs)

I for one would love to see a small code example (or ideally proper documentation) of how we can use the cypress hooks within scenario steps and step definition files. Specifically i'm interested in hooks before/after each feature file, before/after the whole suite and a hook for after each scenario (as we already have the background section for before each scenario). Thanks!

@leksinteractive There are no such hooks as far as I understand. So basically to use beforeAll() hook it should be written in the way

before(() => {
  ...
})

Given('this is first scenario step', () => {
  ...
})

to use beforeEach you could have same approach as ^ with background section, like

beforeEach(() => {
  ...
})

Given('this is background scenario step', () => {
  ...
})

to use after or afterEach

after(() => {
  ...
})

Then('this is the last scenario step', () => {
  ...
})
afterEach(() => {
  ...
})

Then('this is the last scenario step', () => {
  ...
})

According to @lgandecki current Cucumber preprocessor implementation is just a DSL which translates

Your scenario becomes an it() block, and your feature is describe() block.

meaning there should be additional codebase which supports beforeFeature or beforeScenario as these entities are not part of .js, but parts of .feature

Though my example supports any actions before/after particular step (considering you did proper files split, because it applies to _all_ steps the hook is written for). That's not ideal, but somehow could solve issues for small amount and non-reused steps.

@kayvanbree yeah, I'm happy to extend the documentation. I'm a bit worried of duplicating information from either cucumberjs OR cypress - especially if they own the API. But I think this information is so basic that it's ok to have it here. If you would like to make another PR, or you @alexttransisland - you have a good intro here, that would be great.
I think the best doc would also discuss the advantages and disadvantages (or things that are currently not possible or difficult), as Alex was starting to define in the comment above.

@lgandecki I used the background section (added to readme) and not the hooks, so maybe @alexttransisland is better for the job.

About the redundant documentation: The best part of Cypress is that you can find anything about it in one place. You don't have to look up parts in your assertion libraries docs, and some in your test runners docs. The beforeEach and afterEach (after hooks should be avoided) are explained in Cypress' documentation, but I think people would expect hooks to be a supported feature of this plugin, but instead they need to use the standard Cypress way. If we don't document that, it might be confusing.

Hi @lgandecki

To make things clear, I have made few observations regarding the support for hooks.
can you correct those I am wrong.
I have created a hooks.js file in step_definitions folder and have placed the following code:

Here is my directory structure:

Cypress
      -Integrations
            -Folder 1
                 -step_definitions
                         -step_definitions1.js
                          - hooks.js
                  a.feature
            -Folder 2
                  -step_definitions
                         -step_definitions2.js
                   b.feature
before(() => {
    cy.log('I Run for each feature before all the scenarios');
});

beforeEach(() => {
    cy.log('I Run for each feature before Every Scenario');
});


afterEach(() => {
    cy.log('I Run for Each Feature After every Scenario');
});

after(() => {
    cy.log('I Run for Each Feature After all the scenarios');
}

Here are the observations:

  • Before block executed before all my scenario's [suppose if we select run all tests, it will be executed multiple times before all my tests start to run ]

  • BeforeEach block executed before each scenario

  • AfterEach block executed before each scenario
  • After block executed After all my scenario's
  • Even tough we placed hooks.js in Folder1, before, beforeEach, after, afterEach got executed when i am running b.feature

  • before, beforeEach, after, afterEach are global not limited to a feature

Missing functionality for hooks:

  • no way to restrict the hook to run only for a particular feature
  • no way to pass tags to hooks

@darasandeep91 Make sure you have "nonGlobalStepDefinitions": true set in your package.json or .cypress-cucumber-preprocessorrc file. It disables global step definitions and uses the step definitions and before/after hooks based on the folder structure you've setup i.e Folder 1 and Folder 2.

Also be sure to remove "step_definitions": "cypress/integration/**/path/to/step_definitions" if you have it configured

I am facing same issue as @darasandeep91 . Hooks are seem to be always global irrespective of any folder they are placed. @sanik-bajracharya I did the settings mentioned still hooks are global.

Scenario which are affected due to this in my case is:
I have multiple feature files - login.feature, cropping.feature, dashboard.feature. For login scenarios, I don't want user to login but for all other scenario I need user to login to application. Now as hooks are global I can't keep login step in before hook as it logs in for login.feature which fails all tests.

@lgandecki is there any way to make hooks non-global so that they are feature file specific (as this is something much need than global hooks ) which I might be missing how to do or we don't have support for this in Cypress yet?

Thanks in advance!

@ReemaMoroney @sanik-bajracharya this is exactly how it should be, and works, as in the example here: https://github.com/TheBrainFamily/cypress-cucumber-example . Please make a reproduction if it doesn't work for you.

There is one caveat which is running all features inside the GUI at the same time - https://github.com/cypress-io/cypress/issues/3323 which is a bug in cypress. But if you run your tests with run or separately, the hooks should not be global.

:tada: This issue has been resolved in version 1.14.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

Was this page helpful?
0 / 5 - 0 ratings