Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .check and .uncheck commands for immutable checkbox operations #4232

Merged
64 changes: 64 additions & 0 deletions lib/api/element-commands/check.js
Original file line number Diff line number Diff line change
@@ -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;
64 changes: 64 additions & 0 deletions lib/api/element-commands/uncheck.js
Original file line number Diff line number Diff line change
@@ -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;
28 changes: 28 additions & 0 deletions lib/api/web-element/commands/check.js
Original file line number Diff line number Diff line change
@@ -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');
};
26 changes: 26 additions & 0 deletions lib/api/web-element/commands/uncheck.js
Original file line number Diff line number Diff line change
@@ -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');
};
36 changes: 36 additions & 0 deletions lib/transport/selenium-webdriver/method-mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('');
Expand Down
137 changes: 137 additions & 0 deletions test/src/api/commands/element/testCheck.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading
Loading