Repository: nightwatchjs/nightwatch
Pull request #3790 (2023-07-04 11:04:47)

Add types for `chrome` namespace commands
Added Chrome namespace commands:

1. `launchApp()`
2. `getNetworkConditions()`
3. `setNetworkConditions()`
4. `sendDevToolsCommand()`
5. `sendAndGetDevToolsCommand()`
6. `setPermission()`
7. `setDownloadPath()`
8. `getCastSinks()`
9. `setCastSinkToUse()`
10. `startCasttabMirroring()`
11. `getCastIssueMessage()`
12. `stopCasting()`

beatfactor: can you fix the conflicts?
garg3133: This PR is ready now.

Patch:
diff --git a/lib/api/_loaders/chrome.js b/lib/api/_loaders/chrome.js
index 2e8064d2..fdb3a574 100644
--- a/lib/api/_loaders/chrome.js
+++ b/lib/api/_loaders/chrome.js
@@ -6,6 +6,7 @@ class ChromeCommandLoader extends BaseLoader {
       'launchApp',
       'getNetworkConditions',
       'setNetworkConditions',
+      'deleteNetworkConditions',
       'sendDevToolsCommand',
       'sendAndGetDevToolsCommand',
       'setPermission',
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/types/index.d.ts b/types/index.d.ts
index d62c95d9..bf95a5ce 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -500,6 +500,7 @@ export interface NamespacedApi<ReturnType = unknown> {
   logs: LogsNsCommands<ReturnType>;
   window: WindowNsCommands<ReturnType>;
   firefox: FirefoxNsCommands<ReturnType>;
+  chrome: ChromeNsCommands<ReturnType>;
   network: NetworkNsCommands<ReturnType>;
 
   assert: Assert<ReturnType>;
@@ -2516,16 +2517,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
    */
@@ -2535,46 +2541,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 {
@@ -5057,6 +5063,142 @@ export interface FirefoxNsCommands<ReturnType = unknown> {
   uninstallAddon(addonId: string | PromiseLike<string>): Awaitable<IfUnknown<ReturnType, this>, null>;
 } 
 
+type NetworkConditionsSpec = {
+  offline: boolean,
+  latency: number,
+  download_throughput: number,
+  upload_throughput: number
+};
+
+export interface ChromeNsCommands<ReturnType = unknown>{
+  /**
+   * Launch Chrome App with given ID.
+   * @param id ID of the App to launch.
+   */
+  launchApp(
+    id: string
+  ): Awaitable<IfUnknown<ReturnType, this>, null>; 
+
+  /**
+   * Get Chromium network emulation settings.
+   *
+   * Network conditions must be set before it can be retrieved.
+   */
+  getNetworkConditions(): Awaitable<IfUnknown<ReturnType, this>, NetworkConditionsSpec>;
+
+  /**
+   * Set Chromium network emulation settings.
+   *
+   * @example
+   * describe('set network conditions', function() {
+   *  it('sets the network conditions', function() {
+   *    browser
+   *     .chrome.setNetworkConditions({
+   *       offline: false,
+   *       latency: 5, // Additional latency (ms).
+   *       download_throughput: 500 * 1024, // Maximal aggregated download throughput.
+   *       upload_throughput: 500 * 1024 // Maximal aggregated upload throughput.
+   *    });
+   *  });
+   * });
+   *
+   * @param spec Defines the network conditions to set
+   */
+  setNetworkConditions(
+    spec: NetworkConditionsSpec
+  ): Awaitable<IfUnknown<ReturnType, this>, null>;
+
+  /**
+   * Delete Chromium network emulation settings.
+   */
+  deleteNetworkConditions(): Awaitable<IfUnknown<ReturnType, this>, null>;
+
+  /**
+   * Sends an arbitrary devtools command to the browser.
+   *
+   * @param cmd The name of the command to send.
+   * @param params The command parameters.
+   * @see <https://chromedevtools.github.io/devtools-protocol/>
+   */
+  sendDevToolsCommand(
+    cmd: string, 
+    params?: {[key: string]: any}
+  ): Awaitable<IfUnknown<ReturnType, this>, null>;
+
+  /**
+   * Sends an arbitrary devtools command to the browser and get the result.
+   *
+   * @param cmd The name of the command to send.
+   * @param params The command parameters.
+   * @see <https://chromedevtools.github.io/devtools-protocol/>
+   */
+  sendAndGetDevToolsCommand(
+    cmd: string, 
+    params?: {[key: string]: any}
+  ): Awaitable<IfUnknown<ReturnType, this>, unknown>;
+
+  /**
+   * Set a permission state to the given value.
+   *
+   * @param name A name of the permission to update.
+   * @param state State to set permission to.
+   * @see <https://w3c.github.io/permissions/#permission-registry> for valid names
+   */
+  setPermission(
+    name: string, 
+    state: 'granted' | 'denied' | 'prompt'
+  ): Awaitable<IfUnknown<ReturnType, this>, null>;
+
+  /**
+   * Sends a DevTools command to change the browser's download directory.
+   *
+   * @param path The desired download directory.
+   * @see chrome.sendDevToolsCommand
+   */
+  setDownloadPath(
+    path: string
+  ): Awaitable<IfUnknown<ReturnType, this>, null>;
+
+  /**
+   * Returns the list of cast sinks (Cast devices) available to the Chrome media router.
+   *
+   * @return An array of Strings containing the friendly device names of available cast sink targets.
+   */
+  getCastSinks(): Awaitable<IfUnknown<ReturnType, this>, string[]>;
+
+  /**
+   * Selects a cast sink (Cast device) as the recipient of media router intents (connect or play).
+   *
+   * @param deviceName name of the target device.
+   */
+  setCastSinkToUse(
+    deviceName: string
+  ): Awaitable<IfUnknown<ReturnType, this>, null>;
+
+  /**
+   * Initiates tab mirroring for the current browser tab on the specified device.
+   *
+   * @param deviceName name of the target device.
+   */
+  startCastTabMirroring(
+    deviceName: string
+  ): Awaitable<IfUnknown<ReturnType, this>, null>;
+
+  /**
+   * Returns an error message when there is any issue in a Cast session.
+   */
+  getCastIssueMessage(): Awaitable<IfUnknown<ReturnType, this>, string>;
+
+  /**
+   * Stops casting from media router to the specified device, if connected.
+   *
+   * @param deviceName name of the target device.
+   */
+  stopCasting(
+    deviceName: string
+  ): Awaitable<IfUnknown<ReturnType, this>, null>;
+}
+
 export interface NetworkNsCommands<ReturnType = unknown> {
   /**
    * Capture outgoing network calls from the browser.
@@ -5141,12 +5283,7 @@ export interface NetworkNsCommands<ReturnType = unknown> {
    * @see https://nightwatchjs.org/api/setNetworkConditions.html
    */
   setConditions(
-    spec: {
-      offline: boolean;
-      latency: number;
-      download_throughput: number;
-      upload_throughput: number;
-    },
+    spec: NetworkConditionsSpec,
     callback?: (
       this: NightwatchAPI,
       result: NightwatchCallbackResult<null>
@@ -7650,6 +7787,7 @@ export const alerts: AlertsNsCommands;
 export const document: DocumentNsCommands;
 export const window: WindowNsCommands;
 export const firefox: FirefoxNsCommands;
+export const chrome: ChromeNsCommands;
 
 export const assert: Assert;
 export const verify: Assert;


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/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': () => {