Repository: Codeception/CodeceptJS (js)

Issue:
Gherkin: duplicated steps generated with `gherkin:snippets`
#### What are you trying to achieve?
When I generate steps with `gherkin:snippets` command I do not want to have duplicated steps.
Problem appears when we have more than one scenario in feature, e.g.
```
  Scenario: Login to website
    Given I open a browser on a site
    When I click login button at 1.2
    Then I see welcome message

  Scenario: Logout from website
    Given I open a browser on a site
    And I click login button at 1.2
    And I see welcome message
    When I click logout
    Then I see goodbye message
```

#### What do you get instead?
Step `I open a browser on a site` appears twice in `steps.js` file
### Details

* CodeceptJS version: 2.6
* NodeJS Version: 13.11.0


Pull request:
Gherkin: avoid creating duplicated steps
## Motivation/Description of the PR
- Avoid generating duplicated steps with `gherkin:snippets` command
- Resolves #2316

Applicable helpers:

- [ ] WebDriver
- [ ] Puppeteer
- [ ] Nightmare
- [ ] REST
- [ ] FileHelper
- [ ] Appium
- [ ] Protractor
- [ ] TestCafe
- [ ] Playwright

Applicable plugins:

- [ ] allure
- [ ] autoDelay
- [ ] autoLogin
- [ ] customLocator
- [ ] pauseOnFail
- [ ] puppeteerCoverage
- [ ] retryFailedStep
- [ ] screenshotOnFail
- [ ] selenoid
- [ ] stepByStepReport
- [ ] wdio

## Type of change

- [ ] :fire: Breaking changes
- [ ] :rocket: New functionality
- [x] :bug: Bug fix
- [ ] :clipboard: Documentation changes/updates
- [ ] :hotsprings: Hot fix
- [ ] :hammer: Markdown files fix - not related to source code
- [ ] :nail_care: Polish code

## Checklist:

- [x] Tests have been added
- [ ] Documentation has been added (Run `npm run docs`)
- [x] Lint checking (Run `npm run lint`)
- [x] Local tests are passed (Run `npm test`)


Patch:
diff --git a/lib/command/gherkin/snippets.js b/lib/command/gherkin/snippets.js
index 4739bff8..8c7b90c3 100644
--- a/lib/command/gherkin/snippets.js
+++ b/lib/command/gherkin/snippets.js
@@ -49,7 +49,7 @@ module.exports = function (genPath, options) {
   });
   output.print(`Loaded ${files.length} files`);
 
-  let newSteps = [];
+  const newSteps = new Map();
 
   const parseSteps = (steps) => {
     const newSteps = [];
@@ -88,9 +88,9 @@ module.exports = function (genPath, options) {
     const ast = parser.parse(fs.readFileSync(file).toString());
     for (const child of ast.feature.children) {
       if (child.type === 'ScenarioOutline') continue; // skip scenario outline
-      newSteps = newSteps.concat(parseSteps(child.steps).map((step) => {
+      parseSteps(child.steps).map((step) => {
         return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) });
-      }));
+      }).map((step) => newSteps.set(`${step.type}(${step})`, step));
     }
   };
 
@@ -106,7 +106,7 @@ module.exports = function (genPath, options) {
     stepFile = fsPath.join(global.codecept_dir, stepFile);
   }
 
-  const snippets = newSteps
+  const snippets = [...newSteps.values()]
     .filter((value, index, self) => self.indexOf(value) === index)
     .map((step) => {
       return `


Test patch:
diff --git a/test/data/sandbox/codecept.dummy.bdd.json b/test/data/sandbox/codecept.dummy.bdd.json
index c9d053b4..77a50748 100644
--- a/test/data/sandbox/codecept.dummy.bdd.json
+++ b/test/data/sandbox/codecept.dummy.bdd.json
@@ -8,7 +8,7 @@
     }
   },
   "gherkin": {
-    "features": "./support/*.feature",
+    "features": "./support/dummy.feature",
     "steps": [
       "./features/step_definitions/my_steps.js",
       "./features/step_definitions/my_other_steps.js"
@@ -18,4 +18,4 @@
   "bootstrap": false,
   "mocha": {},
   "name": "sandbox"
-}
\ No newline at end of file
+}
diff --git a/test/data/sandbox/codecept.duplicate.bdd.json b/test/data/sandbox/codecept.duplicate.bdd.json
new file mode 100644
index 00000000..ef9b9bbf
--- /dev/null
+++ b/test/data/sandbox/codecept.duplicate.bdd.json
@@ -0,0 +1,21 @@
+{
+  "tests": "./*_no_test.js",
+  "timeout": 10000,
+  "output": "./output",
+  "helpers": {
+    "BDD": {
+      "require": "./support/bdd_helper.js"
+    }
+  },
+  "gherkin": {
+    "features": "./support/duplicate.feature",
+    "steps": [
+      "./features/step_definitions/my_steps.js",
+      "./features/step_definitions/my_other_steps.js"
+    ]
+  },
+  "include": {},
+  "bootstrap": false,
+  "mocha": {},
+  "name": "sandbox"
+}
diff --git a/test/data/sandbox/support/duplicate.feature b/test/data/sandbox/support/duplicate.feature
new file mode 100644
index 00000000..56ae6998
--- /dev/null
+++ b/test/data/sandbox/support/duplicate.feature
@@ -0,0 +1,13 @@
+Feature: Auth
+
+  Scenario: Login to website
+    Given I open a browser on a site
+    When I click login button at 1.2
+    Then I see welcome message
+
+  Scenario: Logout from website
+    Given I open a browser on a site
+    And I click login button at 1.2
+    And I see welcome message
+    When I click logout
+    Then I see goodbye message
diff --git a/test/runner/bdd_test.js b/test/runner/bdd_test.js
index 4cde1e4a..9ae4e744 100644
--- a/test/runner/bdd_test.js
+++ b/test/runner/bdd_test.js
@@ -278,4 +278,12 @@ When(/^I define a step with a \\( paren and a "(.*?)" string$/, () => {
       done();
     });
   });
+
+  it('should not generate duplicated steps', (done) => {
+    exec(`${runner} gherkin:snippets --dry-run --config ${codecept_dir}/codecept.duplicate.bdd.json`, (err, stdout, stderr) => { //eslint-disable-line
+      assert.equal(stdout.match(/I open a browser on a site/g).length, 1);
+      assert(!err);
+      done();
+    });
+  });
 });