Repository: nightwatchjs/nightwatch
Pull request #4130 (2024-03-18 12:19:31)

(feat): Added aliases for find() and findAll() command in new Element API 
I have performed the given TODO's for the issue - https://github.com/nightwatchjs/nightwatch/issues/4057

>  Add missing aliases
>  Add tests for the added aliases
>  Add missing types and tests for the added types

Raising this PR to hopefully get the issue resolved. Please let me know in case any change/s is required. 
Thank You!


garg3133: @AritraLeo You don't need to push the changes you've made in `ecosia.js` file to test your changes locally.

Also, I don't see `browser.element.getAll()` and `browser.element.findElements()` commands being tested there.
AritraLeo: > @AritraLeo You don't need to push the changes you've made in `ecosia.js` file to test your changes locally.
> 
> Also, I don't see `browser.element.getAll()` and `browser.element.findElements()` commands being tested there.

@garg3133 I'll surely revert all the changes from ecosia but before I push changes you can check lines - 166 & 196 where I've tested - `browser.element.getAll()` and `browser.element.findElements() `commands. Also I had executed tests present in testFind.js 
and testFindAll.js they were successful! Please let me know if there's anything else.

Thank You!
AritraLeo: @garg3133 reverted the changes of ecosia.js file tests 
AritraLeo: @garg3133 made the requested changes please have a look !
garg3133: > you can check lines - 166 & 196 where I've tested - browser.element.getAll() and browser.element.findElements() commands.

@AritraLeo That's not what I meant. You should test `browser.element.getAll()` not `browser.element().getAll()`. I'm sure that the former would not work because you've not added any implementation for it.

Please see the issue description for all the commands that should work.
AritraLeo: > > you can check lines - 166 & 196 where I've tested - browser.element.getAll() and browser.element.findElements() commands.
> 
> @AritraLeo That's not what I meant. You should test `browser.element.getAll()` not `browser.element().getAll()`. I'm sure that the former would not work because you've not added any implementation for it.
> 
> Please see the issue description for all the commands that should work.

Apologies for the oversight I'll make it work appropriately this time!
I'll also try to be more cautious from next time.
AutomatedTester: Please add tests to show  the API in use so we can make sure we know it's working as intended. 
AritraLeo: > Please add tests to show the API in use so we can make sure we know it's working as intended.

Actually I wrote the tests in ecosia.js but I later I came to know that, I should not push the changes there. So I did revert the file and I'll try to write all the tests mentioned in description.

Thank You!
garg3133: @AritraLeo The aliases for the `.element` namespace would go in `lib/api/_loaders/element-api.js` file. Once you've added the aliases, you should be able to run `browser.element.findElement('.selector')` command in your `ecosia.js` test suite.

For the unit tests, as you correctly mentioned, you'd need add those in the `nightwatch\test\src\api\commands\web-element` directory. `testFind.js` and `testFindAll.js` are the two files where you are required to add your tests.


AritraLeo: > @AritraLeo The aliases for the `.element` namespace would go in `lib/api/_loaders/element-api.js` file. Once you've added the aliases, you should be able to run `browser.element.findElements('.selector')` and `browser.element.getAll('.selector')` command in your `ecosia.js` test suite.
> 
> For the unit tests, as you correctly mentioned, you'd need add those in the `nightwatch\test\src\api\commands\web-element` directory. `testFind.js` and `testFindAll.js` are the two files where you are required to add your tests.

Thank You so much for the direction @garg3133 I wrote the unit test suite in local but was getting errors it was clear to me that I was missing something I looked into similar merged PR's but couldn't get a solid way to get it resolved. I'll come up with the changes and update UT files asap. Thanks again!
AritraLeo: Hey @garg3133 

I am having some queries regarding the tests to be performed for the given - 

`browser.element.find('selector')`

is this the test (is already present in testFind.js line -70) 
```
it('test .element.find()', async function() {
    const signupElement = this.client.api.element.find('#signupSection');
    assert.strictEqual(signupElement instanceof Element, true);
    assert.strictEqual(await signupElement.getId(), '0');

    const signupWebElement = await signupElement;
    assert.strictEqual(signupWebElement instanceof WebElement, true);
    assert.strictEqual(await signupWebElement.getId(), '0');
  });

```
cuz after adding the aliases like - 

```
class ScopedElementAPILoader {
  static get availableElementCommands() {
    return [
      ['findElement', 'find', 'get'],
      ['findAll', 'findElements', 'getAll'],
      ['findByText'],
      ['findAllByText'],
      ['findByRole'],
      ['findAllByRole'],
      ['findByAltText'],
      ['findAllByAltText'],
      ['findByLabelText'],
      ['findByPlaceholderText'],
      ['findAllByPlaceholderText']
    ];
  }

```
to the file - `lib/api/_loaders/element-api.js`
as you recommended when I am using - 

```
it('test browser.element.find()', async function() {
    const element = this.client.api.element.find('selector');
    assert.strictEqual(element instanceof Element, true);
    assert.strictEqual(await element.getId(), '0');
  
    const webElement = await element;
    assert.strictEqual(webElement instanceof WebElement, true);
    assert.strictEqual(await webElement.getId(), '0');
  });

```

it's failing is it because selector needs to accept an id ?
It might be an irrelevant question apologies if that is the case but I am really finding it hard to make the test work
garg3133: @AritraLeo Your test is failing because you are not passing a valid selector to the `.find()` command. We have mocks for a few selectors (like `#signupSection`) already defined so they work out of the box in our unit tests. You can global search `signupSection` to find out where the mock is defined for the selector.

AritraLeo: > @AritraLeo Your test is failing because you are not passing a valid selector to the `.find()` command. We have mocks for a few selectors (like `#signupSection`) already defined so they work out of the box in our unit tests. You can global search `signupSection` to find out where the mock is defined for the selector.

I was thinking of this exact thing but last time you asked me to follow the TODO list so I did it anyway. I was pretty sure `selector ` is defined in the mock. Thanks for the insight !
It seems I can push my commit now as the tests for these are passing I have added the alias to - `lib/api/_loaders/element-api.js` file as per instruction. I'll commit the chnages shortly! 

Thanks again!
AritraLeo: @garg3133 I've added the tests please have a look also added the alias declaration in element file.

Patch:
diff --git a/lib/api/_loaders/_command-loader.js b/lib/api/_loaders/_command-loader.js
index 6843fc73..808c84bf 100644
--- a/lib/api/_loaders/_command-loader.js
+++ b/lib/api/_loaders/_command-loader.js
@@ -35,8 +35,9 @@ class BaseCommandLoader extends BaseLoader {
     const alwaysAsync = this.module.alwaysAsync || false;
     const isTraceable = this.module.isTraceable;
     const avoidPrematureParentNodeResolution = !!this.module.avoidPrematureParentNodeResolution;
+    const rejectNodeOnAbortFailure = !!this.module.rejectNodeOnAbortFailure;
 
-    return {redact, alwaysAsync, isTraceable, avoidPrematureParentNodeResolution};
+    return {redact, alwaysAsync, isTraceable, avoidPrematureParentNodeResolution, rejectNodeOnAbortFailure};
   }
 
   validateMethod(parent) {
diff --git a/lib/api/_loaders/element-api.js b/lib/api/_loaders/element-api.js
index 6b431444..e42294fb 100644
--- a/lib/api/_loaders/element-api.js
+++ b/lib/api/_loaders/element-api.js
@@ -5,9 +5,10 @@ const {By} = require('selenium-webdriver');
 
 class ScopedElementAPILoader {
   static get availableElementCommands() {
+    // commands directly available on `browser.element`
     return [
       ['findElement', 'find', 'get'],
-      ['findAll'],
+      ['findAll', 'findElements', 'getAll'],
       ['findByText'],
       ['findAllByText'],
       ['findByRole'],
diff --git a/lib/api/client-commands/axeInject.js b/lib/api/client-commands/axeInject.js
index 7d3c1d97..f7ba0544 100644
--- a/lib/api/client-commands/axeInject.js
+++ b/lib/api/client-commands/axeInject.js
@@ -1,5 +1,6 @@
 /**
  * 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
diff --git a/lib/api/client-commands/axeRun.js b/lib/api/client-commands/axeRun.js
index 4b2b5519..e1129ba9 100644
--- a/lib/api/client-commands/axeRun.js
+++ b/lib/api/client-commands/axeRun.js
@@ -16,7 +16,7 @@
  *
  * @method axeRun
  * @syntax browser.axeRun('body')
- * @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 {any} [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
diff --git a/lib/api/client-commands/debug.js b/lib/api/client-commands/debug.js
index 53a3e868..e5632223 100644
--- a/lib/api/client-commands/debug.js
+++ b/lib/api/client-commands/debug.js
@@ -1,15 +1,16 @@
-const util = require('util');
 const EventEmitter = require('events');
 const NightwatchRepl = require('../../testsuite/repl');
 const Debuggability = require('../../utils/debuggability.js');
 
 /**
  * This command halts the test execution and provides users with a REPL interface where they can type
- * any of the available Nightwatch commands and the command will be executed in the running browser
+ * any of the available Nightwatch commands or assertions and it will be executed in the running browser
  * in real-time.
  *
- * This can be used to debug why a certain command in not working as expected, find the correct
- * locators for your assertions or just play around with the available Nightwatch commands.
+ * This can be used to debug why a certain command in not working as expected or a certain assertion is
+ * failing by trying out the commands and assertions in different ways (trying an assertion with different
+ * locators until the correct one if found), or just play around with the available Nightwatch commands
+ * and assertions.
  *
  * @example
  * // async function is required while using the debug
@@ -26,52 +27,51 @@ const Debuggability = require('../../utils/debuggability.js');
  * };
  *
  * @method debug
- * @param {object} config Config options for the REPL interface.
+ * @param {object} [config] Config options for the REPL interface.
  * @param {function} [callback] Optional callback function to be called when the command finishes.
  * @api protocol.utilities
  */
+class Debug extends EventEmitter {
+  static get avoidPrematureParentNodeResolution() {
+    return true;
+  }
 
-function Debug() {
-  EventEmitter.call(this);
-}
-
-util.inherits(Debug, EventEmitter);
-
-Debug.prototype.command = function(config, cb) {
-  const repl = new NightwatchRepl(config);
+  command(config, callback) {
+    const repl = new NightwatchRepl(config);
 
-  // eslint-disable-next-line
-  console.log(NightwatchRepl.introMessage());
+    // eslint-disable-next-line
+    console.log(NightwatchRepl.introMessage());
 
-  // Create context for vm
-  const context = {
-    browser: this.api,
-    app: this.api
-  };
-  if (config?.selector) {
-    this.api.executeScript('console.log("Element ' + config.selector + ':", document.querySelector("' + config.selector + '"))');
-  }
-  // Before starting REPL server, Set debugMode to true
-  Debuggability.debugMode = true;
-  // Set isES6AsyncTestcase to true in debugmode
-  const isES6AsyncTestcase = this.client.isES6AsyncTestcase;
-  this.client.isES6AsyncTestcase = true;
-  repl.startServer(context);
+    // Create context for vm
+    const context = {
+      browser: this.api,
+      app: this.api
+    };
+    if (config?.selector) {
+      this.api.executeScript('console.log("Element ' + config.selector + ':", document.querySelector("' + config.selector + '"))');
+    }
+    // Before starting REPL server, Set debugMode to true
+    Debuggability.debugMode = true;
+    // Set isES6AsyncTestcase to true in debugmode
+    const isES6AsyncTestcase = this.client.isES6AsyncTestcase;
+    this.client.isES6AsyncTestcase = true;
+    repl.startServer(context);
 
-  repl.onExit(() => {
-    // On exit, Set debugMode to false
-    Debuggability.debugMode = false;
-    this.client.isES6AsyncTestcase = isES6AsyncTestcase;
+    repl.onExit(() => {
+      // On exit, Set debugMode to false
+      Debuggability.debugMode = false;
+      this.client.isES6AsyncTestcase = isES6AsyncTestcase;
 
-    // if we have a callback, call it right before the complete event
-    if (cb) {
-      cb.call(this.client.api);
-    }
+      // if we have a callback, call it right before the complete event
+      if (callback) {
+        callback.call(this.client.api);
+      }
 
-    this.emit('complete');
-  });
+      this.emit('complete');
+    });
 
-  return this;
-};
+    return this;
+  }
+}
 
 module.exports = Debug;
diff --git a/lib/api/client-commands/pause.js b/lib/api/client-commands/pause.js
index 695d8a4a..080025de 100644
--- a/lib/api/client-commands/pause.js
+++ b/lib/api/client-commands/pause.js
@@ -1,7 +1,5 @@
-const util = require('util');
 const EventEmitter = require('events');
 const readline = require('readline');
-const tty = require('tty');
 const Debuggability = require('../../utils/debuggability.js');
 
 /**
@@ -31,65 +29,63 @@ const Debuggability = require('../../utils/debuggability.js');
  * @param {function} [callback] Optional callback function to be called when the command finishes.
  * @api protocol.utilities
  */
+class Pause extends EventEmitter {
 
-function Pause() {
-  EventEmitter.call(this);
-}
-
-util.inherits(Pause, EventEmitter);
-
-Pause.prototype.command = function(ms, cb) {
-  // If we don't pass the milliseconds, the client will
-  // be suspended indefinitely, until the user presses some
-  // key in the terminal to resume it.
-  if (ms === undefined) {
-    // eslint-disable-next-line
-    console.log(`Paused...
-  Press <space> or F10 to step over to the next test command and pause again.
-  Press Ctrl+C to exit.
-  Press any other key to RESUME.`);
-
-    readline.emitKeypressEvents(process.stdin);
-    process.stdin.resume();
-    if (process.stdin.isTTY) {
-      process.stdin.setRawMode(true);
-    }
-    process.stdin.once('keypress', (str, key) => {
-      if (key.ctrl && key.name === 'c') {
-        this.api.end(function() {
-          process.exit(0);
-        });
-      } else if (key.name === 'space' || key.name === 'f10') {
-        Debuggability.stepOverAndPause = true;
-      }
+  command(ms, callback) {
+    // If we don't pass the milliseconds, the client will
+    // be suspended indefinitely, until the user presses some
+    // key in the terminal to resume it.
+    if (ms === undefined) {
+      // eslint-disable-next-line
+      console.log(`Paused...
+    Press <space> or F10 to step over to the next test command and pause again.
+    Press Ctrl+C to exit.
+    Press any other key to RESUME.`);
 
+      readline.emitKeypressEvents(process.stdin);
+      process.stdin.resume();
       if (process.stdin.isTTY) {
-        process.stdin.setRawMode(false);
+        process.stdin.setRawMode(true);
       }
-      process.stdin.pause();
+      process.stdin.once('keypress', (str, key) => {
+        if (key.ctrl && key.name === 'c') {
+          this.api.end(function() {
+            process.exit(0);
+          });
+        } else if (key.name === 'space' || key.name === 'f10') {
+          Debuggability.stepOverAndPause = true;
+        } else if (key.name === 'd') {
+          this.api.debug();
+        }
 
-      // Remove the logged paused... information above
-      readline.moveCursor(process.stdout, 0, -4);
-      readline.clearScreenDown(process.stdout);
+        if (process.stdin.isTTY) {
+          process.stdin.setRawMode(false);
+        }
+        process.stdin.pause();
 
-      if (cb) {
-        cb.call(this.client.api);
-      }
+        // Remove the logged paused... information above
+        readline.moveCursor(process.stdout, 0, -4);
+        readline.clearScreenDown(process.stdout);
 
-      this.emit('complete');
-    });
-  } else {
-    setTimeout(() => {
-      // if we have a callback, call it right before the complete event
-      if (cb) {
-        cb.call(this.client.api);
-      }
+        if (callback) {
+          callback.call(this.client.api);
+        }
 
-      this.emit('complete');
-    }, ms);
-  }
+        this.emit('complete');
+      });
+    } else {
+      setTimeout(() => {
+        // if we have a callback, call it right before the complete event
+        if (callback) {
+          callback.call(this.client.api);
+        }
+
+        this.emit('complete');
+      }, ms);
+    }
 
-  return this;
-};
+    return this;
+  }
+}
 
 module.exports = Pause;
diff --git a/lib/api/protocol/waitUntil.js b/lib/api/protocol/waitUntil.js
index feb693a3..65e2fabc 100644
--- a/lib/api/protocol/waitUntil.js
+++ b/lib/api/protocol/waitUntil.js
@@ -65,6 +65,10 @@ module.exports = class WaitUntil extends ProtocolAction {
     return true;
   }
 
+  static get rejectNodeOnAbortFailure() {
+    return true;
+  }
+
   //timeMs, retryInterval, message, callback = function(r) {return r}
   async command(conditionFn, ...args) {
     let callback = function(r) {return r};
diff --git a/lib/api/web-element/commands/find.js b/lib/api/web-element/commands/find.js
index ae682815..584317ae 100644
--- a/lib/api/web-element/commands/find.js
+++ b/lib/api/web-element/commands/find.js
@@ -30,6 +30,8 @@
  * @syntax browser.element.find(selector)
  * @param {String|Object} selector
  * @returns {ScopedWebElement}
+ * @alias get
+ * @alias findElement
  */
 module.exports.command = function(selector) {
   return this.createScopedElement(selector, this);
diff --git a/lib/api/web-element/commands/findAll.js b/lib/api/web-element/commands/findAll.js
index f85df013..de261052 100644
--- a/lib/api/web-element/commands/findAll.js
+++ b/lib/api/web-element/commands/findAll.js
@@ -24,6 +24,8 @@
  * @syntax browser.element.findAll(syntax)
  * @param {Selector} selector
  * @returns {Array.<ScopeWebElement>}
+ * @alias getAll
+ * @alias findElements
  */
 module.exports.command = function(selector) {
   return this.createScopedElements(selector, {parentElement: this, commandName: 'findAll'});
diff --git a/lib/api/web-element/scoped-element.js b/lib/api/web-element/scoped-element.js
index 1aaee5f8..e8dd80f1 100644
--- a/lib/api/web-element/scoped-element.js
+++ b/lib/api/web-element/scoped-element.js
@@ -23,8 +23,10 @@ class ScopedWebElement {
       'findAllByPlaceholderText': ['getAllByPlaceholderText'],
       'findAllByAltText': ['getAllByAltText'],
       'getRect': ['getSize', 'getLocation', 'rect'],
-      'getProperty': ['property'],
+      'getProperty': ['property', 'prop'],
+      'getText': ['text'],
       'getCssProperty': ['css'],
+      'getAriaRole': ['ariaRole'],
       'isVisible': ['isDisplayed']
     };
   }
diff --git a/lib/core/asynctree.js b/lib/core/asynctree.js
index 0493834a..926a45a1 100644
--- a/lib/core/asynctree.js
+++ b/lib/core/asynctree.js
@@ -103,8 +103,10 @@ class AsyncTree extends EventEmitter{
     return Boolean(result);
   }
 
-  shouldRejectNodePromise(err, node = this.currentNode) {
-    if ((err.isExpect || node.namespace === 'assert') && this.currentNode.isES6Async) {
+  shouldRejectNodePromise(err, abortOnFailure, node = this.currentNode) {
+    const rejectNodeOnAbortFailure = node.options?.rejectNodeOnAbortFailure;
+
+    if ((err.isExpect || node.namespace === 'assert' || (abortOnFailure && rejectNodeOnAbortFailure)) && this.currentNode.isES6Async) {
       return true;
     }
 
@@ -146,7 +148,7 @@ class AsyncTree extends EventEmitter{
         err.stack = err.stack.split('\n').slice(1).join('\n');
       }
 
-      if (this.shouldRejectNodePromise(err, node)) {
+      if (this.shouldRejectNodePromise(err, abortOnFailure, node)) {
         node.reject(err);
       } else {
         node.resolve(err);
diff --git a/lib/transport/errors/index.js b/lib/transport/errors/index.js
index 90308435..4e1fc506 100644
--- a/lib/transport/errors/index.js
+++ b/lib/transport/errors/index.js
@@ -155,11 +155,16 @@ module.exports = {
     if (err && (err.name in SeleniumNightwatchErrorCodeMap)) {
       const statusCode = SeleniumNightwatchErrorCodeMap[err.name];
 
-      return {
+      const error = {
         ...err,
-        ...Errors[statusCode],
         id: statusCode
       };
+
+      for (const [key, value] of Object.entries(Errors[statusCode])) {
+        error[key] ||= value;
+      }
+
+      return error;
     } else if (err && err.name && (err.stack || err.stackTrace)){
       return err;
     }
diff --git a/lib/transport/selenium-webdriver/options.js b/lib/transport/selenium-webdriver/options.js
index e17f1ef3..da198b55 100644
--- a/lib/transport/selenium-webdriver/options.js
+++ b/lib/transport/selenium-webdriver/options.js
@@ -37,8 +37,10 @@ module.exports = class SeleniumCapabilities {
     this.desiredCapabilities = this.settings.capabilities || this.settings.desiredCapabilities;
 
     // flatten alwaysMatch param from desired Capabilities
-    Object.assign(this.desiredCapabilities, this.desiredCapabilities?.alwaysMatch);
-    delete this.desiredCapabilities['alwaysMatch'];
+    if (this.desiredCapabilities.alwaysMatch) {
+      Object.assign(this.desiredCapabilities, this.desiredCapabilities.alwaysMatch);
+      delete this.desiredCapabilities['alwaysMatch'];
+    }
 
     if (typeof this.desiredCapabilities == 'function') {
       this.desiredCapabilities = this.desiredCapabilities.call(this.settings);
diff --git a/lib/utils/mobile.js b/lib/utils/mobile.js
index b019fa2f..9d2a6621 100644
--- a/lib/utils/mobile.js
+++ b/lib/utils/mobile.js
@@ -172,7 +172,7 @@ class RealIosDeviceIdError extends Error {
       `Pass ${Logger.colors.green('deviceId')} in the command (for e.g : ${Logger.colors.cyan('--deviceId 00008030-00024C2C3453402E')})`,
       `Or pass ${Logger.colors.green('safari:deviceUDID')} capability in config`,
       `To verify the deviceId run, ${Logger.colors.cyan('system_profiler SPUSBDataType | sed -n \'/iPhone/,/Serial/p\' | grep \'Serial Number:\' | awk -F \': \' \'{print $2}')}`,
-      `For more help, run: ${Logger.colors.cyan('npx run @nightwatch/mobile-helper ios')}\n`
+      `For more help, run: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}\n`
     ];
     this.stack = false;
   }
@@ -275,7 +275,7 @@ class IosSessionNotCreatedError extends Error {
       // 'The session timed out while connecting to a Safari instance.'
       help = [
         'Re-run the test command',
-        `If it doesn't work, try running: ${Logger.colors.cyan('npx run @nightwatch/mobile-helper ios')}`
+        `If it doesn't work, try running: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}`
       ];
     } else if (this.message.includes('not find any session hosts') || this.message.includes('Some devices were found')) {
       // Could not find any session hosts that match the requested capabilities
@@ -284,21 +284,21 @@ class IosSessionNotCreatedError extends Error {
         help = [
           `Run command to get device list: ${Logger.colors.cyan('xcrun simctl list devices')}`,
           `Update the ${Logger.colors.cyan('safari:platformVersion')} and/or ${Logger.colors.cyan('safari:platforName')} in Nightwatch configuration accordingly`,
-          `If it doesn't work, try running: ${Logger.colors.cyan('npx run @nightwatch/mobile-helper ios')}`
+          `If it doesn't work, try running: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}`
         ];
       } else {
         help = [
           `Make sure you have passed correct ${Logger.colors.green('deviceId')} in the command (for e.g : ${Logger.colors.cyan('--deviceId 00008030-00024C2C3453402E')})`,
           `Or pass ${Logger.colors.green('safari:deviceUDID')} capability in config`,
           `To verify the deviceId run, ${Logger.colors.cyan('system_profiler SPUSBDataType | sed -n \'/iPhone/,/Serial/p\' | grep \'Serial Number:\' | awk -F \': \' \'{print $2}')}`,
-          `For more help, run: ${Logger.colors.cyan('npx run @nightwatch/mobile-helper ios')}\n`
+          `For more help, run: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}\n`
         ];
       }
     } else if (this.desiredCapabilities['safari:deviceUDID']) {
       help = [
         `Verify the UDID of the device set in Nightwatch configuration (look for ${Logger.colors.cyan('safari:deviceUDID')} capability) or pass the correct UDID using ${Logger.colors.cyan('--deviceId')} flag in the test command.`,
         'Re-run the test command',
-        `If it doesn't work, try running: ${Logger.colors.cyan('npx run @nightwatch/mobile-helper ios')}`
+        `If it doesn't work, try running: ${Logger.colors.cyan('npx @nightwatch/mobile-helper ios')}`
       ];
 
       if (isRealIos(this.desiredCapabilities)) {
diff --git a/package-lock.json b/package-lock.json
index da13123a..992114d4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "nightwatch",
-  "version": "3.5.0",
+  "version": "3.6.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "nightwatch",
-      "version": "3.5.0",
+      "version": "3.6.0",
       "license": "MIT",
       "dependencies": {
         "@nightwatch/chai": "5.0.3",
diff --git a/package.json b/package.json
index db55b8c8..ed0b72e9 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "nightwatch",
   "description": "Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.",
-  "version": "3.5.0",
+  "version": "3.6.0",
   "author": "Andrei Rusu",
   "homepage": "https://nightwatchjs.org",
   "main": "./lib/index.js",
diff --git a/types/web-element.d.ts b/types/web-element.d.ts
index d8fc0c71..699aa6cf 100644
--- a/types/web-element.d.ts
+++ b/types/web-element.d.ts
@@ -15,6 +15,7 @@ export interface ScopedElement extends Element, PromiseLike<WebElement> {
 
   find(selector: ScopedElementSelector): ScopedElement;
   get(selector: ScopedElementSelector): ScopedElement;
+  findElement(selector: ScopedElementSelector): ScopedElement;
 
   findByText(
     text: string,
@@ -45,30 +46,31 @@ export interface ScopedElement extends Element, PromiseLike<WebElement> {
       readonly expanded?: boolean;
     }
   ): ScopedElement;
-  
+
   findByPlaceholderText(
     text: string,
     options?: Omit<ScopedSelectorObject, 'selector'> & {
       readonly exact?: boolean;
     }
   ): ScopedElement;
-  
+
   findByLabelText(
     text: string,
     options?: Omit<ScopedSelectorObject, 'selector'> & {
       readonly exact?: boolean;
     }
   ): ScopedElement;
-  
+
   findByAltText(
     text: string,
     options?: Omit<ScopedSelectorObject, 'selector'> & {
       readonly exact?: boolean;
     }
   ): ScopedElement;
-  
+
   findAll(selector: ScopedSelector | Promise<ScopedSelector>): Elements;
   getAll(selector: ScopedSelector | Promise<ScopedSelector>): Elements;
+  findElements(selector: ScopedSelector | Promise<ScopedSelector>): Elements;
 
   findAllByText(
     text: string,
@@ -148,6 +150,7 @@ export interface ScopedElement extends Element, PromiseLike<WebElement> {
   getTagName(): ElementValue<string>;
 
   getText(): ElementValue<string>;
+  text(): ElementValue<string>;
 
   click(): Promise<WebElement>;
 
@@ -158,6 +161,8 @@ export interface ScopedElement extends Element, PromiseLike<WebElement> {
   submit(): Promise<WebElement>;
 
   getProperty(name: string): ElementValue<string | null>;
+  prop(name: string): ElementValue<string | null>;
+  property(name: string): ElementValue<string | null>;
 
   setProperty(name: string, value: unknown): Promise<WebElement>;
 
@@ -178,6 +183,7 @@ export interface ScopedElement extends Element, PromiseLike<WebElement> {
   getAccessibleName(): ElementValue<string>;
 
   getAriaRole(): ElementValue<string>;
+  ariaRole(): ElementValue<string>;
 
   getCssProperty(name: string): ElementValue<string>;
 
@@ -347,8 +353,8 @@ export type DragAndDropDestination = {
 export interface ElementFunction
   extends Pick<
     ScopedElement,
-    'find' | 'findByText' | 'findByRole' | 'findByPlaceholderText' | 'findByLabelText' | 'findByAltText' |
-    'findAll' | 'findAllByText' | 'findAllByRole' | 'findAllByPlaceholderText' | 'findAllByAltText'
+    'find' | 'get' | 'findElement' | 'findByText' | 'findByRole' | 'findByPlaceholderText' | 'findByLabelText' | 'findByAltText' |
+    'findAll' | 'getAll' | 'findElements' | 'findAllByText' | 'findAllByRole' | 'findAllByPlaceholderText' | 'findAllByAltText'
   > {
   (selector: ScopedElementSelector): ScopedElement;
   (


Test patch:
diff --git a/test/src/api/commands/web-element/testFind.js b/test/src/api/commands/web-element/testFind.js
index 721b5001..ff6a739f 100644
--- a/test/src/api/commands/web-element/testFind.js
+++ b/test/src/api/commands/web-element/testFind.js
@@ -76,4 +76,24 @@ describe('element().find() commands', function () {
     assert.strictEqual(signupWebElement instanceof WebElement, true);
     assert.strictEqual(await signupWebElement.getId(), '0');
   });
+
+  it('test .element.get()', async function() {
+    const signupElement = this.client.api.element.get('#signupSection');
+    assert.strictEqual(signupElement instanceof Element, true);
+    assert.strictEqual(await signupElement.getId(), '0');
+
+    const signupWebElement = await signupElement;
+    assert.strictEqual(signupWebElement instanceof WebElement, true);
+    assert.strictEqual(await signupWebElement.getId(), '0');
+  });
+
+  it('test .element.findElement()', async function() {
+    const signupElement = this.client.api.element.findElement('#signupSection');
+    assert.strictEqual(signupElement instanceof Element, true);
+    assert.strictEqual(await signupElement.getId(), '0');
+
+    const signupWebElement = await signupElement;
+    assert.strictEqual(signupWebElement instanceof WebElement, true);
+    assert.strictEqual(await signupWebElement.getId(), '0');
+  });
 });
diff --git a/test/src/api/commands/web-element/testFindAll.js b/test/src/api/commands/web-element/testFindAll.js
index 89878b42..24d5ec9c 100644
--- a/test/src/api/commands/web-element/testFindAll.js
+++ b/test/src/api/commands/web-element/testFindAll.js
@@ -115,6 +115,40 @@ describe('element().findAll() commands', function () {
     assert.strictEqual(await btnWEbElements[0].getId(), '5cc459b8-36a8-3042-8b4a-258883ea642b');
   });
 
+  it('test .element.getAll()', async function() {
+    const webLoginElements = this.client.api.element.getAll('#weblogin');
+    assert.strictEqual(webLoginElements instanceof Element, false);
+    assert.strictEqual(typeof webLoginElements.find, 'undefined');
+    assert.strictEqual(typeof webLoginElements.click, 'undefined');
+
+    assert.strictEqual(webLoginElements instanceof Promise, false);
+    assert.strictEqual(typeof webLoginElements.then, 'function');
+    assert.strictEqual(typeof webLoginElements.nth, 'function');
+    assert.strictEqual(typeof webLoginElements.count, 'function');
+
+    const btnWEbElements = await webLoginElements;
+    assert.strictEqual(btnWEbElements.length, 2);
+    assert.strictEqual(btnWEbElements[0] instanceof WebElement, true);
+    assert.strictEqual(await btnWEbElements[0].getId(), '5cc459b8-36a8-3042-8b4a-258883ea642b');
+  });
+
+  it('test .element.findElements()', async function() {
+    const webLoginElements = this.client.api.element.findElements('#weblogin');
+    assert.strictEqual(webLoginElements instanceof Element, false);
+    assert.strictEqual(typeof webLoginElements.find, 'undefined');
+    assert.strictEqual(typeof webLoginElements.click, 'undefined');
+
+    assert.strictEqual(webLoginElements instanceof Promise, false);
+    assert.strictEqual(typeof webLoginElements.then, 'function');
+    assert.strictEqual(typeof webLoginElements.nth, 'function');
+    assert.strictEqual(typeof webLoginElements.count, 'function');
+
+    const btnWEbElements = await webLoginElements;
+    assert.strictEqual(btnWEbElements.length, 2);
+    assert.strictEqual(btnWEbElements[0] instanceof WebElement, true);
+    assert.strictEqual(await btnWEbElements[0].getId(), '5cc459b8-36a8-3042-8b4a-258883ea642b');
+  });
+
   it('test .element().findAll().count()', async function() {
     const resultPromise = this.client.api.element('#signupSection').findAll('.btn').count();
     assert.strictEqual(resultPromise instanceof Element, false);
diff --git a/test/src/api/commands/web-element/testGetAriaRole.js b/test/src/api/commands/web-element/testGetAriaRole.js
index 53e86b65..7cddaad3 100644
--- a/test/src/api/commands/web-element/testGetAriaRole.js
+++ b/test/src/api/commands/web-element/testGetAriaRole.js
@@ -38,6 +38,30 @@ describe('element().getAriaRole() command', function () {
     assert.strictEqual(resultValue, 'signupSection');
   });
 
+  it('test .element().ariaRole()', async function() {
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/computedrole',
+      method: 'GET',
+      response: JSON.stringify({
+        value: 'signupSection'
+      })
+    }, true);
+
+    const resultPromise = this.client.api.element('#signupSection').ariaRole();
+    assert.strictEqual(resultPromise instanceof Element, false);
+    assert.strictEqual(typeof resultPromise.find, 'undefined');
+
+    assert.strictEqual(resultPromise instanceof Promise, false);
+    assert.strictEqual(typeof resultPromise.then, 'function');
+
+    const result = await resultPromise;
+    assert.strictEqual(result instanceof WebElement, false);
+    assert.strictEqual(result, 'signupSection');
+
+    const resultValue = await resultPromise.value;
+    assert.strictEqual(resultValue, 'signupSection');
+  });
+
   it('test .element().find().getAriaRole()', async function() {
     MockServer.addMock({
       url: '/session/13521-10219-202/element/1/computedrole',
diff --git a/test/src/api/commands/web-element/testGetProperty.js b/test/src/api/commands/web-element/testGetProperty.js
index b68b8c3d..58dca869 100644
--- a/test/src/api/commands/web-element/testGetProperty.js
+++ b/test/src/api/commands/web-element/testGetProperty.js
@@ -62,6 +62,30 @@ describe('element().getProperty() command', function () {
     assert.deepStrictEqual(resultValue, ['.signup']);
   });
 
+  it('test .element().prop() alias', async function() {
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/property/classList',
+      method: 'GET',
+      response: JSON.stringify({
+        value: ['.signup']
+      })
+    }, true);
+
+    const resultPromise = this.client.api.element('#signupSection').prop('classList');
+    assert.strictEqual(resultPromise instanceof Element, false);
+    assert.strictEqual(typeof resultPromise.find, 'undefined');
+
+    assert.strictEqual(resultPromise instanceof Promise, false);
+    assert.strictEqual(typeof resultPromise.then, 'function');
+
+    const result = await resultPromise;
+    assert.strictEqual(result instanceof WebElement, false);
+    assert.deepStrictEqual(result, ['.signup']);
+
+    const resultValue = await resultPromise.value;
+    assert.deepStrictEqual(resultValue, ['.signup']);
+  });
+  
   it('test .element().find().getProperty()', async function() {
     MockServer.addMock({
       url: '/session/13521-10219-202/element/1/property/classList',
@@ -109,4 +133,5 @@ describe('element().getProperty() command', function () {
     const resultValue = await resultPromise.value;
     assert.deepStrictEqual(resultValue, ['.signup']);
   });
+
 });
diff --git a/test/src/api/commands/web-element/testGetText.js b/test/src/api/commands/web-element/testGetText.js
index 14162147..056bb49f 100644
--- a/test/src/api/commands/web-element/testGetText.js
+++ b/test/src/api/commands/web-element/testGetText.js
@@ -38,6 +38,30 @@ describe('element().getText() command', function () {
     assert.strictEqual(resultValue, 'Signup');
   });
 
+  it('test .element().text() alias', async function() {
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/text',
+      method: 'GET',
+      response: JSON.stringify({
+        value: 'Signup'
+      })
+    }, true);
+
+    const resultPromise = this.client.api.element('#signupSection').text();
+    assert.strictEqual(resultPromise instanceof Element, false);
+    assert.strictEqual(typeof resultPromise.find, 'undefined');
+
+    assert.strictEqual(resultPromise instanceof Promise, false);
+    assert.strictEqual(typeof resultPromise.then, 'function');
+
+    const result = await resultPromise;
+    assert.strictEqual(result instanceof WebElement, false);
+    assert.strictEqual(result, 'Signup');
+
+    const resultValue = await resultPromise.value;
+    assert.strictEqual(resultValue, 'Signup');
+  });
+
   it('test .element().find().getText()', async function() {
     MockServer.addMock({
       url: '/session/13521-10219-202/element/1/text',
@@ -110,4 +134,5 @@ describe('element().getText() command', function () {
     assert.strictEqual(await resultPromise.assert.not.contains('Signupx'), 'Signup');
     assert.strictEqual(await resultPromise.assert.not.matches(/Si[a-z]{2}upx/), 'Signup');
   });
+
 });
diff --git a/types/tests/webElement.test-d.ts b/types/tests/webElement.test-d.ts
index ed24c4f5..8e183e91 100644
--- a/types/tests/webElement.test-d.ts
+++ b/types/tests/webElement.test-d.ts
@@ -44,6 +44,9 @@ describe('new element() api', function () {
     expectType<ScopedElement>(elementFind);
     expectType<WebElement>(await elementFind);
 
+    expectType<ScopedElement>(browser.element.get('selector'));
+    expectType<ScopedElement>(browser.element.findElement('selector'));
+
     const elementFindByText = browser.element.findByText('some-text', {exact: true, abortOnFailure: false});
     expectType<ScopedElement>(elementFindByText);
 
@@ -65,9 +68,13 @@ describe('new element() api', function () {
     const elementFindAll = browser.element.findAll('selector');
     expectType<Elements>(elementFindAll);
     expectType<WebElement[]>(await elementFindAll);
+
     expectType<ScopedElement>(elementFindAll.nth(2));
     expectType<number>(await elementFindAll.count());
 
+    expectType<Elements>(browser.element.getAll('selector'));
+    expectType<Elements>(browser.element.findElements('selector'));
+
     const elementFindAllByText = browser.element.findAllByText('some-text', {exact: true, abortOnFailure: false});
     expectType<Elements>(elementFindAllByText);
 
@@ -102,6 +109,7 @@ describe('new element() api', function () {
 
     expectType<ScopedElement>(elem.find('selector'));
     expectType<ScopedElement>(elem.get('selector'));
+    expectType<ScopedElement>(elem.findElement('selector'));
 
     expectType<ScopedElement>(elem.findByText('some-text', {exact: true, abortOnFailure: false}));
 
@@ -114,6 +122,7 @@ describe('new element() api', function () {
     expectType<ScopedElement>(elem.findByAltText('some-text', {exact: true, abortOnFailure: false}));
 
     expectType<Elements>(elem.findAll('selector'));
+    expectType<Elements>(elem.findElements('selector'));
     expectType<Elements>(elem.getAll('selector'));
 
     expectType<Elements>(elem.findAllByText('some-text', {exact: true, abortOnFailure: false}));
@@ -136,8 +145,11 @@ describe('new element() api', function () {
     expectType<ElementValue<string>>(elem.getId());
     expectType<ElementValue<string>>(elem.getTagName());
     expectType<ElementValue<string>>(elem.getText());
+    expectType<ElementValue<string>>(elem.text());
 
     expectType<ElementValue<string | null>>(elem.getProperty('property-name'));
+    expectType<ElementValue<string | null>>(elem.prop('property-name'));
+    expectType<ElementValue<string | null>>(elem.property('property-name'));
     expectType<ElementValue<string | null>>(elem.getAttribute('attrib-name'));
     expectType<ElementValue<string | null>>(elem.getValue());
     expectType<ElementValue<boolean>>(elem.isEnabled());
@@ -151,6 +163,7 @@ describe('new element() api', function () {
 
     expectType<ElementValue<string>>(elem.getAccessibleName());
     expectType<ElementValue<string>>(elem.getAriaRole());
+    expectType<ElementValue<string>>(elem.ariaRole());
     expectType<ElementValue<string>>(elem.getCssProperty('height'));
     expectType<ElementValue<string>>(elem.takeScreenshot());