Repository: nightwatchjs/nightwatch
Pull request #2763 (2021-06-28 10:45:01)

Add(new commands): getComputedLabel, getComputedRole, takeElementScre…
…enshot

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.

- [ ] Create a new branch from master (e.g. `features/my-new-feature` or `issue/123-my-bugfix`);
- [ ] If you're fixing a bug also create an issue if one doesn't exist yet;
- [ ] 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;
- [ ] Please avoid sending PRs which contain drastic or low level changes. If you are certain that the changes are needed, please discuss them beforehand and indicate what the impact will be;
- [ ] If your change is based on existing functionality please consider refactoring first. Pull requests that duplicate code will most likely be ignored;
- [ ] Do not include changes that are not related to the issue at hand;
- [ ] Follow the same coding style with regards to spaces, semicolons, variable naming etc.;
- [ ] Always add unit tests - PRs without tests are most of the times ignored.



Patch:
diff --git a/lib/api/element-commands/getAccessibleName.js b/lib/api/element-commands/getAccessibleName.js
new file mode 100644
index 00000000..853fc20f
--- /dev/null
+++ b/lib/api/element-commands/getAccessibleName.js
@@ -0,0 +1,61 @@
+const BaseElementCommand = require('./_baseElementCommand.js');
+
+/**
+ * Returns the computed WAI-ARIA label of an element.
+ *
+ * @example
+ * module.exports = {
+ *   demoTest(browser) {
+ *     browser.getAccessibleName('*[name="search"]', function(result) {
+ *       this.assert.equal(typeof result, 'object);
+ *       this.assert.equal(result.value, 'search input');
+ *     });
+ *
+ *     // with explicit locate strategy
+ *     browser.getAccessibleName('css selector', '*[name="search"]', function(result) {
+ *       console.log('getAccessibleName result', result.value);
+ *     });
+ *
+ *     // with selector object - see https://nightwatchjs.org/guide#element-properties
+ *     browser.getAccessibleName({
+ *       selector: '*[name="search"]',
+ *       index: 1
+ *     }, function(result) {
+ *       console.log('getAccessibleName result', result.value);
+ *     });
+ *
+ *     browser.getAccessibleName({
+ *       selector: '*[name="search"]',
+ *       timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
+ *     }, function(result) {
+ *       console.log('getAccessibleName result', result.value);
+ *     });
+ *   },
+ *
+ *   demoTestAsync: async function(browser) {
+ *     const result = await browser.getAccessibleName('*[name="search"]');
+ *     console.log('getAccessibleName result', result);
+ *   }
+ * }
+ *
+ * @method getAccessibleName
+ * @syntax .getAccessibleName(selector, callback)
+ * @syntax .getAccessibleName(using, selector, callback)
+ * @param {string} [using] The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies)
+ * @param {string} selector The CSS/Xpath selector used to locate the element.
+ * @param {function} callback Callback function which is called with the result value.
+ * @returns {string} The computed WAI-ARIA label of element.
+ * @link /#dfn-get-computed-label
+ * @api protocol.elementstate
+ */
+class GetAccessibleName extends BaseElementCommand {
+  get extraArgsCount() {
+    return 0;
+  }
+
+  get elementProtocolAction() {
+    return 'getElementAccessibleName';
+  }
+}
+
+module.exports = GetAccessibleName;
diff --git a/lib/api/element-commands/getAriaRole.js b/lib/api/element-commands/getAriaRole.js
new file mode 100644
index 00000000..f464bc65
--- /dev/null
+++ b/lib/api/element-commands/getAriaRole.js
@@ -0,0 +1,61 @@
+const BaseElementCommand = require('./_baseElementCommand.js');
+
+/**
+ * Returns the computed WAI-ARIA role of an element.
+ *
+ * @example
+ * module.exports = {
+ *   demoTest(browser) {
+ *     browser.getAriaRole('*[name="search"]', function(result) {
+ *       this.assert.equal(typeof result, 'object');
+ *       this.assert.equal(result.value, 'combobox');
+ *     });
+ *
+ *     // with explicit locate strategy
+ *     browser.getAriaRole('css selector', '*[name="search"]', function(result) {
+ *       console.log('getAriaRole result', result.value);
+ *     });
+ *
+ *     // with selector object - see https://nightwatchjs.org/guide#element-properties
+ *     browser.getAriaRole({
+ *       selector: '*[name="search"]',
+ *       index: 1
+ *     }, function(result) {
+ *       console.log('getAriaRole result', result.value);
+ *     });
+ *
+ *     browser.getAriaRole({
+ *       selector: '*[name="search"]',
+ *       timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
+ *     }, function(result) {
+ *       console.log('getAriaRole result', result.value);
+ *     });
+ *   },
+ *
+ *   demoTestAsync: async function(browser) {
+ *     const result = await browser.getAriaRole('*[name="search"]');
+ *     console.log('getAriaRole result', result);
+ *   }
+ * }
+ *
+ * @method getAriaRole
+ * @syntax .getAriaRole(selector, callback)
+ * @syntax .getAriaRole(using, selector, callback)
+ * @param {string} [using] The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies)
+ * @param {string} selector The CSS/Xpath selector used to locate the element.
+ * @param {function} callback Callback function which is called with the result value.
+ * @returns {string} The computed WAI-ARIA role of element.
+ * @link /#dfn-get-computed-role
+ * @api protocol.elementstate
+ */
+class GetAriaRole extends BaseElementCommand {
+  get extraArgsCount() {
+    return 0;
+  }
+
+  get elementProtocolAction() {
+    return 'getElementAriaRole';
+  }
+}
+
+module.exports = GetAriaRole;
diff --git a/lib/api/element-commands/takeElementScreenshot.js b/lib/api/element-commands/takeElementScreenshot.js
new file mode 100644
index 00000000..6513dc7e
--- /dev/null
+++ b/lib/api/element-commands/takeElementScreenshot.js
@@ -0,0 +1,70 @@
+const BaseElementCommand = require('./_baseElementCommand.js');
+
+/**
+ * Returns the computed WAI-ARIA label of an element.
+ *
+ * @example
+ * module.exports = {
+ *   demoTest(browser) {
+ *     browser.takeElementScreenshot('*[name="search"]', true, function (image, err) {
+ *       require('fs').writeFile('out.png', image.value, 'base64', function (err) {
+ *         console.log(err);
+ *       });
+ *     });
+ *
+ *     // with explicit locate strategy
+ *     browser.takeElementScreenshot('css selector', '*[name="search"]', true, function(image, err) {
+ *       require('fs').writeFile('out.png', image.value, 'base64', function (err) {
+ *         console.log(err);
+ *       });
+ *     });
+ *
+ *     // with selector object - see https://nightwatchjs.org/guide#element-properties
+ *     browser.takeElementScreenshot({
+ *       selector: '#main ul li a',
+ *       true,
+ *       index: 1
+ *     }, function(image, err) {
+ *       require('fs').writeFile('out.png', image.value, 'base64', function (err) {
+ *         console.log(err);
+ *       });
+ *     });
+ *
+ *     browser.takeElementScreenshot({
+ *       selector: '*[name="search"]',
+ *       true,
+ *       timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
+ *     }, function(image, err) {
+ *       require('fs').writeFile('out.png', image.value, 'base64', function (err) {
+ *         console.log(err);
+ *       });
+ *     });
+ *   },
+ *
+ *   demoTestAsync: async function(browser) {
+ *     const result = await browser.takeElementScreenshot('*[name="search"]', true);
+ *     require('fs').writeFile('out.png', result.value, 'base64');
+ *   }
+ * }
+ *
+ * @method takeElementScreenshot
+ * @syntax .takeElementScreenshot(selector, callback)
+ * @syntax .takeElementScreenshot(using, selector, callback)
+ * @param {string} [using] The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies)
+ * @param {string} selector The CSS/Xpath selector used to locate the element.
+ * @param {function} callback Callback function which is called with the result value.
+ * @returns {string} Take a screenshot of the visible region encompassed by this element's bounding rectangle.
+ * @link /#dfn-take-element-screenshot
+ * @api protocol.screens
+ */
+class TakeElementScreenshot extends BaseElementCommand {
+  get extraArgsCount() {
+    return 1;
+  }
+
+  get elementProtocolAction() {
+    return 'takeElementScreenshot';
+  }
+}
+
+module.exports = TakeElementScreenshot;
diff --git a/lib/transport/selenium-webdriver/method-mappings.js b/lib/transport/selenium-webdriver/method-mappings.js
index c5de094f..f01f19fd 100644
--- a/lib/transport/selenium-webdriver/method-mappings.js
+++ b/lib/transport/selenium-webdriver/method-mappings.js
@@ -72,7 +72,7 @@ module.exports = class MethodMappings {
         async setTimeoutsAsyncScript(value) {
           await this.driver.manage().setTimeouts({script: value});
 
-          return  {
+          return {
             value: null
           };
         },
@@ -80,7 +80,7 @@ module.exports = class MethodMappings {
         async setTimeoutsImplicitWait(value) {
           await this.driver.manage().setTimeouts({implicit: value});
 
-          return  {
+          return {
             value: null
           };
         },
@@ -97,7 +97,7 @@ module.exports = class MethodMappings {
         // Session log
         ///////////////////////////////////////////////////////////
         async getSessionLogTypes() {
-          const value =  await this.driver.manage().logs().getAvailableLogTypes();
+          const value = await this.driver.manage().logs().getAvailableLogTypes();
 
           return {
             value
@@ -381,6 +381,33 @@ module.exports = class MethodMappings {
           return elementValue;
         },
 
+        async getElementAccessibleName(webElement) {
+          const element = this.getWebElement(webElement);
+          const elementAccessibleName = await element.getAccessibleName();
+
+          return {
+            value: elementAccessibleName
+          };
+        },
+
+        async getElementAriaRole(webElement) {
+          const element = this.getWebElement(webElement);
+          const elementAccessibleName = await element.getAriaRole();
+
+          return {
+            value: elementAccessibleName
+          };
+        },
+
+        async takeElementScreenshot(webElement, scroll) {
+          const element = this.getWebElement(webElement);
+          const screenshotData = await element.takeScreenshot(scroll);
+
+          return {
+            value: screenshotData
+          };
+        },
+
         async getElementCSSValue(id, cssPropertyName) {
           const element = this.getWebElement(id);
           const elementCssValue = await element.getCssValue(cssPropertyName);


Test patch:
diff --git a/test/src/api/commands/element/testGetAccessibleName.js b/test/src/api/commands/element/testGetAccessibleName.js
new file mode 100644
index 00000000..24a54bc8
--- /dev/null
+++ b/test/src/api/commands/element/testGetAccessibleName.js
@@ -0,0 +1,41 @@
+const assert = require('assert');
+const MockServer = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands.js');
+
+describe('getAccessibleName', function () {
+  before(function (done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function (done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('client.getAccessibleName()', function (done) {
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/element/0/computedlabel',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'search input'
+      })
+    });
+
+    this.client.api
+      .getAccessibleName('#weblogin', function callback(result) {
+        assert.strictEqual(result.value, 'search input');
+      })
+      .getAccessibleName('css selector', '#weblogin', function callback(result) {
+        assert.strictEqual(result.value, 'search input');
+      })
+      .getAccessibleName('css selector', {selector: '#weblogin', timeout: 100}, function callback(result) {
+        assert.strictEqual(result.value, 'search input');
+      })
+      .getAccessibleName({selector: '#weblogin', timeout: 100}, function callback(result) {
+        assert.strictEqual(result.value, 'search input');
+      });
+
+    this.client.start(done);
+  });
+});
diff --git a/test/src/api/commands/element/testGetAriaRole.js b/test/src/api/commands/element/testGetAriaRole.js
new file mode 100644
index 00000000..7935ec88
--- /dev/null
+++ b/test/src/api/commands/element/testGetAriaRole.js
@@ -0,0 +1,54 @@
+const assert = require('assert');
+const MockServer = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands.js');
+
+describe('getAriaRole', function () {
+  before(function (done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function (done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('client.getAriaRole()', function (done) {
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/element/0/computedrole',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'combobox'
+      })
+    });
+
+    this.client.api
+      .getAriaRole('#weblogin', function callback(result) {
+        assert.strictEqual(result.value, 'combobox');
+      })
+      .getAriaRole('css selector', '#weblogin', function callback(result) {
+        assert.strictEqual(result.value, 'combobox');
+      })
+      .getAriaRole(
+        'css selector',
+        {
+          selector: '#weblogin',
+          timeout: 100
+        },
+        function callback(result) {
+          assert.strictEqual(result.value, 'combobox');
+        }
+      )
+      .getAriaRole(
+        {
+          selector: '#weblogin',
+          timeout: 100
+        },
+        function callback(result) {
+          assert.strictEqual(result.value, 'combobox');
+        }
+      );
+
+    this.client.start(done);
+  });
+});
diff --git a/test/src/api/commands/element/testTakeElementScreenshot.js b/test/src/api/commands/element/testTakeElementScreenshot.js
new file mode 100644
index 00000000..14f29c0b
--- /dev/null
+++ b/test/src/api/commands/element/testTakeElementScreenshot.js
@@ -0,0 +1,61 @@
+const assert = require('assert');
+const MockServer = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands.js');
+
+const randomImage =
+      '/9j/4AAQSkZJRgABAQAASABIAAD/4QBqRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAABJKGAAcAAAASAAAAUKABAAMAAAABAAEAAKACAAQAAAABAAABAKADAAQAAAABAAABAAAAAABBU0NJSQAAADEuMTUuMS0yMUj/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs';
+
+describe('takeElementScreenshot', function () {
+  before(function (done) {
+
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function (done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('client.takeElementScreenshot()', function (done) {
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/element/0/screenshot',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value:
+          randomImage
+      })
+    });
+
+    this.client.api
+      .takeElementScreenshot('#weblogin', true, function callback(result) {
+        assert.strictEqual(result.value, randomImage);
+      })
+      .takeElementScreenshot('css selector', '#weblogin', true, function callback(result) {
+        assert.strictEqual(result.value, randomImage);
+      })
+      .takeElementScreenshot(
+        'css selector',
+        {
+          selector: '#weblogin',
+          timeout: 100
+        },
+        true,
+        function callback(result) {
+          assert.strictEqual(result.value, randomImage);
+        }
+      )
+      .takeElementScreenshot(
+        {
+          selector: '#weblogin',
+          timeout: 100
+        },
+        true,
+        function callback(result) {
+          assert.strictEqual(result.value, randomImage);
+        }
+      );
+
+    this.client.start(done);
+  });
+});