Repository: TheBrainFamily/cypress-cucumber-preprocessor
Pull request #236 (2019-09-04 11:26:34)

feat(getstepdefinitionspaths): configurable nonGlobalStepDefinitions base directory
Within my team we have a preference to split the step definitions away from where the feature files are located.

However we love the ability to scope test definitions so using the common folder and global approach doesn't seem right.

To this end I have created a PR that if you agree adds a new configuration option `nonGlobalStepBaseDir` which defines a location from which the feature files folders are matched from. If it is not set then the current behaviour is maintained.

sagorla: Hi @liamchilds 

looks like commonPath is stilling referring to  "cypress/integration/common" path, not switching to "nonGlobalStepBaseDir" path.

liamchilds: Thanks for taking a look, that was via design if I understand your point correctly. I suppose it seems counter intuitive on reflection.

This was only to set the path which is used for the non global step configuration, however I could change it so that the common settings are also overridden.

What is your desired outcome here, am I right in thinking that if `nonGlobalStepBaseDir` is defined and commonPath is not then that location is used for both. However when commonPath is also defined it takes precedence over `nonGlobalStepBaseDir`?
sagorla: @liamchilds 
currently, as per design we need to maintain  "commonPath" & "non global step definitions" in   "cypress/integration/" folder when "nonGlobalStepDefinitions" is true.

The same way we should allow to  maintain "commonPath" & "non global step definitions" in   "nonGlobalStepBaseDir" folder when "nonGlobalStepDefinitions" is true & "nonGlobalStepBaseDiri" is defined. So that all step definitions (common & global) will be in the same folder so that maintaining step definitions will be easy. This is my just personal opinion. 
liamchilds: @sagorla I have updated the branch with your suggestions. Common path is now maintained from nonGlobalStepBaseDir when defined. 
sagorla: Hi @liamchilds 

commonPath step definition file is not getting executed when nonGlobalStepBaseDir is defined. Here I have noticed you are trying to get commonPath with the reference of nonGlobalPath but this path is good for step definitions for the feature which is running. Here we will maintain only non global only .js files for that specific feature file. for common step definitions you may need to consider step base dir.

if (loaded.config.nonGlobalStepBaseDir) {
      const stepBase = `${appRoot}/${loaded.config.nonGlobalStepBaseDir}`;
      nonGlobalPath = nonGlobalPath.replace(featureBase, stepBase);
      **commonPath = `${nonGlobalPath}/${loaded.config.commonPath || "/common"}`;**
    }
correct path will be as below
    **commonPath = loaded.config.commonPath || `${stepBase}/common/`;**

lgandecki: shoot, I completely zoned out about this one. Will review today or tomorrow at max. Putting on the daily todo list :)  

Do you guys think it would also solve https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/pull/275  ? 
lgandecki: @liamchilds do you agree with @sagorla comment? Are you still using it? it'd be great to iron out the API here so we don't have to introduce breaking changes.  
pmelab: This would also enable us to swap out step implementations for tests. For example, using the same scenario to test on API- and UI-level. Just tested it and it works perfectly with a custom cosmic-config:

```js
module.exports = {
  nonGlobalStepDefinitions: true,
  nonGlobalStepBaseDir: process.env.CYPRESS_TEST_LEVEL === 'api'
    ? 'cypress/integration/api/'
    : 'cypress/integration/ui/'
}
```

Also, I would agree with @sagorla on the common-path issue.

Patch:
diff --git a/README.md b/README.md
index 0ae679b..4ed9950 100644
--- a/README.md
+++ b/README.md
@@ -85,8 +85,9 @@ Please make use of [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) t
 
 Option | Default value | Description
 ------------ | ------------- | -------------
-commonPath | `cypress/integration/common` when `nonGlobalStepDefinitions` is true <br> `cypress/support/step_definitions` when `nonGlobalStepDefinitions` is false | Define the path to a folder containing all common step definitions of your tests
-nonGlobalStepDefinitions | false | If true use the Cypress Cucumber Preprocessor Style pattern for placing step definitions files. If false, we will use the "oldschool" (everything is global) Cucumber style
+commonPath | `cypress/integration/common` when `nonGlobalStepDefinitions` is true <br> `cypress/support/step_definitions` when `nonGlobalStepDefinitions` is false <br> `${nonGlobalStepBaseDir}/common` when `nonGlobalStepBaseDir` is defined | Define the path to a folder containing all common step definitions of your tests. When `nonGlobalStepBaseDir` is defined this path is defined from that base location. e.g `${nonGlobalStepBaseDir}/${commonPath}`.
+nonGlobalStepDefinitions | false | If true use the Cypress Cucumber Preprocessor Style pattern for placing step definitions files. If false, we will use the "oldschool" (everything is global) Cucumber style.
+nonGlobalStepBaseDir| undefined | If defined and `nonGlobalStepDefinitions` is also true then step definition searches for folders with the features name will start from the directory provided here. The cwd is already taken into account. e.g `test/step_definitions`.
 stepDefinitions | `cypress/integration` when `nonGlobalStepDefinitions` is true <br> `cypress/support/step_definitions` when `nonGlobalStepDefinitions` is false | Path to the folder containing our step definitions.
 
 ## How to organize the tests
diff --git a/lib/getStepDefinitionsPaths.js b/lib/getStepDefinitionsPaths.js
index 3744fd6..602ea44 100644
--- a/lib/getStepDefinitionsPaths.js
+++ b/lib/getStepDefinitionsPaths.js
@@ -1,5 +1,6 @@
 const path = require("path");
 const glob = require("glob");
+const process = require("process");
 const { getConfig } = require("./getConfig");
 const stepDefinitionPath = require("./stepDefinitionPath.js");
 const { getStepDefinitionPathsFrom } = require("./getStepDefinitionPathsFrom");
@@ -9,18 +10,27 @@ const getStepDefinitionsPaths = filePath => {
   let paths = [];
   const config = getConfig();
   if (config && config.nonGlobalStepDefinitions) {
-    const nonGlobalPattern = `${getStepDefinitionPathsFrom(
-      filePath
-    )}/**/*.+(js|ts)`;
-
+    let nonGlobalPath = getStepDefinitionPathsFrom(filePath);
     let commonPath = config.commonPath || `${stepDefinitionPath()}/common/`;
 
     if (config.commonPath) {
       commonPath = path.resolve(appRoot, commonPath);
     }
+
+    if (config.nonGlobalStepBaseDir) {
+      const stepBase = `${appRoot}/${config.nonGlobalStepBaseDir}`;
+      nonGlobalPath = nonGlobalPath.replace(stepDefinitionPath(), stepBase);
+      commonPath = `${nonGlobalPath}/${config.commonPath ||
+        `${stepBase}/common/`}`;
+    }
+
+    const nonGlobalPattern = `${nonGlobalPath}/**/*.+(js|ts)`;
+
     const commonDefinitionsPattern = `${commonPath}**/*.+(js|ts)`;
-    paths = paths.concat(glob.sync(nonGlobalPattern));
-    paths = paths.concat(glob.sync(commonDefinitionsPattern));
+    paths = paths.concat(
+      glob.sync(nonGlobalPattern),
+      glob.sync(commonDefinitionsPattern)
+    );
   } else {
     const pattern = `${stepDefinitionPath()}/**/*.+(js|ts)`;
     paths = paths.concat(glob.sync(pattern));


Test patch:
diff --git a/lib/getStepDefinitionsPaths.test.js b/lib/getStepDefinitionsPaths.test.js
index 212cb09..4c65a43 100644
--- a/lib/getStepDefinitionsPaths.test.js
+++ b/lib/getStepDefinitionsPaths.test.js
@@ -12,6 +12,7 @@ describe("getStepDefinitionsPaths", () => {
   beforeEach(() => {
     jest.resetModules();
     ({ getConfig } = require("./getConfig"));
+    jest.unmock("path");
     jest.mock("./getConfig");
   });
   it("should return the default common folder", () => {
@@ -23,7 +24,6 @@ describe("getStepDefinitionsPaths", () => {
 
     const actual = getStepDefinitionsPaths("/path");
     const expected = "stepDefinitionPath/common/**/*.+(js|ts)";
-
     expect(actual).to.include(expected);
   });
 
@@ -37,7 +37,6 @@ describe("getStepDefinitionsPaths", () => {
       }
     }));
 
-    jest.mock("fs");
     jest.spyOn(process, "cwd").mockImplementation(() => "cwd");
 
     getConfig.mockReturnValue({
@@ -51,4 +50,32 @@ describe("getStepDefinitionsPaths", () => {
     const expected = "./cwd/myPath/**/*.+(js|ts)";
     expect(actual).to.include(expected);
   });
+  it("should return the default non global step definition pattern", () => {
+    getConfig.mockReturnValue({
+      nonGlobalStepDefinitions: true
+    });
+    // eslint-disable-next-line global-require
+    const { getStepDefinitionsPaths } = require("./getStepDefinitionsPaths");
+    const path = "stepDefinitionPath/test.feature";
+    const actual = getStepDefinitionsPaths(path);
+    const expected = "stepDefinitionPath/test/**/*.+(js|ts)";
+
+    expect(actual).to.include(expected);
+  });
+
+  it("should return the overriden non global step definition pattern if nonGlobalStepBaseDir is defined", () => {
+    jest.spyOn(process, "cwd").mockImplementation(() => "cwd");
+    getConfig.mockReturnValue({
+      nonGlobalStepDefinitions: true,
+      nonGlobalStepBaseDir: "nonGlobalStepBaseDir"
+    });
+    // eslint-disable-next-line global-require
+    const { getStepDefinitionsPaths } = require("./getStepDefinitionsPaths");
+    const path = "stepDefinitionPath/test.feature";
+    const actual = getStepDefinitionsPaths(path);
+    const expected = "cwd/nonGlobalStepBaseDir/test/**/*.+(js|ts)";
+
+    expect(actual).to.include(expected);
+    expect(actual).to.not.include("stepDefinitionPath/test/**/*.+(js|ts)");
+  });
 });