Why is a Stale Element exception occurring in this case? - javascript

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');
});

Related

async wait for element to load in so i can find it with jquery

i don't really understand async. i have a function like this:
function getTeam() {
let sir = setInterval(() => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir)
return firstTeam.trim()
}
}, 100)
}
im not js master. i just want to get that element when it loads in, this code is running in a userscript and // #run-at document-idle doesnt help either. i knew i would have to get into async js promises callbacks and whatever someday but i really dont understand how it works after pages of docs and other stackoverflow.
when i console.log this function it will print undefined once then if i have a console.log inside the if it will print the actual team name.
how do i wait for that result
Answer regarding the javascript language part if the question
You could modify your code to the following (but don't - see further below - I'm just providing this as your StackOverflow tags included async/await):
async function getTeam() {
return new Promise(resolve => {
const sir = setInterval(() => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir);
resolve(firstTeam.trim());
}
}, 100);
});
}
// ... and then anywhere else in your code:
doSomethingSynchronous();
const team = await getTeam();
soSomethingSynchronousWithTeam(team);
Note that this will only work with modern browsers supporting >= ECMAScript 2017:
https://caniuse.com/async-functions (but luckily that's most by now!)
Answer regarding the implicit "howto wait for an element part"
... you really shouldn't actively wait for an element because this is unnecessarily heavy on the CPU. Usually you'll have some kind of event that informs you as soon as the element you're waiting for has been created. Just listen for that and then run your code.
What to do, if there's currently no such event:
If you're in control of the code creating the element, then trigger one yourself (see https://api.jquery.com/trigger/ for example).
If the element is created by a third party lib or by something else you cannot easily modify, you could use a MutationObserver (see this StackBlitz answer to a related question) and run your getTeam code only whenever something has changed instead of every 100ms (smaller impact on performance!)
You can make it async if you want, but the main part us going to be using events instead. There is a special object called mutation observer. It will call a function you give it any time there's a change in the element you're observing.
Check the mutation observer docs to understand the code below: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Without knowing much about your HTML, I can say as much as this should work:
function getTeam() {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
return firstTeam.trim()
}
}
function getTeamWhenAvailable() {
// returning a promise allows you to do "await" and get result when it is available
return new Promise((resolve, reject) => {
// furst try if element is available right now
const teamText = getTeam();
if(teamText) {
// resolve() "returns" the value to whoever is doing "await"
resolve(teamText);
// resolve does not terminate this function, we need to do that using return
return;
}
// Mutation observer gives you list of stuff that changed, but we don't care, we just do our own thing
const observer = new MutationObserver(()=>{
const teamText = getTeam();
if(teamText) {
// stop observing
observer.disconnect();
// resolve the value
resolve(teamText);
}
});
observer.observe(document.body, { childList: true, subtree: true };
})
}
// usage, the extra brackets around the lambda cause it to invoke immediatelly
(async () => {
console.log("Waitinf for team...");
const teamName = await getTeamWhenAvailable();
console.log("Result team name: ", teamName)
})();
Now you might wanna narrow the scope of the mutation observer, in the example above it watches the entire document. Try to instead observe the deepest element that you can rely on not being removed.
If you need to receive team name multiple times, I think you should just go with the obsever alone without the async stuff.
function getTeam() {
let sir = new Promise((res, rej) => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir);
res(firstTeam.trim());
}
});
return sir();
}
From what I understood, you are looking for firstTeam. Also, we assume that there is always firstTeam, so there isnt a case when there would be no team name.
I am not sure where you are making a req that will take time to process honestly from this code. So far it looks that sync function should do just fine. Are you reaching out to any API?

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);
}
};

Catch an error without failing the Protractor test (wait for element to be visible)

My application has a Terms and Conditions modal that only appears under certain circumstances. I would like the Protractor test to click Agree if the modal appears, and do nothing if it doesn't appear.
Here is my method, I try to wait for the element to appear, and catch the error if it doesn't appear. If the button appears, it is clicked and the test passes. However, if the button does not appear, the test fails with the "Element is taking too long..." message.
waitForAgree() {
var until = protractor.ExpectedConditions;
try {
browser.wait(until.visibilityOf(this.agreeTerms), 5000, 'Element taking too long to appear in the DOM');
this.agreeTerms.click();
this.continue.click();
} catch (e) {
console.log("not found");
}
}
I have also tried the following. This gives me the message Failed: Cannot read property 'agreeTerms' of undefined
clickAgree() {
//browser.ignoreSynchronization = true;
this.agreeTerms.isPresent().then(function (isPresent) {
if (isPresent) {
this.agreeTerms.click();
this.continue.click();
}
else {
console.log("not found");
}
})
}
Any help on either of these errors is appreciated.
In order to elaborate why your first method didn't work, we should note that using Promise and not be calling .catch is usually a bad idea because most of the time we will never know what was wrong and try/catch won't help at all. And this is also the reason that your alternate method succeded because you are calling .catch on the Promise.
If you are interested in making your first approach work, then you should be using async/await, something like this:
async waitForAgree() {
var until = protractor.ExpectedConditions;
try {
await browser.wait(until.visibilityOf(this.agreeTerms), 5000, '...too long...');
this.agreeTerms.click();
this.continue.click();
} catch (e) {
console.log("not found");
}
}
Note the keyword async in front of the function name and await in front of the call to browser.wait. Note this is an ES6 feature.
Hope it helps
I would store that web element in a list and if the size of list is equal to 1 (in this case I know button is present, so click it). If that list returns zero than print the fail message.
I will use findElements instead of findElement.
This is how you can defined the list :
var elems = driver.findElements(By.xpath("your locator"));
For calculating the size, you can use this :
var keys = Object.keys(elems);
var len = keys.length
and after that :
if (len == 1){
this.agreeTerms.click();
this.continue.click();
}
else{
Do something here, button did not appear.
}
I tried an alternate method to my first one, and it works. I'm not entirely sure why the first one in my question fails, and this succeeds. If anyone would like to elaborate, thank you ahead of time. If you just need a working method, here:
waitForAgree() {
var until = protractor.ExpectedConditions;
browser.wait(until.visibilityOf(this.agreeTerms), 5000, 'Element taking too long to appear in the DOM')
.then(() => {
this.agreeTerms.click();
this.continue.click();
})
.catch((error) =>{
console.log("not found");
});
}

Socket io not firing events due to if statement, maybe race condition?

My issue seems to be the server is listening on a websocket for certain events and then when a certain event is heard it is supposed to fire its own event through a different websocket, but that event is rarely fired. With the conditional statement inside function availabilityCheck removed, the event is always fired instead of rarely. There are 2 sockets websockets the server is connected on for clarity.
event heard from websocket(1) usually 2-4 times within milliseconds-> backend does logic (but only once event though the event was fired 2-4 times) -> event supposed to fire to websocket(2)
let obj = {available: 0}
if (event.Event == 'AgentConnect') {
const agentExtension = '1001'
availabilityCheck(event, agentExtension)
.then(function () {
socket.emit('AgentConnect', agentExtension); //works rarely, but always works when the if statement inside availabilityCheck() is removed
}).catch(function(err){
console.log(err);
})
}// end if
function availabilityCheck(evt, agentExt) {
return promise = new Promise(function (resolve, reject) {
if (obj.available == 0) {// When this conditional is removed the socket event always fires
obj.available =1;
resolve();
} else {
reject('agent unavailable');
}
})
}
Could you try the following:
var obj = (x =>{
var available = 0;
return {
set available(val) {
debugger;
available=val;
},
get available() {
return available;
}
}
})();
Now it should pause when you change available, if you have the dev tools open (press F12) you can check the stack trace and see what other code is changing available while the Promise is resolving.
The code you provided does not show any problem, available should still be 0 but I'm sure there is more code.
If you want something to happen in serie then you could run the promise in serie instead of parallel:
let agentConnectPromise = Promise.resolve()
if (event.Event == 'AgentConnect') {
agentConnectPromise =
agentConnectPromise
.then(
x=> availabilityCheck(event, agentExtension)
)
.then(...
Moving the assignment of obj.available to the then() fixed my issue.
if (event.Event == 'AgentConnect') {
const agentExtension = '1001'
availabilityCheck(event, agentExtension)
.then(function () {
obj.available = 1; //moved from availability to check to here.
socket.emit('AgentConnect', agentExtension); //works rarely, but always works when the if statement inside availabilityCheck() is removed
}).catch(function(err){
console.log(err);
})
}// end if

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

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();
});
});
})

Categories