I want to retry if nightwatch does not click on an element. How can I do that?
I have the following code:
this.browser.isVisible('.signUp', function (result) {
if (result.value && result.length) {
for(var i = 0; i < 3; i += 1) {
this.browser.click(this.element.login.signInBtn);
var check = this.browser.url(function(result) {
// on login page
console.log(result);
if (result.value.indexOf("#login") !== -1) {
return false;
} else {
return true;
}
});
if (check) {
break;
}
}
}
}.bind(this));
-retries n will not run before and after function so this will not work as expected. You should try --suiteRetries n.
You can retry test cases with --retries command-line option. Example nightwatch --retries 2 will retry failed test case two additional times.
However I will not recommend you using that. I would first explore other options to make sure that element can be clicked. Like example waiting for it to become visible with waitForElementVisible
this.browser.waitForElementVisible(this.element.login.signInBtn, 1000);
Related
I have a case when I need to wait for element (advertising), if it's visible then needs to click it, but if element wasn't found after timeout then needs to keep executing a test.
How to handle the situation with Cypress ?
The way Cypress says to check for a conditional element is Element existence
cy.get('body').then(($body) => {
const modal = $body.find('modal')
if (modal.length) {
modal.click()
}
})
Most likely you put that at the top of the test, and it runs too soon (there's no retry timeoout).
You can add a wait say 30 seconds, but the test is delayed every time.
Better to call recursively
const clickModal = (selector, attempt = 0) => {
if (attempt === 100) return // whole 30 seconds is up
cy.get('body').then(($body) => {
const modal = $body.find('modal')
if (!modal.length) {
cy.wait(300) // wait in small chunks
clickModal(selector, ++attempt)
}
})
return // done, exit
}
cy.get('body')
.then($body => clickModal('modal'))
Intercept the advert
Best is if you can find the url for the advert in network tab, use cy.intercept() to catch it and stub it out to stop the modal displaying.
I tried the above solution, but seems that in some cases parameter $body could not contain necessary element, cause it was not loaded when we invoked cy.get('body'). So, I found another solution, using jQuery via Cypress, here is it:
let counter = 0;
const timeOut: number = Cypress.config('defaultCommandTimeout');
const sleep = (milliseconds) => {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
};
while (true) {
if (Cypress.$(element).length > 0 && Cypress.$(element).is(':visible')) {
Cypress.$(element).click();
break;
} else {
sleep(500);
counter = counter + 500;
if (counter >= timeOut) {
cy.log(elementName+ ' was not found after timeout');
break;
}
}
}
I'm writing some expect.js matchers and I'd like to test the matchers themselves. So I want to write positive and negative tests. Let's say I've written
toContainItem(name);
used like this;
expect(femaleNames).toContainItem('Brad'); // test fails
expect(femaleNames).toContainItem('Angelina'); // test passes
What I want to do is write a test for the negative case, like so;
it('should fail if the item is not in the list', function() {
expect(function() {
expect(femaleNames).toContainItem('Brad');
}).toFailTest('Could not find "Brad" in the array');
});
I'm not sure how to run my failing test code in an environment where it doesn't fail the containing test. Is this possible?
EDIT: Based on Carl Manaster's answer, I've come up with an extension to expect, allowing the code above to work;
expect.extend({
toFailTest(msg) {
let failed = false;
let actualMessage = "";
try
{
this.actual();
}
catch(ex)
{
actualMessage = ex.message;
failed = true;
}
expect.assert(failed, 'function should have failed exception');
if(msg) {
expect.assert(actualMessage === msg, `failed test: expected "${msg}" but was "${actualMessage}"`);
}
}
});
I would think you could wrap the inner expect in a try/catch block, where you clear a failure variable in the catch clause, then make your actual assertion about the value of the variable.
let failed = true;
try {
expect(femaleNames).toContainItem('Brad');
} catch (e) {
failed = false;
}
expected(failed).toBe(false);
I have some code that saves data using Breeze and reports progress over multiple saves that is working reasonably well.
However, sometimes a save will timeout, and I'd like to retry it once automatically. (Currently the user is shown an error and has to retry manually)
I am struggling to find an appropriate way to do this, but I am confused by promises, so I'd appreciate some help.
Here is my code:
//I'm using Breeze, but because the save takes so long, I
//want to break the changes down into chunks and report progress
//as each chunk is saved....
var surveys = EntityQuery
.from('PropertySurveys')
.using(manager)
.executeLocally();
var promises = [];
var fails = [];
var so = new SaveOptions({ allowConcurrentSaves: false});
var count = 0;
//...so I iterate through the surveys, creating a promise for each survey...
for (var i = 0, len = surveys.length; i < len; i++) {
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', surveys[i].ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
promises.push(manager
.saveChanges(changes, so)
.then(function () {
//reporting progress
count++;
logger.info('Uploaded ' + count + ' of ' + promises.length);
},
function () {
//could I retry the fail here?
fails.push(changes);
}
));
}
}
//....then I use $q.all to execute the promises
return $q.all(promises).then(function () {
if (fails.length > 0) {
//could I retry the fails here?
saveFail();
}
else {
saveSuccess();
}
});
Edit
To clarify why I have been attempting this:
I have an http interceptor that sets a timeout on all http requests. When a request times out, the timeout is adjusted upwards, the user is displayed an error message, telling them they can retry with a longer wait if they wish.
Sending all the changes in one http request is looking like it could take several minutes, so I decided to break the changes down into several http requests, reporting progress as each request succeeds.
Now, some requests in the batch might timeout and some might not.
Then I had the bright idea that I would set a low timeout for the http request to start with and automatically increase it. But the batch is sent asynchronously with the same timeout setting and the time is adjusted for each failure. That is no good.
To solve this I wanted to move the timeout adjustment after the batch completes, then also retry all requests.
To be honest I'm not so sure an automatic timeout adjustment and retry is such a great idea in the first place. And even if it was, it would probably be better in a situation where http requests were made one after another - which I've also been looking at: https://stackoverflow.com/a/25730751/150342
Orchestrating retries downstream of $q.all() is possible but would be very messy indeed. It's far simpler to perform retries before aggregating the promises.
You could exploit closures and retry-counters but it's cleaner to build a catch chain :
function retry(fn, n) {
/*
* Description: perform an arbitrary asynchronous function,
* and, on error, retry up to n times.
* Returns: promise
*/
var p = fn(); // first try
for(var i=0; i<n; i++) {
p = p.catch(function(error) {
// possibly log error here to make it observable
return fn(); // retry
});
}
return p;
}
Now, amend your for loop :
use Function.prototype.bind() to define each save as a function with bound-in parameters.
pass that function to retry().
push the promise returned by retry().then(...) onto the promises array.
var query, graph, changes, saveFn;
for (var i = 0, len = surveys.length; i < len; i++) {
query = ...; // as before
graph = ...; // as before
changes = ...; // as before
if (changes.length > 0) {
saveFn = manager.saveChanges.bind(manager, changes, so); // this is what needs to be tried/retried
promises.push(retry(saveFn, 1).then(function() {
// as before
}, function () {
// as before
}));
}
}
return $q.all(promises)... // as before
EDIT
It's not clear why you might want to retry downsteam of $q.all(). If it's a matter of introducing some delay before retrying, the simplest way would be to do within the pattern above.
However, if retrying downstream of $q.all() is a firm requirement, here's a cleanish recursive solution that allows any number of retries, with minimal need for outer vars :
var surveys = //as before
var limit = 2;
function save(changes) {
return manager.saveChanges(changes, so).then(function () {
return true; // true signifies success
}, function (error) {
logger.error('Save Failed');
return changes; // retry (subject to limit)
});
}
function saveChanges(changes_array, tries) {
tries = tries || 0;
if(tries >= limit) {
throw new Error('After ' + tries + ' tries, ' + changes_array.length + ' changes objects were still unsaved.');
}
if(changes_array.length > 0) {
logger.info('Starting try number ' + (tries+1) + ' comprising ' + changes_array.length + ' changes objects');
return $q.all(changes_array.map(save)).then(function(results) {
var successes = results.filter(function() { return item === true; };
var failures = results.filter(function() { return item !== true; }
logger.info('Uploaded ' + successes.length + ' of ' + changes_array.length);
return saveChanges(failures), tries + 1); // recursive call.
});
} else {
return $q(); // return a resolved promise
}
}
//using reduce to populate an array of changes
//the second parameter passed to the reduce method is the initial value
//for memo - in this case an empty array
var changes_array = surveys.reduce(function (memo, survey) {
//memo is the return value from the previous call to the function
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', survey.ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
memo.push(changes)
}
return memo;
}, []);
return saveChanges(changes_array).then(saveSuccess, saveFail);
Progress reporting is slightly different here. With a little more thought it could be made more like in your own answer.
This is a very rough idea of how to solve it.
var promises = [];
var LIMIT = 3 // 3 tris per promise.
data.forEach(function(chunk) {
promises.push(tryOrFail({
data: chunk,
retries: 0
}));
});
function tryOrFail(data) {
if (data.tries === LIMIT) return $q.reject();
++data.tries;
return processChunk(data.chunk)
.catch(function() {
//Some error handling here
++data.tries;
return tryOrFail(data);
});
}
$q.all(promises) //...
Two useful answers here, but having worked through this I have concluded that immediate retries is not really going to work for me.
I want to wait for the first batch to complete, then if the failures are because of timeouts, increase the timeout allowance, before retrying failures.
So I took Juan Stiza's example and modified it to do what I want. i.e. retry failures with $q.all
My code now looks like this:
var surveys = //as before
var successes = 0;
var retries = 0;
var failedChanges = [];
//The saveChanges also keeps a track of retries, successes and fails
//it resolves first time through, and rejects second time
//it might be better written as two functions - a save and a retry
function saveChanges(data) {
if (data.retrying) {
retries++;
logger.info('Retrying ' + retries + ' of ' + failedChanges.length);
}
return manager
.saveChanges(data.changes, so)
.then(function () {
successes++;
logger.info('Uploaded ' + successes + ' of ' + promises.length);
},
function (error) {
if (!data.retrying) {
//store the changes and resolve the promise
//so that saveChanges can be called again after the call to $q.all
failedChanges.push(data.changes);
return; //resolved
}
logger.error('Retry Failed');
return $q.reject();
});
}
//using map instead of a for loop to call saveChanges
//and store the returned promises in an array
var promises = surveys.map(function (survey) {
var changes = //as before
return saveChanges({ changes: changes, retrying: false });
});
logger.info('Starting data upload');
return $q.all(promises).then(function () {
if (failedChanges.length > 0) {
var retries = failedChanges.map(function (data) {
return saveChanges({ changes: data, retrying: true });
});
return $q.all(retries).then(saveSuccess, saveFail);
}
else {
saveSuccess();
}
});
I browsed though many "unexpected token", "illegal" etc. topics, one helped me by checking for invisible characters and copying the script to jslint throwed me some missed brackets error, but there are still one problem with this line:
JS:
document.getElementById('pois').innerHTML =('<p><label><input type="checkbox" id="01" onclick="toggleGroup("01")" CHECKED/></label>01</p>');
The pois div is located in the html file.
Alternatively, how would I write the above line with jQuery, as the inline JS is not considered correct? The below function doesn't work as I'd like:
$("#01").click(function() {
toggleGroup();
});
The corresponding function:
var markerGroups = { "01": [], "02": [] , "03": [] , "04": [] };
function toggleGroup(id_category) {
for (var i = 0; i < markerGroups[id_category].length; i++) {
var marker = markerGroups[id_category][i];
if (marker.getMap()) {
marker.setMap(null);
} else {
marker.setMap(map);
}
}
}
Perhaps you need to change onclick to onchange? This will capture the tick box change event. I'm not too sure what the toggleGroup function is doing but this might help.
The error raised because you have missed one bracket } on end of your function
function toggleGroup(id_category) {
for (var i = 0; i < markerGroups[id_category].length; i++) {
var marker = markerGroups[id_category][i];
if (marker.getMap()) {
marker.setMap(null);
} else {
marker.setMap(map);
}
}
} //<== missing one
http://jsfiddle.net/QF9sN/5/
I am using Selenium WebDriver for crawling a web site(only for example, I will be crawling other web sites too!) which has infinite scroll.
Problem statement:
Scroll down the infinite scroll page till the content stops loading using Selenium web driver.
My Approach:
Currently I am doing this-
Step 1: Scroll to the page bottom
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("javascript:window.onload=toBottom();"+
"function toBottom(){" +
"window.scrollTo(0,Math.max(document.documentElement.scrollHeight," +
"document.body.scrollHeight,document.documentElement.clientHeight));" +
"}");
Then I wait for some time to let the Ajax Request complete like this-
Step 2: Explicitly wait for Ajax request to be over
Thread.sleep(1000);
Then I give another java script to check if the page is scrollable
Step 3:Check if the page is scrollable
//Alternative to document.height is to be used which is document.body.clientHeight
//refer to https://developer.mozilla.org/en-US/docs/DOM/document.height
if((Long)js.executeScript("return " +
"(document.body.clientHeight-(window.pageYOffset + window.innerHeight))")>0)
If the above condition is true then I repeat the from Step 1 - 3, till condition in Step 3 is false.
The Problem:
I do not want to give the Thread.sleep(1000); in step 2, rather I would like to check using Java Script if the background Ajax request is over and then scroll down further if the condition in Step 3 is true .
PS: I am not the developer of the page so I do not have access to the code running the page, I can just inject java scripts(as in Step 1 and 3) in the web page. And, I have to write a generic logic for any web site with Ajax requests during infinite scroll.
I will be grateful to some one could spare some time here!
EDIT : Ok, after struggling for 2 days, I have figured out that the pages which I am crawling through the Selenium WebDriver can have any of these JavaScript libraries and I will have to pool according to the different Library, for example, In case of the web application using jQuery api, I may be waiting for
(Long)((JavascriptExecutor)driver).executeScript("return jQuery.active")
to return a zero.
Likewise if the web application is using the Prototype JavaScript library I will have to wait for
(Long)((JavascriptExecutor)driver).executeScript("return Ajax.activeRequestCount")
to return a zero.
Now, the problem is how do I write a generic code which could handle most the JavaScript libraries available?
Problem I am facing in implementing this-
1. How do I find which JavaScript Library is being used in the Web Application(using Selenium WebDriver in Java), such that I can then write the corresponding wait methods?
Currently, I am using this
Code
2. This way I will have to write as many as 77 methods for separate JavaScript library so, I need a better way to handle this scenario as well.
In short, I need to figure out if the browser is making any call(Ajax or simple) with or without any JavaScript library through Selenium Web Driver's java implementation
PS: there are Add ons for Chorme's JavaScript Lib detector and Firefox's JavaScript Library detector which detect the JavaScript library being used.
For web pages with Ajax Response during the infinite scroll and using jQuery API(or other actions), before starting to opening the web page.
//Inject the pooling status variable
js.executeScript("window.status = 'fail';");
//Attach the Ajax call back method
js.executeScript( "$(document).ajaxComplete(function() {" +
"status = 'success';});");
Step 1: will remain the same as in the original question
Step 2 Pooling the following script(This is the one which removes the need of Thread.Sleep() and makes the logic more dynamic)
String aStatus = (String)js.executeScript("return status;");
if(aStatus!=null && aStatus.equalsIgnoreCase("success")){
js.executeScript("status = 'fail';");
break poolingLoop;
}
Step 3: No need now!
Conclusion: No need to give blunt Thread.sleep(); again and again while using Selenium WebDriver!!
This approach works good only if there's jQuery api being used in the web application.
EDIT:
As per the the link given by #jayati i injected the javascript-
Javascript one:
//XMLHttpRequest instrumentation/wrapping
var startTracing = function (onnew) {
var OldXHR = window.XMLHttpRequest;
// create a wrapper object that has the same interfaces as a regular XMLHttpRequest object
// see http://www.xulplanet.com/references/objref/XMLHttpRequest.html for reference on XHR object
var NewXHR = function() {
var self = this;
var actualXHR = new OldXHR();
// private callbacks (for UI):
// onopen, onsend, onsetrequestheader, onupdate, ...
this.requestHeaders = "";
this.requestBody = "";
// emulate methods from regular XMLHttpRequest object
this.open = function(a, b, c, d, e) {
self.openMethod = a.toUpperCase();
self.openURL = b;
ajaxRequestStarted = 'open';
if (self.onopen != null && typeof(self.onopen) == "function") {
self.onopen(a,b,c,d,e); }
return actualXHR.open(a,b,c,d,e);
}
this.send = function(a) {
ajaxRequestStarted = 'send';
if (self.onsend != null && typeof(this.onsend) == "function") {
self.onsend(a); }
self.requestBody += a;
return actualXHR.send(a);
}
this.setRequestHeader = function(a, b) {
if (self.onsetrequestheader != null && typeof(self.onsetrequestheader) == "function") { self.onsetrequestheader(a, b); }
self.requestHeaders += a + ":" + b + "\r\n";
return actualXHR.setRequestHeader(a, b);
}
this.getRequestHeader = function() {
return actualXHR.getRequestHeader();
}
this.getResponseHeader = function(a) { return actualXHR.getResponseHeader(a); }
this.getAllResponseHeaders = function() { return actualXHR.getAllResponseHeaders(); }
this.abort = function() { return actualXHR.abort(); }
this.addEventListener = function(a, b, c) { return actualXHR.addEventListener(a, b, c); }
this.dispatchEvent = function(e) { return actualXHR.dispatchEvent(e); }
this.openRequest = function(a, b, c, d, e) { return actualXHR.openRequest(a, b, c, d, e); }
this.overrideMimeType = function(e) { return actualXHR.overrideMimeType(e); }
this.removeEventListener = function(a, b, c) { return actualXHR.removeEventListener(a, b, c); }
// copy the values from actualXHR back onto self
function copyState() {
// copy properties back from the actual XHR to the wrapper
try {
self.readyState = actualXHR.readyState;
} catch (e) {}
try {
self.status = actualXHR.status;
} catch (e) {}
try {
self.responseText = actualXHR.responseText;
} catch (e) {}
try {
self.statusText = actualXHR.statusText;
} catch (e) {}
try {
self.responseXML = actualXHR.responseXML;
} catch (e) {}
}
// emulate callbacks from regular XMLHttpRequest object
actualXHR.onreadystatechange = function() {
copyState();
try {
if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); }
} catch (e) {}
// onreadystatechange callback
if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { return self.onreadystatechange(); }
}
actualXHR.onerror = function(e) {
ajaxRequestComplete = 'err';
copyState();
try {
if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); }
} catch (e) {}
if (self.onerror != null && typeof(self.onerror) == "function") {
return self.onerror(e);
} else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") {
return self.onreadystatechange();
}
}
actualXHR.onload = function(e) {
ajaxRequestComplete = 'loaded';
copyState();
try {
if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); }
} catch (e) {}
if (self.onload != null && typeof(self.onload) == "function") {
return self.onload(e);
} else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") {
return self.onreadystatechange();
}
}
actualXHR.onprogress = function(e) {
copyState();
try {
if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); }
} catch (e) {}
if (self.onprogress != null && typeof(self.onprogress) == "function") {
return self.onprogress(e);
} else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") {
return self.onreadystatechange();
}
}
if (onnew && typeof(onnew) == "function") { onnew(this); }
}
window.XMLHttpRequest = NewXHR;
}
window.ajaxRequestComplete = 'no';//Make as a global javascript variable
window.ajaxRequestStarted = 'no';
startTracing();
Or Javascript Two:
var startTracing = function (onnew) {
window.ajaxRequestComplete = 'no';//Make as a global javascript variable
window.ajaxRequestStarted = 'no';
XMLHttpRequest.prototype.uniqueID = function() {
if (!this.uniqueIDMemo) {
this.uniqueIDMemo = Math.floor(Math.random() * 1000);
}
return this.uniqueIDMemo;
}
XMLHttpRequest.prototype.oldOpen = XMLHttpRequest.prototype.open;
var newOpen = function(method, url, async, user, password) {
ajaxRequestStarted = 'open';
/*alert(ajaxRequestStarted);*/
this.oldOpen(method, url, async, user, password);
}
XMLHttpRequest.prototype.open = newOpen;
XMLHttpRequest.prototype.oldSend = XMLHttpRequest.prototype.send;
var newSend = function(a) {
var xhr = this;
var onload = function() {
ajaxRequestComplete = 'loaded';
/*alert(ajaxRequestComplete);*/
};
var onerror = function( ) {
ajaxRequestComplete = 'Err';
/*alert(ajaxRequestComplete);*/
};
xhr.addEventListener("load", onload, false);
xhr.addEventListener("error", onerror, false);
xhr.oldSend(a);
}
XMLHttpRequest.prototype.send = newSend;
}
startTracing();
And checking the status of the status vars ajaxRequestStarted, ajaxRequestComplete in the java code, one can determine if the ajax was started or completed.
Now I have a way to wait till an Ajax is complete, I can also find if the Ajax was triggered on some action
Approach 1:
Your approach is good, just a few changes would do the trick:
Step 1: Improve this step to call the toBottom function at regular interval using window.setInterval. At (c >= totalcount) call window.clearInterval
Setp 2: Instead of checking the page is yet scrollable, check if (c >= totalcount). And this condition every 200ms until (c >= totalcount) returns true.
FYI: If the Step 1 doesn't work in all the browsers then probably, you can refer to line 5210 of Tata-Nano-Reviews-925076578.js and call this with cvariable checking.
Approach 2:
Go to jQuery API and type "ajax". You can find some callback handlers which could be used for ajax requests.
Probably, set a variable before the request is been sent and after it is been received appropriately.
And in between use your original method of scrolling to bottom at regular interval, unless you can no more scroll. At this point clear the interval variable.
Now, regularly check if that interval variable is null or not. Null would mean that you have reached the bottom.
We had to solve the same problem, and managed using a long Javascript function. Just need to add checks to see which library is not undefined.
PS Thanks for giving me an easy answer for how to check for in progress Prototype requests!
eg. Handle JQuery and XHR/Prototype
var jsExecutor = /*Get your WebDriverInstance*/ as IJavaScriptExecutor;
while(/*your required timeout here*/)
{
var ajaxComplete =
jsExecutor.ExecuteScript("return ((typeof Ajax === 'undefined') ||
Ajax.activeRequestCount == 0) && ((typeof jQuery === 'undefined') || $.active == 0)");
if (ajaxIsComplete)
return
}