Repository: nightwatchjs/nightwatch (js)

Issue:
Awaited `assert` doesn't work inside other commands
If we use `await browser.assert.<some-assertion>` inside other commands like a custom-command (see #3286) or inside debug interface, the promise for the assertion is never resolved and the test gets stuck after the assertion.

Please note that the `assert` command currently works correctly inside the `debug()` interface because of a workaround (we replace `assert` with `verify`) but this needs to be fixed as we'll revert the workaround once #3275 is merged.

Pull request:
Fixes/resolve command node in assert commands
### Changes
- Delay the resolution of assert node so that command node (child node of assert resolves first).
- Added test to check if async/await custom commands using asserts works fine.
- fix static assertions (`assert.ok`) to return a promise in case of async `node`.
- reject assert node if error is assertion assertion error.
- fix a test to drop `status` params from promise result.

### Impacts
- fixes #3301 

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 335d6d46..00000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,94 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ""
-labels: ''
-assignees: ''
-
----
-
-## Describe the bug
-<!--
-A clear and concise description of what the bug is, how to reproduce it, and what the expected behaviour is.
-
-Please make sure to format the code samples, verbose output, configuration and other inline debug information (use the Preview functionality). Check out the [GitHub Markdown guide](https://guides.github.com/features/mastering-markdown/).
--->
-
-## Sample test
-<!-- 
-Include a sample test which reproduces the problem you're experiencing. If possible, the test should be against a public url.
-
-Please add the test and other info inline, not as attachments or screenshots.
--->
-
-<details><summary>sampleTest.js</summary><p>
-<!-- browsers demand the next line be empty -->
-
-```js
-// Please add the sample test here
-
-module.exports = {
-  sampleTest: function(browser) {
-
-  }
-}
-```
-</p></details>
-
-**Run with command**
-<!-- Include the command used to run the test, if applicable. -->
-
-```sh
-$ nightwatch test/sampleTest.js --your-other-arguments-here
-```
-
-### Verbose output
-<details><summary>debug.log</summary><p>
-<!-- browsers demand the next line be empty -->
-
-```txt
-<!-- Include the verbose output, if possible (run nightwatch with `--verbose` argument) -->
-```
-</p></details>
-
-
-## Configuration
-<details><summary>nightwatch.json</summary><p>
-<!-- browsers demand the next line be empty -->
-
-<!-- Please paste your nightwatch.json or nightwatch.conf.js here; make sure to leave out any sensitive details -->
-
-```js
-{
-  "your": { "config": "here" }
-}
-```
-</p></details>
-
-## Your Environment
-<!--- Include the relevant details your environment -->
-
-| Executable | Version |
-| ---: | :--- |
-| `nightwatch --version` | VERSION |
-| `npm --version`  | VERSION |
-| `yarn --version` | VERSION |
-| `node --version` | VERSION |
-
-| Browser driver | Version |
-| --- | --- |
-| NAME | VERSION |
-<!-- For example:
-| chromedriver | 76 |
-| geckodriver | v0.25.0 |
-| selenium-server | 3.141 |
--->
-
-| OS | Version |
-| --- | --- |
-| NAME | VERSION |
-<!-- For example:
-| macOS Sierra | 10.12.3 |
-| Windows 10 | 1607 |
-| Ubuntu | 16.10 |
--->
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000..e9070c13
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,164 @@
+name: "Bug Report"
+description: "File a bug report to help us improve Nightwatch."
+body:
+  - type: "markdown"
+    attributes:
+      value: |
+        Thanks in advance for your contribution. Before submitting a new issue, try searching for a similar one here: https://github.com/nightwatchjs/nightwatch/search?type=Issues.
+  - type: "input"
+    id: "description"
+    attributes:
+      label: "Description of the bug/issue"
+      description: "A brief description of the issue, how to reproduce it, and the expected behavior."
+      placeholder: |
+        When I ____, I expected ____ to happen but ____ happened instead.
+    validations:
+      required: true
+  - type: "textarea"
+    id: "steps"
+    attributes:
+      label: "Steps to reproduce"
+      description: |
+        Explain how to cause the issue in the provided reproduction.
+      value: |
+        1. Go to '...'
+        2. Click on '...'
+        3. Scroll down to '...'
+        4. See error
+  - type: textarea
+    id: "sample-test"
+    attributes:
+      label: "Sample test"
+      description:
+        Include a sample test that reproduces the problem you're experiencing. If possible, the test should be against a public URL.
+
+        Please add the test and other info inline, not as attachments or screenshots.
+      placeholder: |
+        ```js
+        // Please add the sample test here
+
+        module.exports = {
+          sampleTest: function() {
+            browser
+              .navigateTo('<URL here>')
+              .click('<selector>')
+              .end();
+          }
+        }
+        ```
+  - type: input
+    id: "run-with-command"
+    attributes:
+      label: "Command to run"
+      description: "Include the command used to run the test."
+      placeholder: |
+        nightwatch test/sampleTest.js --your-other-arguments-here
+  - type: textarea
+    id: "verbose-output"
+    attributes:
+      label: "Verbose Output"
+      description: |
+        "Extended nightwatch command logging during the session. 
+        Please use `nightwatch --verbose` and paste the output here"
+      placeholder: |
+        ```sh
+        [Sample Test] Test Suite
+        ────────────────────────────────────────────────────────
+        ⠋ Starting ChromeDriver on port 9515...
+          Request POST /session
+          {
+            desiredCapabilities: {
+              browserName: 'chrome',
+              'goog:chromeOptions': { w3c: true, args: [] },
+              name: 'Sample Test'
+            },
+            capabilities: {
+              alwaysMatch: {
+                browserName: 'chrome',
+                'goog:chromeOptions': { w3c: true, args: [] }
+              }
+            }
+        ```
+  - type: textarea
+    id: "configuration"
+    attributes:
+      label: "Nightwatch Configuration"
+      description: "Add your nightwatch.json or nightwatch.conf.js here; Make sure to leave out any sensitive details."
+      placeholder: |
+        //
+        // Refer to the online docs for more details:
+        // https://nightwatchjs.org/gettingstarted/configuration/
+        //
+        //  _   _  _         _      _                     _          _
+        // | \ | |(_)       | |    | |                   | |        | |
+        // |  \| | _   __ _ | |__  | |_ __      __  __ _ | |_   ___ | |__
+        // | . ` || | / _` || '_ \ | __|\ \ /\ / / / _` || __| / __|| '_ \
+        // | |\  || || (_| || | | || |_  \ V  V / | (_| || |_ | (__ | | | |
+        // \_| \_/|_| \__, ||_| |_| \__|  \_/\_/   \__,_| \__| \___||_| |_|
+        //             __/ |
+        //            |___/
+        //
+        module.exports = {
+          src_folders: [],
+          page_objects_path: [],
+          custom_commands_path: [],
+          custom_assertions_path: '',
+          plugins: [],
+          globals_path: '',
+          webdriver: {},
+          test_settings: {}
+        }
+    validations:
+      required: true
+  - type: "markdown"
+    attributes:
+      value: |
+        ## Your Environment
+        Include the relevant details related to your environment.
+  - type: "input"
+    id: "nightwatch-version"
+    attributes:
+      label: "Nightwatch.js Version"
+      description: "The version of Nightwatch you use."
+      placeholder: "2.3.0"
+    validations:
+      required: true
+  - type: "input"
+    id: "npm-version"
+    attributes:
+      label: "NPM Version"
+      description: "The version of NPM you use."
+      placeholder: "8.12.1"
+    validations:
+      required: true
+  - type: "input"
+    id: "node-version"
+    attributes:
+      label: "Node Version"
+      description: "The version of Node you use."
+      placeholder: "14.0.0"
+  - type: "input"
+    id: "yarn-version"
+    attributes:
+      label: "Yarn Version"
+      description: "The version of Yarn you use."
+      placeholder: "1.0.1"
+  - type: "input"
+    id: "browser"
+    attributes:
+      label: "Browser"
+      description: "The browser(s) this issue occurred with."
+      placeholder: "Firefox 102.0.1"
+  - type: "input"
+    id: "operating-system"
+    attributes:
+      label: "Operating System"
+      description: "The operating system(s) this issue occurred with."
+      placeholder: "MacOs Monterey 12.5"
+  - type: "textarea"
+    id: "additional-information"
+    attributes:
+      label: "Additional Information"
+      description: |
+        Use this section to provide any additional information you might have
+        like screenshots, notes, or links to ideas.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..df1cfbef
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,11 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Ask a question
+    url: https://github.com/nightwatchjs/nightwatch/discussions
+    about: Ask questions and discuss topics with other community members
+  - name: Documentation Request
+    url: https://github.com/nightwatchjs/nightwatch-docs/issues/new
+    about: Request for documentation to be added/altered
+  - name: Chat with other community members. Ask support questions, discuss news and feature requests.
+    url: https://discord.gg/x5RuXc6Z
+    about: The official Nightwatchjs Discord community
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
new file mode 100644
index 00000000..4d877c88
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -0,0 +1,49 @@
+name: "Feature Request"
+description: "Request a feature or enhancement for Nightwatch"
+labels: ["enhancement"]
+body:
+  - type: "markdown"
+    attributes:
+      value: |
+        Thanks in advance for your contribution. Before requesting a new feature, try searching for a similar issue here: https://github.com/nightwatchjs/nightwatch/search?type=Issues.
+  - type: "textarea"
+    id: "description"
+    attributes:
+      label: "Description"
+      description: "Please describe your request in one or two sentences."
+    validations:
+      required: true
+  - type: "textarea"
+    id: "justification"
+    attributes:
+      label: "Problem Statement/Justification"
+      description: |
+        Please provide valid reason(s) why this should be added to Nightwatch
+
+        If this feature is related to a problem you've noticed, mention it as
+        well.
+    validations:
+      required: true
+  - type: "textarea"
+    id: "proposed-solution"
+    attributes:
+      label: "Proposed Solution or API"
+      description: |
+        Please provide code snippets, gists, or links to the ideal
+        design or API.
+    validations:
+      required: true
+  - type: "textarea"
+    id: "alternatives"
+    attributes:
+      label: "Alternatives"
+      description: |
+        What alternative solutions have you considered before making this
+        request?
+  - type: "textarea"
+    id: "additional-information"
+    attributes:
+      label: "Additional Information"
+      description: |
+        What resources (links, screenshots, etc.) do you have to assist this
+        effort?
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 672afc62..00000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-name: Feature request
-about: Suggest an enhancement for Nightwatch
-title: "[Enhancement]"
-labels: ''
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-<!-- A clear and concise description of what the problem is. -->
-
-**Describe the solution you'd like**
-<!-- Explain the use case in detail. -->
-
-**Describe how the solution will work**
-<!--And include a sample test case and/or usage, if applicable. -->
-
-**Describe alternatives you've considered**
-<!-- A clear and concise description of any alternative solutions or features you've considered, if there are any available.-->
-
-**Additional context**
-<!-- Add any other context or screenshots about the feature request here.-->
diff --git a/.github/label-commenter-config.yml b/.github/label-commenter-config.yml
new file mode 100644
index 00000000..d2f3e615
--- /dev/null
+++ b/.github/label-commenter-config.yml
@@ -0,0 +1,39 @@
+  # Configuration for Label Commenter - https://github.com/peaceiris/actions-label-commenter
+labels:
+  - name: needs-triaging
+    labeled:
+      issue:
+        body: |
+          @{{ issue.user.login }}, thank you for creating this issue. We will troubleshoot it as soon as we can.
+
+          ---
+          <details>
+            <summary>Info for maintainers</summary>
+            <div>
+              <br>
+              <p>
+                Triage this issue by using labels.
+              </p>
+              <p>
+                If information is missing, add a helpful comment and add the <code>I-issue-template</code> label.
+              </p>
+              <p>
+                Thank you!
+              </p>
+            </div>
+          </details>
+  - name: I-issue-template
+    labeled:
+      issue:
+        body: |
+          Hi, @{{ issue.user.login }}.
+          Please follow the issue template, we need more information to reproduce the issue.
+
+          Either a complete code snippet (if more than one file is needed, provide a GitHub repo and instructions to run the code), the specific versions used, or a more detailed description to help us understand the issue.
+
+          Reply to this issuee when all information is provided, thank you.
+  - name: enhancement
+    labeled:
+      issue:
+        body: |
+          Thank you for setting this as an enhancement. One of the product folk will triage this again to help see when we can fit this in an upcoming sprint.
diff --git a/.github/workflows/label-commenter.yml b/.github/workflows/label-commenter.yml
new file mode 100644
index 00000000..5c709475
--- /dev/null
+++ b/.github/workflows/label-commenter.yml
@@ -0,0 +1,18 @@
+# Configuration for Label Commenter - https://github.com/peaceiris/actions-label-commenter
+name: Label Commenter
+
+on:
+  issues:
+    types: [ labeled ]
+
+permissions:
+  contents: read
+  issues: write
+
+jobs:
+  comment:
+    runs-on: ubuntu-20.04
+    steps:
+      - uses: actions/checkout@v2
+      - name: Label Commenter
+        uses: peaceiris/actions-label-commenter@v1
diff --git a/lib/api/_loaders/assertion-scheduler.js b/lib/api/_loaders/assertion-scheduler.js
index fccd331f..0a367c4e 100644
--- a/lib/api/_loaders/assertion-scheduler.js
+++ b/lib/api/_loaders/assertion-scheduler.js
@@ -138,19 +138,17 @@ class AssertionScheduler {
         .then(() => {
           Utils.makePromise(this.instance.doneCallback, this, [resolveValue, this.instance]);
         })
-        .then(_ => {
-          if (resolveValue.passed) {
-            this.deferred.resolve(resolveValue);
-          } else {
-            this.deferred.reject(resolveValue.err);
-          }
-
-          return resolveValue;
-        })
         .then(_ => {
           if (testError && testError.name !== 'NoSuchElementError') {
             this.opts.reporter.registerTestError(testError);
           }
+          setTimeout(()=> {
+            if (resolveValue.passed) {
+              this.deferred.resolve(resolveValue);
+            } else {
+              this.deferred.reject(resolveValue.err);
+            }
+          });
         });
     }.bind(this), this.instance);
   }
diff --git a/lib/api/_loaders/static.js b/lib/api/_loaders/static.js
index 95b2443f..fc1ee08d 100644
--- a/lib/api/_loaders/static.js
+++ b/lib/api/_loaders/static.js
@@ -8,6 +8,8 @@ const chaiExpect = chai.expect;
 const {flag} = chai.util;
 const {AssertionRunner} = require('../../assertion');
 
+let __last_deferred__ = null;
+
 module.exports = class StaticAssert {
   static get assertOperators() {
     return {
@@ -27,6 +29,14 @@ module.exports = class StaticAssert {
     };
   }
 
+  static get lastDeferred() {
+    return __last_deferred__;
+  }
+
+  static set lastDeferred(value) {
+    __last_deferred__ = value;
+  }
+
   get api() {
     return this.nightwatchInstance.api;
   }
@@ -55,13 +65,14 @@ module.exports = class StaticAssert {
       constructor({negate, args}) {
         super();
 
+        StaticAssert.lastDeferred = null;
         this.negate = negate;
         this.args = args;
         this.passed = null;
         this.expected = null;
         this.actual = null;
-        let lastArgument = args[args.length - 1];
-        let isLastArgString = Utils.isString(lastArgument);
+        const lastArgument = args[args.length - 1];
+        const isLastArgString = Utils.isString(lastArgument);
         this.message = isLastArgString && (args.length > 2 || Utils.isBoolean(args[0])) && lastArgument ||
           Utils.isFunction(args[0]) && '[Function]';
       }
@@ -146,9 +157,16 @@ module.exports = class StaticAssert {
         isES6Async
       });
 
-      if (isES6Async) {
+      if (isES6Async || node.isES6Async) {
+        StaticAssert.lastDeferred = deferred;
+
         Object.assign(node.deferred.promise, this.api);
 
+        //prevent unhandled rejection        
+        node.deferred.promise.catch(err => {
+          return StaticAssert.lastDeferred.reject(err);
+        });
+
         return node.deferred.promise;
       }
 
diff --git a/lib/api/expect/elements.js b/lib/api/expect/elements.js
index c2e69d4c..e92b6d8a 100644
--- a/lib/api/expect/elements.js
+++ b/lib/api/expect/elements.js
@@ -56,16 +56,20 @@ class ExpectElements extends BaseExpect {
     return this.elementLocator
       .findElement({element, returnSingleElement: false, cacheElementId: false})
       .then(result => {
-        const {value} = result;
+        const {value, error} = result;
 
+        let elements;
         if (value) {
-          const elements = this.transport.mapWebElementIds(value);
-          this.resultValue = elements;
-
-          return this.resolve(elements);
+          elements = this.transport.mapWebElementIds(value);
+        } else if (error instanceof Error && error.name === 'NoSuchElementError') {
+          elements = this.transport.mapWebElementIds([]);
+        } else {
+          throw result;
         }
 
-        throw result;
+        this.resultValue = elements;
+
+        return this.resolve(elements);
       });
   }
 
diff --git a/lib/core/asynctree.js b/lib/core/asynctree.js
index 34e6b9fe..bf593edf 100644
--- a/lib/core/asynctree.js
+++ b/lib/core/asynctree.js
@@ -88,7 +88,7 @@ class AsyncTree extends EventEmitter{
   }
 
   shouldRejectNodePromise(err) {
-    if (err.isExpect && this.currentNode.isES6Async) {
+    if ((err.isExpect|| this.currentNode.namespace === 'assert') && this.currentNode.isES6Async) {
       return true;
     }
 
@@ -105,7 +105,7 @@ class AsyncTree extends EventEmitter{
       return false;
     }
 
-    return err.name === 'NightwatchAssertError' && !err.isExpect && !parent.isRootNode && parent.isES6Async;
+    return err.name !== 'NightwatchAssertError' && !err.isExpect && !parent.isRootNode && parent.isES6Async;
   }
 
   async runChildNode(node) {
diff --git a/lib/index.js b/lib/index.js
index 8634c053..f3a7380b 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -3,6 +3,7 @@ const Utils = require('./utils');
 const Settings = require('./settings/settings.js');
 const ElementGlobal = require('./api/_loaders/element-global.js');
 const NightwatchClient = require('./core/client.js');
+
 const {Logger} = Utils;
 
 const Nightwatch = module.exports = {};
@@ -193,6 +194,14 @@ Nightwatch.cli = function(callback) {
     ).then(console.log);
   } else if (argv.version) {
     Utils.printVersionInfo();
+  } else if (argv['list-files']) {
+    argv._source = argv['_'].slice(0);
+    const runner = Nightwatch.CliRunner(argv);
+    
+    runner.setupAsync()
+      .then(() => runner.getTestsFiles())
+      // eslint-disable-next-line no-console
+      .then((result) => console.log(JSON.stringify(result)));
   } else {
     if (!Utils.isFunction(callback)) {
       throw new Error('Supplied callback argument needs to be a function.');
@@ -359,4 +368,4 @@ Object.defineProperty(Nightwatch, 'element', {
 
     return ElementGlobal.element({locator, client});
   }
-});
\ No newline at end of file
+});
diff --git a/lib/reporter/global-reporter.js b/lib/reporter/global-reporter.js
index bfc838c0..2cc6d2e5 100644
--- a/lib/reporter/global-reporter.js
+++ b/lib/reporter/global-reporter.js
@@ -185,7 +185,7 @@ module.exports = class GlobalReporter {
   }
 
   async writeReport(reporter, globalResults) {
-    const {globals, output_folder, start_session} = this.settings;
+    const {globals, output_folder, start_session, folder_format} = this.settings;
     const needsCallback = reporter.write.length === 3;
     const options = {
       filename_prefix: this.settings.report_prefix,
@@ -193,7 +193,8 @@ module.exports = class GlobalReporter {
       globals,
       start_session,
       reporter,
-      openReport: this.openReport
+      openReport: this.openReport,
+      folder_format
     };
 
     if (needsCallback) {
diff --git a/lib/reporter/reporters/html.js b/lib/reporter/reporters/html.js
index 701b97be..6fe9b024 100644
--- a/lib/reporter/reporters/html.js
+++ b/lib/reporter/reporters/html.js
@@ -62,6 +62,20 @@ class HtmlReporter extends BaseReporter {
       });
   }
 
+  getFolderPrefix() {
+    let folderPrefix = '';
+    const {folder_format} = this.options; 
+    if (folder_format) {
+      if (typeof folder_format === 'function') {
+        folderPrefix = folder_format(this.results);
+      } else if (typeof folder_format === 'string') {
+        folderPrefix = folder_format;
+      }
+    }
+
+    return folderPrefix;
+  }
+
   async writeReport(data) {
     const results = this.results;
     const {modules}  = results;
@@ -80,7 +94,8 @@ class HtmlReporter extends BaseReporter {
       return prev;
     }, {});
 
-    const destFolder = path.join(output_folder, 'nightwatch-html-report');
+    const destFolder = path.join(output_folder, this.getFolderPrefix(), 'nightwatch-html-report');
+
     await Utils.createFolder(destFolder);
 
     const filename = path.join(destFolder, 'index.html');
diff --git a/lib/runner/cli/argv-setup.js b/lib/runner/cli/argv-setup.js
index cc665229..ab6c34aa 100644
--- a/lib/runner/cli/argv-setup.js
+++ b/lib/runner/cli/argv-setup.js
@@ -205,9 +205,9 @@ module.exports = new (function () {
             desc += dpadding;
           }
 
-          let prelude = '  ' + (kswitch) + ' ' + Logger.colors.stack_trace(spadding);
+          const prelude = '  ' + (kswitch) + ' ' + Logger.colors.stack_trace(spadding);
 
-          let extra = [
+          const extra = [
             this.demanded[key]
               ? '[required]'
               : null,
@@ -216,7 +216,7 @@ module.exports = new (function () {
               : null            
           ].filter(Boolean).join('  ');
 
-          let body = [desc, extra].filter(Boolean).join('  ');
+          const body = [desc, extra].filter(Boolean).join('  ');
 
           content.push(prelude + ' ' + Logger.colors.stack_trace(body));
         });
@@ -452,6 +452,11 @@ module.exports = new (function () {
         description: 'Shows version information.'
       });
 
+      // $ nightwatch --list-files
+      this.option('list-files', {
+        description: 'shows list of files present in the project.'
+      });
+
       return this;
     }
   }
@@ -461,8 +466,8 @@ module.exports = new (function () {
   }
 
   function rebase(base, dir) {
-    let ds = path.normalize(dir).split('/').slice(1);
-    let bs = path.normalize(base).split('/').slice(1);
+    const ds = path.normalize(dir).split('/').slice(1);
+    const bs = path.normalize(base).split('/').slice(1);
 
     for (var i = 0; ds[i] && ds[i] === bs[i]; i++) {
       ;
diff --git a/lib/runner/cli/cli.js b/lib/runner/cli/cli.js
index 2e9dc427..371be27f 100644
--- a/lib/runner/cli/cli.js
+++ b/lib/runner/cli/cli.js
@@ -317,6 +317,10 @@ class CliRunner {
     return this;
   }
 
+  getTestsFiles() {
+    return Runner.readTestSource(this.test_settings, this.argv);
+  }
+
   /**
    *
    * @param [done]
@@ -325,7 +329,7 @@ class CliRunner {
   runTests(done = null) {
     return this.runGlobalHook('before')
       .then(_ => {
-        return Runner.readTestSource(this.test_settings, this.argv);
+        return this.getTestsFiles();
       })
       .then(modules => {
         if (!this.testRunner) {
diff --git a/lib/settings/defaults.js b/lib/settings/defaults.js
index 8c596cbb..aa17f10b 100644
--- a/lib/settings/defaults.js
+++ b/lib/settings/defaults.js
@@ -108,8 +108,13 @@ module.exports = {
   // this can be useful when persisting data between test suites is needed, such as a cookie or session information
   persist_globals: false,
 
-  // The location where the JUnit XML report files will be saved. Set this to false if you want to disable XML reporting
-  output_folder: 'tests_output',
+  reporter_options: {
+    // The location where the JUnit XML and HTML report files will be saved. Set this to false if you want to disable XML reporting
+    output_folder: 'tests_output',
+
+    //The folder formatting to use while saving HTML report
+    folder_format: null
+  },
 
   // A string or array of folders (excluding subfolders) where the tests are located.
   src_folders: null,
diff --git a/lib/settings/settings.js b/lib/settings/settings.js
index 516b5acf..5ed7f3b8 100644
--- a/lib/settings/settings.js
+++ b/lib/settings/settings.js
@@ -275,6 +275,7 @@ class Settings {
     this.setUnitTestsMode();
     this.setParallelMode();
     this.setTestRunner();
+    this.setReporterOptions();
 
     if (typeof this.settings.src_folders == 'string') {
       this.settings.src_folders = [this.settings.src_folders];
@@ -287,6 +288,10 @@ class Settings {
     return this;
   }
 
+  setReporterOptions() {
+    defaultsDeep(this.settings, this.settings.reporter_options);
+  }
+
   setParallelMode() {
     const Concurrency = require('../runner/concurrency');
 
diff --git a/lib/transport/factory.js b/lib/transport/factory.js
index 08992bfd..6bd0164a 100644
--- a/lib/transport/factory.js
+++ b/lib/transport/factory.js
@@ -94,6 +94,13 @@ module.exports = class TransportFactory {
     if (usingSeleniumServer) {
       const Selenium = require('./selenium-webdriver/selenium.js');
 
+      // support for mobile native apps
+      if (browserName === null) {
+        const Mobile = require('./selenium-webdriver/mobile-webdriver.js');
+
+        return new Mobile(nightwatchInstance);
+      }
+
       return new Selenium(nightwatchInstance, browserName);
     }
 
diff --git a/lib/transport/selenium-webdriver/browserstack.js b/lib/transport/selenium-webdriver/browserstack.js
index fa8289de..56e87ea1 100644
--- a/lib/transport/selenium-webdriver/browserstack.js
+++ b/lib/transport/selenium-webdriver/browserstack.js
@@ -1,12 +1,13 @@
 const stripAnsi = require('strip-ansi');
-const Selenium = require('./selenium.js');
 const {Logger} = require('../../utils');
+const BaseDriver = require('./mobile-webdriver.js');
 
-class Browserstack extends Selenium {
-  static get ApiUrl() {
-    return 'https://api.browserstack.com/automate';
+class Browserstack extends BaseDriver {
+  get ApiUrl() {
+    return `https://api.browserstack.com/${this.isMobile ? 'app-automate' : 'automate'}`;
   }
 
+
   get accessKey() {
     return this.bstackOptions.accessKey;
   }
@@ -28,6 +29,7 @@ class Browserstack extends Selenium {
 
     this.adaptSettings();
     this.verifySettings();
+    this.isMobile = (typeof nightwatchInstance.client.api.isMobile === 'function' &&  nightwatchInstance.client.api.isMobile()) ? true : false;
     this.useLocal = false;
     this.nightwatchInstance.on('nightwatch:session.create', (data) => {
       this.sessionId = data.sessionId;
@@ -94,7 +96,7 @@ class Browserstack extends Selenium {
   async getBuildId() {
     try {
       const builds = await this.sendHttpRequest({
-        url: `${Browserstack.ApiUrl}/builds.json`,
+        url: `${this.ApiUrl}/builds.json`,
         method: 'GET',
         use_ssl: true,
         auth: {
@@ -116,7 +118,7 @@ class Browserstack extends Selenium {
   async sendReasonToBrowserstack(isFailure = false, reason = '') {
     reason = stripAnsi(reason);
     await this.sendHttpRequest({
-      url: `${Browserstack.ApiUrl}/sessions/${this.sessionId}.json`,
+      url: `${this.ApiUrl}/sessions/${this.sessionId}.json`,
       method: 'PUT',
       use_ssl: true,
       data: {
@@ -138,7 +140,7 @@ class Browserstack extends Selenium {
       await this.sendReasonToBrowserstack(!!failures, reason);
       // eslint-disable-next-line no-console
       console.log('\n  ' + 'See more info, video, & screenshots on Browserstack:\n' +
-        '  ' + Logger.colors.light_cyan(`https://automate.browserstack.com/builds/${this.buildId}/sessions/${this.sessionId}`));
+        '  ' + Logger.colors.light_cyan(`https://${this.isMobile ? 'app-automate' : 'automate'}.browserstack.com/builds/${this.buildId}/sessions/${this.sessionId}`));
 
       this.sessionId = null;
 
diff --git a/lib/transport/selenium-webdriver/mobile-webdriver.js b/lib/transport/selenium-webdriver/mobile-webdriver.js
new file mode 100644
index 00000000..54408c7f
--- /dev/null
+++ b/lib/transport/selenium-webdriver/mobile-webdriver.js
@@ -0,0 +1,25 @@
+const {WebDriver} = require('selenium-webdriver');
+const {Executor} = require('selenium-webdriver/http');
+const Selenium = require('./selenium');
+const http = require('selenium-webdriver/http');
+
+
+class Mobile extends Selenium {
+
+  get defaultServerUrl() {
+    return 'http://127.0.0.1:4723';
+  }
+
+  get defaultPathPrefix() {
+    return '/wd/hub';
+  }
+
+  createDriver() {
+    const httpClient = new http.HttpClient(this.getServerUrl());
+
+    return WebDriver.createSession(new Executor(httpClient), this.initialCapabilities);
+  }
+
+}
+
+module.exports = Mobile;


Test patch:
diff --git a/lib/runner/test-runners/mocha.js b/lib/runner/test-runners/mocha.js
index ecede70c..d601b02e 100644
--- a/lib/runner/test-runners/mocha.js
+++ b/lib/runner/test-runners/mocha.js
@@ -134,7 +134,7 @@ class MochaRunner {
     }
 
     if (this.mochaOpts.reporter === 'mochawesome' && !this.mochaOpts.reporterOptions.reportDir) {
-      this.mochaOpts.reporterOptions.reportDir = 'html-report';
+      this.mochaOpts.reporterOptions.reportDir =  settings.reportDir || 'html-report';
     }
 
     const MochaNightwatch = MochaRunner.MochaNightwatch({settings, argv, addtOpts});
diff --git a/test/apidemos/custom-commands/testUsingAsyncCustomAssert.js b/test/apidemos/custom-commands/testUsingAsyncCustomAssert.js
new file mode 100644
index 00000000..2d4ae710
--- /dev/null
+++ b/test/apidemos/custom-commands/testUsingAsyncCustomAssert.js
@@ -0,0 +1,5 @@
+describe('custom command using assert', function () {
+  it('element visible using custom command', async function() {
+    await browser.customVisible('#weblogin');
+  });
+});
\ No newline at end of file
diff --git a/test/extra/commands/es6async/customVisible.js b/test/extra/commands/es6async/customVisible.js
new file mode 100644
index 00000000..5fbdab19
--- /dev/null
+++ b/test/extra/commands/es6async/customVisible.js
@@ -0,0 +1,5 @@
+module.exports = class customVisible {
+  async command(selector) {
+    await this.api.assert.visible(selector);
+  }
+};
\ No newline at end of file
diff --git a/test/lib/command-mocks.js b/test/lib/command-mocks.js
index 07897d36..0bfd7043 100644
--- a/test/lib/command-mocks.js
+++ b/test/lib/command-mocks.js
@@ -204,6 +204,16 @@ module.exports = {
     return this;
   },
 
+  w3cVisible(value = true) {
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value
+      })
+    }, true);
+  }, 
+
   findElements({using = 'css selector', value = '#container', response = null, times = 0}) {
     const mockOpts = {
       url: '/session/13521-10219-202/elements',
diff --git a/test/lib/utils.js b/test/lib/utils.js
index 2e5aa589..58378922 100644
--- a/test/lib/utils.js
+++ b/test/lib/utils.js
@@ -1,5 +1,6 @@
 var Nightwatch = require('./nightwatch.js');
 const fs = require('fs');
+const glob = require('glob');
 
 module.exports = {
 
@@ -41,14 +42,19 @@ module.exports = {
     return process.platform === 'win32' ? '\r\n' : '\n';
   },
 
-  readFilePromise(fileName) {
+  readFilePromise(pattern) {
     return new Promise(function(resolve, reject) {
-      fs.readFile(fileName, function(err, result) {
-        if (err) {
-          return reject(err);
+      glob(pattern, (error, matches)=> {
+        if (error) {
+          return reject(error);
         }
-
-        resolve(result);
+        fs.readFile(matches[0], function(err, result) {
+          if (err) {
+            return reject(err);
+          }
+          
+          return resolve(result);
+        });
       });
     });
   },
diff --git a/test/sampletests/es6await/selenium/basicSampleTestSelenium.js b/test/sampletests/es6await/selenium/basicSampleTestSelenium.js
index 0bea4888..8a3e2a9e 100644
--- a/test/sampletests/es6await/selenium/basicSampleTestSelenium.js
+++ b/test/sampletests/es6await/selenium/basicSampleTestSelenium.js
@@ -10,7 +10,7 @@ describe('basicSampleTest', function() {
       assert.deepStrictEqual(result, {value: [{ELEMENT: '0'}], status: 0, returned: 1, passed: true});
     });
 
-    assert.deepStrictEqual(assertResult, {value: [{ELEMENT: '0'}], status: 0, returned: 1, passed: true});
+    assert.deepStrictEqual(assertResult, [{ELEMENT: '0'}]);
 
     await browser.end();
   });
diff --git a/test/sampletests/es6await/selenium/failures/sampleWithFailures.js b/test/sampletests/es6await/selenium/failures/sampleWithFailures.js
index 94d70c81..07fc3800 100644
--- a/test/sampletests/es6await/selenium/failures/sampleWithFailures.js
+++ b/test/sampletests/es6await/selenium/failures/sampleWithFailures.js
@@ -27,7 +27,7 @@ describe('sampleWithFailures', function() {
     await client.url('http://localhost');
     await client.verify.elementPresent({
       selector: '#badElement',
-      timeout: 15,
+      timeout: 25,
       retryInterval: 15
     })
       .verify.elementPresent('#weblogin');
diff --git a/test/sampletests/suiteretries/locate-strategy/suiteRetriesLocateStrategy.js b/test/sampletests/suiteretries/locate-strategy/suiteRetriesLocateStrategy.js
index 11e72ddb..9240f2a5 100644
--- a/test/sampletests/suiteretries/locate-strategy/suiteRetriesLocateStrategy.js
+++ b/test/sampletests/suiteretries/locate-strategy/suiteRetriesLocateStrategy.js
@@ -20,7 +20,8 @@ module.exports = {
       .perform(function() {
         if (failFirstTime) {
           failFirstTime = false;
-          client.assert.ok(false);
+
+          return client.assert.ok(false);
         }
       });
   }
diff --git a/test/sampletests/withuncaughterrors/demoTestWithError.js b/test/sampletests/withuncaughterrors/demoTestWithError.js
index fc5c1202..94f409d4 100644
--- a/test/sampletests/withuncaughterrors/demoTestWithError.js
+++ b/test/sampletests/withuncaughterrors/demoTestWithError.js
@@ -13,7 +13,7 @@ describe('test with uncaught error', function () {
 
     setTimeout(function () {
       throw new Error('Test Error Uncaught');
-    }, 25);
+    }, 100);
   });
 
 });
diff --git a/test/src/api/commands/client/testPause.js b/test/src/api/commands/client/testPause.js
index 1cd2e569..f9620a28 100644
--- a/test/src/api/commands/client/testPause.js
+++ b/test/src/api/commands/client/testPause.js
@@ -10,23 +10,21 @@ describe('.pause()', function() {
     CommandGlobals.afterEach.call(this, done);
   });
 
-  it('browser.pause(10)', function(done) {
+  it('browser.pause(10) pauses atleast 10ms and not more than 2000ms', function(done) {
     const startTime = new Date();
     this.client.api.pause(10, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 10);
-      assert.ok(timeElapsed <= 20);
     });
 
     this.client.start(done);
   });
 
-  it('browser.pause(200)', function(done) {
+  it('browser.pause(200) pauses for atleast 200ms and not more than 2000ms', function(done) {
     const startTime = new Date();
     this.client.api.pause(200, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 210);
     });
 
     this.client.start(done);
@@ -37,7 +35,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
     });
 
     setTimeout(() => {
@@ -52,7 +49,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 1000);
-      assert.ok(timeElapsed <= 1020);
     });
 
     setTimeout(() => {
@@ -71,7 +67,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
       assert.strictEqual(Debuggability.stepOverAndPause, true);
     });
 
@@ -91,7 +86,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
       assert.strictEqual(Debuggability.stepOverAndPause, true);
     });
 
@@ -120,7 +114,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
       assert.strictEqual(apiEndCalledWithCallback, true);
       assert.strictEqual(callbackCalled.toString().includes('process.exit(0)'), true);
 
diff --git a/test/src/api/expect/testExpectCount.js b/test/src/api/expect/testExpectCount.js
index 8c961ff9..1d6494af 100644
--- a/test/src/api/expect/testExpectCount.js
+++ b/test/src/api/expect/testExpectCount.js
@@ -181,7 +181,7 @@ describe('expect.elements count', function() {
         assert.strictEqual(expect.assertion.passed, false);
         assert.ok(expect.assertion.messageParts.includes(' equal: "4"'));
         assert.ok(expect.assertion.message.startsWith('Expected elements <.classname> count to equal: "4"'));
-        assert.strictEqual(expect.assertion.resultValue, undefined);
+        assert.strictEqual(expect.assertion.resultValue, 0);
       });
     });
   });
diff --git a/test/src/apidemos/custom-commands/testCustomCommandsAsync.js b/test/src/apidemos/custom-commands/testCustomCommandsAsync.js
index 2f8c7ebf..7fedf590 100644
--- a/test/src/apidemos/custom-commands/testCustomCommandsAsync.js
+++ b/test/src/apidemos/custom-commands/testCustomCommandsAsync.js
@@ -39,11 +39,41 @@ describe('custom commands with findElements es6 async', function() {
 
     return NightwatchClient.runTests(testsPath, settings({
       output: false,
+      silent: true,
       selenium_host: null,
       custom_commands_path: [path.join(__dirname, '../../../extra/commands/es6async')],
       globals
     }));
   });
 
+  it('custom assert visible es6 async', function() {
+    const testsPath = path.join(__dirname, '../../../apidemos/custom-commands/testUsingAsyncCustomAssert.js');
+    
+    Mocks.createNewW3CSession({
+      testName: 'custom command using assert'
+    });
+
+    Mocks.w3cVisible(true);
+
+    const globals = {
+      waitForConditionPollInterval: 50,
+      waitForConditionTimeout: 120,
+      retryAssertionTimeout: 1000,
+
+      reporter(results) {
+        if (results.lastError) {
+          throw results.lastError;
+        }
+      }
+    };
+
+    return NightwatchClient.runTests(testsPath, settings({
+      output: false,
+      silent: true,
+      selenium_host: null,
+      custom_commands_path: [path.join(__dirname, '../../../extra/commands/es6async')],
+      globals
+    }));
+  });
 
-});
+});
\ No newline at end of file
diff --git a/test/src/cli/testCliRunner.js b/test/src/cli/testCliRunner.js
index a2eb3490..50c7a4b6 100644
--- a/test/src/cli/testCliRunner.js
+++ b/test/src/cli/testCliRunner.js
@@ -56,6 +56,12 @@ describe('Test CLI Runner', function() {
       }
     });
 
+    mockery.registerMock('./reporter_options.json', {
+      reporter_options: {
+        output_folder: 'output'
+      }
+    });
+
     mockery.registerMock('./empty.json', {
       src_folders: 'tests'
     });
@@ -399,6 +405,35 @@ describe('Test CLI Runner', function() {
     assert.strictEqual(runner.settings.output_folder, false);
   });
 
+  it('testSetOutputFolder using reporterOptions', function(done) {
+    mockery.registerMock('fs', {
+      statSync: function(module) {
+        if (module === './settings.json' || module === './nightwatch.conf.js') {
+          throw new Error('Does not exist');
+        }
+
+        return {
+          isFile: function() {
+            return true;
+          }
+        };
+      },
+      constants,
+      rmdirSync
+    });
+
+    const CliRunner = common.require('runner/cli/cli.js');
+    const runner = new CliRunner({
+      config: './reporter_options.json',
+      env: 'default'
+    }).setup();
+
+    assert.strictEqual(runner.test_settings.output_folder, 'output');
+
+    done();
+  });
+  
+
   it('testReadSettingsDeprecated', function(done) {
     mockery.registerMock('fs', {
       statSync: function(module) {
@@ -436,6 +471,8 @@ describe('Test CLI Runner', function() {
     done();
   });
 
+ 
+
   it('testCustomSettingsFileAndEnvironment', function() {
     mockery.registerMock('fs', {
       statSync: function(module) {
diff --git a/test/src/cli/testCliRunnerMocha.js b/test/src/cli/testCliRunnerMocha.js
index 0f8aeb98..cd3a8664 100644
--- a/test/src/cli/testCliRunnerMocha.js
+++ b/test/src/cli/testCliRunnerMocha.js
@@ -168,6 +168,59 @@ describe('test CLI Runner Mocha', function() {
       assert.deepStrictEqual(testFiles, ['test1.js', 'test2.js']);
     });
   });
+
+  it('testRunWithMochaReporterOptions', function() {
+    const testFiles = [];
+
+    const mochaOptions = {
+      timeout: 20000, 
+      reporterOptions: {
+        reportDir: 'html-dir'
+      },
+      reporter: 'mochawesome'
+    };
+
+    if (isCi) {
+      mochaOptions.color = false;
+    }
+
+    mockery.registerMock('mochawesome', {});
+
+    mockery.registerMock('./withmochaReportOptions.json', {
+      src_folders: ['tests'],
+      output_folder: false,
+      test_settings: {
+        'default': {
+          silent: true
+        }
+      },
+      reporter_options: {
+        reportDir: 'html-dir'
+      },
+      test_runner: {
+        type: 'mocha',
+        options: {
+          reporter: 'mochawesome'
+        }
+      }
+    });
+
+    createMockedMocha(function(options) {
+      assert.deepStrictEqual(options, mochaOptions);
+    }, testFiles);
+
+    const CliRunner = common.require('runner/cli/cli.js');
+    const runner = new CliRunner({
+      config: './withmochaReportOptions.json',
+      env: 'default',
+      reporter: 'junit'
+    }).setup();
+
+    return runner.runTests().then(function() {
+      assert.deepStrictEqual(testFiles, ['test1.js', 'test2.js']);
+    });
+  });
+
 });
 
 
diff --git a/test/src/core/testCreateSession.js b/test/src/core/testCreateSession.js
index a6ac54f3..2b871303 100644
--- a/test/src/core/testCreateSession.js
+++ b/test/src/core/testCreateSession.js
@@ -387,4 +387,49 @@ describe('test Request With Credentials', function () {
       });
     }, /Error: Unknown browser:/);
   });
+
+  it('Test create session with browsername null - mobile support', async function () {
+
+    nock('http://localhost:4723')
+      .post('/wd/hub/session')
+      .reply(201, {
+        value: {
+          capabilities: {
+            platformName: 'iOS',
+            platformVersion: '15.5',
+            deviceName: 'iPhone 13',
+            name: 'sample test goes here'
+          },
+          sessionId: '1352110219202'
+        }
+      });
+
+    const client = Nightwatch.createClient({
+      webdriver: {
+        start_process: false
+      },
+      selenium: {
+        start_process: false,
+        host: 'localhost',
+        port: '4723'
+      },
+      desiredCapabilities: {
+        browserName: null,
+        platformName: 'iOS',
+        platformVersion: '15.5',
+        deviceName: 'iPhone 13'
+      }
+    });
+
+    const result = await client.createSession();
+    assert.deepStrictEqual(result, {
+      capabilities: {
+        platformName: 'iOS',
+        platformVersion: '15.5',
+        deviceName: 'iPhone 13',
+        name: 'sample test goes here'
+      },
+      sessionId: '1352110219202'
+    });
+  });
 });
diff --git a/test/src/index/transport/testBrowserstackTransport.js b/test/src/index/transport/testBrowserstackTransport.js
index 3837dfe9..4df9aa4d 100644
--- a/test/src/index/transport/testBrowserstackTransport.js
+++ b/test/src/index/transport/testBrowserstackTransport.js
@@ -194,4 +194,68 @@ describe('BrowserstackTransport', function () {
     }, 100);
 
   });
+
+  it('test create Transport for Browserstack - App automate', function(done) {
+    const client = NightwatchClient.client({
+      webdriver: {
+        host: 'hub-cloud.browserstack.com',
+        port: 443,
+        start_process: false
+      },
+      desiredCapabilities: {
+        'browserstack.user': 'test-acess-user',
+        'browserstack.key': 'test-acess-key',
+        platformName: 'ios',
+        platfromVersion: '12',
+        deviceName: 'iPhone 12'
+      }
+    });
+    nock('https://api.browserstack.com')
+      .get('/app-automate/builds.json')
+      .reply(200, [{
+        automation_build: {
+          name: 'nightwatch-test-build',
+          hashed_id: '123-567-89'
+        } 
+      },
+      {
+        automation_build: {
+          name: 'test-build'
+        }
+      }
+      ]);
+
+    assert.ok(client.transport instanceof Browserstack);
+    assert.strictEqual(client.settings.webdriver.host, 'hub-cloud.browserstack.com');
+    assert.strictEqual(client.settings.webdriver.default_path_prefix, '/wd/hub');
+    assert.strictEqual(client.settings.webdriver.start_process, false);
+    assert.strictEqual(client.settings.webdriver.ssl, true);
+
+    const {transport} = client;
+
+    client.emit('nightwatch:session.create', {
+      sessionId: '1234567'
+    });
+    setTimeout(async function() {
+      assert.strictEqual(transport.buildId, '123-567-89');
+      
+      try {
+        let result;
+        nock('https://api.browserstack.com')
+          .put('/app-automate/sessions/1234567.json', {
+            status: 'passed',
+            reason: ''
+          })
+          .reply(200, {});
+
+        result = await transport.testSuiteFinished(false);
+        assert.strictEqual(result, true);
+        assert.strictEqual(transport.sessionId, null);
+
+        done();
+      } catch (e) {
+        done(e);
+      }
+    }, 100);
+  });
 });
diff --git a/test/src/index/transport/testMobileSupport.js b/test/src/index/transport/testMobileSupport.js
new file mode 100644
index 00000000..30581689
--- /dev/null
+++ b/test/src/index/transport/testMobileSupport.js
@@ -0,0 +1,35 @@
+const nock = require('nock');
+const assert = require('assert');
+const common = require('../../../common.js');
+const NightwatchClient = common.require('index.js');
+const Mobile = common.require('transport/selenium-webdriver/mobile-webdriver.js');
+
+
+describe('MobileTransport', function () {
+  it('test create Transport for Mobile',  function(){
+    const client = NightwatchClient.client({
+      webdriver: {
+        start_process: false
+      },
+      selenium: {
+        start_process: false,
+        host: 'localhost',
+        port: '4723'
+      },
+      output: true,
+      silent: false,
+      desiredCapabilities: {
+        browserName: null,
+        platformName: 'iOS',
+        platformVersion: '15.5',
+        deviceName: 'iPhone 13'
+      }
+    });
+
+    const {transport} = client;
+    assert.ok(transport instanceof Mobile);
+    assert.ok(client.settings.webdriver.host, 'localhost');
+    assert.ok(client.settings.webdriver.default_path_prefix, '/wd/hub');
+    assert.strictEqual(client.settings.webdriver.start_process, false);
+  });
+});
\ No newline at end of file
diff --git a/test/src/runner/testRunnerHtmlOutput.js b/test/src/runner/testRunnerHtmlOutput.js
index ac5f0a0f..c03c7d3a 100644
--- a/test/src/runner/testRunnerHtmlOutput.js
+++ b/test/src/runner/testRunnerHtmlOutput.js
@@ -116,4 +116,43 @@ describe('testRunnerHTMLOutput', function() {
       });
      
   });
+  
+  it('test html report folder with a folder format function', function () {
+
+    const testsPath = [
+      path.join(__dirname, '../../sampletests/withfailures')
+    ];
+
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/screenshot',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: '<faketag>fakedata'
+      })
+    }, true);
+
+
+    return runTests({source: testsPath, reporter: 'html'}, settings({
+      output_folder: outputPath,
+      globals: {
+        waitForConditionPollInterval: 20,
+        waitForConditionTimeout: 50,
+        retryAssertionTimeout: 50,
+        reporter: function () {
+        }
+      },
+      reporter_options: {
+        folder_format: function() {
+          return Date.now().toString();
+        }
+      }
+    }))
+      .then(_ => {
+        return readFilePromise(`${outputPath}${path.sep}*${path.sep}nightwatch-html-report${path.sep}index.html`);
+      }).
+      then(_ => {
+      });
+  });
 });
diff --git a/test/src/runner/testRunnerUncaughtErrors.js b/test/src/runner/testRunnerUncaughtErrors.js
index d0c15092..ccf38137 100644
--- a/test/src/runner/testRunnerUncaughtErrors.js
+++ b/test/src/runner/testRunnerUncaughtErrors.js
@@ -75,6 +75,6 @@ describe('testRunWithUncaughtErrors', function() {
       } catch (e) {
         done(e);
       }
-    }, 500);
+    }, 1500);
   });
 });