Check if an element exists with Selenium, JavaScript, and Node.js - javascript

I'm trying to check if an element exists before I can execute this line:
driver.findElement(webdriver.By.id('test'));
This throws an error "no such element" if the id test doesn't exist in the document, even in a try-block.
I've found answers for Java, where you can check if the size is 0, but in Node.js this throws an error before I can check the size:
throw error; ^ NoSuchElementError: no such element

You can leverage the optional error handler argument of then().
driver.findElement(webdriver.By.id('test')).then(function(webElement) {
console.log('Element exists');
}, function(err) {
if (err.state && err.state === 'no such element') {
console.log('Element not found');
} else {
webdriver.promise.rejected(err);
}
});
I couldn't find it explicitly stated in the documentation, but determined this from the function definition in webdriver/promise.js in the selenium-webdriver module source:
/**
* Registers a callback on this Deferred.
* #param {Function=} opt_callback The callback.
* #param {Function=} opt_errback The errback.
* #return {!webdriver.promise.Promise} A new promise representing the result
* of the callback.
* #see webdriver.promise.Promise#then
*/
function then(opt_callback, opt_errback) {

Just use the isElementPresent(locatorOrElement) method. Here's a link to the code:
http://selenium.googlecode.com/git/docs/api/javascript/source/lib/webdriver/webdriver.js.src.html#l777

Aaron Silverman's answer did not work as expected (err.state was undefined and a NoSuchElementError was always thrown)—though the concept of using the optional callbacks still works.
Since I was getting the same error as the OP is referencing, I believe NoSuchElementError should be referenced when determining if the targeted element exists or not. As its name implies, it is the error that is thrown when an element does not exist. So the condition in the errorCallback should be:
err instanceof webdriver.error.NoSuchElementError
So the complete code block would be as follows (I also am using async/await for those taking advantage of that syntax):
var existed = await driver.findElement(webdriver.By.id('test')).then(function() {
return true; // It existed
}, function(err) {
if (err instanceof webdriver.error.NoSuchElementError) {
return false; // It was not found
} else {
webdriver.promise.rejected(err);
}
});
// Handle value of existed appropriately here

Aaron Silverman's answer did not work for me fully, though some parts of it were helpful. Arthur Weborg's answer worked for me, though some parts caused issues.
My typical use case for "checking if element exists" is when I want to click the element, only if it exists, since if it doesn't exist, it crashes the program due to the error. All I want is for the application to note that it doesn't exist, and move on. So what I do is:
await driver.findElement(By.id("test")).then(found => {
driver.findElement(By.id("test")).click()
}, error => {
if (error instanceof webdriver.error.NoSuchElementError) {
console.log('Element not found.');
}
});
Just a simple console log, and it moves on with the rest of the program like I wanted.

Here the summary for newbies like me ;-)
As described here:
For consistency with the other Selenium language bindings, WebDriver#isElementPresent() and WebElement#isElementPresent() have been deprecated. These methods will be removed in v3.0. Use the findElements command to test for the presence of an element:
driver.findElements(By.css('.foo')).then(found => !!found.length);

There are 2 possible cases for this answer:
Case 1: Using promises
Case 2: Using async / await
Case 1 using promises, then() and the error callback is already well explained in the accepted answer.
In Case 2, if you're in an async function (and therefore likely to be using await statements), then you have the pleasure of avoiding all the nasty chaining of then()'s and callbacks. 🎉
In this scenario, you can simply check if an element exists with code like this:
const check = async (webdriver) => {
let elementExists;
// Solution HERE:
try {
await webdriver.findElement(webdriver.By.id('test'));
elementExists = true;
} catch (error) { // error thrown is: "NoSuchElementError: no such element"
elementExists = false;
}
if (elementExists) {
// Element exists behaviour...
} else {
// Element doesn't exist behaviour...
}
};

You want to check whether an element exists before you want to find an element in the UI. You can wait until the element gets located in the UI, and then you can perform the find element operation.
Example: Below code waits until the element located, and then it will perform the find element.
driver.wait(webdriver.until.elementLocated(webdriver.By.id(LocatorValue)), 20000)
.then(() => {
return driver.findElement(webdriver.By.id('test'));
}).then((element) => {
// Perform any operation what you to do.
return element.click();
}).catch(() => {
console.log('Element not found');
})

This is the code that worked for me.
var assert = require('assert');
var expect = require('chai').expect;
var should = require('chai').should();
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
var webdriver = require('selenium-webdriver');
By = webdriver.By;
until = webdriver.until;
describe('checking if an element id exists', function() {
it.only('element id exists', function () {
var driver = new webdriver.Builder().forBrowser('chrome').build();
driver.get('http://localhost:3000');
this.timeout(6000);
return driver.wait(until.elementLocated(By.id('idWeWantToTest')), 5 * 1000).then((e) => {
}, function(err) {
throw {
msg: "element not found"
}
}).then(() => {
driver.quit();
});
});
})

Related

How to make protractor test fail if function fails?

So basically I have some helpers method to help me debug my protractor test cases, one of my main ones is tho wait for an element to be clickable, I'm creating this loop to give the necessary time for protractor to find and make sure the element is enabled, but when an element is not found, either by the element not being found or a typo on my scrip, I would like the test run to STOP and mark it as a FAILURE..
async WaitToBeClickable(element){
try{
for(var i = 0; i <= 3000; i++){
var wait = await browser.wait(this.EC.elementToBeClickable(element), i);
if(wait == true){
break;
}else{
//this is where I want to fail
}
}
}catch(err){
//this is where I want to fail
await console.log(`WAIT TO BE CLICKABLE FAILED:\n${element.parentElementArrayFinder.locator_.value}\n\nError:\n${err}\n`);
}
};
this would help me a LOT debugging my script since I'm working on VSC, but I can not seem to find a way yet to make the test FAIL and thereby to CLOSE the browser at the first fail, I've seen protractor-fail-fast and protractor-bail-fast but it seems to be for the jasmine test cases not for function, I WOULD REALLY APPRECIATE any help please, protractor its driving me a bit nuts lol...
//method
const checkData = () = {
return new Promise((resolve)=>{
if(true){
// Success case
return resolve(true)
}
else{
// Fail case
return reject(false)
}
})
}
//Specfile
it('sample test',async ()=>{
Let data = await checkData();
expect(data).toEqual(true);
})
Based on resolved value test will pass or fail
If you function throws error you can just use the done function that jasmine provides
Example:
it('should do something', async done => {
try {
await weDoSomething();
} catch (e) {
done.fail(e); // mark test as failed
}
done(); // mark test as done
}, 1 * 60 * 1000);
async function weDoSomething() {
throw Error('failed function');
}
Did you try simply re-throwing the error in the catch? I believe that should cause the test to fail.
Comments:
You do not need to await a console.log as it is an synchronous operation.
broser.waits throw exceptions when the element is not found within the timeout period, it appears your for loop is not required at all.
This function will wait 3000ms for the element to be clickable and if that does not happen it will throw an exception which will be caught in the catch. It will log the message and then rethrow the error which will fail your test (assuming the error is not being caught and handled further up)
async WaitToBeClickable(element){
try {
await browser.wait(this.EC.elementToBeClickable(element), 3000);
} catch (err) {
console.log(`WAIT TO BE CLICKABLE FAILED:\n${element.parentElementArrayFinder.locator_.value}\n\nError:\n${err}\n`);
throw new Error(err);
}
};

Why is a Stale Element exception occurring in this case?

I have the following webdriver function:
this.clickButton = async function () {
try {
var buttonElement = await driver.findElement(By.className('button-class'));
await buttonElement.click();
}
catch (err) {
console.log(err);
}
}
This sometimes gives a Stale Element exception.
I sometimes get that exception even if I change it to:
this.clickButton = async function () {
try {
await driver.findElement(By.className('button-class')).click();
}
catch (err) {
console.log(err);
}
}
My questions are:
Is it normal / expected that a Stale Reference exception can occur in this function, where I get the element reference, then use it on the very next line, or even the same line, doing nothing else with the page? (I could understand getting an 'element not found' exception, if no element of 'button-class' existed, but it doesn't make sense to me that the element exists at the time which I'm searching for it, but it's gone by the time the next line of code is reached.)
If the answer to question 1 is yes, then how is that possible? The element found is immediately acted upon, as it is in this case? As you can see, I am not reusing locators / elements; the function searches for the element each time it is called, and ends immediately after the click.
Is it relevant that clicking the button removes itself from the page? That is, could I be getting this exception because the button is gone after the click?
You must use the wait-until-clickable operator of selenium,this error occurred because it is still not appeared in DOM or still not clickable.
Example Code:
var webDriver= new ChromeDriver(Constants.DriverPath);
WebDriverWait wait = new WebDriverWait(webDriver, TimeSpan.FromSeconds(5));
wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementToBeClickable(By.ClassName('button-class')));
Then after that you can do your operation:
var buttonElement = await webDriver.FindElement(By.ClassName('button-class'));
await buttonElement.Click();
In JavaScript there is no clickable but you can check for visible and enable state like :
driver.findElement(webdriver.By.name('q')).then(function (element) {
driver.wait(function () {
return element.isDisplayed().then(function (displayed) {
if (!displayed)
return false;
return element.isEnabled();
});
});
element.sendKeys('webdriver');
});

Google Firebase Error(Function returned undefined, expected Promise or value)

I'm developing Server with Firebase.
I had copied Google Developer's Video on Youtube.
It works well, but on log there is an error:
Function returned undefined, expected Promise or value
It says function returned undefined, but I make function return a promise `set``
How can I solve this?
function sanitize(s) {
var sanitizedText = s;
console.log('sanitize params: ', sanitizedText);
sanitizedText = sanitizedText.replace(/\bstupid\b/ig, "wonderful");
return sanitizedText;
}
exports.sanitizePost = functions.database
.ref('/posts/{pushId}')
.onWrite(event => {
const post = event.data.val();
if (post.sanitized) return;
console.log('Sanitizing new post', event.params.pushId);
console.log(post);
post.sanitized = true;
post.title = sanitize(post.title);
post.body = sanitize(post.body);
return event.data.ref.set(post);
})
I'm beginner of Firebase, Nodejs.
As Frank indicates in his comment on your post, the return statement that is producing the warning is this one:
if (post.sanitized) return;
The warning can be silenced by returning a dummy value (e.g. null, false, 0). The value is not used.
Earlier versions of Cloud Functions did not complain when a function exited using a return statement with no value. That explains why you see return; in the video you linked and in the documentation. The comment on the question by Firebaser Frank van Pufeelen, explains why the change was made.
The simplest way to eliminate the warning is to add a return value, as Frank suggested:
if (post.sanitized) return 0;
Another option is to change the trigger from onWrite() to onCreate(). Then the function will not be invoked when the post is sanitized and the check that produces the warning is not needed:
exports.sanitizePost = functions.database
.ref('/test/{pushId}')
.onCreate(event => { // <= changed from onWrite()
const post = event.data.val();
//if (post.sanitized) return; // <= no longer needed
console.log('Sanitizing new post', event.params.pushId);
console.log(post);
//post.sanitized = true; // <= not needed when trigger is onCreate()
post.title = sanitize(post.title);
post.body = sanitize(post.body);
return event.data.ref.set(post);
});
I was getting this same error for attempting to read a document using .get() which returns a promise.
I found out in the official Firebase YouTube tutorial that to resolve this error, I needed to return the promise line of code. Check minute 4:18 in the tutorial linked video [https://youtu.be/d9GrysWH1Lc]
Also, as a side note, what lead me to the tutorial solution is when I noticed in the function logs did actually log valid values but only after the function is closed, even though the error says the result was undefined.
Adding to what #bob-snyder said, your problem is that your returning undefined under a condition.
if (post.sanitized) return;
My suggestion is to use a single exit point, which is a common tip when programming. Article.
Example
// code...
const SUCCESS_CODE = 0;
exports.sanitizePost = functions.database
.ref('/posts/{pushId}')
.onWrite(event => {
const post = event.data.val();
let response = Promise.resolve(SUCCESS_CODE);
if (!post.sanitized) {
console.log('Sanitizing new post', event.params.pushId);
console.log(post);
post.sanitized = true;
post.title = sanitize(post.title);
post.body = sanitize(post.body);
response = event.data.ref.set(post);
}
return response;
})
I noticed that when the trigger handler is an async function, the warning isn't raised even if we don't have an explicit return statement.
When the trigger handler is a sync function, we'd need an explicit return statement to silence the warning, like like it was suggested
in the accepted answer.
That is, if we rewrote
exports.updatePost = functions.firestore
.document("posts/{postId}")
.onUpdate(async (snap, context) => { /* async handler*/
...
});
to
exports.updatePost = functions.firestore
.document("posts/{postId}")
.onUpdate((snap, context) => { /* sync handler*/
...
});
We'd need to do return 0; or similar inside the sync handler.
In any case, for async, it's probably a good idea to explicitly return a promise on the async handler, as advised on the official docs:
Resolve functions that perform asynchronous processing (also known as "background functions") by returning a JavaScript promise.

How to promisify this Mongoose code?

I am trying to use Mongoose's built in promise support to write some clean Javascript code for a user sending a friend request to another. However, when I try to ensure proper error handling and sequentiality, I still end up with a (slightly smaller than normal) pyramid of doom.
Here, I first ensure that the friend request is valid, then save the target's Id to the requester's sent requests then, if that save was successful, save the requester's Id to the target's friend requests.
Do I need to use a third party library like q in order to do this as cleanly as possible? How can I structure this such that I can use the traditional single error handler at the end?
function _addFriend (requesterId, targetId) {
// (integer, integer)
User.findById(requesterId)
.exec((requester) => {
if (!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests)) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
requester.save()
.then((err) => {
if (err) throw err;
User.findById(targetId)
.exec((err, target) => {
if (err) throw err;
target.friendRequests = target.friendRequests.concat([requesterId])
target.save().then(err => {if (err) throw err})
})
})
}
})
}
You will need some nesting to do conditionals in promise code, but not as much as with callback-based code.
You seem to have messed up a bit of the if (err) throw err; stuff, you should never need that with promises. Just always use .then(result => {…}), and don't pass callbacks to exec any more.
If you always properly return promises from your asynchronous functions (including then callbacks for chaining), you can add the single error handler in the end.
function _addFriend (requesterId, targetId) {
// (integer, integer)
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests) {
return;
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId])
return requester.save().then(() => {
return User.findById(targetId).exec()
}).then(target => {
target.friendRequests = target.friendRequests.concat([requesterId])
return target.save()
});
});
}
_addFriend(…).catch(err => {
…
})
In English, the way to do this is to use the promises returned by exec() have then blocks return promises, un-indent, then add then to those. Much easier to say in code...
EDIT thanks (again) to #Bergi for making me read and understand the app logic. #Bergi is right that there must be a little nesting to get the job done, but the real point isn't about reducing nesting, but about improving clarity.
Better clarity can come from factoring into logical parts, including some that return in promises.
These few functions conceal the promise nesting that's required by the logic. This doesn't specify (because the OP doesn't indicate how the app should handle) what addFriend should return when it refuses to do so due to an existing request...
function _addFriend (requesterId, targetId) {
// note - pass no params to exec(), use it's returned promise
return User.findById(requesterId).exec().then((requester) => {
return canAddFriend(requester, targetId) ? addFriend(requester, targetId) : null;
});
}
function canAddFriend(requester, targetId) {
return requester && targetId &&
!(targetId in requester.friends
|| targetId in requester.sentfriendRequests
|| targetId in requester.friendRequests);
}
function addFriend(requester, targetId) {
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
return requester.save().then(() => {
return User.findById(targetId).exec();
}).then((target) => {
target.friendRequests = target.friendRequests.concat([requesterId]);
return target.save();
});
}
Once you realise that .exec() returns a promise, you can :
achieve the desired flattening and make the code more readable.
avoid the need to handle errors amongst the "success" code.
handle errors in a terminal .then() or .catch().
As a bonus you can also (more readily) throw meaningful errors for each of those x in y conditions.
Straightforwardly, you could write :
function _addFriend(requesterId, targetId) {
return User.findById(requesterId).exec().then(requester => {
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]); // or just .push()?
return requester.save();
}).then(() => {
return User.findById(targetId).exec().then(target => {
target.friendRequests = target.friendRequests.concat([requesterId]); // or just .push()?
return target.save();
});
});
}
Note the need for returns to control flow.
But you could do even better. As writtten above, the requested stuff could succeed then the target stuff fail, resulting in a db disparity. So what you really want is a db transaction to guarantee that both happen or neither. Mongoose undoubtedly provides for transactions however you can do something client-side to give you something transaction-like with partial benefit.
function _addFriend(requesterId, targetId) {
return Promise.all([User.findById(requesterId).exec(), User.findById(targetId).exec()]).then(([requester, target]) => { // note destructuring
if (targetId in requester.friends) {
throw new Error('target is already a friend');
}
if (targetId in requester.sentfriendRequests) {
throw new Error('friend request already sent to target');
}
if (targetId in requester.friendRequests) {
throw new Error('target already sent a friend request to requester');
}
requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]);
target.friendRequests = target.friendRequests.concat([requesterId]);
return requester.save().then(() => {
return target.save();
});
});
}
Here, you could still get the (unlikely) situation that the first save is successful and the second save fails, but at least you have the assurance that absolutely nothing happens unless both the requester and target exist.
In both cases, call as follows :
_addFriend(requesterId, targetId).then(function() {
// do whatever on success
}, function(error) {
// do whatever on error
});
Even if you don't use the error messages in the live environment, they could be very useful when testing/debugging. Please check them - I may have gotten them wrong.

Node.js - promises and conidtional statements (if, switch, etc) - how to structure?

Can you recommend how to correctly deal with a control flow with many if/switch and promises? All the tutorials on the Internet that I've found tend to deal with simple control flow, without many (any?) different processing branches. Any suggested reading or at least search terms?
The way I do it now is to encapsulate if/switch logic in a function that returns a Promise after evaluating the conditions and returns to the main process loop. Any way to do it better, nicer?
Sample code:
// Check if argument is a valid URL
Promise.promisify(checkUrl)().then(() => {
// Delete all query parameters from URL if present
return sanitizer.cleanAsync(argv.url)
}).then(_cleanUrl => {
cleanUrl = _cleanUrl;
logger.warn(`URL: ${cleanUrl}`);
// Validate Google Analytics view id supplied as '--gaId=<id>' command line argument or exit if it is not present
return Promise.promisify(checkGaId)()
}).then(() => {
// Check if DB exists, if not create it
return db.checkIfDatabaseExistsAsync()
}).then(() => {
// Check if all tables exist, if not create them
return db.checkTablesAsync()
}).then(() => {
// Check DB integrity (possiblDelete all query parameters from URL if presente to turn off in the config)
if (config.database.checkIntegrity) {
return db.integrityChecksAsync();
}
}).then(() => {
// Check if URL already exists in DB, if not insert it
return db.getOrCreateEntryUrlIdAsync(cleanUrl)
}).then(_entryId => {
entryId = _entryId;
// Check if any previous executions for the entry point exist and if so whether the last one completed
return db.getLastExecutionDataAsync(entryId);
}).then(lastExecution => {
// If last execution was not completed prompt for user action
return processLastExecution(entryId, lastExecution)
}).then(_pages => {
... more code follows here...
And psuedo-code for processLasExecution function:
function processLastExecution(entryId, lastExecution) {
return new Promise(
function (resolve, reject) {
// No previous executions found or all was okay
if (lastExecution == null || (lastExecution != null && lastExecution.is_completed == 'Y')) {
...resolves with A;
} else {
Promise.promisify(selectRunOption)().then(option => {
switch (option) {
case 'resume':
...resolves with B;
break;
case 'ignore':
...resolves with C;
break;
case 'delete':
...resolves with D;
break;
default:
...rejects
}
});
}
}
)
}
Any way of having the if/switch logic better/more clearly encapsulated or served?
Oh, if anyone wonders this is a command line script, not a web application, and this not exactly what Node.js was intended for.
I think it is better to use generator, then you can write sync like codes:
co(function* () {
// Check if argument is a valid URL
if (yield checkUrl) {
var cleanUrl = yield sanitizer.cleanAsync(argv.url);
...
}
...
}, ...
co can cooperation with callback and promise, see https://github.com/tj/co

Categories