cypress-cucumber-preprocessor icon indicating copy to clipboard operation
cypress-cucumber-preprocessor copied to clipboard

Upgrade guide from `TheBrainFamily/cypress-cucumber-preprocessor`

Open badeball opened this issue 3 years ago • 127 comments

Transfer of ownership

Due to personal reasons, the previous maintainers of this package are stepping down and handing the reigns over to me, a long-time contributor to the project and a user of it myself. This is a responsibility I'm very excited about. Furthermore, I'd like to thank @lgandecki ++ for all the work that they've done so far.

What's new :tada:

This implementation has been re-written from scratch in TypeScript, has more thorough test coverage and is filled with a bunch of new feature.

  • Support for the Rule keyword.

  • The cypress-tags has been removed and made redundant. Specs containing no matching scenarios are automatically filtered, provided that filterSpecs is set to true.

  • Screenshots are automatically added to JSON reports (including that of failed tests).

  • Other attachments can be added to the report.

  • JSON reports are generated as a single file and manual, post-merging is no longer required.

  • The package is now located under the name @badeball/cypress-cucumber-preprocessor and a deprecation notice will be posted in the old name once NPM transfer is complete.

  • Methods such as Given, When and Then are imported from @badeball/cypress-cucumber-preprocessor, IE. without the ../steps postfix.

  • A large number of issues has implicitly been fixed due to architectural changes.

  • And(..) and But(..) have been deprecated, read more here.

What's missing

  • Bundled features files. This has been deprioritized, due to the associated maintenance cost, awkward implementation and my personal lack of convincing that it's necessary. Using esbuild gives near-instant compilation time of features and removes nearly all performance gain from bundled features anyway.

Changes to configuration

Nearly all configuration options has been removed and there's no distinction between "global" and "non-global" steps anymore. Steps are searched for using patterns and you can chose to include global steps or not.

Below are some configuration examples. Configuration is still implemented using cosmiconfig, meaning that your configuration can reside in multiple locations, such as package.json or .cypress-cucumber-preprocessorrc.json.

Global step definitions exaple

{
  "name": "my project",
  "depdendencies": {},
  "devDepdendencies": {},
  "cypress-cucumber-preprocessor": {
    "stepDefinitions": "cypress/support/step_definitions/**/*.{js,ts}"
  }
}

Step definitions besides scenario

{
  "name": "my project",
  "depdendencies": {},
  "devDepdendencies": {},
  "cypress-cucumber-preprocessor": {
    "stepDefinitions": "cypress/e2e/[filepath].{js,ts}"
  }
}

Step definitions in a directory besides the scenario

{
  "name": "my project",
  "depdendencies": {},
  "devDepdendencies": {},
  "cypress-cucumber-preprocessor": {
    "stepDefinitions": "cypress/e2e/[filepath]/**/*.{js,ts}"
  }
}

Combination of all of the above (default)

{
  "name": "my project",
  "depdendencies": {},
  "devDepdendencies": {},
  "cypress-cucumber-preprocessor": {
    "stepDefinitions": [
      "cypress/e2e/[filepath]/**/*.{js,ts}",
      "cypress/e2e/[filepath].{js,ts}",
      "cypress/support/step_definitions/**/*.{js,ts}"
    ]
  }
}

Still have an issue?

Because this is a re-implementation and thorough test coverage was originally lacking, old issues may resurface and new issue arise. If you have an issue or just a question, please post it below and I'll try to help.

badeball avatar Apr 10 '22 09:04 badeball

First off, thank you for your hard work maintaining this project and coordinating the transfer 👏🏻 I'm very excited to look over the changes and give them a test.

Are you planning to make GitHub releases for the versions you've created and published before the project was fully transferred? That seems like a logical place to start learning about changes.

wrslatz avatar Apr 10 '22 17:04 wrslatz

Are you planning to make GitHub releases for the versions you've created and published before the project was fully transferred? That seems like a logical place to start learning about changes.

This is difficult because some of the version tags would conflict with one another. You can however see the history of the fork here (said link can also be found in the current changelog).

badeball avatar Apr 10 '22 17:04 badeball

Hey @badeball I would like to second a thank you on your continued maintenance of this project, coordination of the transfer and the updates / refactoring you've done. I'm also excited to take a look and try them out and provide any useful feedback. Well done mate

hect1c avatar Apr 12 '22 03:04 hect1c

@badeball - This is great work thanks for keeping it going. I have a question about the config upgrade. Previously steps in folders matching the feature file name would only work in the relevant feature. Is this covered by the config option above:

{
  "name": "my project",
  "depdendencies": {},
  "devDepdendencies": {},
  "cypress-cucumber-preprocessor": {
    "stepDefinitions": "cypress/integration/[filepath]/**/*.{js,ts}"
  }
}

I'm not sure I understand the difference between this one and 'Step definitions besides scenario'. Do we need to explicitly add the filepath for the scenario name or does this value get interpreted at runtime?

andyg101 avatar Apr 12 '22 15:04 andyg101

Hi, @andyg101

Notice the difference between the following two patterns

cypress/integration/[filepath]/**/*.{js,ts}

.. and

cypress/integration/[filepath].{js,ts}

These will match different files, pick whatever suits you the best.

badeball avatar Apr 12 '22 16:04 badeball

I've manged to upgrade from 4.3.1 to 9.0.0 but having an issue with the ESBuild plugin. I get this error when i try to use it:

Transforming const to the configured target environment ("es5") is not supported yet

I'm not super familiar with esbuild. Have I missed some obvious configuration here? The only changes I made was to add this to my index.js

on(
     'file:preprocessor',
     createBundler({
       plugins: [createEsbuildPlugin(config)],
     }),
   );

nb. I have got this working with browserify but I believe there is a performance benefit to using ESbuild

andyg101 avatar Apr 12 '22 16:04 andyg101

Hi, @andyg101

Notice the difference between the following two patterns

cypress/integration/[filepath]/**/*.{js,ts}

.. and

cypress/integration/[filepath].{js,ts}

These will match different files, pick whatever suits you the best.

Thanks @badeball. What I mean is if I have a feature like this

my-feature-with-unique-steps.feature

and a folder

cypress/integration/my-feature-with-unique-steps

Is that covered by the option(s) containing [filepath]?

update: I think i might have managed what I'm trying to do like this: "cypress/integration/**/[filepath]/**/*.{js,ts}"

andyg101 avatar Apr 12 '22 16:04 andyg101

The pattern

cypress/integration/[filepath].{js,ts}

.. will for

cypress/integration/my-feature-with-unique-steps.feature

.. be expanded to

cypress/integration/my-feature-with-unique-steps.{js,ts}

.. which will match both cypress/integration/my-feature-with-unique-steps.js and cypress/integration/my-feature-with-unique-steps.ts.

I recommend reading more about patterns here: https://github.com/isaacs/node-glob

badeball avatar Apr 12 '22 17:04 badeball

I'm not super familiar with esbuild. Have I missed some obvious configuration here? The only changes I made was to add this to my index.js

Do you by any chance have a tsconfig.json lying there as well? If so, what does it contain?

badeball avatar Apr 12 '22 17:04 badeball

I'm not super familiar with esbuild. Have I missed some obvious configuration here? The only changes I made was to add this to my index.js

Do you by any chance have a tsconfig.json lying there as well? If so, what does it contain?

Yep here it is:

  "compilerOptions": {
    "target": "es5",
    "lib": ["es5", "dom"],
    "typeRoots": ["types", "../node_modules/@types"],
    "types": [
      "cypress",
      "node",
      "cypress-keycloak-commands"
    ],
  },
  "include": [
    "./**/*.ts",
    "./**/*.js"
  ]
}

andyg101 avatar Apr 12 '22 17:04 andyg101

Esbuild will look for that and configure its target with es5, in which const is apparently not supported. So you have to bump that to something that supports const. I'm guessing you can safely put es6 there as a minimum.

badeball avatar Apr 12 '22 17:04 badeball

Thanks. I'll give that a go. Help much appreciated @badeball

Hmm. Now I get

node_modules/jju/lib/utils.js:1:18: ERROR: Could not resolve "fs"

andyg101 avatar Apr 12 '22 17:04 andyg101

That is likely because one of your step definitions is requiring / importing something, that eventually tries to require the fs module, which is unavailable in the browser. Browserify handles it somewhat differently than esbuild, a bit lenient. It would help to know what exactly it is you¨re doing. Can you provide me a repository which I can clone myself? Then I can easily point out the error.

badeball avatar Apr 12 '22 18:04 badeball

I see jju there. That's a node module and won't work in the browser, browserify or not. Maybe browserify was able to shim out fs and you coincidentally avoided using anything in jju that used fs, but that was just lucky I guess.

badeball avatar Apr 12 '22 18:04 badeball

I have successfully migrated to badeball/cypress-cucumber-preprocessor and am already observing much faster load times for feature files using webpack, kudos!

I am interested in even better performance from the esbuild example, but am getting similar issues with resolving node modules. The use case is that we have imported a package in a cypress plugin that uses node modules, which runs in cy.task outside of the browser. For any built-in node modules that this package requires, I get an error like this:

[ERROR] Could not resolve "stream" The package "stream" wasn't found on the file system but is built into node. Are you trying to bundle for node? You can use "platform: 'node'" to do that, which will remove this error.

Note that in the original webpack configuration the workaround is to use node-polyfill-webpack-plugin.

Do you have recommendations for how to work around this using esbuild?

jwmickey avatar Apr 12 '22 18:04 jwmickey

Do you have recommendations for how to work around this using esbuild?

Can you try @esbuild-plugins/node-modules-polyfill as described here?

badeball avatar Apr 12 '22 18:04 badeball

I tried that plugin and then got an error about buffer not being defined, so I added the node-globals-polyfill and set buffer to true, then I got require not defined so I set it to true, then got stuck when I got __dirname is not defined. I also tried setting platform: node in the configuration for createBundler but got similar errors.

So no answer yet but we can continue using webpack for now. I can try to use esbuild on a separate/new project to get a working proof of concept and revisit. Thanks for your help!

jwmickey avatar Apr 12 '22 20:04 jwmickey

I see jju there. That's a node module and won't work in the browser, browserify or not. Maybe browserify was able to shim out fs and you coincidentally avoided using anything in jju that used fs, but that was just lucky I guess.

jju is a package we use for parsing json5 (which we are using in our feature files in docstrings). Unfortunately I can't give you a copy of the repo I'm working on. I'm going to try the suggestion in https://esbuild.github.io/getting-started/#bundling-for-node to see if I can get that working.

Update: I tried using the following

on(
   'file:preprocessor',
   createBundler({
     plugins: [createEsbuildPlugin(config)],
     external: [config.projectRoot + 'node_modules/*'],
     platform: 'node',
   }),
 );

This results in a TypeError

TypeError
The following error originated from your test code, not from Cypress.

  > fn is not a function

When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.

Cypress could not associate this error to any specific test.

We dynamically generated a new test to display this failure.

Which I think is down to the following code (line 5705 is failing)

image

Have to admit I'm a bit out of my depth here in terms of next steps. I think that chai is bundled with Cypress itself so not sure how to fix that.

andyg101 avatar Apr 13 '22 07:04 andyg101

I was able to get it to compile with jju using a configuration shown below

import * as createBundler from "@bahmutov/cypress-esbuild-preprocessor";
import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild";
import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";

export default (
  on: Cypress.PluginEvents,
  config: Cypress.PluginConfigOptions
): void => {
  on(
    "file:preprocessor",
    createBundler({
      plugins: [NodeModulesPolyfills(), createEsbuildPlugin(config)],
    })
  );
};

badeball avatar Apr 13 '22 13:04 badeball

That worked @badeball. Thanks so much for the help!

Also I got the glob matching working as per your suggestion too.

andyg101 avatar Apr 13 '22 14:04 andyg101

@lgandecki, do you mind transferring the NPM ownership as well? It's currently a bit difficult to realize that there's a new version published (EG. https://github.com/badeball/cypress-cucumber-preprocessor/issues/657#issuecomment-1099124419).

badeball avatar Apr 14 '22 12:04 badeball

@badeball this is awesome! I was able to get esbuild working with the following configuration similar to what you posted above:

on('file:preprocessor', createBundler({
  plugins: [
    NodeModulesPolyfills(),
    GlobalsPolyfills({
      process: true,
      buffer: true,
    }),
    createEsbuildPlugin(config)
  ],
}));

Incredible performance gains switching to esbuild!

jwmickey avatar Apr 15 '22 03:04 jwmickey

@jwmickey @badeball Thanks for that!!. The configuration you provided worked for the esbuild.

@badeball I do have one issue with the stepDefinitions path. Before I had the following config

  "cypress-cucumber-preprocessor": {
    "nonGlobalStepDefinitions": true,
  },

This allowed me to use a common step folder in cypress/integration/common/[file].ts

While a feature would be in cypress/integration/[feature]/[filepath].feature and the related step definitions in cypress/integration/[feature]/[filepath]/**/*.ts

With the latest version I removed the nonGlobalStepDefinitions as it appears this is no longer necessary however when running cypress it is unable to pickup the step implementation in the cypress/integration/common/[file].ts

I should also mention the related file in here is my Background so my feature file looks like

Feature: Feature #1

	Background:
		Given X
		And Y

	Scenario: Scenario #1
		Given 1
		When 2
		Then 3

The Given step in my Background is not being found which is in a common folder cypress/integration/common/[file].ts. Is there something I'm missing? I've also tried a combination of various stepDefinitions in my config to get this working but still no luck.

hect1c avatar Apr 16 '22 05:04 hect1c

@hect1c I have the same set up as you I think and I found this works for common steps in cypress/integration/common

 "cypress-cucumber-preprocessor": {
    "stepDefinitions": "cypress/integration/common/**/*.{js,ts}"
  }

andyg101 avatar Apr 16 '22 10:04 andyg101

@andyg101 Thanks I had tried that before and it didn't work but I just found out why.

@badeball It appears the cosmiconfig may not be working properly as my configurations were not working when using any variation outside of the package.json . When I moved them back to package.json I got it all working with the following

  "cypress-cucumber-preprocessor": {
    "stepDefinitions": [
      "cypress/integration/common/**/*.{js,ts}",
      "cypress/integration/[filepath]/**/*.{js,ts}",
      "cypress/integration/[filepath].{js,ts}"
    ]
  }

hect1c avatar Apr 17 '22 04:04 hect1c

This is how the configuration should look like if placed somewhere aside from package.json:

{
  "stepDefinitions": [
    "cypress/integration/common/**/*.{js,ts}",
    "cypress/integration/[filepath]/**/*.{js,ts}",
    "cypress/integration/[filepath].{js,ts}"
  ]
}

.. notice it is not wrapped in { "cypress-cucumber-preprocessor": { } }.

badeball avatar Apr 17 '22 08:04 badeball

@badeball Hi Jonas,

I was looking at setting up the json output yesterday. Are the additional parameters you mention meant to go in the json config as well e.g.

  "json": {
    "enabled": true,
    "formatter": "/path/to/formatter"
    "output":"/path/to/output.json"
  }
}

Also, what if I want to have this work on both windows and linux, is there any way to specify an alternative path to the formatter?

andyg101 avatar Apr 17 '22 09:04 andyg101

Are the additional parameters you mention meant to go in the json config as well e.g.

I don't know which additional parameters you're referring to here, but I'm guessing yes.

Also, what if I want to have this work on both windows and linux, is there any way to specify an alternative path to the formatter?

No, that's not possible. The ability to specify custom location isn't really meant to be done by default. Every developer might have different desire as to where to put these kind of things. Hence, the preprocessor will search for the executable in your PATH if no location is specified. That's what both your Linux and Windows users should do, put the executable somewhere that makes it available from PATH.

badeball avatar Apr 17 '22 10:04 badeball

@badeball I have a question about the filterspec so that it wont report test from other feature files. The example below is created in Typescript but for our project we arent using typescript so do you have an example how to resolve the async await in old JS notation. Since we are using the normal module.exports function style.

import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";

export default async (
  on: Cypress.PluginEvents,
  config: Cypress.PluginConfigOptions
): Promise<Cypress.PluginConfigOptions> => {
  await addCucumberPreprocessorPlugin(on, config);

  // Make sure to return the config object as it might have been modified by the plugin.
  return config;
}

Xvier avatar Apr 18 '22 20:04 Xvier

@Xvier CJS:

const {
  addCucumberPreprocessorPlugin
} = require("@badeball/cypress-cucumber-preprocessor");

module.exports = async (on, config) => {
  await addCucumberPreprocessorPlugin(on, config);

  // Make sure to return the config object as it might have been modified by the plugin.
  return config;
};

badeball avatar Apr 19 '22 06:04 badeball