diff --git a/lib/api/web-element/waitUntil.js b/lib/api/web-element/waitUntil.js index c6f3a6446f..6c2bbf1da2 100644 --- a/lib/api/web-element/waitUntil.js +++ b/lib/api/web-element/waitUntil.js @@ -1,6 +1,8 @@ -const until = require('selenium-webdriver/lib/until'); +const {until, Condition} = require('selenium-webdriver'); const {AssertionRunner} = require('../../assertion'); const {isDefined, format} = require('../../utils'); +const {ScopedElementLocator} = require('./element-locator'); +const {NoSuchElementError} = require('../../element/locator'); const mapToSeleniumFunction = { 'selected': until.elementIsSelected, @@ -9,12 +11,14 @@ const mapToSeleniumFunction = { 'not.visible': until.elementIsNotVisible, 'enabled': until.elementIsEnabled, 'not.enabled': until.elementIsDisabled, - 'disabled': until.elementIsDisabled + 'disabled': until.elementIsDisabled, + 'present': (webElement, scopedElementLocator) => until.elementLocated(scopedElementLocator.condition), + 'not.present': (webElement, scopedElementLocator) => new Condition('until elmenet is not present', (driver) => driver.findElements(scopedElementLocator.condition).then(elements => elements.length ? false : true)) }; /** - * Waits a given time in milliseconds (default 5000ms) for an element to be present in a specified state in the page before performing any other commands or assertions. - * If the element fails to be present in the specified state within the given time, the test fails. You can change this by setting `abortOnFailure` to `false`. + * Waits a given time in milliseconds (default 5000ms) for an element to be in the action state provided before performing any other commands or assertions. + * If the element fails to be in the action state withing the given time, the test fails. You can change this by setting `abortOnFailure` to `false`. * * You can change the polling interval by defining a `waitForConditionPollInterval` property (in milliseconds) in as a global property in your `nightwatch.conf.js` or in your external globals file. * Similarly, the default timeout can be specified as a global `waitForConditionTimeout` property (in milliseconds). @@ -55,12 +59,13 @@ const mapToSeleniumFunction = { * * @method waitUntil * @syntax .waitUntil(action, {timeout, retryInterval, message, abortOnFailure}); - * @param {string} action The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies) + * @param {string} action The action state. Should be one of the following: selected, not.selected, visible, not.visible, enabled, disabled, present, not.present * @param {number} [timeout] The total number of milliseconds to wait before failing. Can also be set using 'globals.waitForConditionTimeout' under settings. * @param {number} [retryInterval] The number of milliseconds to wait between retries. You can use this only if you also specify the time parameter. Can also be set using 'globals.waitForConditionPollInterval' under settings. * @param {string} [message] Optional message to be shown in the output. The message supports two placeholders: %s for current selector and %d for the time (e.g. Element %s was not in the page for %d ms). * @param {boolean} [abortOnFailure=abortOnAssertionFailure] By the default if the element is not found the test will fail. Set this to false if you wish for the test to continue even if the assertion fails. To set this globally you can define a property `abortOnAssertionFailure` in your globals. */ + class WaitUntil { constructor(scopedElement, {action, timeout, retryInterval, message, nightwatchInstance, selector, abortOnFailure}) { this.scopedElement = scopedElement; @@ -72,33 +77,41 @@ class WaitUntil { this.timeout = timeout || this.nightwatchInstance.settings.globals.waitForConditionTimeout; this.retryInterval = retryInterval || this.nightwatchInstance.settings.globals.waitForConditionPollInterval; this.abortOnFailure = isDefined(abortOnFailure) ? abortOnFailure : this.nightwatchInstance.settings.globals.abortOnAssertionFailure; + this.scopedElementLocator = ScopedElementLocator.create(selector, nightwatchInstance); } createNode() { - const createAction = (actions, webElement) => function() { - if (!this.conditionFn) { - throw new Error(`Invalid action ${this.action} for element.waitUntil command. Possible actions: ${Object.keys(mapToSeleniumFunction).toString()}`); - } + const createAction = (actions, webElementPromise) => async function() { - return actions.wait(this.conditionFn(webElement), this.timeout, this.message, this.retryInterval, ()=>{}) - .then(result => { - const elapsedTime = new Date().getTime() - node.startTime; - if (result.error) { - const actual = result.error.name === 'NightwatchElementError' ? 'not found' : null; + let result; + try { - return this.fail(result, actual, elapsedTime); - } + if (!this.conditionFn) { + throw new Error(`Invalid action ${this.action} for element.waitUntil command. Possible actions: ${Object.keys(mapToSeleniumFunction).toString()}`); + } - return this.pass(result, elapsedTime); - }) - .catch(err => err) - .then(result => { - node.deferred.resolve(result); + const webElement = await webElementPromise; - return result; - }); - }.bind(this); + if (!webElement && !['present', 'not.present'].includes(this.action)) { + throw new NoSuchElementError({element: this.scopedElementLocator, abortOnFailure: this.abortOnFailure}); + } + + const elapsedTime = new Date().getTime() - node.startTime; + const commandResult = await this.scopedElement.driver.wait(this.conditionFn(webElementPromise, this.scopedElementLocator), this.timeout, this.message, this.retryInterval, ()=>{}); + result = await this.pass(commandResult, elapsedTime).catch(err => err); + return result; + } catch (err) { + const elapsedTime = new Date().getTime() - node.startTime; + const actual = err instanceof NoSuchElementError ? 'not present' : null; + result = await this.fail(err, actual, elapsedTime).catch(err => err); + + return result; + } finally { + node.deferred.resolve(result); + } + }.bind(this); + const node = this.scopedElement.queueAction({name: 'waitUntil', createAction}); return node; diff --git a/test/apidemos/web-elements/waitUntilElementNotPresent.js b/test/apidemos/web-elements/waitUntilElementNotPresent.js new file mode 100644 index 0000000000..f19ea5ecc6 --- /dev/null +++ b/test/apidemos/web-elements/waitUntilElementNotPresent.js @@ -0,0 +1,5 @@ +describe('demo of failure of waitUntil element commands', function() { + it('waitUntil element is visible - element not present', function({element}) { + element.find('#badElement').waitUntil('visible'); + }); +}); \ No newline at end of file diff --git a/test/apidemos/web-elements/waitUntilTest.js b/test/apidemos/web-elements/waitUntilTest.js index c85c13220b..4abd1e3c4d 100644 --- a/test/apidemos/web-elements/waitUntilTest.js +++ b/test/apidemos/web-elements/waitUntilTest.js @@ -4,7 +4,7 @@ describe('demo tests using waitUntil element APIs', function() { }); it('wait until element is selected', function({element, state}) { - element('#weblogin').waitUntil('selected'); + element('#weblogin').waitUntil('present').waitUntil('selected'); }); it('wait until element is enabled', function({element}) { diff --git a/test/src/apidemos/web-elements/testWaitUntil.js b/test/src/apidemos/web-elements/testWaitUntil.js index f833d6b599..9fb74d4f3a 100644 --- a/test/src/apidemos/web-elements/testWaitUntil.js +++ b/test/src/apidemos/web-elements/testWaitUntil.js @@ -83,4 +83,37 @@ describe('waitUntil element command', function() { globals })); }); + + it('test waitUntil element command - element not present', function() { + const testsPath = path.join(__dirname, '../../../apidemos/web-elements/waitUntilElementNotPresent.js'); + + + const globals = { + waitForConditionPollInterval: 50, + waitForConditionTimeout: 120, + retryAssertionTimeout: 100, + + reporter(results) { + if (!results.lastError) { + assert.fail('Should result into failure'); + } + assert.ok(results.lastError.message.includes('not present'), 'err message should be element not present'); + } + }; + + return NightwatchClient.runTests(testsPath, settings({ + selenium: { + port: null, + start_process: false + }, + selenium_host: null, + webdriver: { + port: 10195, + start_process: false + }, + output: false, + skip_testcases_on_fail: false, + globals + })); + }); }); diff --git a/types/web-element.d.ts b/types/web-element.d.ts index 6590d5d716..05d68b02f2 100644 --- a/types/web-element.d.ts +++ b/types/web-element.d.ts @@ -199,7 +199,7 @@ type WaitUntilOptions = { abortOnFailure?: boolean; }; -type WaitUntilActions = 'selected' | 'visible' | 'disabled' | 'enabled' | 'not.selected' | 'not.visible' | 'not.enabled'; +type WaitUntilActions = 'selected' | 'visible' | 'disabled' | 'enabled' | 'not.selected' | 'not.visible' | 'not.enabled' | 'present' | 'not.present'; export class Elements implements PromiseLike { constructor(