Repository: nightwatchjs/nightwatch
Pull request #3992 (2024-01-15 10:32:52)

Fix element lookup with reference to a shadow root.
Instead of calling `.findElements()` directly on `parentElement.webElement` (which is always a `WebElementPromise` due to which `.findElement()` method on it will always hit `/elements/:elementId/elements` endpoint), we first resolve `parentElement.webElement` promise in this PR to get a corresponding instance of `WebElement` or `ShadowRoot` and then apply `.findElements()` on top of the actual result.

Fixes: #3989 


github-actions[bot]: 
## Status
* ❌ No modified files found in the **types** directory.
Please make sure to include types for any changes you have made. Thank you!.

Patch:
diff --git a/lib/api/client-commands/axeInject.js b/lib/api/client-commands/axeInject.js
index 99332b61..7d3c1d97 100644
--- a/lib/api/client-commands/axeInject.js
+++ b/lib/api/client-commands/axeInject.js
@@ -1,10 +1,10 @@
 /**
- * Injects the [axe-core](https://github.com/dequelabs/axe-core) js library into the current page (using the .executeScript() command).
+ * Injects the [axe-core](https://github.com/dequelabs/axe-core) js library into the current page (using the `.executeScript()` command).
+ * To be paired with `.axeRun()` to evaluate the axe-core accessibility rules.
  *
  * @example
- * describe('accessibility testing', function() {
- *
- *   it('accessibility rule subset', function(browser) {
+ * describe('accessibility testing', function () {
+ *   it('accessibility rule subset', function (browser) {
  *     browser
  *       .url('https://www.w3.org/WAI/demos/bad/after/home.html')
  *       .assert.titleEquals('Welcome to CityLights! [Accessible Home Page]')
@@ -13,10 +13,9 @@
  *         runOnly: ['color-contrast', 'image-alt'],
  *       });
  *   });
- * })
+ * });
  *
  * @method axeInject
- * @link https://github.com/dequelabs/axe-core
  * @syntax browser.axeInject()
  * @api protocol.accessibility
  * @since 2.3.6
diff --git a/lib/api/client-commands/axeRun.js b/lib/api/client-commands/axeRun.js
index 6cbaa560..4b2b5519 100644
--- a/lib/api/client-commands/axeRun.js
+++ b/lib/api/client-commands/axeRun.js
@@ -2,9 +2,8 @@
  * Analyzes the current page against applied axe rules.
  *
  * @example
- * describe('accessibility testing', function() {
- *
- *   it('accessibility rule subset', function(browser) {
+ * describe('accessibility testing', function () {
+ *   it('accessibility rule subset', function (browser) {
  *     browser
  *       .url('https://www.w3.org/WAI/demos/bad/after/home.html')
  *       .assert.titleEquals('Welcome to CityLights! [Accessible Home Page]')
@@ -13,17 +12,16 @@
  *         runOnly: ['color-contrast', 'image-alt'],
  *       });
  *   });
- * })
+ * });
  *
  * @method axeRun
- * @link https://github.com/dequelabs/axe-core
  * @syntax browser.axeRun('body')
- * @param {string} selector The CSS selector used to locate the element.
- * @param {object} options Object containing rules configuration to use when performing the analysis
- * @see https://www.deque.com/axe/core-documentation/api-documentation/#options-parameter
- * @param {function} [callback] Optional callback function which is called with the results
+ * @param {*} [context] Defines the scope of the analysis, will cascade to child elements. See [axe-core docs](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#context-parameter) for more details.
+ * @param {object} [options] Object containing rules configuration to use when performing the analysis. See [axe-core docs](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter) for more details.
+ * @param {function} [callback] Optional callback function which is called with the results.
  * @api protocol.accessibility
  * @since 2.3.6
+ * @see https://www.deque.com/axe/core-documentation/api-documentation/#api-name-axerun
  */
 module.exports = class AxeInject {
   static get allowOverride() {
diff --git a/lib/api/web-element/scoped-element.js b/lib/api/web-element/scoped-element.js
index b127f110..69cd4212 100644
--- a/lib/api/web-element/scoped-element.js
+++ b/lib/api/web-element/scoped-element.js
@@ -89,9 +89,15 @@ class ScopedWebElement {
     const createLocateElement = () => {
       return parentElement && parentElement.webElement ? function(locator) {
         return new Condition('for at least one element to be located ' + locator, function (driver) {
-          return parentElement.webElement.findElements(locator).then(function (elements) {
-            return elements.length > 0 ? elements : null;
-          });
+          return parentElement.webElement
+            .then(function (webElement) {
+              // also takes into consideration if `webElement`
+              // resolves to a shadow root.
+              return webElement.findElements(locator);
+            })
+            .then(function (elements) {
+              return elements.length > 0 ? elements : null;
+            });
         });
       } : until.elementsLocated;
     };
@@ -111,7 +117,7 @@ class ScopedWebElement {
     while (allElements.length > 0) {
       const nextElement = allElements.shift();
       if (result) {
-        parentElement = {webElement: result};
+        parentElement = {webElement: Promise.resolve(result)};
       }
 
       const {condition, index} = ScopedElementLocator.create(nextElement, this.nightwatchInstance);
diff --git a/lib/api/web-element/scoped-elements.js b/lib/api/web-element/scoped-elements.js
index 615725ae..c5e0cc49 100644
--- a/lib/api/web-element/scoped-elements.js
+++ b/lib/api/web-element/scoped-elements.js
@@ -56,9 +56,14 @@ class ScopedElements {
   }
 
   async findElements({parentElement, selector, timeout, retryInterval}) {
-    const webElements = parentElement && parentElement.webElement
-      ? await parentElement.webElement.findElements(selector)
-      : await this.nightwatchInstance.transport.driver.wait(until.elementsLocated(selector), timeout,  null, retryInterval);
+    let webElements;
+
+    if (parentElement && parentElement.webElement) {
+      const parentWebElement = await parentElement.webElement;
+      webElements = await parentWebElement.findElements(selector);
+    } else {
+      webElements = await this.nightwatchInstance.transport.driver.wait(until.elementsLocated(selector), timeout,  null, retryInterval);
+    }
 
     return webElements;
   }
diff --git a/lib/core/client.js b/lib/core/client.js
index f9944658..ee1aa3f9 100644
--- a/lib/core/client.js
+++ b/lib/core/client.js
@@ -13,6 +13,7 @@ const ElementGlobal = require('../api/_loaders/element-global.js');
 const Factory = require('../transport/factory.js');
 const {isAndroid, isIos} = require('../utils/mobile');
 const namespacedApi = require('../core/namespaced-api.js');
+const cdp = require('../transport/selenium-webdriver/cdp.js');
 
 const {LocateStrategy, Locator} = Element;
 const {Logger, isUndefined, isDefined, isObject, isFunction, isSafari, isChrome} = Utils;
@@ -757,6 +758,9 @@ class NightwatchClient extends EventEmitter {
 
         Logger.info(`Received session with ID: ${data.sessionId}\n`);
 
+        // Reset cdp connection every time a new webdriver session is created.
+        cdp.resetConnection();
+
         this.emit('nightwatch:session.create', data);
 
         return data;
diff --git a/lib/http/request.js b/lib/http/request.js
index 1b5f6ba9..91b55994 100644
--- a/lib/http/request.js
+++ b/lib/http/request.js
@@ -184,6 +184,11 @@ class HttpRequest extends EventEmitter {
       const url = new URL(options.url);
       reqOptions.path = url.pathname;
       reqOptions.host = url.hostname;
+
+      if (url.search && reqOptions.method === HttpUtil.Method.GET) {
+        // append search query parameters to path.
+        reqOptions.path += url.search;
+      }
     }
 
     this.auth = options.auth || null;
diff --git a/lib/transport/selenium-webdriver/browserstack/browserstack.js b/lib/transport/selenium-webdriver/browserstack/browserstack.js
index f20eae6a..aab48404 100644
--- a/lib/transport/selenium-webdriver/browserstack/browserstack.js
+++ b/lib/transport/selenium-webdriver/browserstack/browserstack.js
@@ -100,7 +100,7 @@ class Browserstack extends AppiumBaseServer {
   async getBuildId() {
     try {
       const builds = await this.sendHttpRequest({
-        url: `${this.ApiUrl}/builds.json`,
+        url: `${this.ApiUrl}/builds.json?status=running`,
         method: 'GET',
         use_ssl: true,
         auth: {
@@ -120,6 +120,22 @@ class Browserstack extends AppiumBaseServer {
   }
 
   async sendReasonToBrowserstack(isFailure = false, reason = '') {
+    const sessionDetails = await this.sendHttpRequest({
+      url: `${this.ApiUrl}/sessions/${this.sessionId}.json`,
+      method: 'GET',
+      use_ssl: true,
+      auth: {
+        user: this.username,
+        pass: this.accessKey
+      }
+    });
+
+    const status = sessionDetails?.automation_session?.status;
+    if (['passed', 'failed'].includes(status)) {
+      // status has already been set by user
+      return;
+    }
+
     reason = stripAnsi(reason);
     await this.sendHttpRequest({
       url: `${this.ApiUrl}/sessions/${this.sessionId}.json`,
@@ -134,7 +150,6 @@ class Browserstack extends AppiumBaseServer {
         pass: this.accessKey
       }
     });
-
   }
 
   async sessionFinished(reason, err) {
diff --git a/lib/transport/selenium-webdriver/index.js b/lib/transport/selenium-webdriver/index.js
index 1622b530..72e9de4e 100644
--- a/lib/transport/selenium-webdriver/index.js
+++ b/lib/transport/selenium-webdriver/index.js
@@ -8,7 +8,6 @@ const {IosSessionNotCreatedError, AndroidConnectionError} = require('../../utils
 const httpClient = require('./httpclient.js');
 const Session = require('./session.js');
 const BaseTransport = require('../');
-const CDP = require('./cdp.js');
 const {colors} = Logger;
 const {isErrorResponse, checkLegacyResponse, throwDecodedError, WebDriverError} = error;
 const {IosSessionErrors} = require('../errors');
@@ -189,7 +188,7 @@ class Transport extends BaseTransport {
 
   async sessionFinished(reason) {
     this.emit('session:finished', reason);
-    CDP.resetConnection();
+
     await this.closeDriver();
   }
 
diff --git a/lib/transport/selenium-webdriver/method-mappings.js b/lib/transport/selenium-webdriver/method-mappings.js
index f06f112f..1ccf2727 100644
--- a/lib/transport/selenium-webdriver/method-mappings.js
+++ b/lib/transport/selenium-webdriver/method-mappings.js
@@ -1062,7 +1062,7 @@ module.exports = class MethodMappings {
         ///////////////////////////////////////////////////////////////////////////
 
         async registerAuth(username, password) {
-          const cdpConnection = await cdp.getConnection(this.driver);
+          const cdpConnection = await cdp.getConnection(this.driver, true);
           await this.driver.register(username, password, cdpConnection);
 
           return {
diff --git a/package-lock.json b/package-lock.json
index 6f039ee5..31b0a863 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,7 +37,7 @@
         "minimatch": "3.1.2",
         "minimist": "1.2.6",
         "mocha": "10.2.0",
-        "nightwatch-axe-verbose": "^2.2.2",
+        "nightwatch-axe-verbose": "^2.3.0",
         "open": "8.4.2",
         "ora": "5.4.1",
         "piscina": "3.2.0",
@@ -2092,9 +2092,9 @@
       }
     },
     "node_modules/axe-core": {
-      "version": "4.7.2",
-      "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz",
-      "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==",
+      "version": "4.8.3",
+      "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.3.tgz",
+      "integrity": "sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw==",
       "engines": {
         "node": ">=4"
       }
@@ -6403,11 +6403,11 @@
       }
     },
     "node_modules/nightwatch-axe-verbose": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.2.2.tgz",
-      "integrity": "sha512-MvCrQb9D/ixtGtyN5fh7YCkelXuPup3X9D2Zhhk1PWyALtkdYCFadnIGif1irpQ1BLNSlqC+FDNnsNald7KHYA==",
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.3.0.tgz",
+      "integrity": "sha512-IC29PLvYrbbKRdIU/NJaxk/UvTmQ5EiNN08UnCWyImpzV0Y7tE1CYchrvFTaHXBZkkZTQC3uHTeHF/41mvK8eQ==",
       "dependencies": {
-        "axe-core": "^4.7.2"
+        "axe-core": "^4.8.3"
       }
     },
     "node_modules/no-case": {
@@ -10798,9 +10798,9 @@
       "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
     },
     "axe-core": {
-      "version": "4.7.2",
-      "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz",
-      "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g=="
+      "version": "4.8.3",
+      "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.3.tgz",
+      "integrity": "sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw=="
     },
     "axios": {
       "version": "1.6.5",
@@ -14006,11 +14006,11 @@
       }
     },
     "nightwatch-axe-verbose": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.2.2.tgz",
-      "integrity": "sha512-MvCrQb9D/ixtGtyN5fh7YCkelXuPup3X9D2Zhhk1PWyALtkdYCFadnIGif1irpQ1BLNSlqC+FDNnsNald7KHYA==",
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.3.0.tgz",
+      "integrity": "sha512-IC29PLvYrbbKRdIU/NJaxk/UvTmQ5EiNN08UnCWyImpzV0Y7tE1CYchrvFTaHXBZkkZTQC3uHTeHF/41mvK8eQ==",
       "requires": {
-        "axe-core": "^4.7.2"
+        "axe-core": "^4.8.3"
       }
     },
     "no-case": {
diff --git a/package.json b/package.json
index ee942572..6b978f40 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
     "minimatch": "3.1.2",
     "minimist": "1.2.6",
     "mocha": "10.2.0",
-    "nightwatch-axe-verbose": "^2.2.2",
+    "nightwatch-axe-verbose": "^2.3.0",
     "open": "8.4.2",
     "ora": "5.4.1",
     "piscina": "3.2.0",
diff --git a/types/index.d.ts b/types/index.d.ts
index de988137..baab012a 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -60,6 +60,11 @@ export interface JSON_WEB_OBJECT extends ElementResult {
 export type ScopedSelector = string | ElementProperties | Element | SeleniumBy | RelativeBy;
 export type Definition = ScopedSelector | WebElement;
 
+export type NightwatchGenericCallback<T> = (
+  this: NightwatchAPI,
+  result: NightwatchCallbackResult<T>
+) => void
+
 export type Awaitable<T, V> = Omit<T, 'then'> & PromiseLike<V>;
 
 // tslint:disable-next-line
@@ -2511,16 +2516,21 @@ export interface ClientCommands extends ChromiumClientCommands {
   ): Awaitable<this, undefined>;
 
   /**
-   * Injects the axe-core js library into the current page (using the .executeScript() command) to be paired
-   * with axeRun to evaluate the axe-core accessibility rules.
+   * Injects the [axe-core](https://github.com/dequelabs/axe-core) js library into the current page (using the `.executeScript()` command).
+   * To be paired with `.axeRun()` to evaluate the axe-core accessibility rules.
    *
    * @example
-   * this.demoTest = function () {
-   *   browser
-   *     .url('https://nightwatchjs.org')
-   *     .axeInject()
-   *     .axeRun();
-   * };
+   * describe('accessibility testing', function () {
+   *   it('accessibility rule subset', function (browser) {
+   *     browser
+   *       .url('https://www.w3.org/WAI/demos/bad/after/home.html')
+   *       .assert.titleEquals('Welcome to CityLights! [Accessible Home Page]')
+   *       .axeInject()
+   *       .axeRun('body', {
+   *         runOnly: ['color-contrast', 'image-alt'],
+   *       });
+   *   });
+   * });
    *
    * @see https://nightwatchjs.org/api/axeInject.html
    */
@@ -2530,46 +2540,46 @@ export interface ClientCommands extends ChromiumClientCommands {
    * Analyzes the current page against applied axe rules.
    *
    * @example
-   * this.demoTest = function () {
-   *   browser
-   *     .url('https://nightwatchjs.org')
-   *     .axeInject()
-   *     .axeRun(
-   *        'body',
-   *        { runOnly: ['color-contrast', 'image-alt'] }
-   *     );
-   * };
-   *
-   * @example
-   * this.demoTest = function () {
-   *   browser
-   *     .url('https://nightwatchjs.org')
-   *     .axeInject()
-   *     .axeRun(
-   *        'body',
-   *        {
-   *          'color-contrast': {
-   *             enabled: false
-   *            }
-   *          },
-   *        }
-   *     );
-   * };
+   * describe('accessibility testing', function () {
+   *   it('accessibility rule subset', function (browser) {
+   *     browser
+   *       .url('https://www.w3.org/WAI/demos/bad/after/home.html')
+   *       .assert.titleEquals('Welcome to CityLights! [Accessible Home Page]')
+   *       .axeInject()
+   *       .axeRun('body', {
+   *         runOnly: ['color-contrast', 'image-alt'],
+   *       });
+   *   });
+   * 
+   *   it('accessibility rule subset 2', function (browser) {
+   *     browser
+   *       .url('https://nightwatchjs.org')
+   *       .axeInject()
+   *       .axeRun(['#navBar', 'nav'], {
+   *         rules: {
+   *           'color-contrast': { enabled: false },
+   *         }
+   *       });
+   *   });
+   * });
    *
-   * @param selector - CSS selector to scope rule analysis against, will cascade to child elements
-   * @param options - Allows configuration of what rules will be run (accessibility standard or rules to enable/disable)
-   * @see {@link https://www.deque.com/axe/core-documentation/api-documentation/#options-parameter}
+   * @param context - Defines the scope of the analysis, will cascade to child elements. See
+   * [axe-core docs](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#context-parameter) for more details.
+   * @param options - Allows configuration of what rules will be run (accessibility standard or rules to enable/disable).
+   * See [axe-core docs](https://www.deque.com/axe/core-documentation/api-documentation/#options-parameter) for more details.
+   * @param callback - Optional callback function which is called with the results.
    *
    * @see {@link https://nightwatchjs.org/api/axeRun.html}
+   * @see {@link https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axerun}
    */
   axeRun(
-    selector?: string,
+    context?: unknown,
     options?: { [key: string]: any },
     callback?: (
       this: NightwatchAPI,
-      result: NightwatchCallbackResult<null>
+      result: NightwatchCallbackResult<{[key: string]: any}>
     ) => void
-  ): Awaitable<this, null>;
+  ): Awaitable<this, {[key: string]: any}>;
 }
 
 export interface ElementCommands {
@@ -3333,6 +3343,8 @@ export interface ElementCommands {
    * @returns `null` if element not found, `Error` otherwise.
    *
    * @example
+   * // 'message' should always be the last argument (if provided).
+   * // 'callback' should only be second-last to the 'message' argument, otherwise always last.
    * module.exports = {
    *  'demo Test': function() {
    *     // with default implicit timeout of 5000ms (can be overwritten in settings under 'globals.waitForConditionTimeout')
@@ -3398,25 +3410,19 @@ export interface ElementCommands {
    */
   waitForElementNotPresent(
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<null | ElementResult[]>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<null | ElementResult[]> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    callbackOrMessage?: NightwatchGenericCallback<null | ElementResult[]> | string,
     message?: string
   ): Awaitable<this, null | Error>;
   waitForElementNotPresent(
     using: LocateStrategy,
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<null | ElementResult[]>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<null | ElementResult[]> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    callbackOrMessage?: NightwatchGenericCallback<null | ElementResult[]> | string,
     message?: string
   ): Awaitable<this, null | Error>;
 
@@ -3432,6 +3438,8 @@ export interface ElementCommands {
    * @returns `false` if element not visible, `Error` otherwise.
    *
    * @example
+   * // 'message' should always be the last argument (if provided).
+   * // 'callback' should only be second-last to the 'message' argument, otherwise always last.
    * module.exports = {
    *  'demo Test': function() {
    *     // with default implicit timeout of 5000ms (can be overwritten in settings under 'globals.waitForConditionTimeout')
@@ -3497,25 +3505,19 @@ export interface ElementCommands {
    */
   waitForElementNotVisible(
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<boolean>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<boolean> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<boolean> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<boolean> | string,
+    callbackOrMessage?: NightwatchGenericCallback<boolean> | string,
     message?: string
   ): Awaitable<this, false | Error>;
   waitForElementNotVisible(
     using: LocateStrategy,
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<boolean>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<boolean> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<boolean> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<boolean> | string,
+    callbackOrMessage?: NightwatchGenericCallback<boolean> | string,
     message?: string
   ): Awaitable<this, false | Error>;
 
@@ -3529,6 +3531,8 @@ export interface ElementCommands {
    * @returns `ElementResult[]` if element is found, `Error` otherwise.
    *
    * @example
+   * // 'message' should always be the last argument (if provided).
+   * // 'callback' should only be second-last to the 'message' argument, otherwise always last.
    * module.exports = {
    *  'demo Test': function() {
    *     // with default implicit timeout of 5000ms (can be overwritten in settings under 'globals.waitForConditionTimeout')
@@ -3593,25 +3597,19 @@ export interface ElementCommands {
    */
   waitForElementPresent(
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<ElementResult[] | null>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<null | ElementResult[]> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    callbackOrMessage?: NightwatchGenericCallback<null | ElementResult[]> | string,
     message?: string
   ): Awaitable<this, ElementResult[] | Error>;
   waitForElementPresent(
     using: LocateStrategy,
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<ElementResult[] | null>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<null | ElementResult[]> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<null | ElementResult[]> | string,
+    callbackOrMessage?: NightwatchGenericCallback<null | ElementResult[]> | string,
     message?: string
   ): Awaitable<this, ElementResult[] | Error>;
 
@@ -3627,6 +3625,8 @@ export interface ElementCommands {
    * @returns `true` is element is visible, `Error` otherwise.
    *
    * @example
+   * // 'message' should always be the last argument (if provided).
+   * // 'callback' should only be second-last to the 'message' argument, otherwise always last.
    * this.demoTest = function (browser) {
    *   browser.waitForElementVisible('body', 1000);
    *   // continue if failed
@@ -3645,26 +3645,20 @@ export interface ElementCommands {
    */
   waitForElementVisible(
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<boolean>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<boolean> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<boolean> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<boolean> | string,
+    callbackOrMessage?: NightwatchGenericCallback<boolean> | string,
     message?: string
   ): Awaitable<this, true | Error>;
 
   waitForElementVisible(
     using: LocateStrategy,
     selector: Definition,
-    time?: number,
-    poll?: number,
-    abortOnFailure?: boolean,
-    callback?: (
-      this: NightwatchAPI,
-      result: NightwatchCallbackResult<boolean>
-    ) => void,
+    timeoutOrCallbackOrMessage?: number | NightwatchGenericCallback<boolean> | string,
+    pollIntervalOrAbortOnFailureOrCallbackOrMessage?: number | boolean | NightwatchGenericCallback<boolean> | string,
+    abortOnFailureOrCallbackOrMessage?: boolean | NightwatchGenericCallback<boolean> | string,
+    callbackOrMessage?: NightwatchGenericCallback<boolean> | string,
     message?: string
   ): Awaitable<this, true | Error>;
 


Test patch:
diff --git a/test/lib/command-mocks.js b/test/lib/command-mocks.js
index 4f79dcc9..78220abb 100644
--- a/test/lib/command-mocks.js
+++ b/test/lib/command-mocks.js
@@ -453,7 +453,8 @@ module.exports = {
     sessionId = '13521-10219-202',
     headless = true,
     deleteSession = true,
-    url = '/wd/hub/session'
+    url = '/wd/hub/session',
+    times = 0
   }) {
     const browserName = 'chrome';
     const headlessOpt = headless ? 'headless=new' : '';
@@ -472,7 +473,7 @@ module.exports = {
       postdata: JSON.stringify({
         capabilities: {firstMatch: [{}], alwaysMatch: {browserName, ...options}}
       }),
-
+      times,
       response: JSON.stringify({
         value: {
           sessionId,
@@ -486,16 +487,19 @@ module.exports = {
     }, !persist);
 
     if (!deleteSession) {
-      return;
+      return this;
     }
 
     MockServer.addMock({
-      url: `/session/${sessionId}`,
+      url: `${url}/${sessionId}`,
       method: 'DELETE',
+      times,
       response: {
         value: null
       }
     }, !persist);
+
+    return this;
   },
 
   createNewW3CSession({
diff --git a/test/src/api/commands/web-element/testShadowFind.js b/test/src/api/commands/web-element/testShadowFind.js
new file mode 100644
index 00000000..403387fa
--- /dev/null
+++ b/test/src/api/commands/web-element/testShadowFind.js
@@ -0,0 +1,190 @@
+const assert = require('assert');
+const {WebElement} = require('selenium-webdriver');
+const {ShadowRoot} = require('selenium-webdriver/lib/webdriver.js');
+const MockServer  = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands-w3c.js');
+const common = require('../../../../common.js');
+const Element = common.require('element/index.js');
+
+describe('element().shadow().find() commands', function () {
+  before(function (done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function (done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('test .element().shadow().find()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '#helpBtn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '5'}]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+    
+    const helpBtnElement = shadowRootEl.find('#helpBtn');
+    assert.strictEqual(helpBtnElement instanceof Element, true);
+    assert.strictEqual(await helpBtnElement.getId(), '5');
+
+    const helpBtnWebElement = await helpBtnElement;
+    assert.strictEqual(helpBtnWebElement instanceof WebElement, true);
+    assert.strictEqual(await helpBtnWebElement.getId(), '5');
+  });
+
+  it('test .element().shadow().get()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '#helpBtn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '5'}]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+    
+    const helpBtnElement = shadowRootEl.get('#helpBtn');
+    assert.strictEqual(helpBtnElement instanceof Element, true);
+    assert.strictEqual(await helpBtnElement.getId(), '5');
+
+    const helpBtnWebElement = await helpBtnElement;
+    assert.strictEqual(helpBtnWebElement instanceof WebElement, true);
+    assert.strictEqual(await helpBtnWebElement.getId(), '5');
+  });
+
+  it('test .element().shadow().findElement()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '#helpBtn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '5'}]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+    
+    const helpBtnElement = shadowRootEl.findElement('#helpBtn');
+    assert.strictEqual(helpBtnElement instanceof Element, true);
+    assert.strictEqual(await helpBtnElement.getId(), '5');
+
+    const helpBtnWebElement = await helpBtnElement;
+    assert.strictEqual(helpBtnWebElement instanceof WebElement, true);
+    assert.strictEqual(await helpBtnWebElement.getId(), '5');
+  });
+
+  it('test .element().shadow().find(selectorObject)', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'xpath',
+          value: '//*[id="helpBtn"]'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '2'}]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+    
+    const helpBtnElement = shadowRootEl.find({
+      locateStrategy: 'xpath', selector: '//*[id="helpBtn"]'});
+    assert.strictEqual(helpBtnElement instanceof Element, true);
+    assert.strictEqual(await helpBtnElement.getId(), '2');
+
+    const helpBtnWebElement = await helpBtnElement;
+    assert.strictEqual(helpBtnWebElement instanceof WebElement, true);
+    assert.strictEqual(await helpBtnWebElement.getId(), '2');
+  });
+
+  it('test .element().shadow().find().getText()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '#helpBtn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '55'}]
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/55/text',
+        method: 'GET',
+        response: JSON.stringify({
+          value: 'Help'
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    const helpBtnElement = shadowRootEl.find('#helpBtn');
+    const helpBtnTextPromise = helpBtnElement.getText();
+
+    assert.strictEqual(typeof helpBtnTextPromise.then, 'function');
+    assert.strictEqual(await helpBtnTextPromise, 'Help');
+  });
+});
diff --git a/test/src/api/commands/web-element/testShadowFindAll.js b/test/src/api/commands/web-element/testShadowFindAll.js
new file mode 100644
index 00000000..f115106e
--- /dev/null
+++ b/test/src/api/commands/web-element/testShadowFindAll.js
@@ -0,0 +1,316 @@
+const assert = require('assert');
+const {WebElement} = require('selenium-webdriver');
+const {ShadowRoot} = require('selenium-webdriver/lib/webdriver.js');
+const MockServer = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands-w3c.js');
+const common = require('../../../../common.js');
+const Element = common.require('element/index.js');
+
+describe('element().shadow().findAll() commands', function() {
+  before(function(done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function(done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('test .element().shadow().findAll()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '.btn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [
+            {'element-6066-11e4-a52e-4f735466cecf': '5'},
+            {'element-6066-11e4-a52e-4f735466cecf': '6'},
+            {'element-6066-11e4-a52e-4f735466cecf': '7'}
+          ]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+
+    const btnElements = shadowRootEl.findAll('.btn');
+    assert.strictEqual(btnElements instanceof Element, false);
+    assert.strictEqual(typeof btnElements.find, 'undefined');
+    assert.strictEqual(typeof btnElements.click, 'undefined');
+
+    assert.strictEqual(btnElements instanceof Promise, false);
+    assert.strictEqual(typeof btnElements.then, 'function');
+    assert.strictEqual(typeof btnElements.nth, 'function');
+    assert.strictEqual(typeof btnElements.count, 'function');
+
+    const btnWEbElements = await btnElements;
+    assert.strictEqual(btnWEbElements.length, 3);
+    assert.strictEqual(btnWEbElements[0] instanceof WebElement, true);
+    assert.strictEqual(await btnWEbElements[0].getId(), '5');
+    assert.strictEqual(await btnElements.nth(0).getId(), '5');
+  });
+
+  it('test .element().shadow().getAll()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '.btn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [
+            {'element-6066-11e4-a52e-4f735466cecf': '5'},
+            {'element-6066-11e4-a52e-4f735466cecf': '6'},
+            {'element-6066-11e4-a52e-4f735466cecf': '7'}
+          ]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+
+    const btnElements = shadowRootEl.getAll('.btn');
+    assert.strictEqual(btnElements instanceof Element, false);
+    assert.strictEqual(typeof btnElements.find, 'undefined');
+    assert.strictEqual(typeof btnElements.click, 'undefined');
+
+    assert.strictEqual(btnElements instanceof Promise, false);
+    assert.strictEqual(typeof btnElements.then, 'function');
+    assert.strictEqual(typeof btnElements.nth, 'function');
+    assert.strictEqual(typeof btnElements.count, 'function');
+
+    const btnWEbElements = await btnElements;
+    assert.strictEqual(btnWEbElements.length, 3);
+    assert.strictEqual(btnWEbElements[0] instanceof WebElement, true);
+    assert.strictEqual(await btnWEbElements[0].getId(), '5');
+    assert.strictEqual(await btnElements.nth(0).getId(), '5');
+  });
+
+  it('test .element().shadow().findElements()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '.btn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [
+            {'element-6066-11e4-a52e-4f735466cecf': '5'},
+            {'element-6066-11e4-a52e-4f735466cecf': '6'},
+            {'element-6066-11e4-a52e-4f735466cecf': '7'}
+          ]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+
+    const btnElements = shadowRootEl.findElements('.btn');
+    assert.strictEqual(btnElements instanceof Element, false);
+    assert.strictEqual(typeof btnElements.find, 'undefined');
+    assert.strictEqual(typeof btnElements.click, 'undefined');
+
+    assert.strictEqual(btnElements instanceof Promise, false);
+    assert.strictEqual(typeof btnElements.then, 'function');
+    assert.strictEqual(typeof btnElements.nth, 'function');
+    assert.strictEqual(typeof btnElements.count, 'function');
+
+    const btnWEbElements = await btnElements;
+    assert.strictEqual(btnWEbElements.length, 3);
+    assert.strictEqual(btnWEbElements[0] instanceof WebElement, true);
+    assert.strictEqual(await btnWEbElements[0].getId(), '5');
+    assert.strictEqual(await btnElements.nth(0).getId(), '5');
+  });
+
+  it('test .element().shadow().findAll(selectorObject)', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'xpath',
+          value: '//*[id="helpBtn"]'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [
+            {'element-6066-11e4-a52e-4f735466cecf': '4'},
+            {'element-6066-11e4-a52e-4f735466cecf': '5'}
+          ]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+
+    const helpBtnElements = shadowRootEl.findAll({
+      locateStrategy: 'xpath', selector: '//*[id="helpBtn"]'
+    });
+    assert.strictEqual(helpBtnElements instanceof Element, false);
+    assert.strictEqual(typeof helpBtnElements.find, 'undefined');
+    assert.strictEqual(typeof helpBtnElements.click, 'undefined');
+
+    assert.strictEqual(helpBtnElements instanceof Promise, false);
+    assert.strictEqual(typeof helpBtnElements.then, 'function');
+    assert.strictEqual(typeof helpBtnElements.nth, 'function');
+    assert.strictEqual(typeof helpBtnElements.count, 'function');
+
+    const btnWEbElements = await helpBtnElements;
+    assert.strictEqual(btnWEbElements.length, 2);
+    assert.strictEqual(btnWEbElements[0] instanceof WebElement, true);
+    assert.strictEqual(await btnWEbElements[0].getId(), '4');
+    assert.strictEqual(await helpBtnElements.nth(0).getId(), '4');
+  });
+
+  it('test .element().shadow().findAll().count()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '.btn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [
+            {'element-6066-11e4-a52e-4f735466cecf': '5'},
+            {'element-6066-11e4-a52e-4f735466cecf': '6'},
+            {'element-6066-11e4-a52e-4f735466cecf': '7'}
+          ]
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+
+    const resultPromise = shadowRootEl.findAll('.btn').count();
+    assert.strictEqual(resultPromise instanceof Element, false);
+    assert.strictEqual(typeof resultPromise.find, 'undefined');
+
+    assert.strictEqual(resultPromise instanceof Promise, false);
+    assert.strictEqual(typeof resultPromise.then, 'function');
+    assert.strictEqual(typeof resultPromise.value, 'object');
+    assert.strictEqual(typeof resultPromise.assert, 'object');
+
+    const result = await resultPromise;
+    assert.strictEqual(result instanceof WebElement, false);
+    assert.strictEqual(result, 3);
+
+    assert.strictEqual(await resultPromise.value, 3);
+    assert.strictEqual(await resultPromise.assert.equals(3), 3);
+    assert.strictEqual(await resultPromise.assert.not.equals(4), 3);
+  });
+
+  it('test .element().findAll().nth()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/shadow',
+        method: 'GET',
+        response: JSON.stringify({
+          value: {'shadow-6066-11e4-a52e-4f735466cecf': '9'}
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/shadow/9/elements',
+        postdata: {
+          using: 'css selector',
+          value: '.btn'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [
+            {'element-6066-11e4-a52e-4f735466cecf': '5'},
+            {'element-6066-11e4-a52e-4f735466cecf': '6'},
+            {'element-6066-11e4-a52e-4f735466cecf': '7'}
+          ]
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/6/elements',
+        postdata: {
+          using: 'css selector',
+          value: 'span'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [
+            {'element-6066-11e4-a52e-4f735466cecf': '9'}
+          ]
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/6/property/role',
+        method: 'GET',
+        response: JSON.stringify({
+          value: 'button'
+        })
+      }, true);
+
+    const shadowRootEl = this.client.api.element('#signupSection').getShadowRoot();
+    assert.strictEqual(await shadowRootEl instanceof ShadowRoot, true);
+    assert.strictEqual(await shadowRootEl.getId(), '9');
+
+    const btnElement = shadowRootEl.findAll('.btn').nth(1);
+    assert.strictEqual(btnElement instanceof Element, false);
+    assert.strictEqual(btnElement instanceof Promise, true);
+    assert.strictEqual(typeof btnElement.find, 'function');
+    assert.strictEqual(typeof btnElement.getValue, 'function');
+
+    const btnWebElement = await btnElement;
+    assert.strictEqual(btnWebElement instanceof WebElement, true);
+    assert.strictEqual(await btnWebElement.getId(), '6');
+
+    const btnSpanElement = btnElement.find('span');
+    assert.strictEqual(await btnSpanElement.getId(), '9');
+
+    const btnElementProperty = await btnElement.getProperty('role');
+    assert.strictEqual(btnElementProperty, 'button');
+  });
+});
+
diff --git a/test/src/apidemos/cdp/testCdpCommands.js b/test/src/apidemos/cdp/testCdpCommands.js
index 90e45ca6..d368b040 100644
--- a/test/src/apidemos/cdp/testCdpCommands.js
+++ b/test/src/apidemos/cdp/testCdpCommands.js
@@ -26,16 +26,16 @@ describe('cdp commands test', function() {
     });
   });
 
-  it('reset cdp connection after each session', function() {
+  it('reset cdp connection after each session is created', function() {
     const testsPath = [path.join(__dirname, '../../../apidemos/cdp')];
-    let resetConnectionCalled = false;
+    let resetConnectionCalled = 0;
 
-    mockery.registerMock('./cdp.js', {
+    mockery.registerMock('../transport/selenium-webdriver/cdp.js', {
       getConnection: function(...args) {
         return Promise.resolve();
       },
       resetConnection: function() {
-        resetConnectionCalled = true;
+        resetConnectionCalled += 1;
       }
     });
 
@@ -49,7 +49,6 @@ describe('cdp commands test', function() {
       })
       .navigateTo({url: 'http://localhost', persist: true});
 
-   
 
     const globals = {
       calls: 0,
@@ -57,9 +56,9 @@ describe('cdp commands test', function() {
       waitForConditionTimeout: 120,
       retryAssertionTimeout: 1000,
 
-
       reporter(results) {
-        assert.strictEqual(resetConnectionCalled, true);
+        // cdp connection is reset once for each session (two test suites).
+        assert.strictEqual(resetConnectionCalled, 2);
         if (results.lastError) {
           throw results.lastError;
         }
@@ -77,5 +76,64 @@ describe('cdp commands test', function() {
     }));
   });
 
-  
+  it('reset cdp connection after each selenium session is created', function() {
+    const testsPath = [path.join(__dirname, '../../../apidemos/cdp')];
+    let resetConnectionCalled = 0;
+
+    Mocks
+      .createChromeSession({
+        headless: false,
+        times: 2
+      });
+
+    MockServer.addMock({
+      url: '/wd/hub/session/13521-10219-202/url',
+      method: 'POST',
+      postdata: JSON.stringify({
+        url: 'http://localhost'
+      }),
+      response: {
+        value: null
+      },
+      times: 2
+    });
+
+    mockery.registerMock('../transport/selenium-webdriver/cdp.js', {
+      getConnection: function(...args) {
+        return Promise.resolve();
+      },
+      resetConnection: function() {
+        resetConnectionCalled += 1;
+      }
+    });
+
+    const globals = {
+      calls: 0,
+      waitForConditionPollInterval: 50,
+      waitForConditionTimeout: 120,
+      retryAssertionTimeout: 1000,
+
+      reporter(results) {
+        // cdp connection is reset once for each session (two test suites).
+        assert.strictEqual(resetConnectionCalled, 2);
+        if (results.lastError) {
+          throw results.lastError;
+        }
+      }
+    };
+
+    return NightwatchClient.runTests(testsPath, settings({
+      desiredCapabilities: {
+        browserName: 'chrome'
+      },
+      selenium: {
+        host: 'localhost',
+        port: 10195,
+        start_process: false
+      },
+      output: false,
+      skip_testcases_on_fail: false,
+      globals
+    }));
+  });
 });
diff --git a/test/src/core/testCreateSession.js b/test/src/core/testCreateSession.js
index 37131c58..533fd152 100644
--- a/test/src/core/testCreateSession.js
+++ b/test/src/core/testCreateSession.js
@@ -543,7 +543,8 @@ describe('test Request With Credentials', function () {
       });
 
     nock('https://api.browserstack.com')
-      .get('/automate/builds.json')
+      .get('/automate/builds.json?status=running')
+      .times(2)
       .reply(200, [
         {
           automation_build: {
@@ -620,6 +621,9 @@ describe('test Request With Credentials', function () {
       }
     });
 
+    const buildId = await client.transport.getBuildId();
+    assert.strictEqual(buildId, undefined);
+
     assert.strictEqual(client.api.isAppiumClient(), false);
   });
 
@@ -667,7 +671,8 @@ describe('test Request With Credentials', function () {
       });
 
     nock('https://api.browserstack.com')
-      .get('/app-automate/builds.json')
+      .get('/app-automate/builds.json?status=running')
+      .times(2)
       .reply(200, [
         {
           automation_build: {
@@ -733,6 +738,9 @@ describe('test Request With Credentials', function () {
         realMobile: true
       }
     });
+
+    const buildId = await client.transport.getBuildId();
+    assert.strictEqual(buildId, undefined);
   
     assert.strictEqual(client.transport.uploadedAppUrl, 'bs://878bdf21505f0004ce');
 
@@ -796,8 +804,16 @@ describe('test Request With Credentials', function () {
       });
 
     nock('https://api.browserstack.com')
-      .get('/app-automate/builds.json')
+      .get('/app-automate/builds.json?status=running')
+      .times(2)
       .reply(200, [
+        {
+          automation_build: {
+            name: 'Nightwatch Programmatic Api Demo',
+            status: 'running',
+            hashed_id: '123456789'
+          }
+        },
         {
           automation_build: {
             name: 'WIN_CHROME_PROD_SANITY_LIVE_1831',
@@ -865,6 +881,9 @@ describe('test Request With Credentials', function () {
       }
     });
 
+    const buildId = await client.transport.getBuildId();
+    assert.strictEqual(buildId, '123456789');
+
     assert.strictEqual(client.transport.uploadedAppUrl, undefined);
 
     assert.strictEqual(client.settings.selenium.use_appium, undefined);
@@ -902,8 +921,16 @@ describe('test Request With Credentials', function () {
       });
 
     nock('https://api.browserstack.com')
-      .get('/automate/builds.json')
+      .get('/automate/builds.json?status=running')
+      .times(2)
       .reply(200, [
+        {
+          automation_build: {
+            name: 'nightwatch-test-build',
+            status: 'running',
+            hashed_id: '123-456-789'
+          }
+        },
         {
           automation_build: {
             name: 'WIN_CHROME_PROD_SANITY_LIVE_1831',
@@ -968,6 +995,9 @@ describe('test Request With Credentials', function () {
         }
       }
     });
+
+    const buildId = await client.transport.getBuildId();
+    assert.strictEqual(buildId, '123-456-789');
   });
 
   it('Test create session with browserstack with random browser and update buildName', async function () {
@@ -1001,8 +1031,16 @@ describe('test Request With Credentials', function () {
       });
 
     nock('https://api.browserstack.com')
-      .get('/automate/builds.json')
+      .get('/automate/builds.json?status=running')
+      .times(2)
       .reply(200, [
+        {
+          automation_build: {
+            name: 'Nightwatch Programmatic Api Demo',
+            status: 'running',
+            hashed_id: '123456789'
+          }
+        },
         {
           automation_build: {
             name: 'WIN_CHROME_PROD_SANITY_LIVE_1831',
@@ -1070,5 +1108,8 @@ describe('test Request With Credentials', function () {
         }
       }
     });
+
+    const buildId = await client.transport.getBuildId();
+    assert.strictEqual(buildId, '123456789');
   });
 });
diff --git a/test/src/index/transport/testBrowserstackTransport.js b/test/src/index/transport/testBrowserstackTransport.js
index f5680c01..65c91a38 100644
--- a/test/src/index/transport/testBrowserstackTransport.js
+++ b/test/src/index/transport/testBrowserstackTransport.js
@@ -75,7 +75,7 @@ xdescribe('BrowserstackTransport', function () {
       });
   
     nock('https://api.browserstack.com')
-      .get('/automate/builds.json')
+      .get('/automate/builds.json?status=running')
       .reply(200, [
         {
           automation_build: {
@@ -129,7 +129,7 @@ xdescribe('BrowserstackTransport', function () {
       });
     
     nock('https://api.browserstack.com')
-      .get('/automate/builds.json')
+      .get('/automate/builds.json?status=running')
       .reply(200, [
         {
           automation_build: {
@@ -160,6 +160,11 @@ xdescribe('BrowserstackTransport', function () {
     assert.strictEqual(transport.accessKey, 'test-access-key');
     assert.strictEqual(client.settings.webdriver.start_process, false);
       
+    nock('https://api.browserstack.com')
+      .get('/automate/sessions/1234567.json')
+      .reply(200, {
+        automation_session: {status: 'done'}
+      });
     nock('https://api.browserstack.com')
       .put('/automate/sessions/1234567.json', {
         status: 'passed',
@@ -201,7 +206,7 @@ xdescribe('BrowserstackTransport', function () {
       });
 
     nock('https://api.browserstack.com')
-      .get('/app-automate/builds.json')
+      .get('/app-automate/builds.json?status=running')
       .reply(200, [
         {
           automation_build: {
@@ -232,6 +237,11 @@ xdescribe('BrowserstackTransport', function () {
     assert.strictEqual(transport.accessKey, 'test-access-key');
     assert.strictEqual(client.settings.webdriver.start_process, false);
       
+    nock('https://api.browserstack.com')
+      .get('/app-automate/sessions/1234567.json')
+      .reply(200, {
+        automation_session: {status: 'done'}
+      });
     nock('https://api.browserstack.com')
       .put('/app-automate/sessions/1234567.json', {
         status: 'passed',
@@ -275,7 +285,7 @@ xdescribe('BrowserstackTransport', function () {
       });
 
     nock('https://api.browserstack.com')
-      .get('/automate/builds.json')
+      .get('/automate/builds.json?status=running')
       .reply(200, [
         {
           automation_build: {
@@ -298,6 +308,11 @@ xdescribe('BrowserstackTransport', function () {
     return new Promise((resolve, reject) => {
       setTimeout(async function() {
         try {
+          nock('https://api.browserstack.com')
+            .get('/automate/sessions/1234567.json')
+            .reply(200, {
+              automation_session: {status: 'done'}
+            });
           nock('https://api.browserstack.com')
             .put('/automate/sessions/1234567.json', {
               status: 'failed',
diff --git a/test/src/runner/testRunBrowserstackTransport.js b/test/src/runner/testRunBrowserstackTransport.js
index 258aa113..f397e657 100644
--- a/test/src/runner/testRunBrowserstackTransport.js
+++ b/test/src/runner/testRunBrowserstackTransport.js
@@ -7,7 +7,8 @@ const {settings} = common;
 const {runTests} = common.require('index.js');
 
 describe('testRunBrowserstackTransport', function() {
-  let nockCalled = 0;
+  let statusSetNockCalled = 0;
+  let sessionNockCalled = 0;
 
   before(function(done) {
     try {
@@ -31,7 +32,7 @@ describe('testRunBrowserstackTransport', function() {
       });
 
     nock('https://api.browserstack.com')
-      .get('/automate/builds.json')
+      .get('/automate/builds.json?status=running')
       .times(2)
       .reply(200, []);
 
@@ -57,14 +58,38 @@ describe('testRunBrowserstackTransport', function() {
         value: []
       });
 
+    nock('https://api.browserstack.com')
+      .get('/automate/sessions/1352110219202.json')
+      .reply(200, function() {
+        sessionNockCalled++;
+
+        return {
+          automation_session: {
+            status: 'failed'
+          }
+        };
+      });
+
+    nock('https://api.browserstack.com')
+      .get('/automate/sessions/1352110219202.json')
+      .reply(200, function() {
+        sessionNockCalled++;
+
+        return {
+          automation_session: {
+            status: 'done'
+          }
+        };
+      });
+
     nock('https://api.browserstack.com')
       .put('/automate/sessions/1352110219202.json', {
         status: 'failed',
         reason: /^NightwatchAssertError: Testing if element <#weblogin> is present in 10ms/
       })
-      .times(2)
+      .times(1)
       .reply(200, function() {
-        nockCalled++;
+        statusSetNockCalled++;
 
         return {};
       });
@@ -103,7 +128,8 @@ describe('testRunBrowserstackTransport', function() {
         waitForConditionTimeout: 11,
         retryAssertionTimeout: 10,
         reporter(results) {
-          assert.strictEqual(nockCalled, 2);
+          assert.strictEqual(sessionNockCalled, 2);
+          assert.strictEqual(statusSetNockCalled, 1);
           assert.strictEqual(Object.keys(results.modules).length, 1);
         }
       }
diff --git a/types/tests/elementCommands.test-d.ts b/types/tests/elementCommands.test-d.ts
index 0bf215b7..581b7602 100644
--- a/types/tests/elementCommands.test-d.ts
+++ b/types/tests/elementCommands.test-d.ts
@@ -466,6 +466,54 @@ describe('waitForElementVisible command demo', function () {
       expectType<NightwatchAPI>(this);
       expectType<NightwatchCallbackResult<boolean>>(result);
     });
+
+    // - with no arguments; in this case a global default timeout is used
+    browser.waitForElementVisible('body');
+
+    // specify the locate strategy (css selector/xpath) as the first argument
+    browser.waitForElementVisible('css selector', '#dialog');
+
+    // with custom output message - the locate strategy is required
+    browser.waitForElementVisible('css selector', '#dialog', 'The dialog container is removed.');
+  
+    // - with a global default timeout and a callback
+    browser.waitForElementVisible('body', function() {});
+  
+    // - with a global default timeout, a callback, and a custom message
+    browser.waitForElementVisible('body', function() {}, 'test message');
+  
+    // - with a global default timeout a custom message
+    browser.waitForElementVisible('body', 'test message');
+  
+    // - with only the timeout
+    browser.waitForElementVisible('body', 500);
+  
+    // - with a timeout and a custom message
+    browser.waitForElementVisible('body', 500, 'test message');
+  
+    // - with a timeout and a callback
+    browser.waitForElementVisible('body', 500, function() {});
+  
+    // - with a timeout and a custom abortOnFailure
+    browser.waitForElementVisible('body', 500, true);
+  
+    // - with a timeout, a custom abortOnFailure, and a custom message
+    browser.waitForElementVisible('body', 500, true, 'test message');
+  
+    // - with a timeout, a custom abortOnFailure, and a callback
+    browser.waitForElementVisible('body', 500, true, function() {});
+  
+    // - with a timeout, a custom abortOnFailure, a callback and a custom message
+    browser.waitForElementVisible('body', 500, true, function() {}, 'test message');
+  
+    // - with a timeout, a custom reschedule interval, and a callback
+    browser.waitForElementVisible('body', 500, 100, function() {});
+  
+    // - with a timeout, a custom rescheduleInterval, and a custom abortOnFailure
+    browser.waitForElementVisible('body', 500, 100, false);
+
+    // - with a timeout, a custom rescheduleInterval, a custom abortOnFailure, and a custom message
+    browser.waitForElementVisible('body', 500, 100, false, 'test message');
   });
 
   test('async demo test', async function (browser) {
@@ -488,6 +536,54 @@ describe('waitForElementPresent command demo', function () {
 
       expectType<NightwatchCallbackResult<null | ElementResult[]>>(result);
     });
+
+    // - with no arguments; in this case a global default timeout is used
+    browser.waitForElementPresent('body');
+
+    // specify the locate strategy (css selector/xpath) as the first argument
+    browser.waitForElementPresent('css selector', '#dialog');
+
+    // with custom output message - the locate strategy is required
+    browser.waitForElementPresent('css selector', '#dialog', 'The dialog container is removed.');
+  
+    // - with a global default timeout and a callback
+    browser.waitForElementPresent('body', function() {});
+  
+    // - with a global default timeout, a callback, and a custom message
+    browser.waitForElementPresent('body', function() {}, 'test message');
+  
+    // - with a global default timeout a custom message
+    browser.waitForElementPresent('body', 'test message');
+  
+    // - with only the timeout
+    browser.waitForElementPresent('body', 500);
+  
+    // - with a timeout and a custom message
+    browser.waitForElementPresent('body', 500, 'test message');
+  
+    // - with a timeout and a callback
+    browser.waitForElementPresent('body', 500, function() {});
+  
+    // - with a timeout and a custom abortOnFailure
+    browser.waitForElementPresent('body', 500, true);
+  
+    // - with a timeout, a custom abortOnFailure, and a custom message
+    browser.waitForElementPresent('body', 500, true, 'test message');
+  
+    // - with a timeout, a custom abortOnFailure, and a callback
+    browser.waitForElementPresent('body', 500, true, function() {});
+  
+    // - with a timeout, a custom abortOnFailure, a callback and a custom message
+    browser.waitForElementPresent('body', 500, true, function() {}, 'test message');
+  
+    // - with a timeout, a custom reschedule interval, and a callback
+    browser.waitForElementPresent('body', 500, 100, function() {});
+  
+    // - with a timeout, a custom rescheduleInterval, and a custom abortOnFailure
+    browser.waitForElementPresent('body', 500, 100, false);
+
+    // - with a timeout, a custom rescheduleInterval, a custom abortOnFailure, and a custom message
+    browser.waitForElementPresent('body', 500, 100, false, 'test message');
   });
 
   test('async demo test', async function (browser) {
diff --git a/types/tests/index.test-d.ts b/types/tests/index.test-d.ts
index da523258..265e2dbb 100644
--- a/types/tests/index.test-d.ts
+++ b/types/tests/index.test-d.ts
@@ -123,22 +123,18 @@ const testGeneral: NightwatchTests = {
 
   'Can run accessibility tests': () => {
     browser
-      .url('https://www.google.com')
+      .url('https://nightwatchjs.org')
       .axeInject()
-      .axeRun(
-        'body',
-        {
-          rules: {
-            'color-contrast': {
-              enabled: false,
-            },
-            region: {
-              enabled: false,
-            },
-          },
+      .axeRun(['#navBar', 'nav'], {
+        rules: {
+          'color-contrast': { enabled: false },
+          region: { enabled: false }
         },
-        (results) => {}
-      );
+      }, (result) => {
+        if (result.status === 0) {
+          expectType<{[key: string]: any}>(result.value);
+        }
+      });
   },
 
   'step one: navigate to google': () => {