Repository: Codeception/CodeceptJS
Pull request #2307 (2020-04-01 14:40:04)

(fix): some typos in custom-helpers.md 
## Motivation/Description of the PR
- Fix typos and rephrase some places.

## Type of change

- [x] :hammer: Markdown files fix - not related to source code



Patch:
diff --git a/docs/custom-helpers.md b/docs/custom-helpers.md
index 00e9dc88..d07c9bd9 100644
--- a/docs/custom-helpers.md
+++ b/docs/custom-helpers.md
@@ -3,13 +3,13 @@ permalink: /helpers
 title: Custom Helpers
 ---
 
-# Extending CodeceptJS with Custom Helopers
+# Extending CodeceptJS With Custom Helpers
 
-Helpers is a core concept of CodeceptJS. Helper is a wrapper on top of various libraries providing unified interface around them.
+Helper is the core concept of CodeceptJS. Helper is a wrapper on top of various libraries providing unified interface around them.
 
-Methods of Helper class will be available in tests in `I` object. This abstracts test scenarios from the implementation and allows easily switching between backends.
+Methods of Helper class will be available in tests in `I` object. This abstracts test scenarios from the implementation and allows switching between backends seamlessly.
 
-Functionality of CodeceptJS should be extended by writing a custom helpers.
+Functionality of CodeceptJS could be extended by writing custom helpers.
 
 Helpers can also be installed as Node packages and required by corresponding Node modules.
 
@@ -37,7 +37,7 @@ helpers: {
 ```
 
 Helpers are ES6 classes inherited from [corresponding abstract class](https://github.com/Codeception/CodeceptJS/blob/master/lib/helper.js).
-Generated Helper will be added to `codecept.conf.js` config. It should look like this:
+Generated Helper will be added to `codecept.conf.js` config file. It should look like this:
 
 ```js
 const Helper = codecept_helper;
@@ -113,7 +113,7 @@ module.exports = MyHelper;
 
 ## Puppeteer Example
 
-Puppeteer has [nice and elegant API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) which you can use inside helpers. Access `page` instance via `this.helpers.Puppeteer.page` from inside a helper.
+Puppeteer has [nice and elegant API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md) which you can use inside helpers. Accessing `page` instance via `this.helpers.Puppeteer.page` from inside a helper.
 
 Let's see how we can use [emulate](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pageemulateoptions) function to emulate iPhone browser in a test.
 
@@ -327,4 +327,4 @@ Retry rules are available in array `recorder.retries`. The last retry rule can b
 
 ## Using Typescript
 
-When using typescript, replace `module.exports` with `export` for autocompletion.
+With Typescript, just simply replacing `module.exports` with `export` for autocompletion.
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 `
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index a8e6b673..52bf9772 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -1876,10 +1876,16 @@ class WebDriver extends Helper {
    */
   async waitForElement(locator, sec = null) {
     const aSec = sec || this.options.waitForTimeout;
+    if (isWebDriver5()) {
+      return this.browser.waitUntil(async () => {
+        const res = await this.$$(withStrictLocator(locator));
+        return res && res.length;
+      }, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
+    }
     return this.browser.waitUntil(async () => {
       const res = await this.$$(withStrictLocator(locator));
       return res && res.length;
-    }, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
+    }, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
   }
 
   /**
diff --git a/lib/plugin/screenshotOnFail.js b/lib/plugin/screenshotOnFail.js
index d9144d06..fddfea64 100644
--- a/lib/plugin/screenshotOnFail.js
+++ b/lib/plugin/screenshotOnFail.js
@@ -129,7 +129,7 @@ module.exports = function (config) {
       return test.uuid;
     }
 
-    if (test.ctx) {
+    if (test.ctx && test.ctx.test.uuid) {
       return test.ctx.test.uuid;
     }
 


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();
+    });
+  });
 });
diff --git a/test/unit/plugin/screenshotOnFail_test.js b/test/unit/plugin/screenshotOnFail_test.js
index 6412c47d..6c47c1dc 100644
--- a/test/unit/plugin/screenshotOnFail_test.js
+++ b/test/unit/plugin/screenshotOnFail_test.js
@@ -44,5 +44,15 @@ describe('screenshotOnFail', () => {
     assert.equal('test1_1.failed.png', screenshotSaved.getCall(0).args[0]);
   });
 
+  it('should create screenshot with unique name when uuid is null', async () => {
+    screenshotOnFail({ uniqueScreenshotNames: true });
+    event.dispatcher.emit(event.test.failed, { title: 'test1' });
+    await recorder.promise();
+    assert.ok(screenshotSaved.called);
+    const fileName = screenshotSaved.getCall(0).args[0];
+    const regexpFileName = /test1_[0-9]{10}.failed.png/;
+    assert.equal(fileName.match(regexpFileName).length, 1);
+  });
+
   // TODO: write more tests for different options
 });