badeball / cypress-cucumber-preprocessor

Run cucumber/gherkin-syntaxed specs with Cypress
MIT License
1.32k stars 147 forks source link

Support feature files in a sub directory #788

Closed FDIM closed 2 years ago

FDIM commented 2 years ago

Current behavior

In our project we have sub folders with feature files in integration folder. This used to work with previous package, but gives an error with this one.

image

This has been very fun to debug :)

In this process I actually ended up moving from webpack to esbuild files as I could not make it work with webpack v5. Now I can share a useful stack trace:

`[ERROR] [plugin feature] The "path" argument must be of type string. Received null

../node_modules/common-ancestor-path/index.js:14:4:
  14 Γöé   : parse(a).root !== parse(b).root ? null
     Γò╡     ^

at validateString (internal/validators.js:124:11)
at parse (path.js:818:5)
at commonAncestorPath (C:\ffd\abcdefget\node_modules\common-ancestor-path\index.js:14:5)
at Array.reduce (<anonymous>)
at Object.module.exports [as default] (C:\ffd\abcdefget\node_modules\common-ancestor-path\index.js:17:38)
at compile (C:\ffd\abcdefget\node_modules\@badeball\cypress-cucumber-preprocessor\lib\template.js:36:55)
at C:\ffd\abcdefget\node_modules\@badeball\cypress-cucumber-preprocessor\esbuild.js:13:60
at callback (C:\ffd\abcdefget\node_modules\esbuild\lib\main.js:993:28)
at handleRequest (C:\ffd\abcdefget\node_modules\esbuild\lib\main.js:721:30)

This error came from the "onLoad" callback registered here:

../node_modules/@badeball/cypress-cucumber-preprocessor/esbuild.js:10:18:
  10 Γöé             build.onLoad({ filter: /\.feature$/ }, async (args) => {
     Γò╡                   ~~~~~~

at setup (C:\ffd\abcdefget\node_modules\@badeball\cypress-cucumber-preprocessor\esbuild.js:10:19)
at handlePlugins (C:\ffd\abcdefget\node_modules\esbuild\lib\main.js:855:23)
at Object.buildOrServe (C:\ffd\abcdefget\node_modules\esbuild\lib\main.js:1149:7)
at C:\ffd\abcdefget\node_modules\esbuild\lib\main.js:2110:17
at new Promise (<anonymous>)
at Object.build (C:\ffd\abcdefget\node_modules\esbuild\lib\main.js:2109:14)
at Object.build (C:\ffd\abcdefget\node_modules\esbuild\lib\main.js:1956:51)
at Object.cypressESBuildFilePreprocessor [as handler] (C:\ffd\abcdefget\node_modules\@bahmutov\cypress-esbuild-preprocessor\src\index.js:68:33)
at invoke (C:\Users\abcdef\AppData\Local\Cypress\Cache\8.5.0\Cypress\resources\app\packages\server\lib\plugins\child\run_plugins.js:22:16)
at C:\Users\abcdef\AppData\Local\Cypress\Cache\8.5.0\Cypress\resources\app\packages\server\lib\plugins\util.js:45:14
at tryCatcher (C:\Users\abcdef\AppData\Local\Cypress\Cache\8.5.0\Cypress\resources\app\packages\server\node_modules\bluebird\js\release\util.js:16:23)
at Function.Promise.attempt.Promise.try (C:\Users\abcdef\AppData\Local\Cypress\Cache\8.5.0\Cypress\resources\app\packages\server\node_modules\bluebird\js\release\method.js:39:29)
at Object.wrapChildPromise (C:\Users\abcdef\AppData\Local\Cypress\Cache\8.5.0\Cypress\resources\app\packages\server\lib\plugins\util.js:44:23)
at Object.wrap (C:\Users\abcdef\AppData\Local\Cypress\Cache\8.5.0\Cypress\resources\app\packages\server\lib\plugins\child\preprocessor.js:28:8)
at execute (C:\Users\abcdef\AppData\Local\Cypress\Cache\8.5.0\Cypress\resources\app\packages\server\lib\plugins\child\run_plugins.js:119:27)

Desired behavior

Build should work as normal and not issue a cryptic error message. Webpack version literally threw The "path" argument must be of type string. Received null with stack trace originating in internal node package!

My current workaround is to patch template.js file, specifically the place that assigns value to implicitIntegrationFolder.

{
        // workaround to support sub folders in integration folder
        file: './node_modules/@badeball/cypress-cucumber-preprocessor/lib/template.js',
        find: 'const implicitIntegrationFolder = (0, assertions_1.assertAndReturn)((0, common_ancestor_path_1.default)(...(0, cypress_configuration_1.getTestFiles)(configuration).map(path_1.default.dirname)), "Expected to find a common ancestor path");',
        replace: `const implicitIntegrationFolder = (0, assertions_1.assertAndReturn)((0, common_ancestor_path_1.default)(...(0, cypress_configuration_1.getTestFiles)(configuration).map(path_1.default.dirname).filter(p => p.endsWith('/integration'))), "Expected to find a common ancestor path");`
    }

Test code to reproduce

Simply create a sub folder in integration folder and add a feature file there to reproduce this issue.

Versions

Checklist

badeball commented 2 years ago

I'm not able to reproduce your issue. Can you provide me an example that fails?

gonzmed commented 2 years ago

Same issue, i think it is a Windows issue when using glob and common-ancestor-path. ...getTestFiles(configuration).map(path.dirname) returns [ 'C:/Foo/1', 'C:/Foo/2', 'C:/Foo/3', 'C:/Foo/4', ] but should be [ 'C:\Foo\1', 'C:\Foo\2', 'C:\Foo\3', 'C:\Foo\4', ]

commonAncestorPath use delimiter from path which is based on OS which in windows is \ this causes parse to throw null exception

FDIM commented 2 years ago

@badeball I tried to reproduce the issue at home and I could not :/ I forked and pushed updated example that I will test on work PC tomorrow.

Few things that I should have mentioned:

kchiron commented 2 years ago

Hello everyone,

EDIT: here an example repository to reproduce https://github.com/kchiron/cypress-preprocessor-tests

I reproduce the issue within my project. I have also needed to switch from webpack to esbuild as @FDIM did to get more information about the error :).

This is what I got :

node_modules/common-ancestor-path/index.js:17:37:
      17 │ module.exports = (...paths) => paths.reduce(commonAncestorPath)
         ╵   

followed by :

 This error came from the "onLoad" callback registered here:
    node_modules/@badeball/cypress-cucumber-preprocessor/esbuild.js:10:18:
      10 │             build.onLoad({ filter: /\.feature$/ }, async (args) => {
         ╵                   ~~~~~~

Our testing projet is organized as follow :

e2e-tests/
    features/
         feature_01/
              awesome.feature
         feature_02/
              misc/
                  lonely.feature
        great.feature
    web/
         cypress/
             integrations/
             cypress.config.ts
             package.json
    mobile/
        ... appium magic used in there ...
    sdk/
         ... dart magic used in there ...

Our tests are written in gherkin language, we use cucumber framework to bind features with sdk folder, or mobile folder or web folder (depends on where we want to run our tests). This folder organization worked fine under cypress-cucumber-preprocessor: 7.0, we are currently trying to migrate :).

Here is the content of the cypress.config.ts :

export default defineConfig({
  e2e: {
    setupNodeEvents,
    baseUrl: 'http://localhost:3000',
    chromeWebSecurity: false,
    scrollBehavior: 'center',
    videosFolder: 'cypress/reports',
    screenshotsFolder: 'cypress/reports',
    specPattern: '../features/',
    supportFile: false,
  },
})

async function setupNodeEvents(
  on: Cypress.PluginEvents,
  config: Cypress.PluginConfigOptions
): Promise<Cypress.PluginConfigOptions> {
  await addCucumberPreprocessorPlugin(on, config)

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

I also kept this block in my package.json file :

  "cypress-cucumber-preprocessor": {
    "nonGlobalStepDefinitions": false,
    "step_definitions": "cypress/integration",
    "commonPath": "cypress/integration",
    "cucumberJson": {
      "generate": true,
      "outputFolder": "cypress/reports",
      "fileSuffix": ""
    }
  }

I tried to move all the content of the features folder into web/cypress/ but nothing changed, I still got the same error.

I run my tests within the folder web/ using this command line :

npm run tags --tags="@myTag"

which calls a npm script defined in package.json :

"tags": "cypress run -e TAGS=$npm_config_tags --browser chrome",

This is the list of all dependance I used, if it might help:

note: I kept cypress-cucumber-preprocessor: 4.0.1 because TableDefinition used in step definitions were not recognized otherwise. I use this dependance only for TableDefinition, I use @badeball/cypress-cucumber-preprocessor for everything else.

  "devDependencies": {
    "@cucumber/cucumber": "^7.1.0",
    "@cypress/webpack-preprocessor": "5.11.1",
    "@faker-js/faker": "6.3.1",
    "@testing-library/cypress": "^8.0.3",
    "@types/cypress-cucumber-preprocessor": "4.0.1",
    "@types/fs-extra": "9.0.13",
    "@types/node": "14.18.5",
    "@types/tough-cookie": "4.0.2",
    "@typescript-eslint/eslint-plugin": "5.22.0",
    "@typescript-eslint/parser": "5.22.0",
    "axios": "0.27.2",
    "cross-env": "7.0.3",
    "cypress": "10.4.0",
    "date-fns": "2.28.0",
    "eslint": "8.15.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-plugin-cypress": "2.12.1",
    "eslint-plugin-prettier": "4.0.0",
    "fs-extra": "10.1.0",
    "google-libphonenumber": "3.2.27",
    "got": "11.8.5",
    "lint-staged": "10.5.4",
    "node-polyfill-webpack-plugin": "1.1.4",
    "npm-run-all": "4.1.5",
    "path": "0.12.7",
    "prettier": "2.6.2",
    "tough-cookie": "4.0.0",
    "ts-loader": "9.3.0",
    "typescript": "4.6.4",
    "webpack": "5.72.0"
  },
  "dependencies": {
    "@badeball/cypress-cucumber-preprocessor": "12.0.0",
    "@bahmutov/cypress-esbuild-preprocessor": "latest",
    "moment": "2.29.4",
    "moment-with-locales-es6": "1.0.1",
    "tsconfig-paths-webpack-plugin": "3.5.2"
  }

Have a nice day :)

FDIM commented 2 years ago

@badeball I finally managed to reproduce the issue. For some reason if I have multiple files in a sub-folder, I'll get an error:

image

If I remove "sub copy.feature" file, then it works again.

The only code change I've done to webpack-ts example is to make it run with cypress 8.5, it's probably the same with v10.

badeball commented 2 years ago

This has been fixed in v12.0.1.