This following code behaving randomly sometime it works fine and sometime it throws error like this Stale Element Reference Exception
what i want is i want to get this below executed first
element(by.id('FiltItemTransDocNo')).sendKeys(grno);
after above i want this to get executed this below
element.all(by.name('chkGrd')).first().click();
i have tried this way but it didnt seems to work
element(by.id('FiltItemTransDocNo')).sendKeys(grno).then(function(el){
element.all(by.name('chkGrd')).first().click();
});
help me out with this
i have attached the image
i am sending keys to Purchase Rquisition field and according i came up with result which will show only one result and i want to click
and if i will put condition for visiblity it will always be true
then i will lead to same issue
sendKeys
and e1
A quick note that sendKeys
does not return a WebElement or ElementFinder. This means that e1 in the example above is probably undefined.
Quick note on assumptions: The answer has an assumption that sending text to a filter would change the row count or number of elements on the screen. If there are the same number of elements on the screen after you send in the text, then this would not work. I would look at Florent's comment below on the stale reference error.
Stale elements usually happen when the DOM has changed. If you are using some structural directive in Angular, the DOM will change if you are using *ngFor or *ngIf. My guess is that after you filter the the element, you are getting the elements in the DOM during or before the DOM actually changes based on your filter. This would result in a stale referenced web element. In my examplex below, I am using async / await with the control flow off.
You could explicitly set a sleep so the DOM will update before you make a call to click the first element. This could lead to potentially flaky tests because the timeout is unknown based on the environment you will run in.
it('should do something', async () => {
const filterItem = element(by.id('FiltItemTransDocNo'));
await filterItem.sendKeys(grno);
await browser.sleep(1000); // or some other waits
await element.all(by.name('chkGrd')).first().click();
});
Alternatively you could do a comparison check for the amount of element.all
items you have before and after the click and only move on when things are updated.
it('should do something', async () => {
const filterItem = element(by.id('FiltItemTransDocNo'));
const table = element.all(by.name('chkGrd'));
const length = await table.count();
await filterItem.sendKeys(grno);
// After the filter keys are sent, check to see if the current table
// count is not equal to the `table.count()`.
let updated = false;
await browser.wait(async () => {
updated = length !== await table.count();
return updated;
}, 5000);
// So if we use the entire 5 seconds and the count has not changed,
// we should probably fail before clicking on stuff.
expect(updated).toBeTruthy();
// now we can click on the next element.
await element.all(by.name('chkGrd')).first().click();
});
Why does calling length !== await table.count()
work? This is because the table represents a promise to get the web element. When you call the count
method, it executes the action by first resolving the web element. This could potentially be different depending if the DOM changes. We then compare the current count to the previous one.
In your configuration file, you will need to specify that you are off the control flow:
exports.config = {
// In this case, I plan to use a selenium standalone server
// at http://127.0.0.1:4444/wd/hub. You could also use other
// methods like direct connect or starting it up with 'local'.
seleniumAddress: 'http://127.0.0.1:4444/wd/hub',
// Required flag to tell Protractor you do not want to use the
// control flow. This means that you will have to either chain
// your promises or async / await your actions. Historically
// jasminewd node module would resolve promises for you. This
// package will no longer be used in future releases since the
// control flow is deprecated.
SELENIUM_PROMISE_MANAGER: false,
// The rest of your config...
}
Hope that helps.
there is several solutions for stale element reference.
First:
let filterItem = element(by.id('FiltItemTransDocNo'));
browser.wait(ExpectedConditions.visibilityOf(filterItem), 5000, 'Element is not visible.');
filterItem.sendKeys('some random text');
let elementToClick = element.all(by.name('chkGrd')).first();
browser.wait(ExpectedConditions.elementToBeClickable(elementToClick), 5000, 'Element is not clickable.');
elementToClick.click();
You can also chain them:
browser.wait(ExpectedConditions.visibilityOf(filterItem), 5000, 'Element is not visible.').then( () => {
filterItem.sendKeys('some random text');
browser.wait(ExpectedConditions.elementToBeClickable(elementToClick), 5000, 'Element is not clickable.').then( () => {
elementToClick.click();
});
});
Or second way, to refresh the element on stale error:
let filterItem = element(by.id('FiltItemTransDocNo'));
try {
filterItem.sendKeys('some random text');
} catch (e) {
if (e instanceof StaleElementReferenceError) {
filterItem = element(by.id('FiltItemTransDocNo'));
filterItem.sendKeys('text');
}
}
As I explained in an answer to another question, Protractor and the tools it uses are all changing to use native JavaScript promises, with the expectation that everyone will migrate their code to a fully async style of coding. Unfortunately, they are not making it easy because you cannot mix the old style code with the new style. So first things first, make sure you entire suite is written async style and you have set SELENIUM_PROMISE_MANAGER: false
in your config.
What happens in response to the sendKeys
? If that triggers an AJAX call and the response to that call changes the DOM, then you are probably going to have to do what @cnishina suggests and poll the DOM to wait for the change to land. If, however, only Angular client-side changes happen, then this code should work:
it('should do something', async () => {
await element(by.id('FiltItemTransDocNo')).sendKeys(grno);
await element.all(by.name('chkGrd')).first().click();
});
Both element
calls synchronize with Angular to make sure that Angular updates are finished before the locators proceed, so you should be OK as far as Angular is concerned.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With