Repository: nightwatchjs/nightwatch
Pull request #4232 (2024-07-06 15:08:52)

Add .check and .uncheck commands for immutable checkbox operations
This will add the .check and .uncheck commands for changing the checked/selected state of toggleable input elements like checkboxes to checked or unchecked taking into account their current checked/selected state (e.g. .check() won't click an already checked checkbox input, .uncheck() won't click an unchecked input).

This is a useful feature so that users don't have to first check the current checked/selected state of the element before issuing the click command (or not) to alter the checked state. Additionally, its less error-prone and more easily conveys the intention versus issuing the check command.

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] Before marking your PR for review, please test and verify your changes by making appropriate modifications to any of the Nightwatch example tests (present in `examples/tests` directory of the project) and running them. `ecosia.js` and `duckDuckGo.js` are good examples to work with.
- [X] Create a new branch from master (e.g. `features/my-new-feature` or `issue/123-my-bugfix`);
- [X] If you're fixing a bug also create an issue if one doesn't exist yet;
- [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] 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;
- [X] If your change is based on existing functionality please consider refactoring first. Pull requests that duplicate code will most likely be ignored;
- [X] Do not include changes that are not related to the issue at hand;
- [X] Follow the same coding style with regards to spaces, semicolons, variable naming etc.;
- [X] Always add unit tests - PRs without tests are most of the times ignored.



Patch:
diff --git a/lib/api/element-commands/check.js b/lib/api/element-commands/check.js
new file mode 100644
index 00000000..e985f74f
--- /dev/null
+++ b/lib/api/element-commands/check.js
@@ -0,0 +1,64 @@
+const BaseElementCommand = require('./_baseElementCommand.js');
+
+/**
+ * Will check, by clicking, on a checkbox or radio input if it is not already checked.
+ *
+ * @example
+ * module.exports = {
+ *   demoTest(browser) {
+ *     browser.check('input[type=checkbox]:not(:checked)');
+ *
+ *     browser.check('input[type=checkbox]:not(:checked)', function(result) {
+ *       console.log('Check result', result);
+ *     });
+ *
+ *     // with explicit locate strategy
+ *     browser.check('css selector', 'input[type=checkbox]:not(:checked)');
+ *
+ *     // with selector object - see https://nightwatchjs.org/guide#element-properties
+ *     browser.check({
+ *       selector: 'input[type=checkbox]:not(:checked)',
+ *       index: 1,
+ *       suppressNotFoundErrors: true
+ *     });
+ *
+ *     browser.check({
+ *       selector: 'input[type=checkbox]:not(:checked)',
+ *       timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
+ *     });
+ *   },
+ *
+ *   demoTestAsync: async function(browser) {
+ *     const result = await browser.check('input[type=checkbox]:not(:checked)');
+ *     console.log('Check result', result);
+ *   }
+ * }
+ *
+ * @method check
+ * @syntax .check(selector, [callback])
+ * @syntax .check(using, selector, [callback])
+ * @syntax browser.element(selector).check()
+ * @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] Optional callback function to be called when the command finishes.
+ * @api protocol.elementinteraction
+ */
+class CheckElement extends BaseElementCommand {
+  get extraArgsCount() {
+    return 0;
+  }
+
+  get elementProtocolAction() {
+    return 'checkElement';
+  }
+
+  static get isTraceable() {
+    return true;
+  }
+
+  async protocolAction() {
+    return this.executeProtocolAction(this.elementProtocolAction);
+  }
+}
+
+module.exports = CheckElement;
diff --git a/lib/api/element-commands/uncheck.js b/lib/api/element-commands/uncheck.js
new file mode 100644
index 00000000..888f455a
--- /dev/null
+++ b/lib/api/element-commands/uncheck.js
@@ -0,0 +1,64 @@
+const BaseElementCommand = require('./_baseElementCommand.js');
+
+/**
+ * Will uncheck, by clicking, on a checkbox or radio input if it is not already unchecked.
+ *
+ * @example
+ * module.exports = {
+ *   demoTest(browser) {
+ *     browser.uncheck('input[type=checkbox]:checked)');
+ *
+ *     browser.uncheck('input[type=checkbox]:checked)', function(result) {
+ *       console.log('Check result', result);
+ *     });
+ *
+ *     // with explicit locate strategy
+ *     browser.uncheck('css selector', 'input[type=checkbox]:checked)');
+ *
+ *     // with selector object - see https://nightwatchjs.org/guide#element-properties
+ *     browser.uncheck({
+ *       selector: 'input[type=checkbox]:checked)',
+ *       index: 1,
+ *       suppressNotFoundErrors: true
+ *     });
+ *
+ *     browser.uncheck({
+ *       selector: 'input[type=checkbox]:checked)',
+ *       timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
+ *     });
+ *   },
+ *
+ *   demoTestAsync: async function(browser) {
+ *     const result = await browser.uncheck('input[type=checkbox]:checked)');
+ *     console.log('Check result', result);
+ *   }
+ * }
+ *
+ * @method check
+ * @syntax .uncheck(selector, [callback])
+ * @syntax .uncheck(using, selector, [callback])
+ * @syntax browser.element(selector).uncheck()
+ * @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] Optional callback function to be called when the command finishes.
+ * @api protocol.elementinteraction
+ */
+class UncheckElement extends BaseElementCommand {
+  get extraArgsCount() {
+    return 0;
+  }
+
+  get elementProtocolAction() {
+    return 'uncheckElement';
+  }
+
+  static get isTraceable() {
+    return true;
+  }
+
+  async protocolAction() {
+    return this.executeProtocolAction(this.elementProtocolAction);
+  }
+}
+
+module.exports = UncheckElement;
diff --git a/lib/api/web-element/commands/check.js b/lib/api/web-element/commands/check.js
new file mode 100644
index 00000000..cb91ffc4
--- /dev/null
+++ b/lib/api/web-element/commands/check.js
@@ -0,0 +1,28 @@
+/**
+ * Will check, by clicking, on a checkbox or radio input if it is not already checked.
+ * The element is scrolled into view if it is not already pointer-interactable. See the WebDriver specification for <a href="https://www.w3.org/TR/webdriver/#element-interactability" target="_blank">element interactability</a>.
+ *
+ * For more info on working with DOM elements in Nightwatch, refer to the <a href="https://nightwatchjs.org/guide/writing-tests/finding-interacting-with-dom-elements.html">Finding & interacting with DOM Elements</a> guide page.
+ *
+ * @example
+ * export default {
+ *   demoTest(browser: NightwatchAPI): void {
+ *     browser.element('input[type=checkbox]:not(:checked)').check();
+ *     browser.element('input[type=radio]:not(:checked)').check();
+ *   },
+ *   async demoTestAsync(browser: NightwatchAPI): Promise<void> {
+ *     await browser.element('input[type=checkbox]:not(:checked)').check();
+ *     await browser.element('input[type=radio]:not(:checked)').check();
+ *   },
+ * }
+ *
+ * @since 3.6.4
+ * @method check
+ * @memberof ScopedWebElement
+ * @instance
+ * @syntax browser.element(selector).check()
+ * @returns {ScopedWebElement}
+ */
+module.exports.command = function () {
+  return this.runQueuedCommand('checkElement');
+};
diff --git a/lib/api/web-element/commands/uncheck.js b/lib/api/web-element/commands/uncheck.js
new file mode 100644
index 00000000..5bab1354
--- /dev/null
+++ b/lib/api/web-element/commands/uncheck.js
@@ -0,0 +1,26 @@
+/**
+ * Will uncheck, by clicking, on a checkbox or radio input if it is not already unchecked.
+ * The element is scrolled into view if it is not already pointer-interactable. See the WebDriver specification for <a href="https://www.w3.org/TR/webdriver/#element-interactability" target="_blank">element interactability</a>.
+ *
+ * For more info on working with DOM elements in Nightwatch, refer to the <a href="https://nightwatchjs.org/guide/writing-tests/finding-interacting-with-dom-elements.html">Finding & interacting with DOM Elements</a> guide page.
+ *
+ * @example
+ * export default {
+ *   demoTest(browser: NightwatchAPI): void {
+ *     browser.element('input[type=checkbox]:checked)').check();
+ *   },
+ *   async demoTestAsync(browser: NightwatchAPI): Promise<void> {
+ *     await browser.element('input[type=checkbox]:checked)').check();
+ *   },
+ * }
+ *
+ * @since 3.6.4
+ * @method uncheck
+ * @memberof ScopedWebElement
+ * @instance
+ * @syntax browser.element(selector).check()
+ * @returns {ScopedWebElement}
+ */
+module.exports.command = function () {
+  return this.runQueuedCommand('uncheckElement');
+};
diff --git a/lib/transport/selenium-webdriver/method-mappings.js b/lib/transport/selenium-webdriver/method-mappings.js
index 0f10c8f7..5843598a 100644
--- a/lib/transport/selenium-webdriver/method-mappings.js
+++ b/lib/transport/selenium-webdriver/method-mappings.js
@@ -606,6 +606,42 @@ module.exports = class MethodMappings {
           return this.methods.session.setElementValue.call(this, webElementOrId, modifiedValue);
         },
 
+        async checkElement(webElementOrId) {
+          const element = await this.getWebElement(webElementOrId);
+          const elementType = await element.getAttribute('type');
+          const checkableTypes = ['checkbox', 'radio'];
+
+          if (!checkableTypes.includes(elementType)) {
+            throw new Error('must be an input element with type attribute \'checkbox\' or \'radio\'');
+          }
+
+          const value = await element.isSelected();
+
+          if (!value) {
+            await element.click();
+          }
+
+          return null;
+        },
+
+        async uncheckElement(webElementOrId) {
+          const element = await this.getWebElement(webElementOrId);
+          const elementType = await element.getAttribute('type');
+          const checkableTypes = ['checkbox', 'radio'];
+
+          if (!checkableTypes.includes(elementType)) {
+            throw new Error('must be an input element with type attribute \'checkbox\' or \'radio\'');
+          }
+
+          const value = await element.isSelected();
+
+          if (value) {
+            await element.click();
+          }
+
+          return null;
+        },
+
         async setElementValue(webElementOrId, value) {
           if (Array.isArray(value)) {
             value = value.join('');
diff --git a/package-lock.json b/package-lock.json
index edb31acc..7ebbfa93 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "nightwatch",
-  "version": "3.6.3",
+  "version": "3.6.4",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "nightwatch",
-      "version": "3.6.3",
+      "version": "3.6.4",
       "license": "MIT",
       "dependencies": {
         "@nightwatch/chai": "5.0.3",
@@ -28,7 +28,7 @@
         "ejs": "^3.1.10",
         "envinfo": "7.11.0",
         "glob": "7.2.3",
-        "jsdom": "^23.1.0",
+        "jsdom": "^24.1.0",
         "lodash": "^4.17.21",
         "minimatch": "3.1.2",
         "minimist": "1.2.6",
@@ -37,7 +37,7 @@
         "open": "8.4.2",
         "ora": "5.4.1",
         "piscina": "^4.3.1",
-        "selenium-webdriver": "4.21.0",
+        "selenium-webdriver": "4.22.0",
         "semver": "7.5.4",
         "stacktrace-parser": "0.1.10",
         "strip-ansi": "6.0.1",
@@ -1724,9 +1724,9 @@
       }
     },
     "node_modules/agent-base": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
-      "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+      "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
       "dependencies": {
         "debug": "^4.3.4"
       },
@@ -2183,11 +2183,11 @@
       }
     },
     "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
@@ -3599,9 +3599,9 @@
       }
     },
     "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dependencies": {
         "to-regex-range": "^5.0.1"
       },
@@ -4124,9 +4124,9 @@
       "dev": true
     },
     "node_modules/http-proxy-agent": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
-      "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
       "dependencies": {
         "agent-base": "^7.1.0",
         "debug": "^4.3.4"
@@ -4136,9 +4136,9 @@
       }
     },
     "node_modules/https-proxy-agent": {
-      "version": "7.0.2",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
-      "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+      "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
       "dependencies": {
         "agent-base": "^7.0.2",
         "debug": "4"
@@ -4892,30 +4892,30 @@
       }
     },
     "node_modules/jsdom": {
-      "version": "23.1.0",
-      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.1.0.tgz",
-      "integrity": "sha512-wRscu8dBFxi7O65Cvi0jFRDv0Qa7XEHPix8Qg/vlXHLAMQsRWV1EDeQHBermzXf4Dt7JtFgBLbva3iTcBZDXEQ==",
+      "version": "24.1.0",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz",
+      "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==",
       "dependencies": {
         "cssstyle": "^4.0.1",
         "data-urls": "^5.0.0",
         "decimal.js": "^10.4.3",
         "form-data": "^4.0.0",
         "html-encoding-sniffer": "^4.0.0",
-        "http-proxy-agent": "^7.0.0",
-        "https-proxy-agent": "^7.0.2",
+        "http-proxy-agent": "^7.0.2",
+        "https-proxy-agent": "^7.0.4",
         "is-potential-custom-element-name": "^1.0.1",
-        "nwsapi": "^2.2.7",
+        "nwsapi": "^2.2.10",
         "parse5": "^7.1.2",
-        "rrweb-cssom": "^0.6.0",
+        "rrweb-cssom": "^0.7.0",
         "saxes": "^6.0.0",
         "symbol-tree": "^3.2.4",
-        "tough-cookie": "^4.1.3",
+        "tough-cookie": "^4.1.4",
         "w3c-xmlserializer": "^5.0.0",
         "webidl-conversions": "^7.0.0",
         "whatwg-encoding": "^3.1.1",
         "whatwg-mimetype": "^4.0.0",
         "whatwg-url": "^14.0.0",
-        "ws": "^8.16.0",
+        "ws": "^8.17.0",
         "xml-name-validator": "^5.0.0"
       },
       "engines": {
@@ -4930,27 +4930,10 @@
         }
       }
     },
-    "node_modules/jsdom/node_modules/tough-cookie": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
-      "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
-      "dependencies": {
-        "psl": "^1.1.33",
-        "punycode": "^2.1.1",
-        "universalify": "^0.2.0",
-        "url-parse": "^1.5.3"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/jsdom/node_modules/universalify": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
-      "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
-      "engines": {
-        "node": ">= 4.0.0"
-      }
+    "node_modules/jsdom/node_modules/rrweb-cssom": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+      "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="
     },
     "node_modules/jsesc": {
       "version": "2.5.2",
@@ -6363,9 +6346,9 @@
       }
     },
     "node_modules/nwsapi": {
-      "version": "2.2.7",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
-      "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
+      "version": "2.2.10",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz",
+      "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ=="
     },
     "node_modules/nyc": {
       "version": "15.1.0",
@@ -7608,9 +7591,9 @@
       "dev": true
     },
     "node_modules/selenium-webdriver": {
-      "version": "4.21.0",
-      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.21.0.tgz",
-      "integrity": "sha512-WaEJHZjOWNth1QG5FEpxpREER0qptZBMonFU6GtAqdCNLJVxbtC3E7oS/I/+Q1sf1W032Wg0Ebk+m46lANOXyQ==",
+      "version": "4.22.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.22.0.tgz",
+      "integrity": "sha512-GNbrkCHmy249ai885wgXqTfqL2lZnclUH/P8pwTDIqzyFxU3YhDiN7p/c9tMFA4NhgRdEBO2QCG+CWmG7xr/Mw==",
       "dependencies": {
         "jszip": "^3.10.1",
         "tmp": "^0.2.3",
@@ -8425,6 +8408,28 @@
       "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
       "dev": true
     },
+    "node_modules/tough-cookie": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+      "dependencies": {
+        "psl": "^1.1.33",
+        "punycode": "^2.1.1",
+        "universalify": "^0.2.0",
+        "url-parse": "^1.5.3"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/tough-cookie/node_modules/universalify": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+      "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+      "engines": {
+        "node": ">= 4.0.0"
+      }
+    },
     "node_modules/tr46": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
@@ -8942,9 +8947,9 @@
       }
     },
     "node_modules/ws": {
-      "version": "8.16.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
-      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "version": "8.18.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
       "engines": {
         "node": ">=10.0.0"
       },
@@ -10336,9 +10341,9 @@
       "dev": true
     },
     "agent-base": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
-      "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+      "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
       "requires": {
         "debug": "^4.3.4"
       }
@@ -10680,11 +10685,11 @@
       }
     },
     "braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "requires": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       }
     },
     "browser-stdout": {
@@ -11720,9 +11725,9 @@
       }
     },
     "fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "requires": {
         "to-regex-range": "^5.0.1"
       }
@@ -12078,18 +12083,18 @@
       "dev": true
     },
     "http-proxy-agent": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
-      "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
       "requires": {
         "agent-base": "^7.1.0",
         "debug": "^4.3.4"
       }
     },
     "https-proxy-agent": {
-      "version": "7.0.2",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
-      "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+      "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
       "requires": {
         "agent-base": "^7.0.2",
         "debug": "4"
@@ -12608,48 +12613,37 @@
       }
     },
     "jsdom": {
-      "version": "23.1.0",
-      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.1.0.tgz",
-      "integrity": "sha512-wRscu8dBFxi7O65Cvi0jFRDv0Qa7XEHPix8Qg/vlXHLAMQsRWV1EDeQHBermzXf4Dt7JtFgBLbva3iTcBZDXEQ==",
+      "version": "24.1.0",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz",
+      "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==",
       "requires": {
         "cssstyle": "^4.0.1",
         "data-urls": "^5.0.0",
         "decimal.js": "^10.4.3",
         "form-data": "^4.0.0",
         "html-encoding-sniffer": "^4.0.0",
-        "http-proxy-agent": "^7.0.0",
-        "https-proxy-agent": "^7.0.2",
+        "http-proxy-agent": "^7.0.2",
+        "https-proxy-agent": "^7.0.4",
         "is-potential-custom-element-name": "^1.0.1",
-        "nwsapi": "^2.2.7",
+        "nwsapi": "^2.2.10",
         "parse5": "^7.1.2",
-        "rrweb-cssom": "^0.6.0",
+        "rrweb-cssom": "^0.7.0",
         "saxes": "^6.0.0",
         "symbol-tree": "^3.2.4",
-        "tough-cookie": "^4.1.3",
+        "tough-cookie": "^4.1.4",
         "w3c-xmlserializer": "^5.0.0",
         "webidl-conversions": "^7.0.0",
         "whatwg-encoding": "^3.1.1",
         "whatwg-mimetype": "^4.0.0",
         "whatwg-url": "^14.0.0",
-        "ws": "^8.16.0",
+        "ws": "^8.17.0",
         "xml-name-validator": "^5.0.0"
       },
       "dependencies": {
-        "tough-cookie": {
-          "version": "4.1.3",
-          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
-          "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
-          "requires": {
-            "psl": "^1.1.33",
-            "punycode": "^2.1.1",
-            "universalify": "^0.2.0",
-            "url-parse": "^1.5.3"
-          }
-        },
-        "universalify": {
-          "version": "0.2.0",
-          "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
-          "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
+        "rrweb-cssom": {
+          "version": "0.7.1",
+          "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+          "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="
         }
       }
     },
@@ -13778,9 +13772,9 @@
       }
     },
     "nwsapi": {
-      "version": "2.2.7",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
-      "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
+      "version": "2.2.10",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz",
+      "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ=="
     },
     "nyc": {
       "version": "15.1.0",
@@ -14721,9 +14715,9 @@
       "dev": true
     },
     "selenium-webdriver": {
-      "version": "4.21.0",
-      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.21.0.tgz",
-      "integrity": "sha512-WaEJHZjOWNth1QG5FEpxpREER0qptZBMonFU6GtAqdCNLJVxbtC3E7oS/I/+Q1sf1W032Wg0Ebk+m46lANOXyQ==",
+      "version": "4.22.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.22.0.tgz",
+      "integrity": "sha512-GNbrkCHmy249ai885wgXqTfqL2lZnclUH/P8pwTDIqzyFxU3YhDiN7p/c9tMFA4NhgRdEBO2QCG+CWmG7xr/Mw==",
       "requires": {
         "jszip": "^3.10.1",
         "tmp": "^0.2.3",
@@ -15337,6 +15331,24 @@
       "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
       "dev": true
     },
+    "tough-cookie": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+      "requires": {
+        "psl": "^1.1.33",
+        "punycode": "^2.1.1",
+        "universalify": "^0.2.0",
+        "url-parse": "^1.5.3"
+      },
+      "dependencies": {
+        "universalify": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+          "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
+        }
+      }
+    },
     "tr46": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
@@ -15712,9 +15724,9 @@
       }
     },
     "ws": {
-      "version": "8.16.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
-      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "version": "8.18.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
       "requires": {}
     },
     "xml": {
diff --git a/package.json b/package.json
index 551f1eca..35df8101 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.6.3",
+  "version": "3.6.4",
   "author": "Andrei Rusu",
   "homepage": "https://nightwatchjs.org",
   "main": "./lib/index.js",
@@ -34,7 +34,7 @@
     "ejs": "^3.1.10",
     "envinfo": "7.11.0",
     "glob": "7.2.3",
-    "jsdom": "^23.1.0",
+    "jsdom": "^24.1.0",
     "lodash": "^4.17.21",
     "minimatch": "3.1.2",
     "minimist": "1.2.6",
@@ -43,7 +43,7 @@
     "open": "8.4.2",
     "ora": "5.4.1",
     "piscina": "^4.3.1",
-    "selenium-webdriver": "4.21.0",
+    "selenium-webdriver": "4.22.0",
     "semver": "7.5.4",
     "stacktrace-parser": "0.1.10",
     "strip-ansi": "6.0.1",
diff --git a/types/index.d.ts b/types/index.d.ts
index 56616264..64b5c4fe 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -2587,6 +2587,57 @@ export interface ClientCommands extends ChromiumClientCommands {
 }
 
 export interface ElementCommands {
+  /**
+   * Will check, click, on an unchecked checkbox or radio input if not already checked.
+   *
+   * @example
+   * module.exports = {
+   *   demoTest(browser) {
+   *     browser.check('input[type=checkbox]:not(:checked)');
+   *
+   *     browser.check('input[type=checkbox]:not(:checked)', function(result) {
+   *       console.log('Check result', result);
+   *     });
+   *
+   *     // with explicit locate strategy
+   *     browser.check('css selector', 'input[type=checkbox]:not(:checked)');
+   *
+   *     // with selector object - see https://nightwatchjs.org/guide#element-properties
+   *     browser.check({
+   *       selector: 'input[type=checkbox]:not(:checked)',
+   *       index: 1,
+   *       suppressNotFoundErrors: true
+   *     });
+   *
+   *     browser.check({
+   *       selector: 'input[type=checkbox]:not(:checked)',
+   *       timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
+   *     });
+   *   },
+   *
+   *   demoTestAsync: async function(browser) {
+   *     const result = await browser.check('input[type=checkbox]:not(:checked)');
+   *     console.log('Check result', result);
+   *   }
+   * }
+   *
+   * @see https://nightwatchjs.org/api/check.html
+   */
+  check(
+    selector: Definition,
+    callback?: (
+      this: NightwatchAPI,
+      result: NightwatchCallbackResult<null>
+    ) => void
+  ): Awaitable<this, null>;
+  check(
+    using: LocateStrategy,
+    selector: Definition,
+    callback?: (
+      this: NightwatchAPI,
+      result: NightwatchCallbackResult<null>
+    ) => void
+  ): Awaitable<this, null>;
   /**
    * Clear a textarea or a text input element's value.
    * Starting with v1.1 `clearValue()` will wait for the element to be present (until the specified timeout).
@@ -4521,6 +4572,60 @@ export interface ElementCommands {
       result: NightwatchCallbackResult<string>
     ) => void
   ): Awaitable<this, string>;
+
+  /**
+   * Will uncheck, click, on a checked checkbox or radio input if not already unchecked.
+   *
+   * @example
+   * module.exports = {
+   *   demoTest(browser) {
+   *     browser.uncheck('input[type=checkbox]:checked)');
+   *
+   *     browser.uncheck('input[type=checkbox]:checked)', function(result) {
+   *       console.log('Check result', result);
+   *     });
+   *
+   *     // with explicit locate strategy
+   *     browser.uncheck('css selector', 'input[type=checkbox]:checked)');
+   *
+   *     // with selector object - see https://nightwatchjs.org/guide#element-properties
+   *     browser.uncheck({
+   *       selector: 'input[type=checkbox]:checked)',
+   *       index: 1,
+   *       suppressNotFoundErrors: true
+   *     });
+   *
+   *     browser.uncheck({
+   *       selector: 'input[type=checkbox]:checked)',
+   *       timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
+   *     });
+   *   },
+   *
+   *   demoTestAsync: async function(browser) {
+   *     const result = await browser.uncheck('input[type=checkbox]:checked)');
+   *     console.log('Check result', result);
+   *   }
+   * }
+   * 
+   * Will uncheck, click, on a checked checkbox or radio input if not already unchecked.
+   *
+   * @see https://nightwatchjs.org/api/uncheck.html
+   */
+  uncheck(
+    selector: Definition,
+    callback?: (
+      this: NightwatchAPI,
+      result: NightwatchCallbackResult<null>
+    ) => void
+  ): Awaitable<this, null>;
+  uncheck(
+    using: LocateStrategy,
+    selector: Definition,
+    callback?: (
+      this: NightwatchAPI,
+      result: NightwatchCallbackResult<null>
+    ) => void
+  ): Awaitable<this, null>;
 }
 
 export interface AppiumCommands<ReturnType = unknown> {
diff --git a/types/web-element.d.ts b/types/web-element.d.ts
index a5e2c778..bde0cc4c 100644
--- a/types/web-element.d.ts
+++ b/types/web-element.d.ts
@@ -157,6 +157,9 @@ export interface ScopedElement extends Element, PromiseLike<WebElement> {
 
   clear(): Promise<WebElement>;
 
+  check(): Promise<WebElement>;
+  uncheck(): Promise<WebElement>;
+
   sendKeys<E extends readonly unknown[]>(...keys: E): Promise<WebElement>;
 
   submit(): Promise<WebElement>;


Test patch:
diff --git a/examples/tests/selectElement.js b/examples/tests/selectElement.js
index c026cd54..9b42ec12 100644
--- a/examples/tests/selectElement.js
+++ b/examples/tests/selectElement.js
@@ -1,13 +1,18 @@
 const {Select} = require('selenium-webdriver');
 
 module.exports = {
+  before(browser) {
+    browser.url('https://www.selenium.dev/selenium/web/formPage.html');
+  },
+
   async demoTest(browser) {
     const selectElement = browser.element('select[name=selectomatic]');
 
     await browser
-      .url('https://www.selenium.dev/selenium/web/formPage.html')
       .perform(async function() {
-        const select = new Select(selectElement);
+        const selectWebElement = await selectElement; // `Select` class expects a WebElement
+
+        const select = new Select(selectWebElement);
 
         await select.selectByVisibleText('Four');
       })
diff --git a/test/lib/command-mocks.js b/test/lib/command-mocks.js
index 78220abb..f7807689 100644
--- a/test/lib/command-mocks.js
+++ b/test/lib/command-mocks.js
@@ -411,6 +411,7 @@ module.exports = {
     const headlessOpt = headless ? '-headless' : '';
     const options = {
       ['moz:firefoxOptions']: {
+        prefs: {'remote.active-protocols': 3},
         args: [headlessOpt]
       }
     };
diff --git a/test/lib/mocks/mocks-jsonwire.yaml b/test/lib/mocks/mocks-jsonwire.yaml
index f00d294d..a6004f73 100644
--- a/test/lib/mocks/mocks-jsonwire.yaml
+++ b/test/lib/mocks/mocks-jsonwire.yaml
@@ -7,6 +7,9 @@ mocks:
       firstMatch: [{}]
       alwaysMatch:
         browserName: firefox
+        'moz:firefoxOptions':
+          prefs:
+            'remote.active-protocols': 3
   response:
     status: 0
     sessionId: &sessionId '1352110219202'
diff --git a/test/lib/mocks/mocks-w3c.yaml b/test/lib/mocks/mocks-w3c.yaml
index b23cc7c1..77457aa2 100644
--- a/test/lib/mocks/mocks-w3c.yaml
+++ b/test/lib/mocks/mocks-w3c.yaml
@@ -9,6 +9,9 @@ mocks:
       firstMatch: [{}]
       alwaysMatch:
         browserName: firefox
+        'moz:firefoxOptions':
+          prefs:
+            'remote.active-protocols': 3
   response:
     value:
       sessionId: &webdriverSessionId '13521-10219-202'
diff --git a/test/src/api/commands/element/testCheck.js b/test/src/api/commands/element/testCheck.js
new file mode 100644
index 00000000..8d9d925f
--- /dev/null
+++ b/test/src/api/commands/element/testCheck.js
@@ -0,0 +1,137 @@
+const assert = require('assert');
+const MockServer  = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands.js');
+
+describe('.check()', function () {
+  beforeEach(function(done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  afterEach(function(done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('client.check() will click unselected checkbox', function (done) {
+    MockServer.addMock({
+      'url': '/wd/hub/session/1352110219202/element/0/click',
+      'response': {
+        sessionId: '1352110219202',
+        status: 0
+      }
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: false
+      })
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'checkbox'
+      })
+    });
+
+    this.client.api.check('css selector', '#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    }).check('#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    });
+
+    this.client.start(done);
+  });
+
+  it('client.check() will click unselected radio input', function (done) {
+    MockServer.addMock({
+      'url': '/wd/hub/session/1352110219202/element/0/click',
+      'response': {
+        sessionId: '1352110219202',
+        status: 0
+      }
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: false
+      })
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'radio'
+      })
+    });
+
+    this.client.api.check('css selector', '#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    }).check('#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    });
+
+    this.client.start(done);
+  });
+
+  it('client.check() will not click selected checkbox', function (done) {
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: true
+      })
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'checkbox'
+      })
+    });
+
+    this.client.api.check('css selector', '#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    }).check('#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    });
+
+    this.client.start(done);
+  });
+
+  it('client.check() will not click selected radio input', function (done) {
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: true
+      })
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'checkbox'
+      })
+    });
+
+    this.client.api.check('css selector', '#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    }).check('#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    });
+
+    this.client.start(done);
+  });
+});
diff --git a/test/src/api/commands/element/testUncheck.js b/test/src/api/commands/element/testUncheck.js
new file mode 100644
index 00000000..82374e6e
--- /dev/null
+++ b/test/src/api/commands/element/testUncheck.js
@@ -0,0 +1,109 @@
+const assert = require('assert');
+const MockServer  = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands.js');
+
+describe('.uncheck()', function () {
+  beforeEach(function(done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  afterEach(function(done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('client.uncheck() will uncheck selected checkbox input', function (done) {
+    MockServer.addMock({
+      'url': '/wd/hub/session/1352110219202/element/0/click',
+      'response': {
+        sessionId: '1352110219202',
+        status: 0
+      }
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: true
+      })
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'checkbox'
+      })
+    });
+
+    this.client.api.uncheck('css selector', '#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    }).uncheck('#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    });
+
+    this.client.start(done);
+  });
+
+  it('client.uncheck() will uncheck selected radio input', function (done) {
+    MockServer.addMock({
+      'url': '/wd/hub/session/1352110219202/element/0/click',
+      'response': {
+        sessionId: '1352110219202',
+        status: 0
+      }
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: true
+      })
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'radio'
+      })
+    });
+
+    this.client.api.uncheck('css selector', '#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    }).uncheck('#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    });
+
+    this.client.start(done);
+  });
+
+  it('client.uncheck() will not click unselected checkbox element', function (done) {
+    MockServer.addMock({
+      url: '/wd/hub/session/1352110219202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: false
+      })
+    }).addMock({
+      url: '/wd/hub/session/1352110219202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        sessionId: '1352110219202',
+        status: 0,
+        value: 'checkbox'
+      })
+    });
+
+    this.client.api.uncheck('css selector', '#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    }).uncheck('#weblogin', function callback(result) {
+      assert.strictEqual(result.status, 0);
+    });
+
+    this.client.start(done);
+  });
+});
diff --git a/test/src/api/commands/web-element/testCheck.js b/test/src/api/commands/web-element/testCheck.js
new file mode 100644
index 00000000..941632f9
--- /dev/null
+++ b/test/src/api/commands/web-element/testCheck.js
@@ -0,0 +1,201 @@
+const assert = require('assert');
+const {WebElement} = require('selenium-webdriver');
+const MockServer  = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands-w3c.js');
+const common = require('../../../../common.js');
+const Element = common.require('element/index.js');
+
+describe('element().check() command', function() {
+  before(function (done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function (done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('test .element().check() will check checkbox if not selected', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: false
+      })
+    }, true);
+
+    // For returning 'checkbox' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'checkbox'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').check();
+
+    // Click command should have been used one time to uncheck
+    assert.strictEqual(nCallsToClick, 1);
+
+    // 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(), '0');
+  });
+
+  it('test .element().check() will check radio input if not selected', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: false
+      })
+    }, true);
+
+    // For returning 'radio' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'radio'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').check();
+
+    // Click command should have been used one time to uncheck
+    assert.strictEqual(nCallsToClick, 1);
+
+    // 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(), '0');
+  });
+
+  it('test .element().check() will not check radio input if type not a checkbox or radio input', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: false
+      })
+    }, true);
+
+    // For returning 'submit' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'submit'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').check();
+
+    // Click command should have been used one time to uncheck
+    assert.strictEqual(nCallsToClick, 0);
+
+    // 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(), '0');
+  });
+
+  it('test .element().check() will not click if checked already', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: true
+      })
+    }, true);
+
+    // For returning 'radio' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'radio'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').check();
+
+    // Click command should not have been executed since element is unchecked already
+    assert.strictEqual(nCallsToClick, 0);
+    
+    // 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(), '0');
+  });
+});
+
diff --git a/test/src/api/commands/web-element/testUncheck.js b/test/src/api/commands/web-element/testUncheck.js
new file mode 100644
index 00000000..23942a77
--- /dev/null
+++ b/test/src/api/commands/web-element/testUncheck.js
@@ -0,0 +1,201 @@
+const assert = require('assert');
+const {WebElement} = require('selenium-webdriver');
+const MockServer  = require('../../../../lib/mockserver.js');
+const CommandGlobals = require('../../../../lib/globals/commands-w3c.js');
+const common = require('../../../../common.js');
+const Element = common.require('element/index.js');
+
+describe('element().uncheck() command', function() {
+  before(function (done) {
+    CommandGlobals.beforeEach.call(this, done);
+  });
+
+  after(function (done) {
+    CommandGlobals.afterEach.call(this, done);
+  });
+
+  it('test .element().uncheck() will uncheck checkbox if selected', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: true
+      })
+    }, true);
+
+    // For returning 'checkbox' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'checkbox'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').uncheck();
+
+    // Click command should have been used one time to uncheck
+    assert.strictEqual(nCallsToClick, 1);
+
+    // 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(), '0');
+  });
+
+  it('test .element().uncheck() will uncheck radio input if selected', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: true
+      })
+    }, true);
+
+    // For returning 'radio' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'radio'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').uncheck();
+
+    // Click command should have been used one time to uncheck
+    assert.strictEqual(nCallsToClick, 1);
+
+    // 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(), '0');
+  });
+
+  it('test .element().uncheck() will not uncheck if not a checkbox', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: true
+      })
+    }, true);
+
+    // For returning 'submit' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'submit'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').uncheck();
+
+    // Click command should not have been fired
+    assert.strictEqual(nCallsToClick, 0);
+
+    // 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(), '0');
+  });
+
+  it('test .element().uncheck() will not click if unchecked already', async function () {
+    let nCallsToClick = 0;
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/click',
+      method: 'POST',
+      response: JSON.stringify({
+        value: null
+      }),
+      onRequest(_) {
+        nCallsToClick++;
+      }
+    }, true);
+
+    MockServer.addMock({
+      url: '/session/13521-10219-202/element/0/selected',
+      method: 'GET',
+      response: JSON.stringify({
+        value: false
+      })
+    }, true);
+
+    // For returning 'checkbox' from getAttribute for type attribute
+    MockServer.addMock({
+      url: '/session/13521-10219-202/execute/sync',
+      method: 'POST',
+      response: JSON.stringify({
+        value: 'checkbox'
+      })
+    }, true);
+
+    const resultPromise = await this.client.api.element('#signupSection').uncheck();
+
+    // Click command should not have been executed since element is unchecked already
+    assert.strictEqual(nCallsToClick, 0);
+    
+    // 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(), '0');
+  });
+});
+
diff --git a/test/src/core/testCreateSession.js b/test/src/core/testCreateSession.js
index 00d365fe..556dfc74 100644
--- a/test/src/core/testCreateSession.js
+++ b/test/src/core/testCreateSession.js
@@ -164,7 +164,12 @@ describe('test Request With Credentials', function () {
       port: 10195,
       capabilities: {browserName: 'firefox', version: 'TEST', platform: 'TEST'}
     });
-    assert.deepStrictEqual(sessionOptions.get('moz:firefoxOptions'), {args: ['-headless']});
+    assert.deepStrictEqual(sessionOptions.get('moz:firefoxOptions'), {
+      args: ['-headless'],
+      prefs: {
+        'remote.active-protocols': 3
+      }
+    });
   });
 
   it('Test create session with headless mode in Chrome', async function () {
@@ -900,6 +905,11 @@ describe('test Request With Credentials', function () {
             firstMatch: [{}],
             alwaysMatch: {
               browserName: 'firefox',
+              'moz:firefoxOptions': {
+                prefs: {
+                  'remote.active-protocols': 3
+                }
+              },
               'bstack:options': {
                 local: 'false',
                 userName: 'test_user',
@@ -984,6 +994,11 @@ describe('test Request With Credentials', function () {
         firstMatch: [{}],
         alwaysMatch: {
           browserName: 'firefox',
+          'moz:firefoxOptions': {
+            prefs: {
+              'remote.active-protocols': 3
+            }
+          },
           'bstack:options': {
             local: 'false',
             userName: 'test_user',
diff --git a/test/src/index/transport/testFirefoxOptions.js b/test/src/index/transport/testFirefoxOptions.js
index 18c2c0f9..e99159d2 100644
--- a/test/src/index/transport/testFirefoxOptions.js
+++ b/test/src/index/transport/testFirefoxOptions.js
@@ -135,7 +135,8 @@ describe('Firefox driver options', function(){
     assert.ok(options instanceof FirefoxOptions);
     assert.strictEqual(options.get('acceptInsecureCerts'), true);
     assert.deepStrictEqual(options.get('moz:firefoxOptions'), {
-      args: ['--headless']
+      args: ['--headless'],
+      prefs: {'remote.active-protocols': 3}
     });
   });
 
diff --git a/test/src/runner/testRunnerSessionCreate.js b/test/src/runner/testRunnerSessionCreate.js
index b8a886de..cb06d57c 100644
--- a/test/src/runner/testRunnerSessionCreate.js
+++ b/test/src/runner/testRunnerSessionCreate.js
@@ -35,7 +35,11 @@ describe('testRunnerSessionCreate', function () {
       url: '/session',
       statusCode: 500,
       postdata: JSON.stringify({
-        capabilities: {firstMatch: [{}], alwaysMatch: {browserName: 'firefox', 'nightwatch:options': {name: 'test-Name'}}}
+        capabilities: {firstMatch: [{}], alwaysMatch: {
+          browserName: 'firefox',
+          'nightwatch:options': {name: 'test-Name'},
+          'moz:firefoxOptions': {prefs: {'remote.active-protocols': 3}}
+        }}
       }),
       response: JSON.stringify({
         value: {
@@ -49,7 +53,11 @@ describe('testRunnerSessionCreate', function () {
       url: '/session',
       statusCode: 500,
       postdata: JSON.stringify({
-        capabilities: {firstMatch: [{}], alwaysMatch: {browserName: 'firefox', 'nightwatch:options': {name: 'test-Name'}}}
+        capabilities: {firstMatch: [{}], alwaysMatch: {
+          browserName: 'firefox',
+          'nightwatch:options': {name: 'test-Name'},
+          'moz:firefoxOptions': {prefs: {'remote.active-protocols': 3}}
+        }}
       }),
       response: JSON.stringify({
         value: {
@@ -101,7 +109,11 @@ describe('testRunnerSessionCreate', function () {
       url: '/session',
       statusCode: 500,
       postdata: JSON.stringify({
-        capabilities: {firstMatch: [{}], alwaysMatch: {browserName: 'firefox', 'nightwatch:options': {name: 'test-Name'}}}
+        capabilities: {firstMatch: [{}], alwaysMatch: {
+          browserName: 'firefox',
+          'nightwatch:options': {name: 'test-Name'},
+          'moz:firefoxOptions': {prefs: {'remote.active-protocols': 3}}
+        }}
       }),
       response: JSON.stringify({
         value: {
@@ -115,7 +127,11 @@ describe('testRunnerSessionCreate', function () {
       url: '/session',
       statusCode: 500,
       postdata: JSON.stringify({
-        capabilities: {firstMatch: [{}], alwaysMatch: {browserName: 'firefox', 'nightwatch:options': {name: 'test-Name'}}}
+        capabilities: {firstMatch: [{}], alwaysMatch: {
+          browserName: 'firefox',
+          'nightwatch:options': {name: 'test-Name'},
+          'moz:firefoxOptions': {prefs: {'remote.active-protocols': 3}}
+        }}
       }),
       response: JSON.stringify({
         value: {
@@ -170,7 +186,11 @@ describe('testRunnerSessionCreate', function () {
       url: '/session',
       statusCode: 500,
       postdata: JSON.stringify({
-        capabilities: {firstMatch: [{}], alwaysMatch: {browserName: 'firefox', 'nightwatch:options': {name: 'test-Name'}}}
+        capabilities: {firstMatch: [{}], alwaysMatch: {
+          browserName: 'firefox',
+          'nightwatch:options': {name: 'test-Name'},
+          'moz:firefoxOptions': {prefs: {'remote.active-protocols': 3}}
+        }}
       }),
       response: JSON.stringify({
         value: {
@@ -184,7 +204,11 @@ describe('testRunnerSessionCreate', function () {
       url: '/session',
       statusCode: 500,
       postdata: JSON.stringify({
-        capabilities: {firstMatch: [{}], alwaysMatch: {browserName: 'firefox', 'nightwatch:options': {name: 'test-Name'}}}
+        capabilities: {firstMatch: [{}], alwaysMatch: {
+          browserName: 'firefox',
+          'nightwatch:options': {name: 'test-Name'},
+          'moz:firefoxOptions': {prefs: {'remote.active-protocols': 3}}
+        }}
       }),
       response: JSON.stringify({
         value: {
diff --git a/types/tests/elementCommands.test-d.ts b/types/tests/elementCommands.test-d.ts
index 581b7602..e20b8b6a 100644
--- a/types/tests/elementCommands.test-d.ts
+++ b/types/tests/elementCommands.test-d.ts
@@ -1,6 +1,66 @@
 import { expectType } from 'tsd';
 import { JSON_WEB_OBJECT, NightwatchSizeAndPosition, ElementResult, NightwatchAPI, NightwatchCallbackResult, ElementGlobal } from '..';
 
+//
+// .check
+//
+describe('check command demo', function () {
+  test('demo test', function () {
+    browser
+      .url('https://www.selenium.dev/selenium/web/formPage.html')
+      .waitForElementVisible('#checkbox-with-label')
+      .check('#checkbox-with-label', function (result) {
+        expectType<NightwatchAPI>(this);
+        expectType<NightwatchCallbackResult<null>>(result);
+      })
+      .expect.element('#checkbox-with-label').to.be.selected;
+    
+    // Should not uncheck the checkbox if .check is rerun on the same element
+    browser
+      .check('#checkbox-with-label')
+      .expect.element('#checkbox-with-label').to.be.selected
+  });
+
+  test('async demo test', async function (browser) {
+    const result = await browser
+      .url('https://www.selenium.dev/selenium/web/formPage.html')
+      .waitForElementVisible('#checkbox-with-label')
+      .check('#checkbox-with-label');
+    expectType<null>(result);
+  });
+});
+
+//
+// .uncheck
+//
+describe('uncheck command demo', function () {
+  test('demo test', function () {
+    browser
+      .url('https://www.selenium.dev/selenium/web/formPage.html')
+      .waitForElementVisible('#checkbox-with-label')
+      .click('#checkbox-with-label')
+      .assert.selected('#checkbox-with-label')
+      .uncheck('#checkbox-with-label', function (result) {
+        expectType<NightwatchAPI>(this);
+        expectType<NightwatchCallbackResult<null>>(result);
+      })
+      .expect.element('#checkbox-with-label').to.not.be.selected;
+    
+    // Should not check the checkbox if .check is rerun on the same element
+    browser
+      .uncheck('#checkbox-with-label')
+      .expect.element('#checkbox-with-label').to.not.be.selected
+  });
+
+  test('async demo test', async function (browser) {
+    const result = await browser
+      .url('https://www.selenium.dev/selenium/web/formPage.html')
+      .waitForElementVisible('#checkbox-with-label')
+      .uncheck('#checkbox-with-label');
+    expectType<null>(result);
+  });
+});
+
 //
 // .clearValue
 //
diff --git a/types/tests/webElement.test-d.ts b/types/tests/webElement.test-d.ts
index e7a3d0ff..b530c42d 100644
--- a/types/tests/webElement.test-d.ts
+++ b/types/tests/webElement.test-d.ts
@@ -175,6 +175,8 @@ describe('new element() api', function () {
 
     expectType<Promise<WebElement>>(elem.click());
     expectType<Promise<WebElement>>(elem.clear());
+    expectType<Promise<WebElement>>(elem.check());
+    expectType<Promise<WebElement>>(elem.uncheck());
     expectType<Promise<WebElement>>(elem.sendKeys('something', 1));
     expectType<Promise<WebElement>>(elem.update('something', 1));
     expectType<Promise<WebElement>>(elem.setValue('something', 1));