How to I find an element with protractor? - protractor

It seems if I use element(by.css('#id')); protractor sometimes finds it, sometimes doesn't. Depends if the DOM is settled down or not. I've gone and some this:
getElementsByCss: function (cssVal) {
// return element.all((by.css(cssVal)));
return self.wait(function () {
return element.all((by.css(cssVal))).then(function (els) {
return els;
}, function () {
return false; // Don't fail, we want to retry
});
}, GET_ELEMENT_TIMEOUT, 'Elements not found by css: ' + cssVal);
},
which plays a game of retrying to get the element for some timeout period. We use 5 seconds. This seems to work, most of the time. When running locally we have no issues.
When we run on the cloud (sauce labs) they have exceedingly slow VMs. We still get random issues. Unfortunately these games have to be played everywhere, for example:
getText: function (el) {
return self.wait(function () {
return el.getText();
}, 1000, 'Could not get element text.');
},
or
expectBulletin: function () {
var el, text;
return self.wait(function () {
// Find the bulleting element, make sure it's visible and grab its text.
// If any of those three fail, try all of them again.
return self.getElementByCss('.bulletin')
.then(function (elm) {
el = elm;
return self.isElementVisible(el);
}, function () {
return false;
})
.then(function () {
return self.getText(el).then(function (text) {
return text;
}, function () {
return false;
});
}, function () {
return false;
});
}, 10000, 'Count not get bulletin text.')
.then(function (result) {
text = result;
return self.executeScript('arguments[0].click();', el.getWebElement());
})
.then(function () {
return self.isElementNotVisible(el);
})
.then(function () {
return text;
});
},
all the self.waits are just a browser.driver.wait wrapper...
wait: function (fn, timeout, msg) {
return browser.driver.wait(fn, timeout, msg);
},
This feels like a big pack of bandaids and its not working all the time. expectBulletin works 99% of the time locally, but when run remotely on the slow VMs, it works about 50% of the time. Sometimes the text comes back blank, or issues about clicking an invisible element or just not finding the bulletin element.
Is there any better way to do this?

it seems if I use element(by.css('#id')); protractor sometimes finds
it, sometimes doesn't.
You know that it returns a promise right? If you want to get the absolute value of an element, you have to handle its promise first.
Protractor waits for the page to be ready before doing any assertions. If your page is "ready" but some data is missing, there is something wrong with your angular application.

Related

Protractor not waiting for modal

I have a modal that shows up in a protractor test. When I run the test case solo, it works perfectly, but when I run it as part of the larger file (currently 10 cases, some lengthy) it slows things down and, as a result, the modal is slower to open and close. The chain effect is that, when I try to click on a button on the page, it crashes since the modal (which was supposed to be closed by now) is still there and blocking the button.
How can I properly sense when the modal is open/closed so that I can work around this?
Thanks!
(Also, this is an AngularJS application)
These are my helper functions for manual waiting:
static async forElementAbsent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.stalenessOf(element), 10000,
`Timed out waiting for element to be absent`).then(() => {
resolve();
});
});
}
static async forElementPresent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.presenceOf(element), 10000,
`Timed out waiting for element to be present`).then(() => {
resolve();
});
});
}
In our tests, we are waiting for modals to be displayed manually. We have helper functions such as
export async function forCondition(condition: () => boolean | PromiseLike<boolean>, timeout = 20000): Promise<boolean> {
try {
return await browser.wait(condition, timeout);
} catch (err) {
return false;
}
}
Given function waits for a condition to fullfill. So in your particular case you would have to find out the css selector of the modal which is displayed.
Let's say the selector is by.css('.modal.visible'), then we would have to write something like the following if we wanted to wait for the modal to be displayed:
t.it('modal should be displayed', async function() {
// wait for modal to be displayed
var result = await forCondition(function() {
return element(by.css('.modal.visible')).isDisplayed();
});
expect(result).toBeTruthy();
});

Changing protractor default timeout inside function

I have a function to be called in some of my protractor tests which does some tasks that take more than the protractor default timeout (which seems to be 60 seconds)
I've read that you should be able to change the default timeout with "jasmine.DEFAULT_TIMEOUT_INTERVAL", however with the following code, the timeout still happens before the 4 minutes I have set up. Since I want to reuse this test part in the future, I cannot simply add it as a parameter to the test function.
Here is the sample code, can anyone tell me what I'm doing wrong?
describe('reset data', function() {
it('should reset data', function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 240000;
browser.ignoreSynchronization = true;
// ... test code here
});
});
I get the following error, after the test fails after roughly 60 seconds:
Error: Timeout - Async callback was not invoked within timeout
specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I have created two functions to override, then restore the default protractor timeouts: (Only tested in Chrome)
import { browser } from 'protractor';
export function DefaultTimeoutOverride(milliseconds: number) {
browser.driver.manage().timeouts().setScriptTimeout(milliseconds);
}
export function DefaultTimeoutRestore() {
browser.driver.manage().timeouts().setScriptTimeout(browser.allScriptsTimeout);
}
EDIT
I have now created a helper function ('itTO') that wraps Jasmine's 'it' statement and applies the timeout automatically :)
import { browser } from 'protractor';
export function itTO(expectation: string, assertion: (done: DoneFn) => void, timeout: number): void {
it(expectation, AssertionWithTimeout(assertion, timeout), timeout);
}
function AssertionWithTimeout<T extends Function>(fn: T, timeout: number): T {
return <any>function(...args) {
DefaultTimeoutOverride(timeout);
const response = fn(...args);
DefaultTimeoutRestore();
return response;
};
}
function DefaultTimeoutOverride(milliseconds: number) {
browser.driver.manage().timeouts().setScriptTimeout(milliseconds);
}
function DefaultTimeoutRestore() {
browser.driver.manage().timeouts().setScriptTimeout(browser.allScriptsTimeout);
}
use like this:
itTO('should run longer than protractors default', async () => {
await delay(14000);
}, 15000);
const delay = ms => new Promise(res => setTimeout(res, ms))
Try his one instead:
By using a recursive function to identify if it is present.
function checkIfPresent(maxSec, elm, blnPresent) {
if (maxSec > 0) {
browser.sleep(1000).then(function() {
elm.isPresent().then(function(bln) {
if (bln != blnPresent) {
checkIfPresent(maxSec - 1, elm, blnPresent)
}
});
});
}
}
If you pass checkIfPresent(300000, elm, true)
It will check if the object is present every second, within 5mins.
Hope it helps. :)
Previous Comment:
I agree with the comment.
It should be declared on config file (conf.js)
jasmineNodeOpts: {
onComplete: null,
isVerbose: true,
showColors: true,
includeStackTrace: true,
defaultTimeoutInterval: 1000000
}

function to take screenshots for cucumber-html-reporter generates "function timed out after 5000.." error

I am using protractor-cucumber-framework and I wanted to generate html report for the tests I wrote. I decided to use cucumber-html-reporter to achieve it. In my hooks.js I wrote a this.After object to take screenshot on test failure:
this.After(function(scenario, callback) {
if (scenario.isFailed()) {
browser.takeScreenshot().then(function(buffer) {
return scenario.attach(new Buffer(buffer, 'base64'), function(err) {
callback(err);
});
});
}
else {
callback();
}
});
Everything works just fine, the report is generated and the screenshots are taken and attached only on test failure. But I also got an error message when After step is proceeded (so when there is some failure):
function timed out after 5000 milliseconds
I would like to get rid of this message as it also appears on my html report. Can anyone provide me solution to do that?
Below code is working for me. I have added this in step definition js file. At the end of scenario in the report, it adds the screenshot.
defineSupportCode(({After}) => {
After(function(scenario) {
if (scenario.isFailed()) {
var attach = this.attach;
return browser.takeScreenshot().then(function(png) {
var decodedImage = new Buffer(png, "base64");
return attach(decodedImage, "image/png");
});
}
});
});
I had similar problem and it failed even after waiting for 60 seconds. The issue was that i didn't had proper callback implemented.
The below code worked for me. ( I am new to JavaScript and so my callback usage might be the right way. Please feel free to educate me if there is better way to do it. :))
After(function(scenario,done)
{
const world = this;
if (scenario.result.status === 'failed') {
browser.takeScreenshot().then(function (stream) {
let decodedImage = new Buffer(stream.replace(/^data:image\/(png|gif|jpeg);base64,/, ''), 'base64');
world.attach(decodedImage, 'image/png');
}).then(function () {
done();
});
}else {
done();
}
});
Your code seems absolutely fine.
Perhaps, it just needs longer timeouts?
You can set a timeout on hooks like this:
this.After({ timeout: 20 * 1000 }, function (scenario, callback) {
if (scenario.isFailed()) {
browser.takeScreenshot().then(function(buffer) {
return scenario.attach(new Buffer(buffer, 'base64'), function(err) {
callback(err);
});
});
}
else {
callback();
}
});

Protractor: browser.wait function, isElementPresent times out

Here is my code. For some reason it cannot detect the element present, and just times out. Site is in angular. I have tried isPresent, as well as ExpectedConditions and it times out nonetheless. For some reason, it just cannot detect the element no matter how I try to locate it. I have tried multiple elements as well. I'm open to any ideas.
browser.wait(function()
{
return browser.isElementPresent(by.xpath('//[#id="ngdialog1"]/div[2]/div/div')).then(function(present)
{
console.log('\n' + 'looking for element')
if(present)
{
console.log('\n' + 'recognized dialog');
var jccSelect = element(by.xpath('//*[#id="ghId_GameSelectBottomRow"]/div[1]'));
jccSelect.click();
return true;
}
})}, 50000);
});
You have kept return statement in if(present){return true;}, if present value is false then control will not be return, that's why your getting timed out issue.
I have rearranged the code as below:
EC = protractor.ExpectedConditions;
targetElement=element(by.xpath('//[#id="ngdialog1"]/div[2]/div/div'));
browser.wait(function(){
return EC.visibilityOf(targetElement).call().then(function(present){
console.log('\n' + 'looking for element')
if(present)
{
//do what would you like to do
return true;
}
else{
//do what would you like to do
return false;
}
});
}, 50000);

How to get karma to clear the dom after each test?

The component I'm testing does some changes to the dom in an it() test, however it remains there in the next it() test, and this breaks my test. Is there a way to reset the DOM each it() test?
For none JQuery users:
afterEach(function () {
document.body.innerHTML = '';
});
Above removes all the <script> tags which are added by default, but that does not matter, because they have alreay been executed once (and Karma does never refresh the browser, at least not till all tests finish).
Hence you can also use beforeEach(...) (but I like to think of it as a dining table, you don't clean up before you start, you clean up after yourself).
I wrote a little DomCleaner that keeps track of events boud during your tests and cleans the body on cleanup (requireJS but you can change the code to your needs):
define(function () {
'use strict';
var installed = false,
documentAddListener,
documentListeners,
windowAddListener,
windowListeners;
return {
install: function () {
if (installed) {
throw new Error('Trying to install document cleaner, but its already installed!');
}
installed = true;
documentAddListener = document.addEventListener;
windowAddListener = window.addEventListener;
spyOn(document, 'addEventListener');
spyOn(window, 'addEventListener');
documentListeners = [];
windowListeners = [];
document.addEventListener.and.callFake(function () {
documentListeners.push(arguments);
documentAddListener.apply(null, arguments);
});
window.addEventListener.and.callFake(function () {
documentListeners.push(arguments);
windowAddListener.apply(null, arguments);
});
},
cleanup: function () {
if (!installed) {
throw new Error('Trying to cleanup document, but cleaner is not installed!');
}
installed = false;
documentListeners.forEach(function (listener) {
document.removeEventListener.apply(null, listener);
});
windowListeners.forEach(function (listener) {
window.removeEventListener.apply(null, listener);
});
documentListeners = [];
windowListeners = [];
document.body.innerHTML = '';
},
};
});
use it like this (in your first describe):
beforeEach(function () {
domCleaner.install();
});
afterEach(function () {
domCleaner.cleanup();
});
If using Jasmine - Use the beforeEach function to run a command before each test. In that function, you can clear the DOM. An example using jQuery:
describe("A test suite", function() {
beforeEach(function () {
$('body').empty();
});
});
I had to do this manually in each test method.
Just use DOM operations that remove stuff from the html using javascript.
stuff like
document.body.innerHTML = '';