I have 'skip_testcases_on_fail' set to true and whenever there is a test failure due to css locator being incorrect, the other test methods in that file are not executed and the browser window remains open.
Is there a way to handle this issue?
One way to handle it is to check for the element using this.element (or browser.element) before doing anything else with it, such as getting text or clicking.
var self = this;
self.element('css selector', 'some_selector', function (present) {
if (present.status !== -1) {
self.getText('some_selector', function (result) {
if (result.status !== -1) {
console.log('result: ' + result.value);
self.click('some_selector');
}
});
}else{
// the selector was not found
}
})
Related
Is there a way to ensure that it will be executed first before the if condition located below it?
Sample Code:
it('should click ' + strName + ' from the list', function() {
exports.waitForElementDisp(10, global.elmGravityPanel, false);
browser.sleep(2000).then(function() {
element(by.css('[cellvalue="' + strName + '"]')).click();
element(by.id('uniqueid')).getText().then(function(tmpText) {
global.tempObject.isPresent = false;
if (tmpText == strName) {
global.tempObject.isPresent = true;
}
});
});
});
if (global.tempObject.isPresent == true) {
it('should click the settings icon', function() {
global.elmSettingBtn.click();
});
it...
}
Currently, global.tempObject.isPresent was set to null and protractor did not go inside the IF even though it will be set as true inside the first IT.
Because this 'if' is executed on file parsing.
I would suggest to try something like this:
it(`should click ${strName} from the list`, function () {
exports.waitForElementDisp(10, global.elmGravityPanel, false);
browser.sleep(2000)
$(`[cellvalue="${strName}"]`).click();
$('#uniqueid').getText().then(function (tmpText) {
global.tempObject.isPresent = false;
if (tmpText == strName) {
global.tempObject.isPresent = true;
}
});
});
/**
*
* #param condition Any boolean condition to check
* #param suiteOrTest function, that contains scheduling of tests.
*/
function runIf(condition, suiteOrTest) {
if (condition) {
return suiteOrTest
} else {
console.log('Skipping execution')
}
}
describe('Conditional execution', () => {
it('1', function () {
global.elmSettingBtn.click();
});
it('This test is with condition', runIf(process.env.IS_LINUX, ()=> {
// Some other test
global.elmSettingBtn.click();
}))
}))
I'm not a fan of wrapping the it block in a conditional. So below are my thoughts of how you would change your code to avoid using the conditional around the it block.
If you are expecting the unique text to appear on the element for the following it block, you could increase the browser wait for the expected conditions that element appears instead of having an arbitrary sleep time. After the element is available we could then do some checks against the text.
If you have a mixture of workflows that sometimes test to see if a unique text is present and other times where the unique tests is not present, I would then split that out into two different workflows.
If the element is not there, we could fail the tests after that based on the uniqueText not being present. This could be overkill.
let EC = ExpectedConditions;
let button = element(by.css('[cellvalue="' + strName + '"]'));
let uniqueText = element(by.id('uniqueid'));
// your locator strategy here.
let elemSettingBtn = element(by.css('something'));
it('should click ' + strName + ' from the list', function() {
// Leaving this line alone. Not sure what this is doing.
exports.waitForElementDisp(10, global.elmGravityPanel, false);
browser.wait(EC.elementToBeClickable(button), 3000);
button.click();
browser.wait(EC.presenceOf(uniqueText), 3000);
expect(uniqueText.getText()).toEqual(strName);
});
it('should click the settings icon', function() {
uniqueText.isPresent().then(present => {
if (present) {
// click the settings button because the unique text is there.
return elmSettingBtn.click();
} else {
// Maybe fail the test when the button is not present?
fail('unique text is not present.');
}
});
});
How can I check until an element is clickable using nightwatch js? I want to click on an element but when I run nightwatch, selenium does not click on the element because it is not clickable yet.
Something like this should work. Let me know if you have questions
var util = require('util');
var events = require('events');
/*
* This custom command allows us to locate an HTML element on the page and then wait until the element is both visible
* and does not have a "disabled" state. It rechecks the element state every 500ms until either it evaluates to true or
* it reaches maxTimeInMilliseconds (which fails the test). Nightwatch uses the Node.js EventEmitter pattern to handle
* asynchronous code so this command is also an EventEmitter.
*/
function WaitUntilElementIsClickable() {
events.EventEmitter.call(this);
this.startTimeInMilliseconds = null;
}
util.inherits(WaitUntilElementIsClickable, events.EventEmitter);
WaitUntilElementIsClickable.prototype.command = function (element, timeoutInMilliseconds) {
this.startTimeInMilliseconds = new Date().getTime();
var self = this;
var message;
if (typeof timeoutInMilliseconds !== 'number') {
timeoutInMilliseconds = this.api.globals.waitForConditionTimeout;
}
this.check(element, function (result, loadedTimeInMilliseconds) {
if (result) {
message = '#' + element + ' was clickable after ' + (loadedTimeInMilliseconds - self.startTimeInMilliseconds) + ' ms.';
} else {
message = '#' + element + ' was still not clickable after ' + timeoutInMilliseconds + ' ms.';
}
self.client.assertion(result, 'not visible or disabled', 'visible and not disabled', message, true);
self.emit('complete');
}, timeoutInMilliseconds);
return this;
};
WaitUntilElementIsClickable.prototype.check = function (element, callback, maxTimeInMilliseconds) {
var self = this;
var promises =[];
promises.push(new Promise(function(resolve) {
self.api.isVisible(element, function(result) {
resolve(result.status === 0 && result.value === true);
});
}));
promises.push(new Promise(function(resolve) {
self.api.getAttribute(element, 'disabled', function (result) {
resolve(result.status === 0 && result.value === null);
});
}));
Promise.all(promises)
.then(function(results) {
var now = new Date().getTime();
const visibleAndNotDisabled = !!results[0] && !!results[1];
if (visibleAndNotDisabled) {
callback(true, now);
} else if (now - self.startTimeInMilliseconds < maxTimeInMilliseconds) {
setTimeout(function () {
self.check(element, callback, maxTimeInMilliseconds);
}, 500);
} else {
callback(false);
}
})
.catch(function(error) {
setTimeout(function () {
self.check(element, callback, maxTimeInMilliseconds);
}, 500);
});
};
module.exports = WaitUntilElementIsClickable;
Add this code as a file to your commands folder. It should be called waitUntilElementIsClickable.js or whatever you want your command to be.
Usage is:
browser.waitUntilElementIsClickable('.some.css');
You can also use page elements:
var page = browser.page.somePage();
page.waitUntilElementIsClickable('#someElement');
You can use waitForElementVisible() combined with the :enabled CSS pseudo-class.
For example, the following will wait up to 10 seconds for #element to become enabled, then click it (note that the test will fail if the element doesn't become enabled after 10 seconds):
browser
.waitForElementVisible('#element:enabled', 10000)
.click('#element');
Can you show an example element,usually there should be an attribute name "disabled" if the button is not clickable, this should work.
browser.assert.attributeEquals(yourCSS, 'disabled', true)
I'm unable to comment but there are a couple of issues with the code suggested by Alex R.
First, the code will not work with Firefox as geckodriver does not return a 'status'. So this:
resolve(result.status === 0 && result.value === true)
needs to be changed to this:
resolve(result.value === true).
Second, the line:
self.client.assertion(result, 'not visible or disabled', 'visible and not disabled', message, true);
doesn't work and needs to be commented out in
order to get the code to run.
I have the following function in my controller:
$scope.model.listApplicantStatuses = function(){
var statusChoices = jobsService.getApplicantStatuses();
if(statusChoices !== null)
return statusChoices;
//if($scope.model.listApplicantStatuses.inProgress)
// return;
//$scope.model.listApplicantStatuses.inProgress = true;
jobsService.fetchApplicantStatuses().then(function(data){
jobsService.setApplicantStatuses(data.data);
return data.data;
},
function(data){
$scope.layout.showNotification('error', 10 * 1000, 'we are is experiencing technical difficulties Contact Support');
});
}
corresponding service code:
jobsServ.fetchApplicantStatuses = function(){
return $http.get(utils.getBaseUrl() + '/applications/status_choices', utils.getConfig());
}
jobsServ.getApplicantStatuses = function(){
return that.applicantStatusChoices;
},
jobsServ.setApplicantStatuses = function(choices){
that.applicantStatusChoices = choices;
},
Example DOM usage:
<select class="statusSelect" data-ng-model="model.editedApplicant[applicant.id].status" data-ng-show="layout.statusVisible(applicant.id) && !layout.statusLoader[applicant.id]" data-ng-options="key as val for (key, val) in model.listApplicantStatuses()" data-ng-change="model.updateStatus(applicant.id)"></select>
Now, the problem that I am having is that while chrome waits until the first function call completes, and then gives me the data that I get from the AJAX call, and returns undefined in the meanwhile, Firefox calls the function over and over again, creating a whole lot of uneeded XHR requests.
I commented out the code that was setting the inProgress $scope variable, a because it seems to much of a jQurish solution to me, and because that would force me to change my code in many places, and create another Boolean flag such as this per every request to the server.
I would expect the Firefox behaviour. Perhaps you should change the logic as:
// a variable to keep statuses, or a promise
$scope.applicantStatusList = $scope.model.listApplicantStatuses();
Change listApplicantStatuses() to return the promise:
$scope.model.listApplicantStatuses = function() {
var statusChoices = jobsService.getApplicantStatuses();
if(statusChoices !== null)
return statusChoices;
return jobsService.fetchApplicantStatuses().then(function(data){
jobsService.setApplicantStatuses(data.data);
return data.data;
},
function(data){
$scope.layout.showNotification(...);
});
};
And use the applicantStatusList in the <select>:
<select ... data-ng-options="key as val for (key, val) in applicantStatusList" ...></select>
I solved this by allocating a local variable named _root at the function that sends the xhr, setting _root.ingProgress to true once the request is send, and switching it to false once an answer is received.
Works like a charm.
code:
$scope.model.listApplicantStatuses = function(){
var statusChoices = jobsService.getApplicantStatuses();
if(statusChoices !== null)
return statusChoices;
var _root = this;
if(_root.inProgress)
return;
_root.inProgress = true;
jobsService.fetchApplicantStatuses().then(function(data){
jobsService.setApplicantStatuses(data.data);
_root.inProgress = false;
return data.data;
},
function(data){
_root.inProgress = false;
$scope.layout.showNotification('error', 10 * 1000, 'We are currently experiencing technical difficulties Contact Support');
});
}
I have an iframe with a differing domain, I have no trouble resizing the iframe by communicating with the parent or the child using jQuery postMessage plugin. However, I have a single page app that scrolls through some steps and then loads in a results page via ajax. On this results page there are product links that need to load in the parent window. This works fine too. My problem is when the user clicks the back button, I want to regenerate and show the results, not the initial state of the page. On Chrome, this works fine but on Firefox it doesn't (when it's in an iframe, otherwise the url directly worked). It seems that Firefox ignores the hashes, cookies, pushState, etc that I've set. I'm not sure how to fix this, and any help would be appreciated.
First, I wanted to see if history api was available, when I call app.getResults() I check if it's available, otherwise set location.hash = "results". app.resultPageSetup(currentState) loads nicely the way I'd prefer because it skips the ajax call used to get the object, since I already saved it in the state.
var isHistory = function() {
return !!(window.history && history.pushState);
};
//This "if" is for a different part of the app. Just ignore.
if ($('.not_container2').length) {
setTimeout(function() {
app.setFrameHeight("2500");
}, 1000);
} else {
if ( isHistory() === true ) {
var currentState = history.state;
history.pushState('', '', '#steps');
if (currentState !== null ) {
$('.container').hide();
$('.container2').show();
app.resultPageSetup(currentState);
app.resultPageComplete();
} else {
$('.container').show();
$('.container2').hide();
setTimeout(function() {
if ($('.container').length){
app.setFrameHeight("610");
} else if ($('.not_container2').length) {
app.setFrameHeight("2500");
}
}, 1000);
}
} else {
//Firefox ignores this stuff. Chrome works. Doing without a cookie at all would be nice
if (location.hash === "#results" && $.cookie("results") === "true") {
$('.container').hide();
$('.container2').show();
app.getResults();
} else if (location.hash === "") {
$('.container').show();
$('.container2').hide();
$.cookie("results", null);
setTimeout(function() {
if ($('.container').length){
app.setFrameHeight("610");
} else if ($('.not_container2').length) {
app.setFrameHeight("2500");
}
}, 1000);
}
}
}
Here are the links: The parent is http://www.hhgregg.com/productfinder/tv (choose the first option) and the iframe source is http://hhgregg-finders.gst.api.igodigital.com
I could also leverage $.postMessage by setting hashes on the parent, but I had trouble with this as well. Here is the general idea there:
$(function() {
var iframe = $('#baseFrame');
var h;
var saved_state = "igo_steps";
$.receiveMessage(function(e){
var data = e.data.split("|");
var height = data[0];
var state = data[1];
if (!isNaN(height)) {
h = height;
} else {
h = "610";
}
iframe.attr("height", h);
if (saved_state !== state) {
window.location.hash = state;
$.postMessage(window.location.hash, '*', document.getElementById("baseFrame").contentWindow);
}
}, 'http://hhgregg-finders.gst.api.igodigital.com');
});
Did anyone who used jQuery Easy Confirmation plugin run into this issue - the button upon which the confirm box is bound loses its original click event after the first click? I had to change the plugin code to this to make it work. The difference here is between .bind and .click. Can anyone explain why? Pls. let me know if my question is not clear. Thx!
Original plugin code:
// Re-bind old events
var rebindHandlers = function () {
if (target._handlers != undefined) {
jQuery.each(target._handlers, function () {
//this is the difference
$target.bind(type, this);
});
}
}
Changed (working) code:
// Re-bind old events
var rebindHandlers = function () {
if (target._handlers != undefined) {
jQuery.each(target._handlers, function () {
//this is the difference
if(type == 'click')
$target.click(this);
else {
$target.bind(type, this);
}
});
}
}
Try using some alerts to see what's happening...
// Re-bind old events
var rebindHandlers = function () {
if (target._handlers != undefined) {
jQuery.each(target._handlers, function () {
if(type == 'click')
alert('$target.click(' + this + ');');
//$target.click(this);
else {
alert('$target.bind(' + type + ', ' + this + ');');
//$target.bind(type, this);
}
});
}
}