Skip to content

Commit c31582e

Browse files
authored
Add .check and .uncheck commands for immutable checkbox operations (#4232)
1 parent 5d3c749 commit c31582e

File tree

13 files changed

+1036
-0
lines changed

13 files changed

+1036
-0
lines changed

lib/api/element-commands/check.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const BaseElementCommand = require('./_baseElementCommand.js');
2+
3+
/**
4+
* Will check, by clicking, on a checkbox or radio input if it is not already checked.
5+
*
6+
* @example
7+
* module.exports = {
8+
* demoTest(browser) {
9+
* browser.check('input[type=checkbox]:not(:checked)');
10+
*
11+
* browser.check('input[type=checkbox]:not(:checked)', function(result) {
12+
* console.log('Check result', result);
13+
* });
14+
*
15+
* // with explicit locate strategy
16+
* browser.check('css selector', 'input[type=checkbox]:not(:checked)');
17+
*
18+
* // with selector object - see https://nightwatchjs.org/guide#element-properties
19+
* browser.check({
20+
* selector: 'input[type=checkbox]:not(:checked)',
21+
* index: 1,
22+
* suppressNotFoundErrors: true
23+
* });
24+
*
25+
* browser.check({
26+
* selector: 'input[type=checkbox]:not(:checked)',
27+
* timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
28+
* });
29+
* },
30+
*
31+
* demoTestAsync: async function(browser) {
32+
* const result = await browser.check('input[type=checkbox]:not(:checked)');
33+
* console.log('Check result', result);
34+
* }
35+
* }
36+
*
37+
* @method check
38+
* @syntax .check(selector, [callback])
39+
* @syntax .check(using, selector, [callback])
40+
* @syntax browser.element(selector).check()
41+
* @param {string} [using] The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies)
42+
* @param {string} selector The CSS/Xpath selector used to locate the element.
43+
* @param {function} [callback] Optional callback function to be called when the command finishes.
44+
* @api protocol.elementinteraction
45+
*/
46+
class CheckElement extends BaseElementCommand {
47+
get extraArgsCount() {
48+
return 0;
49+
}
50+
51+
get elementProtocolAction() {
52+
return 'checkElement';
53+
}
54+
55+
static get isTraceable() {
56+
return true;
57+
}
58+
59+
async protocolAction() {
60+
return this.executeProtocolAction(this.elementProtocolAction);
61+
}
62+
}
63+
64+
module.exports = CheckElement;

lib/api/element-commands/uncheck.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const BaseElementCommand = require('./_baseElementCommand.js');
2+
3+
/**
4+
* Will uncheck, by clicking, on a checkbox or radio input if it is not already unchecked.
5+
*
6+
* @example
7+
* module.exports = {
8+
* demoTest(browser) {
9+
* browser.uncheck('input[type=checkbox]:checked)');
10+
*
11+
* browser.uncheck('input[type=checkbox]:checked)', function(result) {
12+
* console.log('Check result', result);
13+
* });
14+
*
15+
* // with explicit locate strategy
16+
* browser.uncheck('css selector', 'input[type=checkbox]:checked)');
17+
*
18+
* // with selector object - see https://nightwatchjs.org/guide#element-properties
19+
* browser.uncheck({
20+
* selector: 'input[type=checkbox]:checked)',
21+
* index: 1,
22+
* suppressNotFoundErrors: true
23+
* });
24+
*
25+
* browser.uncheck({
26+
* selector: 'input[type=checkbox]:checked)',
27+
* timeout: 2000 // overwrite the default timeout (in ms) to check if the element is present
28+
* });
29+
* },
30+
*
31+
* demoTestAsync: async function(browser) {
32+
* const result = await browser.uncheck('input[type=checkbox]:checked)');
33+
* console.log('Check result', result);
34+
* }
35+
* }
36+
*
37+
* @method check
38+
* @syntax .uncheck(selector, [callback])
39+
* @syntax .uncheck(using, selector, [callback])
40+
* @syntax browser.element(selector).uncheck()
41+
* @param {string} [using] The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies)
42+
* @param {string} selector The CSS/Xpath selector used to locate the element.
43+
* @param {function} [callback] Optional callback function to be called when the command finishes.
44+
* @api protocol.elementinteraction
45+
*/
46+
class UncheckElement extends BaseElementCommand {
47+
get extraArgsCount() {
48+
return 0;
49+
}
50+
51+
get elementProtocolAction() {
52+
return 'uncheckElement';
53+
}
54+
55+
static get isTraceable() {
56+
return true;
57+
}
58+
59+
async protocolAction() {
60+
return this.executeProtocolAction(this.elementProtocolAction);
61+
}
62+
}
63+
64+
module.exports = UncheckElement;

lib/api/web-element/commands/check.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Will check, by clicking, on a checkbox or radio input if it is not already checked.
3+
* 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>.
4+
*
5+
* 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.
6+
*
7+
* @example
8+
* export default {
9+
* demoTest(browser: NightwatchAPI): void {
10+
* browser.element('input[type=checkbox]:not(:checked)').check();
11+
* browser.element('input[type=radio]:not(:checked)').check();
12+
* },
13+
* async demoTestAsync(browser: NightwatchAPI): Promise<void> {
14+
* await browser.element('input[type=checkbox]:not(:checked)').check();
15+
* await browser.element('input[type=radio]:not(:checked)').check();
16+
* },
17+
* }
18+
*
19+
* @since 3.6.4
20+
* @method check
21+
* @memberof ScopedWebElement
22+
* @instance
23+
* @syntax browser.element(selector).check()
24+
* @returns {ScopedWebElement}
25+
*/
26+
module.exports.command = function () {
27+
return this.runQueuedCommand('checkElement');
28+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Will uncheck, by clicking, on a checkbox or radio input if it is not already unchecked.
3+
* 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>.
4+
*
5+
* 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.
6+
*
7+
* @example
8+
* export default {
9+
* demoTest(browser: NightwatchAPI): void {
10+
* browser.element('input[type=checkbox]:checked)').check();
11+
* },
12+
* async demoTestAsync(browser: NightwatchAPI): Promise<void> {
13+
* await browser.element('input[type=checkbox]:checked)').check();
14+
* },
15+
* }
16+
*
17+
* @since 3.6.4
18+
* @method uncheck
19+
* @memberof ScopedWebElement
20+
* @instance
21+
* @syntax browser.element(selector).check()
22+
* @returns {ScopedWebElement}
23+
*/
24+
module.exports.command = function () {
25+
return this.runQueuedCommand('uncheckElement');
26+
};

lib/transport/selenium-webdriver/method-mappings.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,42 @@ module.exports = class MethodMappings {
606606
return this.methods.session.setElementValue.call(this, webElementOrId, modifiedValue);
607607
},
608608

609+
async checkElement(webElementOrId) {
610+
const element = await this.getWebElement(webElementOrId);
611+
const elementType = await element.getAttribute('type');
612+
const checkableTypes = ['checkbox', 'radio'];
613+
614+
if (!checkableTypes.includes(elementType)) {
615+
throw new Error('must be an input element with type attribute \'checkbox\' or \'radio\'');
616+
}
617+
618+
const value = await element.isSelected();
619+
620+
if (!value) {
621+
await element.click();
622+
}
623+
624+
return null;
625+
},
626+
627+
async uncheckElement(webElementOrId) {
628+
const element = await this.getWebElement(webElementOrId);
629+
const elementType = await element.getAttribute('type');
630+
const checkableTypes = ['checkbox', 'radio'];
631+
632+
if (!checkableTypes.includes(elementType)) {
633+
throw new Error('must be an input element with type attribute \'checkbox\' or \'radio\'');
634+
}
635+
636+
const value = await element.isSelected();
637+
638+
if (value) {
639+
await element.click();
640+
}
641+
642+
return null;
643+
},
644+
609645
async setElementValue(webElementOrId, value) {
610646
if (Array.isArray(value)) {
611647
value = value.join('');
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
const assert = require('assert');
2+
const MockServer = require('../../../../lib/mockserver.js');
3+
const CommandGlobals = require('../../../../lib/globals/commands.js');
4+
5+
describe('.check()', function () {
6+
beforeEach(function(done) {
7+
CommandGlobals.beforeEach.call(this, done);
8+
});
9+
10+
afterEach(function(done) {
11+
CommandGlobals.afterEach.call(this, done);
12+
});
13+
14+
it('client.check() will click unselected checkbox', function (done) {
15+
MockServer.addMock({
16+
'url': '/wd/hub/session/1352110219202/element/0/click',
17+
'response': {
18+
sessionId: '1352110219202',
19+
status: 0
20+
}
21+
}).addMock({
22+
url: '/wd/hub/session/1352110219202/element/0/selected',
23+
method: 'GET',
24+
response: JSON.stringify({
25+
sessionId: '1352110219202',
26+
status: 0,
27+
value: false
28+
})
29+
}).addMock({
30+
url: '/wd/hub/session/1352110219202/execute/sync',
31+
method: 'POST',
32+
response: JSON.stringify({
33+
sessionId: '1352110219202',
34+
status: 0,
35+
value: 'checkbox'
36+
})
37+
});
38+
39+
this.client.api.check('css selector', '#weblogin', function callback(result) {
40+
assert.strictEqual(result.status, 0);
41+
}).check('#weblogin', function callback(result) {
42+
assert.strictEqual(result.status, 0);
43+
});
44+
45+
this.client.start(done);
46+
});
47+
48+
it('client.check() will click unselected radio input', function (done) {
49+
MockServer.addMock({
50+
'url': '/wd/hub/session/1352110219202/element/0/click',
51+
'response': {
52+
sessionId: '1352110219202',
53+
status: 0
54+
}
55+
}).addMock({
56+
url: '/wd/hub/session/1352110219202/element/0/selected',
57+
method: 'GET',
58+
response: JSON.stringify({
59+
sessionId: '1352110219202',
60+
status: 0,
61+
value: false
62+
})
63+
}).addMock({
64+
url: '/wd/hub/session/1352110219202/execute/sync',
65+
method: 'POST',
66+
response: JSON.stringify({
67+
sessionId: '1352110219202',
68+
status: 0,
69+
value: 'radio'
70+
})
71+
});
72+
73+
this.client.api.check('css selector', '#weblogin', function callback(result) {
74+
assert.strictEqual(result.status, 0);
75+
}).check('#weblogin', function callback(result) {
76+
assert.strictEqual(result.status, 0);
77+
});
78+
79+
this.client.start(done);
80+
});
81+
82+
it('client.check() will not click selected checkbox', function (done) {
83+
MockServer.addMock({
84+
url: '/wd/hub/session/1352110219202/element/0/selected',
85+
method: 'GET',
86+
response: JSON.stringify({
87+
sessionId: '1352110219202',
88+
status: 0,
89+
value: true
90+
})
91+
}).addMock({
92+
url: '/wd/hub/session/1352110219202/execute/sync',
93+
method: 'POST',
94+
response: JSON.stringify({
95+
sessionId: '1352110219202',
96+
status: 0,
97+
value: 'checkbox'
98+
})
99+
});
100+
101+
this.client.api.check('css selector', '#weblogin', function callback(result) {
102+
assert.strictEqual(result.status, 0);
103+
}).check('#weblogin', function callback(result) {
104+
assert.strictEqual(result.status, 0);
105+
});
106+
107+
this.client.start(done);
108+
});
109+
110+
it('client.check() will not click selected radio input', function (done) {
111+
MockServer.addMock({
112+
url: '/wd/hub/session/1352110219202/element/0/selected',
113+
method: 'GET',
114+
response: JSON.stringify({
115+
sessionId: '1352110219202',
116+
status: 0,
117+
value: true
118+
})
119+
}).addMock({
120+
url: '/wd/hub/session/1352110219202/execute/sync',
121+
method: 'POST',
122+
response: JSON.stringify({
123+
sessionId: '1352110219202',
124+
status: 0,
125+
value: 'checkbox'
126+
})
127+
});
128+
129+
this.client.api.check('css selector', '#weblogin', function callback(result) {
130+
assert.strictEqual(result.status, 0);
131+
}).check('#weblogin', function callback(result) {
132+
assert.strictEqual(result.status, 0);
133+
});
134+
135+
this.client.start(done);
136+
});
137+
});

0 commit comments

Comments
 (0)