Repository: nightwatchjs/nightwatch
Pull request #3709 (2023-04-25 08:42:17)

Fix sendKeys to allow multiple value arguments; add setValue.


Patch:
diff --git a/lib/api/assertions/hasAttribute.js b/lib/api/assertions/hasAttribute.js
index 802c9e34..54b3ac13 100644
--- a/lib/api/assertions/hasAttribute.js
+++ b/lib/api/assertions/hasAttribute.js
@@ -19,7 +19,7 @@
  * @api assertions
  */
 
-const {setElementSelectorProps, containsMultiple} = require('../../utils');
+const {setElementSelectorProps, isString} = require('../../utils');
 
 exports.assertion = function(definition, expectedAttribute, msg) {
   this.options = {
@@ -32,35 +32,31 @@ exports.assertion = function(definition, expectedAttribute, msg) {
  
 
   this.formatMessage = function() {
+    if (!isString(expectedAttribute)) {
+      throw new Error('Expected attribute must be a string');
+    }
+
     let message = msg || `Testing if element %s ${this.negate ? 'doesn\'t have attribute %s' : 'has attribute %s'}`;
  
     return {
       message,
-      args: [this.elementSelector, `'${Array.isArray(expectedAttribute) ? expectedAttribute.join(' ') : expectedAttribute}'`]
+      args: [this.elementSelector, `'${expectedAttribute}'`]
     };
   };
 
   this.evaluate = function() {
-    if (!this.attributeList) {
-      return false;
-    }
- 
-    return containsMultiple(this.attributeList, expectedAttribute, ' ');
-  };
+    const {result} = this;
 
-  this.value = function(result) {
     if (!result || !result.value) {
-      return '';
+      return false;
     }
-
-    this.attributeList = result.value.map(attribute =>  attribute.name);
-
-    return this.attributeList;
+ 
+    return true;
   };
 
   this.command = function(callback) {
     this.api.getAttribute(setElementSelectorProps(definition, {
       suppressNotFoundErrors: true
-    }), 'attributes', callback);
+    }), expectedAttribute, callback);
   };
 };
diff --git a/lib/api/web-element/commands/findByLabelText.js b/lib/api/web-element/commands/findByLabelText.js
index 60f82299..44a5d55c 100644
--- a/lib/api/web-element/commands/findByLabelText.js
+++ b/lib/api/web-element/commands/findByLabelText.js
@@ -45,12 +45,11 @@ module.exports.command = function (text, {exact = true, timeout, retryInterval,
       return null;
     }
 
-    const elements = await labelWebElement.findElement(By.css('input'));
-    if (!elements) {
+    try {
+      return await labelWebElement.findElement(By.css('input'));
+    } catch (err) {
       return null;
     }
-
-    return elements[0];
   };
 
   const findByDeepNesting = async (text, {exact}) => {
@@ -69,13 +68,7 @@ module.exports.command = function (text, {exact = true, timeout, retryInterval,
     }
 
     try {
-      const element = await labelElement.findElement(By.css('input'));
-
-      if (element.length === 0) {
-        return null;
-      }
-
-      return element[0];
+      return await labelElement.findElement(By.css('input'));
     } catch (err) {
       return null;
     }
diff --git a/lib/api/web-element/commands/sendKeys.js b/lib/api/web-element/commands/sendKeys.js
index 7c5fa292..eca11527 100644
--- a/lib/api/web-element/commands/sendKeys.js
+++ b/lib/api/web-element/commands/sendKeys.js
@@ -1,5 +1,12 @@
-module.exports.command = function(...keys) {
+module.exports.command = function(...args) {
+  const keys = args.reduce((prev, key) => {
+    const keyList = Array.isArray(key) ? key : [key];
+    prev.push(...keyList);
+
+    return prev;
+  }, []);
+
   return this.runQueuedCommand('sendKeysToElement', {
-    args: keys
+    args: [keys]
   });
-};
\ No newline at end of file
+};
diff --git a/lib/api/web-element/commands/setValue.js b/lib/api/web-element/commands/setValue.js
new file mode 100644
index 00000000..d27c86ae
--- /dev/null
+++ b/lib/api/web-element/commands/setValue.js
@@ -0,0 +1,12 @@
+module.exports.command = function(...args) {
+  const keys = args.reduce((prev, key) => {
+    const keyList = Array.isArray(key) ? key : [key];
+    prev.push(...keyList);
+
+    return prev;
+  }, []);
+
+  return this.runQueuedCommand('setElementValue', {
+    args: [keys]
+  });
+};
diff --git a/lib/api/web-element/commands/update.js b/lib/api/web-element/commands/update.js
index 4916657e..d27c86ae 100644
--- a/lib/api/web-element/commands/update.js
+++ b/lib/api/web-element/commands/update.js
@@ -1,5 +1,12 @@
-module.exports.command = function(...keys) {
+module.exports.command = function(...args) {
+  const keys = args.reduce((prev, key) => {
+    const keyList = Array.isArray(key) ? key : [key];
+    prev.push(...keyList);
+
+    return prev;
+  }, []);
+
   return this.runQueuedCommand('setElementValue', {
-    args: keys
+    args: [keys]
   });
-};
\ No newline at end of file
+};


Test patch:
diff --git a/test/src/api/assertions/testHasAttribute.js b/test/src/api/assertions/testHasAttribute.js
index 390aa7ed..8037314b 100644
--- a/test/src/api/assertions/testHasAttribute.js
+++ b/test/src/api/assertions/testHasAttribute.js
@@ -13,7 +13,7 @@ describe('assert.hasAttribute', function () {
     return assertionTest({
       args: ['.test_element', 'data-test', 'Test message'],
       commandResult: {
-        value: [{name: 'data-track'}, {name: 'data-test'}]
+        value: 'data-track'
       },
       assertMessage: true,
       assertion({instance, failure}) {
@@ -28,7 +28,7 @@ describe('assert.hasAttribute', function () {
     return assertionTest({
       args: ['.test_element', 'data-test'],
       commandResult: {
-        value: [{name: 'data-track'}]
+        value: 'data-track'
       },
       negate: true,
       assertion({instance, queueOpts, message}) {
@@ -36,8 +36,8 @@ describe('assert.hasAttribute', function () {
         assert.strictEqual(queueOpts.negate, true);
 
         assert.strictEqual(instance.hasFailure(), false);
-        assert.deepStrictEqual(instance.getValue(), ['data-track']);
-        assert.deepStrictEqual(instance.getActual(), ['data-track']);
+        assert.deepStrictEqual(instance.getValue(), 'data-track');
+        assert.deepStrictEqual(instance.getActual(), 'data-track');
         assert.strictEqual(instance.message, 'Testing if element <.test_element> doesn\'t have attribute \'data-test\'');
         assert.ok(message.startsWith('Testing if element <.test_element> doesn\'t have attribute \'data-test\''), message);
       }
@@ -48,15 +48,15 @@ describe('assert.hasAttribute', function () {
     return assertionTest({
       args: ['.test_element', 'data-test'],
       commandResult: {
-        value: [{name: 'data-test'}]
+        value: 'data-test'
       },
       negate: true,
       assertError: true,
       assertion({instance, queueOpts, err}) {
         assert.strictEqual(queueOpts.negate, true);
         assert.strictEqual(instance.hasFailure(), false);
-        assert.deepStrictEqual(instance.getValue(), ['data-test']);
-        assert.deepStrictEqual(instance.getActual(), ['data-test']);
+        assert.deepStrictEqual(instance.getValue(), 'data-test');
+        assert.deepStrictEqual(instance.getActual(), 'data-test');
         assert.strictEqual(err.message, `Testing if element <.test_element> doesn't have attribute 'data-test' in 5ms - expected "has not data-test" but got: "data-test" (${instance.elapsedTime}ms)`);
       }
     });
@@ -66,13 +66,13 @@ describe('assert.hasAttribute', function () {
     return assertionTest({
       args: [{selector: '.test_element'}, 'data-test'],
       commandResult: {
-        value: [{name: 'data-test'}]
+        value: 'data-test'
       },
       assertResult: true,
       assertion({instance, failure, message, err}) {
         assert.strictEqual(err, undefined);
         assert.deepStrictEqual(instance.options, {elementSelector: true});
-        assert.deepStrictEqual(instance.getActual(), ['data-test']);
+        assert.deepStrictEqual(instance.getActual(), 'data-test');
         assert.strictEqual(instance.hasFailure(), false);
         assert.ok(message.startsWith('Testing if element <.test_element> has attribute \'data-test\''), message);
         assert.strictEqual(failure, false);
@@ -84,33 +84,13 @@ describe('assert.hasAttribute', function () {
     return assertionTest({
       args: ['.test_element', 'data-test', 'Test message'],
       commandResult: {
-        value: [{name: 'not_expected'}]
+        value: null
       },
       assertError: true,
       assertResult: true,
       assertion({instance, failure}) {
-        assert.deepStrictEqual(instance.getActual(), ['not_expected']);
-        assert.strictEqual(failure, 'Expected "has data-test" but got: "not_expected"');
-      }
-    });
-  });
-
-  it('hasAttribute assertion - element not found', function() {
-    return assertionTest({
-      args: ['.test_element', 'data-test', 'Test attribute %s from element "%s" == %s'],
-      commandResult: {
-        status: -1,
-        value: []
-      },
-      assertError: true,
-      assertFailure: true,
-      assertResult: true,
-      assertion({instance, failure, err}) {
-        assert.strictEqual(instance.getActual(), 'element could not be located');
-        assert.strictEqual(instance.expected(), 'has data-test');
-        assert.strictEqual(instance.getValue(), null);
-        assert.strictEqual(failure, 'Expected "has data-test" but got: "element could not be located"');
-        assert.strictEqual(err.message, `Test attribute <.test_element> from element "'data-test'" == %s in 5ms - expected "has data-test" but got: "element could not be located" (${instance.elapsedTime}ms)`);
+        assert.deepStrictEqual(instance.getActual(), null);
+        assert.strictEqual(failure, 'Expected "has data-test" but got: "null"');
       }
     });
   });
@@ -132,4 +112,17 @@ describe('assert.hasAttribute', function () {
       }
     });
   });
+
+  it('hasAttribute assertion failed for wrong parameters', function () {
+    return assertionTest({
+      args: ['.test_element', ['data-test', 'dummy'], 'Test message'],
+      commandResult: {
+        value: 'data-track'
+      },
+      assertMessage: true
+    }).catch((err) => {
+      assert.ok(err instanceof Error);
+      assert.strictEqual(err.message, 'Error while running "assert.hasAttribute" command: Expected attribute must be a string');
+    });
+  });
 });
diff --git a/test/src/api/commands/web-element/testFindByLabelText.js b/test/src/api/commands/web-element/testFindByLabelText.js
index 8751a133..e9ec85cd 100644
--- a/test/src/api/commands/web-element/testFindByLabelText.js
+++ b/test/src/api/commands/web-element/testFindByLabelText.js
@@ -184,7 +184,7 @@ describe('.findByLabelText() commands', function () {
         },
         method: 'POST',
         response: JSON.stringify({
-          value: [{'element-6066-11e4-a52e-4f735466cecf': '2'}]
+          value: {'element-6066-11e4-a52e-4f735466cecf': '2'}
         })
       }, true);
 
@@ -234,7 +234,7 @@ describe('.findByLabelText() commands', function () {
         },
         method: 'POST',
         response: JSON.stringify({
-          value: [{'element-6066-11e4-a52e-4f735466cecf': '2'}]
+          value: {'element-6066-11e4-a52e-4f735466cecf': '2'}
         })
       }, true);
 
diff --git a/test/src/api/commands/web-element/testSendKeys.js b/test/src/api/commands/web-element/testSendKeys.js
index 6bff24be..b69b9937 100644
--- a/test/src/api/commands/web-element/testSendKeys.js
+++ b/test/src/api/commands/web-element/testSendKeys.js
@@ -78,7 +78,7 @@ describe('element().sendKeys() command', function () {
         })
       }, true);
 
-    const resultPromise = this.client.api.element('#signupSection').find('input[name=q]').sendKeys('nightwatch');
+    const resultPromise = this.client.api.element('#signupSection').find('input[name=q]').sendKeys('night', 'watch');
     // neither an instance of Element or Promise, but an instance of ScopedWebElement.
     assert.strictEqual(resultPromise instanceof Element, false);
     assert.strictEqual(typeof resultPromise.find, 'undefined');
@@ -116,7 +116,7 @@ describe('element().sendKeys() command', function () {
         })
       }, true);
 
-    const resultPromise = this.client.api.element.find('#signupSection').find('input[name=q]').sendKeys('nightwatch');
+    const resultPromise = this.client.api.element.find('#signupSection').find('input[name=q]').sendKeys(['night', 'watch']);
     // neither an instance of Element or Promise, but an instance of ScopedWebElement.
     assert.strictEqual(resultPromise instanceof Element, false);
     assert.strictEqual(typeof resultPromise.find, 'undefined');
diff --git a/test/src/api/commands/web-element/testSetValue.js b/test/src/api/commands/web-element/testSetValue.js
new file mode 100644
index 00000000..fa05494b
--- /dev/null
+++ b/test/src/api/commands/web-element/testSetValue.js
@@ -0,0 +1,153 @@
+const assert = require('assert');
+const {WebElement} = require('selenium-webdriver');
+const MockServer  = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands-w3c.js');
+const Element = require('../../../../../lib/element/index.js');
+
+describe('element().setValue() command', function () {
+  before(function (done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function (done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('test .element().setValue()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/elements',
+        postdata: {
+          using: 'css selector',
+          value: 'input[name=q]'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '9'}]
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/9/clear',
+        method: 'POST',
+        postdata: {},
+        response: JSON.stringify({
+          value: null
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/9/value',
+        method: 'POST',
+        postdata: {
+          text: 'nightwatch',
+          value: [
+            'n', 'i', 'g', 'h', 't', 'w', 'a', 't', 'c', 'h'
+          ]
+        },
+        response: JSON.stringify({
+          value: null
+        })
+      }, true);
+
+    const resultPromise = this.client.api.element('input[name=q]').setValue('nightwatch');
+    // neither an instance of Element or Promise, but an instance of ScopedWebElement.
+    assert.strictEqual(resultPromise instanceof Element, false);
+    assert.strictEqual(typeof resultPromise.find, 'undefined');
+    assert.strictEqual(resultPromise instanceof Promise, false);
+
+    const result = await resultPromise;
+    assert.strictEqual(result instanceof WebElement, true);
+    assert.strictEqual(await result.getId(), '9');
+  });
+
+  it('test .element().find().setValue()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/elements',
+        postdata: {
+          using: 'css selector',
+          value: 'input[name=q]'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '9'}]
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/9/clear',
+        method: 'POST',
+        postdata: {},
+        response: JSON.stringify({
+          value: null
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/9/value',
+        method: 'POST',
+        postdata: {
+          text: 'nightwatch',
+          value: [
+            'n', 'i', 'g', 'h', 't', 'w', 'a', 't', 'c', 'h'
+          ]
+        },
+        response: JSON.stringify({
+          value: null
+        })
+      }, true);
+
+    const resultPromise = this.client.api.element('#signupSection').find('input[name=q]').setValue('night', 'watch');
+    // neither an instance of Element or Promise, but an instance of ScopedWebElement.
+    assert.strictEqual(resultPromise instanceof Element, false);
+    assert.strictEqual(typeof resultPromise.find, 'undefined');
+    assert.strictEqual(resultPromise instanceof Promise, false);
+
+    const result = await resultPromise;
+    assert.strictEqual(result instanceof WebElement, true);
+    assert.strictEqual(await result.getId(), '9');
+  });
+
+  it('test .element.find().setValue()', async function() {
+    MockServer
+      .addMock({
+        url: '/session/13521-10219-202/element/0/elements',
+        postdata: {
+          using: 'css selector',
+          value: 'input[name=q]'
+        },
+        method: 'POST',
+        response: JSON.stringify({
+          value: [{'element-6066-11e4-a52e-4f735466cecf': '9'}]
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/9/clear',
+        method: 'POST',
+        postdata: {},
+        response: JSON.stringify({
+          value: null
+        })
+      }, true)
+      .addMock({
+        url: '/session/13521-10219-202/element/9/value',
+        method: 'POST',
+        postdata: {
+          text: 'nightwatch',
+          value: [
+            'n', 'i', 'g', 'h', 't', 'w', 'a', 't', 'c', 'h'
+          ]
+        },
+        response: JSON.stringify({
+          value: null
+        })
+      }, true);
+
+    const resultPromise = this.client.api.element.find('#signupSection').find('input[name=q]').setValue(['night', 'watch']);
+    // neither an instance of Element or Promise, but an instance of ScopedWebElement.
+    assert.strictEqual(resultPromise instanceof Element, false);
+    assert.strictEqual(typeof resultPromise.find, 'undefined');
+    assert.strictEqual(resultPromise instanceof Promise, false);
+
+    const result = await resultPromise;
+    assert.strictEqual(result instanceof WebElement, true);
+    assert.strictEqual(await result.getId(), '9');
+  });
+});
diff --git a/test/src/api/commands/web-element/testUpdate.js b/test/src/api/commands/web-element/testUpdate.js
index e31a23d7..441084d7 100644
--- a/test/src/api/commands/web-element/testUpdate.js
+++ b/test/src/api/commands/web-element/testUpdate.js
@@ -94,7 +94,7 @@ describe('element().update() command', function () {
         })
       }, true);
 
-    const resultPromise = this.client.api.element('#signupSection').find('input[name=q]').update('nightwatch');
+    const resultPromise = this.client.api.element('#signupSection').find('input[name=q]').update('night', 'watch');
     // neither an instance of Element or Promise, but an instance of ScopedWebElement.
     assert.strictEqual(resultPromise instanceof Element, false);
     assert.strictEqual(typeof resultPromise.find, 'undefined');
@@ -140,7 +140,7 @@ describe('element().update() command', function () {
         })
       }, true);
 
-    const resultPromise = this.client.api.element.find('#signupSection').find('input[name=q]').update('nightwatch');
+    const resultPromise = this.client.api.element.find('#signupSection').find('input[name=q]').update(['night', 'watch']);
     // neither an instance of Element or Promise, but an instance of ScopedWebElement.
     assert.strictEqual(resultPromise instanceof Element, false);
     assert.strictEqual(typeof resultPromise.find, 'undefined');