Repository: nightwatchjs/nightwatch
Pull request #3128 (2022-04-03 14:23:09)

Features/sauce labs config
…ration file

Thanks in advance for your contribution. Please follow the below steps in submitting a pull request, as it will help us with reviewing it quicker.

- [x] Create a new branch from master (e.g. `features/my-new-feature` or `issue/123-my-bugfix`);
- [x] If it's a new feature explain why do you think it's necessary. Please check with the maintainers beforehand to make sure it is something that we will accept. Usually we only accept new features if we feel that they will benefit the entire community;
- [x] Always add unit tests - PRs without tests are most of the times ignored.

We enjoy the utility of Nightwatch automatically generating a templated configuration file and feel that adding the SauceLabs settings, similar to what is already being done for BrowserStack, will aid in the ease of onboarding SauceLabs users as well. This should prevent confusion matching SauceLabs configuration settings to their appropriate settings in the Nightwatch configuration file.


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/nightwatchjs/nightwatch?pullRequest=3128) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/nightwatchjs/nightwatch?pullRequest=3128) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/nightwatchjs/nightwatch?pullRequest=3128) it.</sub>

Patch:
diff --git a/README.md b/README.md
index c6609cc8..dba77b41 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ Nightwatch uses a [WebDriver](https://www.w3.org/TR/webdriver/) compatible serve
 Nightwatch includes support for automatically managing the following services:
 #### ChromeDriver 
 - for running tests against the Chrome browser;
-- download url: [https://sites.google.com/a/chromium.org/chromedriver/downloads](https://sites.google.com/a/chromium.org/chromedriver/downloads).
+- download url: [https://sites.google.com/chromium.org/driver/](https://sites.google.com/chromium.org/driver/).
 
 #### GeckoDriver
 - for running tests against the Mozilla Firefox browser;
diff --git a/lib/api/_loaders/element-global.js b/lib/api/_loaders/element-global.js
index f250921a..c4448b4d 100644
--- a/lib/api/_loaders/element-global.js
+++ b/lib/api/_loaders/element-global.js
@@ -156,7 +156,17 @@ class ElementGlobal {
     if (locator instanceof Element) {
       this.locator = locator;
     } else {
-      this.locator = Locator.create(locator);
+      let value;
+      if (isString(locator) && this.client && this.client.locateStrategy) {
+        value = {
+          value: locator,
+          using: this.client.locateStrategy
+        };  
+      } else {
+        value = locator;
+      }
+
+      this.locator = Locator.create(value);
     }
   }
 
diff --git a/lib/api/_loaders/expect.js b/lib/api/_loaders/expect.js
index ce373b21..23583f89 100644
--- a/lib/api/_loaders/expect.js
+++ b/lib/api/_loaders/expect.js
@@ -108,6 +108,13 @@ class ExpectLoader extends BaseCommandLoader {
         stackTrace
       });
 
+      if (isES6Async && (expectCommand.instance instanceof Promise)) {
+        // prevent unhandledRejection errors
+        expectCommand.instance.catch(err => {
+          deferred.reject(err);
+        });
+      }
+      
       return expectCommand.instance;
     }.bind(this));
   }
diff --git a/lib/api/element-commands/moveToElement.js b/lib/api/element-commands/moveToElement.js
index dc17bcf8..d97bd2a0 100644
--- a/lib/api/element-commands/moveToElement.js
+++ b/lib/api/element-commands/moveToElement.js
@@ -14,8 +14,8 @@ const BaseElementCommand = require('./_baseElementCommand.js');
  * @syntax .moveToElement(using, selector, xoffset, yoffset, [callback])
  * @param {string} [using] The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies)
  * @param {string|object} selector The selector (CSS/Xpath) used to locate the element. Can either be a string or an object which specifies [element properties](https://nightwatchjs.org/guide#element-properties).
- * @param {number} xoffset X offset to move to, relative to the top-left corner of the element.
- * @param {number} yoffset Y offset to move to, relative to the top-left corner of the element.
+ * @param {number} xoffset X offset to move to, relative to the center of the element.
+ * @param {number} yoffset Y offset to move to, relative to the center of the element.
  * @param {function} [callback] Optional callback function to be called when the command finishes.
  * @jsonwire
  * @api protocol.elementinteraction
diff --git a/lib/api/protocol/moveTo.js b/lib/api/protocol/moveTo.js
index e64256ef..17f3c554 100644
--- a/lib/api/protocol/moveTo.js
+++ b/lib/api/protocol/moveTo.js
@@ -14,8 +14,8 @@ const ProtocolAction = require('./_base-action.js');
  * @syntax .moveTo(null, xoffset, yoffset, [callback])
  * @editline L1335
  * @param {string} [webElementId] The [Web Element ID](https://www.w3.org/TR/webdriver1/#dfn-web-elements) assigned to the element to move to. If not specified or is null, the offset is relative to current position of the mouse.
- * @param {number} xoffset X offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
- * @param {number} yoffset Y offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
+ * @param {number} xoffset X offset to move to, relative to the center of the element. If not specified, the mouse will move to the middle of the element.
+ * @param {number} yoffset Y offset to move to, relative to the center of the element. If not specified, the mouse will move to the middle of the element.
  * @param {function} [callback] Optional callback function to be called when the command finishes.
  * @api protocol.useractions
  */
diff --git a/lib/runner/cli/nightwatch.conf.ejs b/lib/runner/cli/nightwatch.conf.ejs
index cb7853ce..1dc2cd0c 100644
--- a/lib/runner/cli/nightwatch.conf.ejs
+++ b/lib/runner/cli/nightwatch.conf.ejs
@@ -253,6 +253,61 @@ module.exports = {
       }
     },
     //////////////////////////////////////////////////////////////////////////////////
+    // Configuration for when using the SauceLabs cloud service                      |
+    //                                                                               |
+    // Please set the username and access key by setting the environment variables:  |
+    // - SAUCE_USERNAME                                                              |
+    // - SAUCE_ACCESS_KEY                                                            |
+    //////////////////////////////////////////////////////////////////////////////////
+    saucelabs: {
+      selenium: {
+        host: 'ondemand.saucelabs.com',
+        port: 443
+      },
+      // More info on configuring capabilities can be found on:
+      // https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options
+      desiredCapabilities: {
+        'sauce:options' : {
+          username: '${SAUCE_USERNAME}',
+          accessKey: '${SAUCE_ACCESS_KEY}',
+          // https://docs.saucelabs.com/dev/cli/sauce-connect-proxy/#--region
+          // region: 'us-west-1'
+          // https://docs.saucelabs.com/dev/test-configuration-options/#tunnelidentifier
+          // parentTunnel: '',
+          // tunnelIdentifier: '',
+        }
+      },
+      disable_error_log: false,
+      webdriver: {
+        start_process: false
+      }
+    },
+    'saucelabs.chrome': {
+      extends: 'saucelabs',
+      desiredCapabilities: {
+        browserName: 'chrome',
+        screenResolution: '1280x1024',
+        browserVersion: 'latest',
+        javascriptEnabled: true,
+        acceptSslCerts: true,
+        timeZone: 'London',
+        chromeOptions : {
+          w3c: true
+        }
+      }
+    },
+    'saucelabs.firefox': {
+      extends: 'saucelabs',
+      desiredCapabilities: {
+        browserName: 'firefox',
+        screenResolution: '1280x1024',
+        browserVersion: 'latest',
+        javascriptEnabled: true,
+        acceptSslCerts: true,
+        timeZone: 'London'
+      }
+    },
+    //////////////////////////////////////////////////////////////////////////////////
     // Configuration for when using the Selenium service, either locally or remote,  |
     //  like Selenium Grid                                                           |
     //////////////////////////////////////////////////////////////////////////////////


Test patch:
diff --git a/lib/testsuite/index.js b/lib/testsuite/index.js
index 408aedc6..5fc91edc 100644
--- a/lib/testsuite/index.js
+++ b/lib/testsuite/index.js
@@ -279,7 +279,7 @@ class TestSuite {
     Object.defineProperty(global, 'element', {
       configurable: true,
       value: function(locator, options = {}) {
-        return ElementGlobal.element({locator, testSuite: this, options});
+        return ElementGlobal.element({locator, testSuite: this, options, client: this.client});
       }.bind(this),
       writable: false
     });
diff --git a/test/apidemos/elements/testGlobalLocateStrategy.js b/test/apidemos/elements/testGlobalLocateStrategy.js
new file mode 100644
index 00000000..8d8d7a4a
--- /dev/null
+++ b/test/apidemos/elements/testGlobalLocateStrategy.js
@@ -0,0 +1,16 @@
+const assert = require('assert');
+const {WebElement} = require('selenium-webdriver');
+
+describe('element-global demo for locateStrategy', function () {
+  after(browser => browser.end());
+
+  test('to determine whether or not globally set locate-strategy is ignored',  async function() {
+    const weblogin = element('//weblogin');
+    const webElement = await weblogin.getWebElement();
+
+    assert.ok(webElement instanceof WebElement);
+    assert.strictEqual(weblogin.locateStrategy, 'use_xpath');
+    assert.strictEqual(browser.options.use_xpath, true);
+  });
+  
+});
diff --git a/test/sampletests/asyncwithexpectfailures/sample.js b/test/sampletests/asyncwithexpectfailures/sample.js
new file mode 100644
index 00000000..aa929c30
--- /dev/null
+++ b/test/sampletests/asyncwithexpectfailures/sample.js
@@ -0,0 +1,11 @@
+describe('browse.expect with failures in async step', function() {
+  it('async step failure', async function() {
+    browser.url('http://localhost');
+    browser.expect.element('#weblogin').to.be.contain.text('some text');
+  });
+
+  it('second step without failure', function() {
+    browser.url('http://localhost');
+    browser.expect.element('#weblogin').to.be.present;
+  });
+});
\ No newline at end of file
diff --git a/test/src/apidemos/elements/testElementGlobal.js b/test/src/apidemos/elements/testElementGlobal.js
index 12762fd0..b2034096 100644
--- a/test/src/apidemos/elements/testElementGlobal.js
+++ b/test/src/apidemos/elements/testElementGlobal.js
@@ -46,5 +46,13 @@ describe('element global demos', function() {
     }));
   });
 
+  it('set use_xpath locate-strategy in element global', function() {
+    const testsPath = path.join(__dirname, '../../../apidemos/elements/testGlobalLocateStrategy.js');
+
+    return NightwatchClient.runTests(testsPath, settings({
+      output: false,
+      use_xpath: true
+    }));
+  });
   
 });
diff --git a/test/src/cli/testCliRunnerGenerate.js b/test/src/cli/testCliRunnerGenerate.js
index e3c0d110..e8898a05 100644
--- a/test/src/cli/testCliRunnerGenerate.js
+++ b/test/src/cli/testCliRunnerGenerate.js
@@ -167,6 +167,57 @@ describe('Test CLI Runner Generate', function() {
           }
         });
 
+        assert.deepStrictEqual(configData.test_settings.saucelabs, {
+          desiredCapabilities: {
+            'sauce:options': {
+              username: '${SAUCE_USERNAME}',
+              accessKey: '${SAUCE_ACCESS_KEY}'
+              // https://docs.saucelabs.com/dev/cli/sauce-connect-proxy/#--region
+              // region: 'us-west-1'
+              // https://docs.saucelabs.com/dev/test-configuration-options/#tunnelidentifier
+              // parentTunnel: ''
+              // tunnelIdentifier: ''
+            }
+          },
+
+          selenium: {
+            host: 'ondemand.saucelabs.com',
+            port: 443
+          },
+
+          disable_error_log: false,
+          webdriver: {
+            start_process: false
+          }
+        });
+
+        assert.deepStrictEqual(configData.test_settings['saucelabs.chrome'], {
+          extends: 'saucelabs',
+          desiredCapabilities: {
+            browserName: 'chrome',
+            screenResolution: '1280x1024',
+            browserVersion: 'latest',
+            javascriptEnabled: true,
+            acceptSslCerts: true,
+            timeZone: 'London',
+            chromeOptions: {
+              w3c: true
+            }
+          }
+        });
+
+        assert.deepStrictEqual(configData.test_settings['saucelabs.firefox'], {
+          extends: 'saucelabs',
+          desiredCapabilities: {
+            browserName: 'firefox',
+            screenResolution: '1280x1024',
+            browserVersion: 'latest',
+            javascriptEnabled: true,
+            acceptSslCerts: true,
+            timeZone: 'London'
+          }
+        });
+
         assert.deepStrictEqual(configData.test_settings['cucumber-js'], {
           src_folders: ['examples/cucumber-js/features/step_definitions'],
           test_runner: {
diff --git a/test/src/index/testSettings.js b/test/src/index/testSettings.js
index 917dcd89..84f13419 100644
--- a/test/src/index/testSettings.js
+++ b/test/src/index/testSettings.js
@@ -7,7 +7,7 @@ const eq = assert.strictEqual;
 
 describe('test Settings', function () {
   it('testSetOptions', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       use_xpath: true,
       launch_url: '/home'
     });
@@ -21,7 +21,7 @@ describe('test Settings', function () {
 
   it('test set test_runner invalid value', function () {
     assert.throws(function() {
-      let client = Nightwatch.createClient({
+      const client = Nightwatch.createClient({
         test_runner: null
       });
     }, /Error: Invalid "test_runner" settings specified; received: null/);
@@ -29,7 +29,7 @@ describe('test Settings', function () {
 
   it('test set test_runner invalid value - function', function () {
     assert.throws(function() {
-      let client = Nightwatch.createClient({
+      const client = Nightwatch.createClient({
         test_runner: function() {}
       });
     }, /Error: Invalid "test_runner" settings specified; received: function\(\) \{\}/);
@@ -37,7 +37,7 @@ describe('test Settings', function () {
 
   it('test set test_runner string value', function () {
 
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       test_runner: 'mocha'
     });
 
@@ -49,7 +49,7 @@ describe('test Settings', function () {
 
   it('testSetWebdriverOptionsDefaults', function () {
     HttpRequest.globalSettings = {};
-    let client = Nightwatch.createClientDefaults();
+    const client = Nightwatch.createClientDefaults();
 
     eq(client.options.webdriver.host, 'localhost');
     eq(client.options.webdriver.port, 4444);
@@ -63,7 +63,7 @@ describe('test Settings', function () {
   });
 
   it('testSetWebdriverOptions', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       webdriver: {
         host: '127.0.0.1',
         ssl: false,
@@ -78,8 +78,8 @@ describe('test Settings', function () {
       }
     });
 
-    let HttpRequest = common.require('http/request.js');
-    let request = new HttpRequest({});
+    const HttpRequest = common.require('http/request.js');
+    const request = new HttpRequest({});
 
     eq(request.reqOptions.host, '127.0.0.1');
     eq(request.reqOptions.port, 10195);
@@ -124,7 +124,7 @@ describe('test Settings', function () {
     const Nightwatch = common.require('index.js');
     const Settings = common.require('settings/settings.js');
 
-    let settings = Settings.parse({
+    const settings = Settings.parse({
       selenium: {
         start_process: false
       },
@@ -134,8 +134,8 @@ describe('test Settings', function () {
 
     Nightwatch.client(settings, null);
 
-    let HttpRequest = common.require('http/request.js');
-    let request = new HttpRequest({});
+    const HttpRequest = common.require('http/request.js');
+    const request = new HttpRequest({});
 
     eq(request.reqOptions.port, 80);
   });
@@ -143,7 +143,7 @@ describe('test Settings', function () {
   it('test create Transport for remote with Firefox', function() {
     const Settings = common.require('settings/settings.js');
 
-    let settings = Settings.parse({
+    const settings = Settings.parse({
       selenium: {
         start_process: false
       }
@@ -162,7 +162,7 @@ describe('test Settings', function () {
   });
 
   it('testSetOptionsCredentials', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       username: 'test-user',
       accessKey: 'test-access-key'
     });
@@ -174,7 +174,7 @@ describe('test Settings', function () {
   });
 
   it('testEnableFailFast', function () {
-    let client = Nightwatch.createClient({}, null, {
+    const client = Nightwatch.createClient({}, null, {
       'fail-fast': true
     });
 
@@ -182,7 +182,7 @@ describe('test Settings', function () {
   });
 
   it('testSetOptionsScreenshots – path empty', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       screenshots: {
         enabled: true,
         path: ''
@@ -197,7 +197,7 @@ describe('test Settings', function () {
   });
 
   it('testSetOptionsScreenshots – path not empty', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       screenshots: {
         enabled: true,
         path: '/tmp/'
@@ -207,7 +207,7 @@ describe('test Settings', function () {
 
     eq(client.api.options.log_screenshot_data, true);
     eq(client.options.screenshots.on_error, true);
-
+    
     if (process.platform === 'win32') {
       assert.ok(client.settings.screenshots.path.endsWith('\\tmp'));
     } else {
@@ -217,7 +217,7 @@ describe('test Settings', function () {
 
 
   it('testSetOptionsScreenshotsOnError', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       screenshots: {
         enabled: true,
         on_error: true,
@@ -230,7 +230,7 @@ describe('test Settings', function () {
   });
 
   it('testSetOptionsScreenshotsThrows', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       screenshots: true
     });
 
@@ -242,7 +242,7 @@ describe('test Settings', function () {
   });
 
   it('testEndSessionOnFail', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       end_session_on_fail: true
     });
 
@@ -253,7 +253,7 @@ describe('test Settings', function () {
   });
 
   it('testSetRequestTimeoutOptions', function () {
-    let client = Nightwatch.createClient({
+    const client = Nightwatch.createClient({
       webdriver: {
         timeout_options: {
           timeout: 10000,
@@ -268,15 +268,15 @@ describe('test Settings', function () {
     });
 
 
-    let HttpRequest = common.require('http/request.js');
-    let request = new HttpRequest({});
+    const HttpRequest = common.require('http/request.js');
+    const request = new HttpRequest({});
 
     assert.strictEqual(request.httpOpts.timeout, 10000);
     assert.strictEqual(request.retryAttempts, 5);
   });
 
   it('Test initialize with parallel cli argument', function () {
-    let settings = Settings.parse({
+    const settings = Settings.parse({
       selenium_port: 10195,
       silent: false,
       output: false
diff --git a/test/src/runner/testRunnerEs6Async.js b/test/src/runner/testRunnerEs6Async.js
index ce8dd90a..b4940f54 100644
--- a/test/src/runner/testRunnerEs6Async.js
+++ b/test/src/runner/testRunnerEs6Async.js
@@ -272,4 +272,32 @@ describe('testRunner ES6 Async', function () {
       globals
     }));
   });
+
+  it('test runner with async expect failure', function() {
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/element/0/text',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'Barn owl'
+      })
+    }, true);
+
+    const testsPath = path.join(__dirname, '../../sampletests/asyncwithexpectfailures');
+    const globals = {
+      waitForConditionPollInterval: 50,
+      waitForConditionTimeout: 100,
+      retryAssertionTimeout: 150,
+      reporter(results) {
+        assert.strictEqual(results.assertions, 2);
+        assert.strictEqual(results.failed, 1);
+        assert.strictEqual(results.passed, 1);
+      }
+    };
+
+    return runTests(testsPath, settings({
+      globals
+    }));
+  });
 });