Repository: Codeception/CodeceptJS
Pull request #679 (2017-08-24 11:18:31)

Multiple fix
Fix https://github.com/Codeception/CodeceptJS/issues/485 - run-multiple: the first browser-resolution combination was be used in all configurations


Patch:
diff --git a/bin/codecept.js b/bin/codecept.js
index 719ecc45..3cb0d3ad 100755
--- a/bin/codecept.js
+++ b/bin/codecept.js
@@ -92,6 +92,7 @@ program.command('run-multiple [suites...]')
   .option('-f, --fgrep <string>', 'only run tests containing <string>')
   .option('--steps', 'show step-by-step execution')
   .option('--verbose', 'output internal logging information')
+  .option('--debug', 'output additional information')
   .option('-o, --override [value]', 'override current config options')
   .option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
   .option('-R, --reporter <name>', 'specify the reporter to use')
diff --git a/lib/command/run-multiple.js b/lib/command/run-multiple.js
index 3f2f5c96..4f24bd78 100644
--- a/lib/command/run-multiple.js
+++ b/lib/command/run-multiple.js
@@ -10,7 +10,7 @@ let output = require('../output');
 const path = require('path');
 const runner = path.join(__dirname, '/../../bin/codecept');
 let config, childOpts = {};
-const copyOptions = ['steps', 'reporter', 'verbose', 'config', 'reporter-options', 'grep', 'fgrep'];
+const copyOptions = ['steps', 'reporter', 'verbose', 'config', 'reporter-options', 'grep', 'fgrep', 'debug'];
 
 // codeceptjs run:multiple smoke:chrome regression:firefox - will launch smoke suite in chrome and regression in firefox
 // codeceptjs run:multiple smoke:chrome regression - will launch smoke suite in chrome and regression in firefox and chrome
@@ -60,29 +60,45 @@ module.exports = function (suites, options) {
       throw new Error(`Suite ${suiteName} was not configured in "multiple" section of config`);
     }
 
-    runSuite(suiteName, suiteConf, browser);
+    // filter run configurations by browser if exists
+    if (browser) {
+      suiteConf.browsers = suiteConf.browsers.filter((b) => {
+        if (typeof b === 'object') {
+          return b.browser === browser;
+        }
+        return b === browser;
+      });
+    }
+
+    //throw error if no configuration
+    if (suiteConf.browsers.length === 0) throw new Error(`Browser ${browser} not found in multiple suite "${suiteName}" config`);
+
+    runSuite(suiteName, suiteConf);
   });
 };
 
 function runSuite(suite, suiteConf, browser) {
-  let suiteBrowsers = suiteConf.browsers.map((b) => typeof b === 'object' ? b.browser : b);
 
   if (!browser) {
-    // run all browsers in this suite
-    suiteBrowsers.forEach((b) => runSuite(suite, suiteConf, b));
+    // run configurations
+    suiteConf.browsers.forEach((b) => {
+      let browserConf = Object.assign({}, suiteConf);
+      if (typeof b === 'object') {
+        browserConf.browser = b;
+        runSuite(suite, browserConf, b.browser);
+      } else {
+        browserConf.browser = { browser: b };
+        runSuite(suite, browserConf, b);
+      }
+    });
     return;
   }
 
   // clone config
   let overriddenConfig = Object.assign({}, config);
 
-  if (suiteBrowsers.indexOf(browser) === -1) {
-    throw new Error(`Browser ${browser} not found in multiple suite "${suite}" config`);
-  }
-
-  let browserConfig = suiteConf.browsers
-    .map((b) => typeof b === 'object' ? b : { browser: b })
-    .filter((b) => b.browser == browser)[0];
+  //get configuration
+  let browserConfig = suiteConf.browser;
 
   for (let key in browserConfig) {
     overriddenConfig.helpers = replaceValue(overriddenConfig.helpers, key, browserConfig[key]);
@@ -118,8 +134,8 @@ function runSuite(suite, suiteConf, browser) {
 
 
 /**
-* search key in object recursive and replace value in it
-*/
+ * search key in object recursive and replace value in it
+ */
 function replaceValue(obj, key, value) {
   if (!obj) return;
   if (obj instanceof Array) {
diff --git a/lib/helper/Mochawesome.js b/lib/helper/Mochawesome.js
index 0015cfba..d2669546 100644
--- a/lib/helper/Mochawesome.js
+++ b/lib/helper/Mochawesome.js
@@ -57,9 +57,8 @@ class Mochawesome extends Helper {
       fileName = clearString(test.title);
     }
     if (this.options.uniqueScreenshotNames) {
-      fileName =
-        fileName.substring(0, 10) + '-' + hashCode(fileName) + '-' +
-        hashCode(test.file);
+      let uuid = test.uuid || test.ctx.test.uuid;
+      fileName = `${fileName.substring(0, 10)}_${uuid}`;
     }
     if (test._retries < 1 || test._retries == test.retryNum) {
       fileName = `${fileName}.failed.png`;
diff --git a/lib/helper/Nightmare.js b/lib/helper/Nightmare.js
index 38de3bda..6d2844f0 100644
--- a/lib/helper/Nightmare.js
+++ b/lib/helper/Nightmare.js
@@ -6,7 +6,6 @@ const urlEquals = require('../assert/equal').urlEquals;
 const equals = require('../assert/equal').equals;
 const empty = require('../assert/empty').empty;
 const truth = require('../assert/truth').truth;
-const hashCode = require('../utils').hashCode;
 const xpathLocator = require('../utils').xpathLocator;
 const fileExists = require('../utils').fileExists;
 const clearString = require('../utils').clearString;
@@ -908,7 +907,8 @@ class Nightmare extends Helper {
       let fileName = clearString(test.title);
       if (test.ctx && test.ctx.test && test.ctx.test.type == 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`);
       if (this.options.uniqueScreenshotNames) {
-        fileName = `${fileName.substring(0, 10)}-${hashCode(fileName)}-${hashCode(test.file)}.failed.png`;
+        let uuid = test.uuid || test.ctx.test.uuid;
+        fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`;
       } else {
         fileName = fileName + '.failed.png';
       }
diff --git a/lib/helper/SeleniumWebdriver.js b/lib/helper/SeleniumWebdriver.js
index 15a79e61..fcb04c29 100644
--- a/lib/helper/SeleniumWebdriver.js
+++ b/lib/helper/SeleniumWebdriver.js
@@ -8,7 +8,6 @@ const urlEquals = require('../assert/equal').urlEquals;
 const equals = require('../assert/equal').equals;
 const empty = require('../assert/empty').empty;
 const truth = require('../assert/truth').truth;
-const hashCode = require('../utils').hashCode;
 const xpathLocator = require('../utils').xpathLocator;
 const fileExists = require('../utils').fileExists;
 const clearString = require('../utils').clearString;
@@ -199,7 +198,8 @@ class SeleniumWebdriver extends Helper {
       let fileName = clearString(test.title);
       if (test.ctx && test.ctx.test && test.ctx.test.type == 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`);
       if (this.options.uniqueScreenshotNames) {
-        fileName = `${fileName.substring(0, 10)}-${hashCode(fileName)}-${hashCode(test.file)}.failed.png`;
+        let uuid = test.uuid || test.ctx.test.uuid;
+        fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`;
       } else {
         fileName = fileName + '.failed.png';
       }
diff --git a/lib/helper/WebDriverIO.js b/lib/helper/WebDriverIO.js
index d6559934..537a38a2 100644
--- a/lib/helper/WebDriverIO.js
+++ b/lib/helper/WebDriverIO.js
@@ -7,7 +7,6 @@ const equals = require('../assert/equal').equals;
 const empty = require('../assert/empty').empty;
 const truth = require('../assert/truth').truth;
 const xpathLocator = require('../utils').xpathLocator;
-const hashCode = require('../utils').hashCode;
 const fileExists = require('../utils').fileExists;
 const clearString = require('../utils').clearString;
 const decodeUrl = require('../utils').decodeUrl;
@@ -350,10 +349,8 @@ class WebDriverIO extends Helper {
       let fileName = clearString(test.title);
       if (test.ctx && test.ctx.test && test.ctx.test.type == 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`);
       if (this.options.uniqueScreenshotNames) {
-        fileName =
-          fileName.substring(0, 10) + '-' + hashCode(fileName) + '-' +
-          hashCode(test.file) +
-          '.failed.png';
+        let uuid = test.uuid || test.ctx.test.uuid;
+        fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`;
       } else {
         fileName = fileName + '.failed.png';
       }
diff --git a/lib/listener/exit.js b/lib/listener/exit.js
index 025fba7a..06eb57f8 100644
--- a/lib/listener/exit.js
+++ b/lib/listener/exit.js
@@ -11,14 +11,16 @@ module.exports = function () {
   event.dispatcher.on(event.test.failed, function (testOrSuite) {
     // NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
     // is a suite and not a test
-    failedTests.push(testOrSuite.fullTitle());
+    let uuid = testOrSuite.uuid || testOrSuite.ctx.test.uuid;
+    failedTests.push(`${testOrSuite.fullTitle()}_${uuid}`);
   });
 
   // if test was successful after retries
   event.dispatcher.on(event.test.passed, function (testOrSuite) {
     // NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
     // is a suite and not a test
-    failedTests = failedTests.filter((failed) => testOrSuite.fullTitle() !== failed);
+    let uuid = testOrSuite.uuid || testOrSuite.ctx.test.uuid;
+    failedTests = failedTests.filter((failed) => `${testOrSuite.fullTitle()}_${uuid}` !== failed);
   });
 
   event.dispatcher.on(event.all.result, function () {
diff --git a/lib/utils.js b/lib/utils.js
index d666e563..8eb093e6 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -109,21 +109,6 @@ module.exports.lcfirst = function (str) {
   return str.charAt(0).toLowerCase() + str.substr(1);
 };
 
-module.exports.hashCode = function (str) {
-  /* Getting 32bit hashCode of string like in Java.
-   *  Used in Screenshot name to provide short screenshot names
-   */
-  var hash = 0;
-  var char;
-  if (str.length == 0) return hash;
-  for (var i = 0; i < str.length; i++) {
-    char = str.charCodeAt(i);
-    hash = (hash << 5) - hash + char;
-    hash = hash & hash; // Convert to 32bit integer
-  }
-  return hash;
-};
-
 module.exports.chunkArray = function (arr, chunk) {
   var i;
   var j;


Test patch:
diff --git a/test/data/fake_driver.js b/test/data/fake_driver.js
index d4dc6f47..d0e207bf 100644
--- a/test/data/fake_driver.js
+++ b/test/data/fake_driver.js
@@ -10,6 +10,10 @@ class FakeDriver extends Helper {
     this.debug(this.config.browser);
   }
 
+  printWindowSize() {
+    this.debug(this.config.windowSize);
+  }
+
 }
 
-module.exports = FakeDriver;
\ No newline at end of file
+module.exports = FakeDriver;
diff --git a/test/data/sandbox/codecept.multiple.json b/test/data/sandbox/codecept.multiple.json
index cbf7d079..918d9cff 100644
--- a/test/data/sandbox/codecept.multiple.json
+++ b/test/data/sandbox/codecept.multiple.json
@@ -5,7 +5,8 @@
   "helpers": {
     "FakeDriver": {
       "require": "../fake_driver",
-      "browser": "dummy"
+      "browser": "dummy",
+      "windowSize": "maximize"
     }
   },
   "multiple": {
@@ -13,11 +14,21 @@
       "browsers": ["chrome", { "browser": "firefox"}]
     },
     "mobile": {
-      "browsers": ["android", "safari"]
+      "browsers": [
+        "android",
+        {"browser": "safari", "windowSize": "maximize"},
+        {"browser": "chrome", "windowSize": "maximize"},
+        {"browser": "safari", "windowSize": "1200x840"}
+        ]
+    },
+    "grep": {
+      "grep": "@grep",
+      "browsers": ["chrome",
+        { "browser": "firefox", "windowSize": "1200x840"}]
     }
   },
   "include": {},
   "bootstrap": false,
   "mocha": {},
   "name": "sandbox"
-}
\ No newline at end of file
+}
diff --git a/test/data/sandbox/grep_test.multiple.js b/test/data/sandbox/grep_test.multiple.js
new file mode 100644
index 00000000..56a0ea99
--- /dev/null
+++ b/test/data/sandbox/grep_test.multiple.js
@@ -0,0 +1,5 @@
+Feature('@grep print browser size');
+
+Scenario('print browser size info', (I) => {
+  I.printWindowSize();
+});
diff --git a/test/runner/run_multiple_test.js b/test/runner/run_multiple_test.js
index c2bd3f29..6b0d6271 100644
--- a/test/runner/run_multiple_test.js
+++ b/test/runner/run_multiple_test.js
@@ -26,16 +26,6 @@ describe('CodeceptJS Multiple Runner', function() {
     });
   });
 
-  it('should execute one suite with browser', (done) => {
-    exec(codecept_run+'default:firefox', (err, stdout, stderr) => {
-      stdout.should.include('CodeceptJS'); // feature
-      stdout.should.include('.default:firefox] print browser ');
-      stdout.should.not.include('.default:chrome] print browser ');
-      assert(!err);
-      done();
-    });
-  });
-
   it('should execute all suites', (done) => {
     exec(codecept_run+'--all', (err, stdout, stderr) => {
       stdout.should.include('CodeceptJS'); // feature
@@ -43,6 +33,23 @@ describe('CodeceptJS Multiple Runner', function() {
       stdout.should.include('[2.default:firefox] print browser ');
       stdout.should.include('[3.mobile:android] print browser ');
       stdout.should.include('[4.mobile:safari] print browser ');
+      stdout.should.include('[5.mobile:chrome] print browser ');
+      stdout.should.include('[6.mobile:safari] print browser ');
+      stdout.should.include('[7.grep:chrome] @grep print browser size ');
+      stdout.should.include('[8.grep:firefox] @grep print browser size ');
+      stdout.should.not.include('[7.grep:chrome] print browser ');
+      stdout.should.include('[1.default:chrome] @grep print browser size ');
+      stdout.should.include('[3.mobile:android] @grep print browser size ');
+      assert(!err);
+      done();
+    });
+  });
+
+  it('should replace parameters', (done) => {
+    exec(codecept_run+'grep --debug', (err, stdout, stderr) => {
+      stdout.should.include('CodeceptJS'); // feature
+      stdout.should.include('[1.grep:chrome]  > maximize');
+      stdout.should.include('[2.grep:firefox]  > 1200x840');
       assert(!err);
       done();
     });
@@ -53,8 +60,22 @@ describe('CodeceptJS Multiple Runner', function() {
       stdout.should.include('CodeceptJS'); // feature
       stdout.should.include('[1.mobile:android] print browser ');
       stdout.should.include('[2.mobile:safari] print browser ');
+      stdout.should.include('[3.mobile:chrome] print browser ');
+      stdout.should.include('[4.mobile:safari] print browser ');
+      stdout.should.include('[5.default:chrome] print browser ');
+      stdout.should.include('[6.default:firefox] print browser ');
+
+      assert(!err);
+      done();
+    });
+  });
+
+  it('should execute multiple suites with setted browsers', (done) => {
+    exec(codecept_run+'mobile:safari default:chrome ', (err, stdout, stderr) => {
+      stdout.should.include('CodeceptJS'); // feature
+      stdout.should.include('[1.mobile:safari] print browser ');
+      stdout.should.include('[2.mobile:safari] print browser ');
       stdout.should.include('[3.default:chrome] print browser ');
-      stdout.should.include('[4.default:firefox] print browser ');
       assert(!err);
       done();
     });
@@ -72,4 +93,16 @@ describe('CodeceptJS Multiple Runner', function() {
     });
   });
 
-});
\ No newline at end of file
+  it('should pass grep to configuration', (done) => {
+    exec(codecept_run+'default --grep @grep', (err, stdout, stderr) => {
+      stdout.should.include('CodeceptJS'); // feature
+      stdout.should.include('[1.default:chrome] @grep print browser size');
+      stdout.should.include('[2.default:firefox] @grep print browser size');
+      stdout.should.not.include('[1.default:chrome] print browser ');
+      stdout.should.not.include('[2.default:firefox] print browser ');
+      assert(!err);
+      done();
+    });
+  });
+
+});