Repository: Codeception/CodeceptJS (js)

Issue:
Node version 12 support
#### What are you trying to achieve?
Run tests on node v12.6.0

#### What do you get instead?
Extra text (`Runner: runTest`) from `stdout`.
It works fine in Node v10.16.0

> Provide console output if related. Use `--verbose` mode for more details.

```bash
  4) CodeceptJS session
       should run with 3 sessions:

      check steps execution order
      + expected - actual

       [
      -  "Runner: runTest "
         "I do \"writing\""
         "davert: I do \"reading\""
      -  "Runner: runTest "
         "I do \"playing\""
         "john: I do \"crying\""
         "davert: I do \"smiling\""
      -  "Runner: runTest "
         "I do \"laughing\""
         "mike: I do \"spying\""
         "john: I do \"lying\""
      -  "Runner: runTest "
         "I do \"waving\""
       ]
      
      at test/runner/session_test.js:24:19                                                                                                                                                      
      at ChildProcess.exithandler (child_process.js:288:7)                                                                                                                                      
      at maybeClose (internal/child_process.js:1021:16)                                                                                                                                         
      at Process.ChildProcess._handle.onexit (internal/child_process.js:283:5)                                                                                                                  

```

> Provide test source code if related
[`session_test.js:16`](https://github.com/Codeception/CodeceptJS/blob/38e04f564cbb3e954dc591636c9d2f26a3f8c920/test/runner/session_test.js#L16)

```js

  it('should run with 3 sessions', (done) => {
    exec(`${codecept_run} --steps --grep "@1"`, (err, stdout, stderr) => {
      const lines = stdout.match(/\S.+/g);

      const list = grepLines(lines, 'basic session @1');
      list.pop();
      testStatus = list.pop();
      testStatus.should.include('OK');
      list.should.eql([
        'I do "writing"',
        'davert: I do "reading"',
        'I do "playing"',
        'john: I do "crying"',
        'davert: I do "smiling"',
        'I do "laughing"',
        'mike: I do "spying"',
        'john: I do "lying"',
        'I do "waving"',
      ], 'check steps execution order');
      done();
    });
  });
```

### Details

* CodeceptJS version: 2.2.0
* NodeJS Version: 12.6.0
* Operating System: Linux


Pull request description:
fix: skip Runner to detect MetaStep
resolves #1763

Patch:
diff --git a/RoboFile.php b/RoboFile.php
index 8160e07a..d1a134c1 100644
--- a/RoboFile.php
+++ b/RoboFile.php
@@ -159,7 +159,7 @@ class RoboFile extends \Robo\Tasks
         //issue
         $changelog = preg_replace(
             '~#(\d+)~',
-            '[#$1](https://github.com/Codeception/Codeception/issues/$1)',
+            '[#$1](https://github.com/Codeception/CodeceptJS/issues/$1)',
             $changelog
         );
 
@@ -203,4 +203,4 @@ class RoboFile extends \Robo\Tasks
         $this->_exec('npm publish');
         $this->yell('It is released!');
     }
-}
\ No newline at end of file
+}
diff --git a/lib/command/definitions.js b/lib/command/definitions.js
index e519a216..380d19ed 100644
--- a/lib/command/definitions.js
+++ b/lib/command/definitions.js
@@ -182,15 +182,16 @@ module.exports = function (genPath, options) {
   const supports = container.support(); // return all support objects
   const exportPageObjects = [];
   const callbackParams = [];
-  for (const name in supports) {
-    if (name === 'I') continue;
-    callbackParams.push(`${name}:CodeceptJS.${name}`);
+  // See #1795 and #1799, there's no obvious way to use for-in with Proxies
+  Reflect.ownKeys(supports).forEach((name) => {
+    if (name === 'I') return;
+    callbackParams.push(`${String(name)}:CodeceptJS.${String(name)}`);
     const pageObject = supports[name];
     const pageMethods = addAllMethodsInObject(pageObject, {}, []);
     let pageObjectExport = pageObjectTemplate.replace('{{methods}}', pageMethods.join(''));
-    pageObjectExport = pageObjectExport.replace('{{name}}', name);
+    pageObjectExport = pageObjectExport.replace('{{name}}', String(name));
     exportPageObjects.push(pageObjectExport);
-  }
+  });
 
   let definitionsTemplate = template.replace('{{methods}}', methods.join(''));
   definitionsTemplate = definitionsTemplate.replace('{{exportPageObjects}}', exportPageObjects.join('\n'));
diff --git a/lib/command/utils.js b/lib/command/utils.js
index c9cc65fa..ae488772 100644
--- a/lib/command/utils.js
+++ b/lib/command/utils.js
@@ -11,7 +11,7 @@ module.exports.getConfig = function (configFile) {
   try {
     return require('../config').load(configFile);
   } catch (err) {
-    fail(err.message);
+    fail(err.stack);
   }
 };
 
diff --git a/lib/container.js b/lib/container.js
index a0f85cb6..3473a379 100644
--- a/lib/container.js
+++ b/lib/container.js
@@ -162,6 +162,12 @@ function createHelpers(config) {
 
 function createSupportObjects(config) {
   const objects = container.support = new Proxy({}, {
+    has(target, key) {
+      return key in config;
+    },
+    ownKeys() {
+      return Reflect.ownKeys(config);
+    },
     get(target, key) {
       // configured but not in support object, yet: load the module
       if (key in config && !(key in target)) target[key] = lazyLoad(key);
diff --git a/lib/data/dataScenarioConfig.js b/lib/data/dataScenarioConfig.js
index 2ecf335a..9cb9173f 100644
--- a/lib/data/dataScenarioConfig.js
+++ b/lib/data/dataScenarioConfig.js
@@ -61,6 +61,24 @@ class DataScenarioConfig {
     this.scenarios.forEach(scenario => scenario.tag(tagName));
     return this;
   }
+
+  /**
+     * Pass in additional objects to inject into test
+     * @param {*} obj
+     */
+  inject(obj) {
+    this.scenarios.forEach(scenario => scenario.inject(obj));
+    return this;
+  }
+
+  /**
+     * Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection
+     * @param {*} dependencies
+     */
+  injectDependencies(dependencies) {
+    this.scenarios.forEach(scenario => scenario.injectDependencies(dependencies));
+    return this;
+  }
 }
 
 module.exports = DataScenarioConfig;
diff --git a/lib/helper/Polly.js b/lib/helper/Polly.js
index 859f2f8c..019bc405 100644
--- a/lib/helper/Polly.js
+++ b/lib/helper/Polly.js
@@ -67,9 +67,12 @@ class Polly extends Helper {
   // Connect Puppeteer helper to mock future requests.
   async _connectPuppeteer(title) {
     const adapter = require('@pollyjs/adapter-puppeteer');
-
     PollyJS.register(adapter);
+
     const { page } = this.helpers.Puppeteer;
+    if (!page) {
+      throw new Error('Looks like, there is no open tab');
+    }
     await page.setRequestInterception(true);
 
     this.polly = new PollyJS(title, {
@@ -148,16 +151,18 @@ class Polly extends Helper {
    */
   async stopMocking() {
     if (!this._checkIfMockingStarted()) return;
-
     await this._disconnectPuppeteer();
-    await this.polly.flush();
-    await this.polly.stop();
-    this.polly = undefined;
+
+    const { polly } = this;
+    if (!polly) return;
+    await polly.flush();
+    await polly.stop();
+    delete this.polly;
   }
 
   async _disconnectPuppeteer() {
     const { page } = this.helpers.Puppeteer;
-    await page.setRequestInterception(false);
+    if (page) await page.setRequestInterception(false);
   }
 }
 
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index 90c2d317..c51ea3b2 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -15,7 +15,6 @@ const {
   chunkArray,
   convertCssPropertiesToCamelCase,
   screenshotOutputFolder,
-  fileToBase64Zip,
 } = require('../utils');
 const {
   isColorProperty,
@@ -852,12 +851,11 @@ class WebDriver extends Helper {
     assertElementExists(res, locator, 'File field');
     const el = usingFirstElement(res);
 
-    // Remote Uplaod (when running Selenium Server)
+    // Remote Upload (when running Selenium Server)
     if (this.options.remoteFileUpload) {
-      const fileCompressed = await fileToBase64Zip(file);
       try {
         this.debugSection('File', 'Uploading file to remote server');
-        file = await this.browser.uploadFile(fileCompressed);
+        file = await this.browser.uploadFile(file);
       } catch (err) {
         throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`);
       }
diff --git a/lib/step.js b/lib/step.js
index 1e770157..877f33aa 100644
--- a/lib/step.js
+++ b/lib/step.js
@@ -154,8 +154,11 @@ function detectMetaStep(stack) {
     if (isTest(line) || isBDD(line)) break;
     const fnName = line.match(/^at (\w+)\.(\w+)\s\(/);
     if (!fnName) continue;
-    if (fnName[1] === 'Generator') return; // don't track meta steps inside generators
-    if (fnName[1] === 'recorder') return; // don't track meta steps inside generators
+    if (fnName[1] === 'Generator'
+      || fnName[1] === 'recorder'
+      || fnName[1] === 'Runner'
+    ) { return; } // don't track meta steps inside generators
+
     if (fnName[1] === 'Object') {
       // detect PO name from includes
       for (const name in support) {
diff --git a/lib/ui.js b/lib/ui.js
index 9ebcbcfa..1a6a2f80 100644
--- a/lib/ui.js
+++ b/lib/ui.js
@@ -138,7 +138,7 @@ module.exports = function (suite) {
      * Pending test case.
      */
     context.xScenario = context.Scenario.skip = function (title) {
-      context.Scenario(title, {});
+      return context.Scenario(title, {});
     };
 
     addDataContext(context);
diff --git a/lib/utils.js b/lib/utils.js
index a0b6efd5..8f42b4cb 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -281,26 +281,6 @@ module.exports.screenshotOutputFolder = function (fileName) {
   return path.join(global.codecept_dir, fileName);
 };
 
-module.exports.fileToBase64Zip = async function (localPath) {
-  const archiver = require('archiver');
-
-  const zipData = [];
-  const source = fs.createReadStream(localPath);
-
-  return new Promise((resolve, reject) => {
-    archiver('zip')
-      .on('error', (e) => { throw new Error(e); })
-      .on('data', data => zipData.push(data))
-      .on('end', () => resolve(Buffer.concat(zipData).toString('base64')))
-      .append(source, { name: path.basename(localPath) })
-      .finalize((err) => {
-        if (err) {
-          reject(err);
-        }
-      });
-  });
-};
-
 module.exports.beautify = function (code) {
   const format = require('js-beautify').js;
   return format(code, { indent_size: 2, space_in_empty_paren: true });


Test patch:
diff --git a/test/runner/list_test.js b/test/runner/list_test.js
index 816204dc..90785d37 100644
--- a/test/runner/list_test.js
+++ b/test/runner/list_test.js
@@ -47,4 +47,19 @@ describe('list/def commands', () => {
       done();
     });
   });
+
+  it('def should create definition file with support object', (done) => {
+    try {
+      fs.unlinkSync(`${codecept_dir}/steps.d.ts`);
+    } catch (e) {
+      // continue regardless of error
+    }
+    exec(`${runner} def --config ${codecept_dir}/codecept.inject.po.json`, () => {
+      const content = fs.readFileSync(`${codecept_dir}/steps.d.ts`).toString();
+      content.should.include('    openDir() : void');
+      content.should.include('  export interface MyPage {');
+      content.should.include('    hasFile() : void');
+      done();
+    });
+  });
 });